diff --git a/manifests/deviceservice.yaml b/manifests/deviceservice.yaml
index e49ba23995b07f4e7e956a69f8d5383c8e976e89..3c9a941e0ac67e752d5461c4859a7b03d3692a62 100644
--- a/manifests/deviceservice.yaml
+++ b/manifests/deviceservice.yaml
@@ -39,7 +39,7 @@ spec:
             - containerPort: 9192
           env:
             - name: LOG_LEVEL
-              value: "INFO"
+              value: "DEBUG"
           startupProbe:
             exec:
               command: ["/bin/grpc_health_probe", "-addr=:2020"]
diff --git a/manifests/serviceservice.yaml b/manifests/serviceservice.yaml
index 1dd383d615bb167ae6de15ae03c404a50bdee942..bcfb47ee392d86b8a46d22e58f8efdd6e10da329 100644
--- a/manifests/serviceservice.yaml
+++ b/manifests/serviceservice.yaml
@@ -36,7 +36,7 @@ spec:
             - containerPort: 9192
           env:
             - name: LOG_LEVEL
-              value: "INFO"
+              value: "DEBUG"
           readinessProbe:
             exec:
               command: ["/bin/grpc_health_probe", "-addr=:3030"]
diff --git a/manifests/webuiservice.yaml b/manifests/webuiservice.yaml
index a519aa4a2f8a1e81f1b7f2a1be1965ec0b8bb386..d25f5f8873d4cd3df8206fe88ec0e5805032c3fb 100644
--- a/manifests/webuiservice.yaml
+++ b/manifests/webuiservice.yaml
@@ -39,7 +39,7 @@ spec:
             - containerPort: 8004
           env:
             - name: LOG_LEVEL
-              value: "INFO"
+              value: "DEBUG"
             - name: WEBUISERVICE_SERVICE_BASEURL_HTTP
               value: "/webui/"
           readinessProbe:
diff --git a/my_deploy.sh b/my_deploy.sh
index dbaaaa527a44b67281cc17d842ac1a2405a69af4..10f15bbbddc775273a72eb7b739bc6d9a8786d5e 100755
--- a/my_deploy.sh
+++ b/my_deploy.sh
@@ -106,7 +106,7 @@ export CRDB_DATABASE="tfs"
 export CRDB_DEPLOY_MODE="single"
 
 # Disable flag for dropping database, if it exists.
-export CRDB_DROP_DATABASE_IF_EXISTS=""
+export CRDB_DROP_DATABASE_IF_EXISTS="YES"
 
 # Disable flag for re-deploying CockroachDB from scratch.
 export CRDB_REDEPLOY=""
@@ -154,7 +154,7 @@ export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis"
 export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups"
 
 # Disable flag for dropping tables if they exist.
-export QDB_DROP_TABLES_IF_EXIST=""
+export QDB_DROP_TABLES_IF_EXIST="YES"
 
 # Disable flag for re-deploying QuestDB from scratch.
 export QDB_REDEPLOY=""
diff --git a/proto/context.proto b/proto/context.proto
index 87f69132df022e2aa4a0766dc9f0a7a7fae36d59..9cfe8fe07c87926530f8973c964d68dd0d3fac83 100644
--- a/proto/context.proto
+++ b/proto/context.proto
@@ -300,6 +300,7 @@ enum ServiceTypeEnum {
   SERVICETYPE_TE = 4;
   SERVICETYPE_E2E = 5;
   SERVICETYPE_OPTICAL_CONNECTIVITY = 6;
+  SERVICETYPE_IPLINK = 7;
 }
 
 enum ServiceStatusEnum {
@@ -512,11 +513,17 @@ message ConfigRule_ACL {
   acl.AclRuleSet rule_set = 2;
 }
 
+message ConfigRule_IP_LIK {
+  EndPointId endpoint_id = 1;
+  string  subnet_ip = 2;
+}
+
 message ConfigRule {
   ConfigActionEnum action = 1;
   oneof config_rule {
-    ConfigRule_Custom custom = 2;
-    ConfigRule_ACL acl = 3;
+    ConfigRule_Custom custom  = 2;
+    ConfigRule_ACL acl        = 3;
+    ConfigRule_IP_LIK ip_link = 4;
   }
 }
 
diff --git a/src/context/service/database/ConfigRule.py b/src/context/service/database/ConfigRule.py
index 7d816b3e87803f71678511f4fadc6bbe7eba548e..0f204c505005fdfe84755c3d179f3cea56c75355 100644
--- a/src/context/service/database/ConfigRule.py
+++ b/src/context/service/database/ConfigRule.py
@@ -68,6 +68,9 @@ def compose_config_rules_data(
             _, _, endpoint_uuid = endpoint_get_uuid(config_rule.acl.endpoint_id, allow_random=False)
             rule_set_name = config_rule.acl.rule_set.name
             configrule_name = '{:s}:{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid, rule_set_name)
+        elif kind == ConfigRuleKindEnum.IP_LINK:
+            _, _, endpoint_uuid = endpoint_get_uuid(config_rule.ip_link.endpoint_id, allow_random=False)
+            configrule_name = '{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid)
         else:
             MSG = 'Name for ConfigRule({:s}) cannot be inferred '+\
                   '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})'
diff --git a/src/context/service/database/Link.py b/src/context/service/database/Link.py
index 8aa2563e4b3bb5b46ffdefe4644cdfe321f1e734..87ec27eb8bc46945807c5b902638fd8414b027ee 100644
--- a/src/context/service/database/Link.py
+++ b/src/context/service/database/Link.py
@@ -74,12 +74,12 @@ def link_set(db_engine : Engine, messagebroker : MessageBroker, request : Link)
     related_topologies : List[Dict] = list()
 
     # By default, always add link to default Context/Topology
-    _,topology_uuid = topology_get_uuid(TopologyId(), allow_random=False, allow_default=True)
-    related_topologies.append({
-        'topology_uuid': topology_uuid,
-        'link_uuid'    : link_uuid,
-    })
-    topology_uuids.add(topology_uuid)
+    # _,topology_uuid = topology_get_uuid(TopologyId(), allow_random=False, allow_default=True)
+    # related_topologies.append({
+    #     'topology_uuid': topology_uuid,
+    #     'link_uuid'    : link_uuid,
+    # })
+    # topology_uuids.add(topology_uuid)
 
     link_endpoints_data : List[Dict] = list()
     for i,endpoint_id in enumerate(request.link_endpoint_ids):
diff --git a/src/context/service/database/models/ConfigRuleModel.py b/src/context/service/database/models/ConfigRuleModel.py
index f57c90b82b950e68103e1381c6ff0b118e6307df..73a667e6bc1d19a8182ff3f836e872c42766728d 100644
--- a/src/context/service/database/models/ConfigRuleModel.py
+++ b/src/context/service/database/models/ConfigRuleModel.py
@@ -21,8 +21,9 @@ from ._Base import _Base
 
 # Enum values should match name of field in ConfigRule message
 class ConfigRuleKindEnum(enum.Enum):
-    CUSTOM = 'custom'
-    ACL    = 'acl'
+    CUSTOM  = 'custom'
+    ACL     = 'acl'
+    IP_LINK = 'ip_link'
 
 class DeviceConfigRuleModel(_Base):
     __tablename__ = 'device_configrule'
diff --git a/src/device/requirements.in b/src/device/requirements.in
index 73ea741d16dcdafd7a9be87ad79b457ccb6c5d5e..6f20b0de1c62eee000a22244f38b2ab0fd4aefd5 100644
--- a/src/device/requirements.in
+++ b/src/device/requirements.in
@@ -23,7 +23,7 @@ Flask==2.1.3
 Flask-HTTPAuth==4.5.0
 Flask-RESTful==0.3.9
 Jinja2==3.0.3
-ncclient==0.6.13
+ncclient==0.6.15
 p4runtime==1.3.0
 pandas==1.5.*
 paramiko==2.9.2
diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py
index fd36e2dc40e38a125f1812f00eeb304106a40c8a..936fb94391bbb6522a26f75d9c7237bf514b3ad5 100644
--- a/src/device/service/drivers/openconfig/OpenConfigDriver.py
+++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py
@@ -210,6 +210,8 @@ def edit_config(
     commit_per_rule=False, target='running', default_operation='merge', test_option=None, error_option=None,
     format='xml' # pylint: disable=redefined-builtin
 ):
+    logger.debug('Reglas mandadas = {:s}'.format(str(resources)))
+
     str_method = 'DeleteConfig' if delete else 'SetConfig'
     results = []
     if "L2VSI" in resources[0][1] and netconf_handler.vendor == "CISCO":
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json
new file mode 100644
index 0000000000000000000000000000000000000000..e57011be614cc6af8bc011b4fd8581e86f0856c5
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json
@@ -0,0 +1,231 @@
+{
+   "l3vpn-svc": {
+      "vpn-services": {
+         "vpn-service": [
+            {
+               "vpn-id": "vpn2"
+            }
+         ]
+      },
+      "sites": {
+         "site": [
+            {
+               "site-id": "site_OLT",
+               "locations": {
+                  "location": [
+                     {
+                        "location-id": "OLT"
+                     }
+                  ]
+               },
+               "devices": {
+                  "device": [
+                     {
+                        "device-id": "128.32.33.5",
+                        "location": "OLT"
+                     }
+                  ]
+               },
+               "management": {
+                  "type": "ietf-l3vpn-svc:provider-managed"
+               },
+               "routing-protocols": {
+                  "routing-protocol": [
+                     {
+                        "type": "ietf-l3vpn-svc:static",
+                        "static": {
+                           "cascaded-lan-prefixes": {
+                              "ipv4-lan-prefixes": [
+                                 {
+                                    "lan": "128.32.10.0/24",
+                                    "next-hop": "128.32.33.2",
+                                    "lan-tag": "vlan31"
+                                 },
+                                 {
+                                    "lan": "128.32.20.0/24",
+                                    "next-hop": "128.32.33.2",
+                                    "lan-tag": "vlan31"
+                                 }
+                              ]
+                           }
+                        }
+                     }
+                  ]
+               },
+               "site-network-accesses": {
+                  "site-network-access": [
+                     {
+                        "site-network-access-id": "500",
+                        "site-network-access-type": "ietf-l3vpn-svc:multipoint",
+                        "device-reference": "128.32.33.5",
+                        "ip-connection": {
+                           "ipv4": {
+                              "address-allocation-type": "ietf-l3vpn-svc:static-address",
+                              "addresses": {
+                                 "provider-address": "128.32.33.254",
+                                 "customer-address": "128.32.33.2",
+                                 "prefix-length": 24
+                              }
+                           }
+                        },
+                        "service": {
+                           "svc-input-bandwidth": 1000000000,
+                           "svc-output-bandwidth": 1000000000,
+                           "svc-mtu": 1500,
+                           "qos": {
+                              "qos-profile": {
+                                 "classes": {
+                                    "class": [
+                                       {
+                                          "class-id": "qos-realtime",
+                                          "direction": "ietf-l3vpn-svc:both",
+                                          "latency": {
+                                             "latency-boundary": 10
+                                          },
+                                          "bandwidth": {
+                                             "guaranteed-bw-percent": 100.0
+                                          }
+                                       }
+                                    ]
+                                 }
+                              }
+                           }
+                        },
+                        "routing-protocols": {
+                           "routing-protocol": [
+                              {
+                                 "type": "ietf-l3vpn-svc:static",
+                                 "static": {
+                                    "cascaded-lan-prefixes": {
+                                       "ipv4-lan-prefixes": [
+                                          {
+                                             "lan": "172.1.201.0/24",
+                                             "next-hop": "128.32.33.254",
+                                             "lan-tag": "vlan31"
+                                          }
+                                       ]
+                                    }
+                                 }
+                              }
+                           ]
+                        },
+                        "vpn-attachment": {
+                           "vpn-id": "vpn2",
+                           "site-role": "ietf-l3vpn-svc:spoke-role"
+                        }
+                     }
+                  ]
+               }
+            },
+            {
+               "site-id": "site_POP",
+               "locations": {
+                  "location": [
+                     {
+                        "location-id": "POP"
+                     }
+                  ]
+               },
+               "devices": {
+                  "device": [
+                     {
+                        "device-id": "172.10.33.5",
+                        "location": "POP"
+                     }
+                  ]
+               },
+               "management": {
+                  "type": "ietf-l3vpn-svc:provider-managed"
+               },
+               "routing-protocols": {
+                  "routing-protocol": [
+                     {
+                        "type": "ietf-l3vpn-svc:static",
+                        "static": {
+                           "cascaded-lan-prefixes": {
+                              "ipv4-lan-prefixes": [
+                                 {
+                                    "lan": "172.1.201.0/24",
+                                    "next-hop": "172.10.33.2",
+                                    "lan-tag": "vlan201"
+                                 }
+                              ]
+                           }
+                        }
+                     }
+                  ]
+               },
+               "site-network-accesses": {
+                  "site-network-access": [
+                     {
+                        "site-network-access-id": "500",
+                        "site-network-access-type": "ietf-l3vpn-svc:multipoint",
+                        "device-reference": "172.10.33.5",
+                        "ip-connection": {
+                           "ipv4": {
+                              "address-allocation-type": "ietf-l3vpn-svc:static-address",
+                              "addresses": {
+                                 "provider-address": "172.10.33.254",
+                                 "customer-address": "172.10.33.2",
+                                 "prefix-length": 24
+                              }
+                           }
+                        },
+                        "service": {
+                           "svc-input-bandwidth": 1000000000,
+                           "svc-output-bandwidth": 1000000000,
+                           "svc-mtu": 1500,
+                           "qos": {
+                              "qos-profile": {
+                                 "classes": {
+                                    "class": [
+                                       {
+                                          "class-id": "qos-realtime",
+                                          "direction": "ietf-l3vpn-svc:both",
+                                          "latency": {
+                                             "latency-boundary": 10
+                                          },
+                                          "bandwidth": {
+                                             "guaranteed-bw-percent": 100.0
+                                          }
+                                       }
+                                    ]
+                                 }
+                              }
+                           }
+                        },
+                        "routing-protocols": {
+                           "routing-protocol": [
+                              {
+                                 "type": "ietf-l3vpn-svc:static",
+                                 "static": {
+                                    "cascaded-lan-prefixes": {
+                                       "ipv4-lan-prefixes": [
+                                          {
+                                             "lan": "128.32.10.0/24",
+                                             "next-hop": "172.10.33.254",
+                                             "lan-tag": "vlan201"
+                                          },
+                                          {
+                                             "lan": "128.32.20.0/24",
+                                             "next-hop": "172.10.33.254",
+                                             "lan-tag": "vlan201"
+                                          }
+                                       ]
+                                    }
+                                 }
+                              }
+                           ]
+                        },
+                        "vpn-attachment": {
+                           "vpn-id": "vpn2",
+                           "site-role": "ietf-l3vpn-svc:hub-role"
+                        }
+                     }
+                  ]
+               }
+            }
+         ]
+      }
+   }
+}
\ No newline at end of file
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/FunctionDefinition.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/FunctionDefinition.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a42d2578efd7194a713804d8fe6a44bea3ec36a
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/FunctionDefinition.py
@@ -0,0 +1,426 @@
+from typing import Dict, List
+
+
+class FunctionDefinition:
+        
+    #Metodo para leer el campo vpn-services
+    @staticmethod  
+    def readVpnServices(vpnServicesALeer: Dict) -> List:
+        # Acceder al campo 'vpn-service' que es un array de Dict con un campo vpn-id
+        vpn_service_list = vpnServicesALeer.get('vpn-service', [])#Utilizar get para manejar casos donde 'vpn-service' no está presente
+        vpn_ids = [] #Array con todos los ID de cada VPN
+        
+        # Iterar sobre los elementos de la lista
+        for vpn_serviceEnFor in vpn_service_list:
+            vpn_id : str = vpn_serviceEnFor.get('vpn-id') #obtengo vpn-id del json iterando
+            # baseDatos["vpn-id_" + str(i)] = vpn_id  # Utilizar f-strings para formatear las claves
+            vpn_ids.append(vpn_id)
+            
+        return vpn_ids
+    
+    #Metodo para escribir en la baseDatos lo leido en vpn-services
+    @staticmethod
+    def writeVpnServices(listaVPNs : List, baseDatos : Dict) -> Dict:
+        
+        baseDatos["VPN_IDs"] = listaVPNs
+        #baseDatos.update({"VPN_IDs": vpn_ids}) #ambas lineas son validas para añadir al Dict
+        baseDatos["VPNs_Dict"] = {}
+        #Obtengo cada vpn-id del array y lo meto en VPNs_Dict
+        for clave in baseDatos["VPN_IDs"]:
+            baseDatos["VPNs_Dict"][clave] = {}#ya tengo un Dict asociado a cada clave
+            
+        return baseDatos #devuelve la baseDatos pasada por argumento pero actualizada
+                    
+    #Lee el array site
+    @staticmethod
+    def readSiteArray(site: List, baseDatos: Dict) -> Dict:
+        print("Elementos en site: " + str(len(site)))
+        temp : Dict = {} #Diccionario temporal que guarda el ultimo site leido
+        temp2 : Dict = {} #Diccionario temporal que registra todos los sites leidos
+        lista_SitesID = [] #lista que almacenara los IDs de cada site del array
+        i = 1
+        for siteElement in site:
+            temp = FunctionDefinition.readSiteElement(lista_SitesID, siteElement) #Leo un site y me quedo con ID y Dict
+            temp2.update({lista_SitesID[-1] : temp}) # Almaceno esos datos en temp2
+            i += 1            
+       
+        res : Dict = {}
+        res["sitesIDs"] = lista_SitesID
+        res["sites_Dict"] = temp2 # A sites_Dict se le añaden todos los sites
+        return res
+            
+    #Lee un site
+    @staticmethod
+    def readSiteElement(listaID: List, siteElement: Dict) -> Dict:
+        res: Dict = {}
+        listaID.append(siteElement.get('site-id'))
+        res.update({"site-id": siteElement.get('site-id')})
+        res.update({"management": siteElement.get("management")}) #revistar si está bien
+        res.update({"devices": FunctionDefinition.readDevices(siteElement.get('devices'))})
+        res.update({"routing-protocol" : FunctionDefinition.readRoutingProtocols(siteElement.get('routing-protocols'))})
+        res.update({"site-network-accesses" : FunctionDefinition.readSiteNetworkAccesses(siteElement.get('site-network-accesses'))})
+        return res
+    #FALTAN MUCHOS CAMPOS A LEER EN site, como locations, site-diversity, maximum-routes, security, service, traffic-protection
+
+    #Lee array de Devices. Si hay más de un device, cambiar código
+    @staticmethod
+    def readDevices(devices: Dict) -> Dict:
+        res: Dict = {}
+        devices_list: List = devices.get('device', [])  # Cambié 'devices' por 'device' aquí
+        if len(devices_list) > 1:
+            i: int = 1
+            for eachDevice in devices_list:
+                res.update({f"device-id {i}": eachDevice.get('device-id')})
+                i += 1
+        elif len(devices_list) == 1:
+            res.update({"device-id": devices_list[0].get('device-id')})
+        return res
+
+    @staticmethod
+    def readRoutingProtocols(routingProtocols : Dict) -> Dict:
+        res : Dict = {} #todo el dict routing-protocols
+        rpArray : List = routingProtocols.get("routing-protocol") #el array routing-protocol           
+        res.update({"type":rpArray[0].get("type")})#escribo el tipo en el diccionario
+        tipoRoutingProtocol : str = rpArray[0].get("type")#obtiene string "ietf-l3vpn-svc:static"
+        tipoResumido : str = tipoRoutingProtocol.split(":")[1] #se queda con la palabra, ospf, bgp, static,... sin el ietf...
+        #print("El tipo de routingProtocol es: "+ str(tipoResumido))
+        def case1():            
+            ospfEntrada : Dict = rpArray[0].get("ospf",{}) #Funciona solo en caso de que el array tenga un elemento
+            ospfIntermedio : Dict = {}
+            ospfIntermedio.update({"address-family":ospfEntrada.get("address-family",{})})
+            ospfIntermedio.update({"area-address":ospfEntrada.get("area-address",{})})
+            ospfIntermedio.update({"metric":ospfEntrada.get("metric",{})})
+            arrayShamLinksEntrada : List = ospfEntrada.get("sham-links",[]) #el array
+            shamLinksFinal : Dict = {}
+            arrayShamLinksFinal : List = []
+            for elemento in arrayShamLinksEntrada:
+                shamLinksFinal.update({"target-site":elemento.get("target-site")})
+                shamLinksFinal.update({"metric":elemento.get("metric")})
+                arrayShamLinksFinal.append(shamLinksFinal)
+                
+            ospfIntermedio.update({"sham-links":shamLinksFinal})
+                        
+            res.update({"ospf":ospfIntermedio})
+        
+        def case2():
+            bgpEntrada : Dict = rpArray[0].get("bgp",{})
+            bgpSalida : Dict = {}
+            bgpSalida.update({"autonomous-system":bgpEntrada.get("autonomous-system")})
+            bgpSalida.update({"address-family":bgpEntrada.get("address-family")})
+            res.update({"bgp":bgpSalida})
+        
+        def case3(): #SIMPLEMENTE COPIA, NO LEE. A IMPLEMENTAR
+            cascadedDict: Dict = rpArray[0].get("static", {}).get("cascaded-lan-prefixes", {})
+            valueRoutingProtocol = cascadedDict
+            res.update({"static":valueRoutingProtocol})   
+            print("se ha copiado 'static' pero no se ha leido, falta implementar")        
+             
+        def case4():
+            ripEntrada : Dict = rpArray[0].get("rip",{})
+            ripSalida : Dict = {}
+            ripSalida.update({"address-family":ripEntrada.get("address-family")})
+            res.update({"rip":ripSalida})
+            
+        def case5():
+            vrrpEntrada : Dict = rpArray[0].get("vrrp",{})
+            vrrpSalida : Dict = {}
+            vrrpSalida.update({"address-family":vrrpEntrada.get("address-family")})
+            res.update({"vrrp":vrrpSalida})        
+        
+        def default():
+            #return ValueError(f"Tipo de protocolo no compatible: {tipoResumido}")
+            print("TIPO DE PROTOCOLO NO VALIDO")
+        
+        switch = {
+            "ospf": case1,
+            "bgp": case2,
+            "static": case3,
+            "rip":case4,
+            "vrrp": case5
+        }
+        #res.update({tipoResumido: switch.get(tipoResumido, default)()})
+        switch.get(tipoResumido)()
+        # print("_______________________d____________"+str(res))
+        return res
+    
+    
+    @staticmethod
+    def readSiteNetworkAccesses(siteNetworkAccesses : Dict) -> Dict:
+        siteNetworkAccessArray : List = siteNetworkAccesses.get("site-network-access")
+        res : Dict = {} 
+        arrayDeIDs : List = []       
+        for siteNetworkAccess in siteNetworkAccessArray:
+            variableIncial : str = siteNetworkAccess.get("site-network-access-id")
+            variableIncial = variableIncial.replace(" ", "-")#ESTO LO HAGO PARA QUE NO HAYA PROBLEMAS CON LOS ESPACIOS A LA HORA DE LEER EL JSON
+            arrayDeIDs.append(variableIncial)
+        res.update({"site-network-accesses-IDs":arrayDeIDs})
+        dictSiteNetworkAccesses : Dict = {}
+        
+        for site in arrayDeIDs:
+            dictDeCadaID : Dict = {}
+            
+            # try:
+            dictDeCadaID.update({"site-network-access-type":siteNetworkAccess.get("site-network-access-type")})
+            dictDeCadaID.update({"site-role":siteNetworkAccess.get("vpn-attachment", {}).get("site-role")}) 
+            #dictDeCadaID.update({"location-flavor" : FunctionDefinition.readLocationFlavor(siteNetworkAccess.get("location-flavor"))}) DE MOMENTO DA ERROR PORQUE ESE CAMPO NO EXISTE
+            #dictDeCadaID.update({"access-diversity" : FunctionDefinition.readAccessDiversity(siteNetworkAccess.get("access-diversity"))}) CAMPO NO EXISTE AUN
+            #dictDeCadaID.update({"bearer" : FunctionDefinition.readBearer(siteNetworkAccess.get("bearer"))}) CAMPO NO EXISTE AUN
+            dictDeCadaID.update({"ip-connection":FunctionDefinition.readIpConnection(siteNetworkAccess.get("ip-connection"))})
+            dictDeCadaID.update({"routing-protocols":FunctionDefinition.readRoutingProtocols(siteNetworkAccess.get("routing-protocols"))})
+            dictDeCadaID.update({"service":FunctionDefinition.readService(siteNetworkAccess.get("service"))})
+            # except NoJSONFieldException as e:
+            #     print(e)
+                
+            dictSiteNetworkAccesses.update({site:dictDeCadaID})
+        
+        res.update({"site-network-accesses-Dict": dictSiteNetworkAccesses})       
+        return res
+    
+    @staticmethod
+    def readLocationFlavor(locationFlavor : Dict) -> Dict:
+        res : Dict = {}
+        
+        if "location" in locationFlavor:
+            res.update({"location-reference":locationFlavor.get("location", {}).get("location-reference")})
+        elif "device" in locationFlavor:
+            res.update({"device-reference":locationFlavor.get("device", {}).get("device-reference")})
+        else:
+            raise NoJSONFieldException("location-flavor")
+            
+        return res
+        
+    @staticmethod
+    def readAccessDiversity(accessDiversity : Dict) -> Dict:
+        res : Dict = {}
+        arrayGroupsIni : List = accessDiversity.get("groups",{}).get("group") #obtengo el array de objetos
+        arrayGroupsIDs : List = []
+        for groupId in arrayGroupsIni:
+            arrayGroupsIDs.append(groupId.get("group-id"))             
+        res.update({"groups":arrayGroupsIDs})
+        
+        arrayConstraintsIni : List = accessDiversity.get("constraints", {}).get("constraint")
+        arrayConstraintsFin : List = []
+        for constraint in arrayConstraintsIni: #recordar que constraint es un objeto dentro del array arrayConstraintsIni
+            dictConstraint : Dict = {}
+            dictConstraint.update({"constraint-type" : constraint.get("constraint-type")})
+            dictTargetFlavor : Dict = constraint.get("target", {}).get("target-flavor")
+            
+            if "id" in dictTargetFlavor:
+                dictConstraint.update({"target-flavor": dictTargetFlavor.get("id")})
+            elif "all-accesses" in dictTargetFlavor:
+                dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-accesses")})
+            elif "all-groups" in dictTargetFlavor:
+                dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-groups")}) 
+            else:
+                raise NoJSONFieldException("read-access-divesity>constraints>constraint>target>target-flavor")               
+            arrayConstraintsFin.append(dictConstraint)
+            
+        res.update({"constraints":arrayConstraintsFin})       
+        return res
+
+    @staticmethod
+    def readBearer(bearer : Dict) -> Dict:
+        res : Dict = {}
+        
+        res.update({"always-on":bearer.get("always-on")})
+        res.update({"bearer-reference":bearer.get("bearer-reference")})
+        res.update({"requested-type":bearer.get("requested-type",{}).get("requested-type")})
+        res.update({"strict":bearer.get("requested-type",{}).get("strict")})       
+        
+        return res
+    
+    @staticmethod
+    def readIpConnection(ipConnection : Dict) -> Dict: #IPV4 E IPV6 TIENEN LOS MISMOS CAMPOS
+        res : Dict = {}
+        if ipConnection.get("ipv4") is not None:
+            res.update({"ipv4": FunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv4"))})
+        elif ipConnection.get("ipv6") is not None:
+            res.update({"ipv6":FunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv6"))}) 
+        else:
+            raise NoJSONFieldException("ip-connection>ipv4/6")
+            
+        bfd : Dict = ipConnection.get("oam",{}).get("bfd")
+        if bfd is not None:
+            oamFinal : Dict = {}
+            oamFinal.update({"enabled":bfd.get("enabled")})
+            holdtime : Dict = bfd.get("holdtime")
+            holdtimeFinal : Dict = {}
+            if "fixed" in holdtime:
+                holdtimeFinal.update({"fixed":holdtime.get("fixed")})
+            elif "profile" in holdtime:
+                holdtimeFinal.update({"profile":holdtime.get("profile")})
+            else:
+                raise NoJSONFieldException("oam>bfd>holdtime")
+            
+            oamFinal.update({"holdtime":holdtimeFinal})
+            res.update({"oam":oamFinal})
+        
+        return res
+    
+    @staticmethod
+    def readIpv4_Ipv6(ipv : Dict) -> Dict :
+        res : Dict = {}
+        
+        res.update({"address-allocation-type":ipv.get("address-allocation-type")})
+        
+        providerDHCP : Dict = ipv.get("provider-dhcp") #None si no existe ese campo, puede mejorarse eficiencia
+        if providerDHCP is not None:            
+            providerDHCPFinal : Dict = {}        
+            providerDHCPFinal.update({"provider-dhcp":providerDHCP.get("provider-address")})
+            providerDHCPFinal.update({"prefix-length":providerDHCP.get("prefix-lenght")})
+            addressAsign : Dict = providerDHCP.get("address-asign")
+            addressAsignFinal : Dict = {}
+            if "number" in addressAsign:
+                addressAsignFinal.update({"number":addressAsign.get("number")})
+            elif "explicit" in addressAsign:
+                addressAsignFinal.update({"explicit":addressAsign.get("explicit")})
+            else:
+                raise NoJSONFieldException("ipv4/6>address-asign")
+            
+            providerDHCPFinal.update({"address-asign":addressAsignFinal})         
+            res.update({"provider-dhcp":providerDHCPFinal})
+        
+        
+        dhcpRelay : Dict = ipv.get("dhcp-relay")
+        if dhcpRelay is not None:
+            dhcpRelayFinal : Dict = {}
+            dhcpRelayFinal.update({"provider-address":dhcpRelay.get("provider-address")})
+            dhcpRelayFinal.update({"prefix-lenght":dhcpRelay.get("prefix-lenght")})
+            dhcpRelayFinal.update({"server-ip-address":dhcpRelay.get("customer-dhcp-servers",{}).get("server-ip-address")})
+            res.update({"dhcp-relay":dhcpRelayFinal})
+        
+        addresses : Dict = ipv.get("addresses")
+        if addresses is not None:
+            addressesFinal : Dict = {}
+            addressesFinal.update({"provider-address":addresses.get("provider-address")})
+            addressesFinal.update({"customer-address":addresses.get("customer-address")})
+            addressesFinal.update({"prefixe-length":addresses.get("prefixe-length")})
+            res.update({"addresses":addressesFinal})
+        
+        return res
+    
+    # @staticmethod
+    # def readSecurity(security : Dict) -> Dict:
+    #     res : Dict = {}
+        
+    #     res.update({"authentication" : security.get("authentication")})
+    #     encryption : Dict = security.get("encryption")
+    #     encryptionFinal : Dict = {}
+        
+        
+    #     return res
+    
+    #readService todavía me da error
+    @staticmethod
+    def readService(service : Dict) -> Dict :
+        res : Dict = {}
+        #try:                
+        
+        res.update({"svc-input-bandwidth":service.get("svc-input-bandwidth")})
+        res.update({"svc-output-bandwidth":service.get("svc-output-bandwidth")})
+        res.update({"svc-mtu":service.get("svc-mtu")})
+        qos : Dict = service.get("qos")
+        ruleArrayIni : List = qos.get("qos-classification-policy")
+        ruleArrayFin : List = []
+        for elementRule in ruleArrayIni : 
+            elementRuleFin : Dict = {}
+            elementRuleFin.update({"id":elementRule.get("id")})
+            matchTypeIni : Dict = elementRule.get("match-type")
+            #CUIDADO, FALTAN MUCHOS CAMPOS DENTRO DE match-flow Y match-application POR PROGRAMAR
+            matchTypeFin : Dict = {}
+            if "match-flow" in matchTypeIni:
+                matchTypeFin.update({"match-flow":matchTypeIni})
+            elif "match-application" in matchTypeIni:
+                matchTypeFin.update({"match-application" : matchTypeIni})
+            #else:
+                #raise NoJSONFieldException("qos>qos-classification-policy>match-type")
+                
+            
+            elementRuleFin.update({"match-type":matchTypeFin})
+            elementRuleFin.update({"target-class-id":elementRule.get("target-class-id")})
+            ruleArrayFin.append(elementRuleFin)
+        res.update({"qos-classification-policy":ruleArrayFin})
+        
+        #Clase que lee el campo custom varias lineas mas adelante
+        
+        def readCustom (custom : Dict) -> Dict: #custom es un Dict que es: {classes: [class-id: *, ..., latency: {flavor:{lowest:*///boundary:*}}],[...],[...]}
+            res : Dict = {}
+                        
+            arrayClassesEntrada : List = custom.get("class") #un array con elementos indeterminados
+            arrayClassesFinal : List = []
+            
+            for elementEntrada in arrayClassesEntrada:
+                elementFinal : Dict = {}
+                elementFinal.update({"class-id":elementEntrada.get("class-id")})
+                elementFinal.update({"direction":elementEntrada.get("direction")})
+                elementFinal.update({"rate-limit":elementEntrada.get("rate-limit")})
+                #Campo latency--->
+                    #latency: {
+                    #     flavor:{
+                    #         lowest:*
+                    #         o quizás
+                    #         boundary:*
+                    #     }
+                    # }
+                                    
+                latency : Dict = elementEntrada.get("latency")
+                jitter : Dict = elementEntrada.get("jitter")
+                if latency is not None:
+                    if "use-lowest-latency" in latency:
+                        elementFinal.update({"latency-flavor-lowest":latency.get("use-lowest-latency")})
+                    elif "latency-boundary" in latency:
+                        elementFinal.update({"latency-flavor-boundary":latency.get("latency-boundary")})
+                elif jitter is not None:
+                    if "use-lowest-jitter" in jitter:
+                        elementFinal.update({"jitter-flavor-lowest":jitter.get("use-lowest-jiter")})
+                    elif "latency-boundary" in jitter:
+                        elementFinal.update({"jitter-flavor-boundary":jitter.get("latency-boundary")})
+                    else:
+                        print("Debe haber un campo lowest o boundary dentro de jitter>flavor")
+                else:
+                    print("FALTAN CAMPOS EN CUSTOM")
+                
+                bandwidthEntrada : Dict = elementEntrada.get("bandwidth")
+                bandwidthSalida : Dict = {}
+                bandwidthSalida.update({"guaranteed-bw-percent":bandwidthEntrada.get("guaranteed-bw-percent")})
+                bandwidthSalida.update({"end-to-end":bandwidthEntrada.get("end-to-end")})
+                elementFinal.update({"bandwidth":bandwidthSalida})
+                
+                arrayClassesFinal.append(elementFinal)
+                
+            res.update({"latency-flavor":arrayClassesFinal})
+            return res
+            #END READCUSTOM
+        
+        qosProfileEntrada : Dict = qos.get("qos-profile")
+        qosProfileFin : Dict = {}
+                
+        if "profile" in qosProfileEntrada:
+            qosProfileFin.update({"standard-profile":qosProfileEntrada.get("standard")})
+        elif "classes" in qosProfileEntrada:
+            qosProfileFin.update({"custom-classes":readCustom(qosProfileEntrada.get("classes"))})
+        else:
+            print("falta campo en qos>qos-profile")
+            #raise NoJSONFieldException("qos>qos-profile")
+                    
+        res.update({"qos-profile":qosProfileFin})
+        res.update({"carrierscarrier":qos.get("carrierscarrier")})
+        multicastEntrada : Dict = qos.get("multicast")
+        multicastSalida : Dict = {}
+        multicastSalida.update({"multicast-site-type":multicastEntrada.get("multicast-site-type")})
+        multicastAddressSalida : Dict = {}
+        multicastAddressSalida.update({"ipv4":multicastEntrada.get("multicast-address-family", {}).get("ipv4")})
+        multicastAddressSalida.update({"ipv6":multicastEntrada.get("multicast-address-family", {}).get("ipv6")})
+        multicastSalida.update({"multicast-address-family":multicastAddressSalida})
+        multicastSalida.update({"protocol-type":multicastEntrada.get("protocol-type")})
+        
+        res.update({"multicast":multicastSalida})
+            
+        # except KeyError:
+        #     print("falta alguna clave en service")
+        # except NoJSONFieldException as e:
+        #     print(e)        
+        # finally:  
+        return res
+    
+    
\ No newline at end of file
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py
index f7329cb35666f423e85f99510e5f89a82e89b7f8..1a87ec910262df5671fd681c4205c13b4a5678f6 100644
--- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import logging, netaddr
+import logging, netaddr, json
 from typing import Dict, List, Optional, Tuple
 from common.Constants import DEFAULT_CONTEXT_NAME
 from common.proto.context_pb2 import Service, ServiceStatusEnum, ServiceTypeEnum
@@ -26,6 +26,9 @@ from common.tools.grpc.EndPointIds import update_endpoint_ids
 from context.client.ContextClient import ContextClient
 from service.client.ServiceClient import ServiceClient
 
+from .ReadServiceFunctionDefinition import ReadServiceFunctionDefinition
+from .WriteNetworkFunctionDefinition import WriteNetworkFunctionDefinition
+
 LOGGER = logging.getLogger(__name__)
 
 def create_service(
@@ -209,3 +212,55 @@ def process_site(site : Dict, errors : List[Dict]) -> None:
     network_accesses : List[Dict] = site['site-network-accesses']['site-network-access']
     for network_access in network_accesses:
         process_site_network_access(site_id, network_access, site_static_routing, errors)
+        
+        
+def convert_l3sm_to_l3nm():
+    jsonRequest = open("/home/ubuntu/tfs-ctrl/src/nbi/tests/ietf_l3vpn_req_svc1.json", "r") #devuelve un file-object. "r" es por reading
+    output_file_path = "/home/ubuntu/tfs-ctrl/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json"
+    jsonRequestData : str = jsonRequest.read() #jsonRequestData es de tipo str. Para leer el contenido, usamos read()
+
+    jParse : Dict = json.loads(jsonRequestData) #jParse es de tipo Dict. esto va a parsear jsonRequetData.
+                        #cuando obtengamos el valor de una clave, y ese valor sea un array [], el tipo será 'list'
+                        #in order to convert a jsonObject to an string, you can use .dumps
+
+    ietf_l3vpn : Dict  = jParse['ietf-l3vpn-svc:l3vpn-svc']
+
+    vpn_services : Dict = ietf_l3vpn['vpn-services']
+    #print(vpn_services)
+    # vpn_service = vpn_services['vpn-service'] #ahora, al ser vpn-service un array, creo que la variable vpn_service es de tipo 'list'
+
+    # print(type(vpn_service)) #list
+    # print("VPN_SERVICE: " + str(vpn_service))
+    # print(vpn_service[0])
+    # print(vpn_service[0]['vpn-id'])
+
+    bd : Dict[any, any] = {} #Base datos de variables del JSON
+
+    #vpn_id = MiClase.readVpnServices(vpn_services) #vpn_id es variable de tipo List[str] con todas las vpn posibles
+
+    #try:
+    bd = ReadServiceFunctionDefinition.writeVpnServices( ReadServiceFunctionDefinition.readVpnServices(vpn_services) , bd)
+    #print(json.dumps(bd, indent=2))
+
+    sites : List = ietf_l3vpn['sites']['site'] #En sites se almacena el valor de la clave 'sites'.
+    #print(json.dumps(sites, indent=2))
+
+    #### PEQUEÑA CHAPUZA PARA CUANDO SOLO HAY UNA VPN ####
+    lista_VPNs : list = bd["VPN_IDs"]
+    bd["VPNs_Dict"][lista_VPNs[0]].update(ReadServiceFunctionDefinition.readSiteArray(sites, bd)) #vpn1.update Updatea el valor de vpn1
+    ####  #  ####
+
+
+    # except Exception as e: #En principio llegaran valueError o NoJSONFieldException
+    #     print(e)
+
+    #print(json.dumps(bd, indent=2))
+
+    #YANG SUITE ENTRADA, SERVICE ->creo que IETF-NBI-TEF-DECEMBER-23; ietf-l3vpn-svc; edit-config; none selected
+    #YANG SUITE SALIDA, RED (RFC9182) -> IETF-NBI-TEF-DECEMBER-23; ietf-l3vpn-ntw; edit-config; none selected
+    #Codigo para crear archivo .json de salida
+    with open(output_file_path, 'w') as json_file:
+        json.dump(bd, json_file, indent=3)
+        
+    jsonL3VPN_net : Dict = WriteNetworkFunctionDefinition.write()
+    # print(json.dumps(jsonL3VPN_net, indent = 3))
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Main.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Main.py
new file mode 100644
index 0000000000000000000000000000000000000000..d215a73ae373420011e18f95f2d02fe4b5b47ddd
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Main.py
@@ -0,0 +1,6 @@
+from src.nbi.service.rest_server.nbi_plugins.ietf_l3vpn import L3VPN_Services
+#from src.nbi.service.rest_server.nbi_plugins.ietf_l3vpn.L3VPN_Services import L3VPN_Services
+
+
+service : L3VPN_Services = L3VPN_Services()
+service.post()
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/ReadServiceFunctionDefinition.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/ReadServiceFunctionDefinition.py
new file mode 100644
index 0000000000000000000000000000000000000000..3218a18ddd2d5205c170723b2adaaadf323caddd
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/ReadServiceFunctionDefinition.py
@@ -0,0 +1,418 @@
+from typing import Dict, List
+
+class ReadServiceFunctionDefinition:
+        
+    #Metodo para leer el campo vpn-services
+    @staticmethod  
+    def readVpnServices(vpnServicesALeer: Dict) -> List:
+        # Acceder al campo 'vpn-service' que es un array de Dict con un campo vpn-id
+        vpn_service_list = vpnServicesALeer.get('vpn-service', [])#Utilizar get para manejar casos donde 'vpn-service' no está presente
+        vpn_ids = [] #Array con todos los ID de cada VPN
+        
+        # Iterar sobre los elementos de la lista
+        for vpn_serviceEnFor in vpn_service_list:
+            vpn_id : str = vpn_serviceEnFor.get('vpn-id') #obtengo vpn-id del json iterando
+            # baseDatos["vpn-id_" + str(i)] = vpn_id  # Utilizar f-strings para formatear las claves
+            vpn_ids.append(vpn_id)
+            
+        return vpn_ids
+    
+    #Metodo para escribir en la baseDatos lo leido en vpn-services
+    @staticmethod
+    def writeVpnServices(listaVPNs : List, baseDatos : Dict) -> Dict:
+        
+        baseDatos["VPN_IDs"] = listaVPNs
+        #baseDatos.update({"VPN_IDs": vpn_ids}) #ambas lineas son validas para añadir al Dict
+        baseDatos["VPNs_Dict"] = {}
+        #Obtengo cada vpn-id del array y lo meto en VPNs_Dict
+        for clave in baseDatos["VPN_IDs"]:
+            baseDatos["VPNs_Dict"][clave] = {}#ya tengo un Dict asociado a cada clave
+            
+        return baseDatos #devuelve la baseDatos pasada por argumento pero actualizada
+                    
+    #Lee el array site
+    @staticmethod
+    def readSiteArray(site: List, baseDatos: Dict) -> Dict:
+        print("Elementos en site: " + str(len(site)))
+        temp : Dict = {} #Diccionario temporal que guarda el ultimo site leido
+        temp2 : Dict = {} #Diccionario temporal que registra todos los sites leidos
+        lista_SitesID = [] #lista que almacenara los IDs de cada site del array
+        i = 1
+        for siteElement in site:
+            temp = ReadServiceFunctionDefinition.readSiteElement(lista_SitesID, siteElement) #Leo un site y me quedo con ID y Dict
+            temp2.update({lista_SitesID[-1] : temp}) # Almaceno esos datos en temp2
+            i += 1            
+        
+        res : Dict = {}
+        res["sitesIDs"] = lista_SitesID
+        res["sites_Dict"] = temp2 # A sites_Dict se le añaden todos los sites
+        return res
+            
+    #Lee un site
+    @staticmethod
+    def readSiteElement(listaID: List, siteElement: Dict) -> Dict:
+        res: Dict = {}
+        listaID.append(siteElement.get('site-id'))
+        res.update({"site-id": siteElement.get('site-id')})
+        res.update({"management": siteElement.get("management")}) #revistar si está bien
+        res.update({"locations":siteElement.get("locations")})
+        res.update({"devices": ReadServiceFunctionDefinition.readDevices(siteElement.get('devices'))})
+        res.update({"routing-protocol" : ReadServiceFunctionDefinition.readRoutingProtocols(siteElement.get('routing-protocols'))})
+        res.update({"site-network-accesses" : ReadServiceFunctionDefinition.readSiteNetworkAccesses(siteElement.get('site-network-accesses'))})
+        print("readSiteElement Finiquitaoooo")
+        return res
+    #FALTAN MUCHOS CAMPOS A LEER EN site, como site-diversity, maximum-routes, security, service, traffic-protection
+
+    #Lee array de Devices. Si hay más de un device, cambiar código
+    @staticmethod
+    def readDevices(devices: Dict) -> Dict:
+        res: Dict = {}
+        devices_list: List = devices.get('device', [])  # Cambié 'devices' por 'device' aquí
+        if len(devices_list) > 1:
+            i: int = 1
+            for eachDevice in devices_list:
+                res.update({f"device-id {i}": eachDevice.get('device-id')})
+                i += 1
+        elif len(devices_list) == 1:
+            res.update({"device-id": devices_list[0].get('device-id')})
+        return res
+
+    @staticmethod
+    def readRoutingProtocols(routingProtocols : Dict) -> Dict:
+        res : Dict = {} #todo el dict routing-protocols
+        rpArray : List = routingProtocols.get("routing-protocol") #el array routing-protocol           
+        res.update({"type":rpArray[0].get("type")})#escribo el tipo en el diccionario
+        tipoRoutingProtocol : str = rpArray[0].get("type")#obtiene string "ietf-l3vpn-svc:static"
+        tipoResumido : str = tipoRoutingProtocol.split(":")[1] #se queda con la palabra, ospf, bgp, static,... sin el ietf...
+        #print("El tipo de routingProtocol es: "+ str(tipoResumido))
+        def case1():            
+            ospfEntrada : Dict = rpArray[0].get("ospf",{}) #Funciona solo en caso de que el array tenga un elemento
+            ospfIntermedio : Dict = {}
+            ospfIntermedio.update({"address-family":ospfEntrada.get("address-family",{})})
+            ospfIntermedio.update({"area-address":ospfEntrada.get("area-address",{})})
+            ospfIntermedio.update({"metric":ospfEntrada.get("metric",{})})
+            arrayShamLinksEntrada : List = ospfEntrada.get("sham-links",[]) #el array
+            shamLinksFinal : Dict = {}
+            arrayShamLinksFinal : List = []
+            for elemento in arrayShamLinksEntrada:
+                shamLinksFinal.update({"target-site":elemento.get("target-site")})
+                shamLinksFinal.update({"metric":elemento.get("metric")})
+                arrayShamLinksFinal.append(shamLinksFinal)
+                
+            ospfIntermedio.update({"sham-links":shamLinksFinal})
+                        
+            res.update({"ospf":ospfIntermedio})
+        
+        def case2():
+            bgpEntrada : Dict = rpArray[0].get("bgp",{})
+            bgpSalida : Dict = {}
+            bgpSalida.update({"autonomous-system":bgpEntrada.get("autonomous-system")})
+            bgpSalida.update({"address-family":bgpEntrada.get("address-family")})
+            res.update({"bgp":bgpSalida})
+        
+        def case3(): #SIMPLEMENTE COPIA, NO LEE. A IMPLEMENTAR
+            cascadedDict: Dict = rpArray[0].get("static", {}).get("cascaded-lan-prefixes", {})
+            valueRoutingProtocol = cascadedDict
+            res.update({"static":valueRoutingProtocol})   
+            print("se ha copiado 'static' pero no se ha leido, falta implementar")        
+             
+        def case4():
+            ripEntrada : Dict = rpArray[0].get("rip",{})
+            ripSalida : Dict = {}
+            ripSalida.update({"address-family":ripEntrada.get("address-family")})
+            res.update({"rip":ripSalida})
+            
+        def case5():
+            vrrpEntrada : Dict = rpArray[0].get("vrrp",{})
+            vrrpSalida : Dict = {}
+            vrrpSalida.update({"address-family":vrrpEntrada.get("address-family")})
+            res.update({"vrrp":vrrpSalida})        
+        
+        def default():
+            #return ValueError(f"Tipo de protocolo no compatible: {tipoResumido}")
+            print("TIPO DE PROTOCOLO NO VALIDO")
+        
+        switch = {
+            "ospf": case1,
+            "bgp": case2,
+            "static": case3,
+            "rip":case4,
+            "vrrp": case5
+        }
+        #res.update({tipoResumido: switch.get(tipoResumido, default)()})
+        switch.get(tipoResumido)()
+        # print("_______________________d____________"+str(res))
+        print("read Routing Protocols finished")
+        return res
+    
+    
+    @staticmethod
+    def readSiteNetworkAccesses(siteNetworkAccesses : Dict) -> Dict:
+        siteNetworkAccessArray : List = siteNetworkAccesses.get("site-network-access")
+        res : Dict = {}
+        arrayDeIDs : List = []
+        for siteNetworkAccess in siteNetworkAccessArray:
+            variableIncial : str = siteNetworkAccess.get("site-network-access-id")
+            variableIncial = variableIncial.replace(" ", "-")#ESTO LO HAGO PARA QUE NO HAYA PROBLEMAS CON LOS ESPACIOS A LA HORA DE LEER EL JSON
+            arrayDeIDs.append(variableIncial)
+        res.update({"site-network-accesses-IDs":arrayDeIDs})
+        dictSiteNetworkAccesses : Dict = {}
+        
+        for site in arrayDeIDs:
+            dictDeCadaID : Dict = {}
+            
+            # try:
+            dictDeCadaID.update({"site-network-access-type":siteNetworkAccess.get("site-network-access-type")})
+            dictDeCadaID.update({"device-reference":siteNetworkAccess.get("device-reference")})
+            dictDeCadaID.update({"site-role":siteNetworkAccess.get("vpn-attachment", {}).get("site-role")}) 
+            #dictDeCadaID.update({"location-flavor" : FunctionDefinition.readLocationFlavor(siteNetworkAccess.get("location-flavor"))}) DE MOMENTO DA ERROR PORQUE ESE CAMPO NO EXISTE
+            #dictDeCadaID.update({"access-diversity" : FunctionDefinition.readAccessDiversity(siteNetworkAccess.get("access-diversity"))}) CAMPO NO EXISTE AUN
+            #dictDeCadaID.update({"bearer" : FunctionDefinition.readBearer(siteNetworkAccess.get("bearer"))}) CAMPO NO EXISTE AUN
+            dictDeCadaID.update({"ip-connection":ReadServiceFunctionDefinition.readIpConnection(siteNetworkAccess.get("ip-connection"))})
+            dictDeCadaID.update({"routing-protocols":ReadServiceFunctionDefinition.readRoutingProtocols(siteNetworkAccess.get("routing-protocols"))})
+            dictDeCadaID.update({"service":ReadServiceFunctionDefinition.readService(siteNetworkAccess.get("service"))})
+            # except NoJSONFieldException as e:
+            #     print(e)
+                
+            dictSiteNetworkAccesses.update({site:dictDeCadaID})
+        print("readSiteNetworkAccesses finiquitaoo")
+        res.update({"site-network-accesses-Dict": dictSiteNetworkAccesses})       
+        return res
+    
+    @staticmethod
+    def readLocationFlavor(locationFlavor : Dict) -> Dict:
+        res : Dict = {}
+        
+        if "location" in locationFlavor:
+            res.update({"location-reference":locationFlavor.get("location", {}).get("location-reference")})
+        elif "device" in locationFlavor:
+            res.update({"device-reference":locationFlavor.get("device", {}).get("device-reference")})
+        # else:
+            # raise NoJSONFieldException("location-flavor")
+            
+        return res
+        
+    @staticmethod
+    def readAccessDiversity(accessDiversity : Dict) -> Dict:
+        res : Dict = {}
+        arrayGroupsIni : List = accessDiversity.get("groups",{}).get("group") #obtengo el array de objetos
+        arrayGroupsIDs : List = []
+        for groupId in arrayGroupsIni:
+            arrayGroupsIDs.append(groupId.get("group-id"))             
+        res.update({"groups":arrayGroupsIDs})
+        
+        arrayConstraintsIni : List = accessDiversity.get("constraints", {}).get("constraint")
+        arrayConstraintsFin : List = []
+        for constraint in arrayConstraintsIni: #recordar que constraint es un objeto dentro del array arrayConstraintsIni
+            dictConstraint : Dict = {}
+            dictConstraint.update({"constraint-type" : constraint.get("constraint-type")})
+            dictTargetFlavor : Dict = constraint.get("target", {}).get("target-flavor")
+            
+            if "id" in dictTargetFlavor:
+                dictConstraint.update({"target-flavor": dictTargetFlavor.get("id")})
+            elif "all-accesses" in dictTargetFlavor:
+                dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-accesses")})
+            elif "all-groups" in dictTargetFlavor:
+                dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-groups")}) 
+            # else:
+            #     raise NoJSONFieldException("read-access-divesity>constraints>constraint>target>target-flavor")               
+            arrayConstraintsFin.append(dictConstraint)
+            
+        res.update({"constraints":arrayConstraintsFin})       
+        return res
+
+    @staticmethod
+    def readBearer(bearer : Dict) -> Dict:
+        res : Dict = {}
+        
+        res.update({"always-on":bearer.get("always-on")})
+        res.update({"bearer-reference":bearer.get("bearer-reference")})
+        res.update({"requested-type":bearer.get("requested-type",{}).get("requested-type")})
+        res.update({"strict":bearer.get("requested-type",{}).get("strict")})       
+        
+        return res
+    
+    @staticmethod
+    def readIpConnection(ipConnection : Dict) -> Dict: #IPV4 E IPV6 TIENEN LOS MISMOS CAMPOS
+        res : Dict = {}
+        if ipConnection.get("ipv4") is not None:
+            res.update({"ipv4": ReadServiceFunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv4"))})
+        elif ipConnection.get("ipv6") is not None:
+            res.update({"ipv6":ReadServiceFunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv6"))}) 
+        # else:
+        #     raise NoJSONFieldException("ip-connection>ipv4/6")
+            
+        bfd : Dict = ipConnection.get("oam",{}).get("bfd")
+        if bfd is not None:
+            oamFinal : Dict = {}
+            oamFinal.update({"enabled":bfd.get("enabled")})
+            holdtime : Dict = bfd.get("holdtime")
+            holdtimeFinal : Dict = {}
+            if "fixed" in holdtime:
+                holdtimeFinal.update({"fixed":holdtime.get("fixed")})
+            elif "profile" in holdtime:
+                holdtimeFinal.update({"profile":holdtime.get("profile")})
+            # else:
+            #     raise NoJSONFieldException("oam>bfd>holdtime")
+            
+            oamFinal.update({"holdtime":holdtimeFinal})
+            res.update({"oam":oamFinal})
+        
+        return res
+    
+    @staticmethod
+    def readIpv4_Ipv6(ipv : Dict) -> Dict :
+        res : Dict = {}
+        
+        res.update({"address-allocation-type":ipv.get("address-allocation-type")})
+        
+        providerDHCP : Dict = ipv.get("provider-dhcp") #None si no existe ese campo, puede mejorarse eficiencia
+        if providerDHCP is not None:            
+            providerDHCPFinal : Dict = {}        
+            providerDHCPFinal.update({"provider-dhcp":providerDHCP.get("provider-address")})
+            providerDHCPFinal.update({"prefix-length":providerDHCP.get("prefix-lenght")})
+            addressAsign : Dict = providerDHCP.get("address-asign")
+            addressAsignFinal : Dict = {}
+            if "number" in addressAsign:
+                addressAsignFinal.update({"number":addressAsign.get("number")})
+            elif "explicit" in addressAsign:
+                addressAsignFinal.update({"explicit":addressAsign.get("explicit")})
+            # else:
+            #     raise NoJSONFieldException("ipv4/6>address-asign")
+            
+            providerDHCPFinal.update({"address-asign":addressAsignFinal})         
+            res.update({"provider-dhcp":providerDHCPFinal})
+        
+        
+        dhcpRelay : Dict = ipv.get("dhcp-relay")
+        if dhcpRelay is not None:
+            dhcpRelayFinal : Dict = {}
+            dhcpRelayFinal.update({"provider-address":dhcpRelay.get("provider-address")})
+            dhcpRelayFinal.update({"prefix-lenght":dhcpRelay.get("prefix-lenght")})
+            dhcpRelayFinal.update({"server-ip-address":dhcpRelay.get("customer-dhcp-servers",{}).get("server-ip-address")})
+            res.update({"dhcp-relay":dhcpRelayFinal})
+        
+        addresses : Dict = ipv.get("addresses")
+        if addresses is not None:
+            addressesFinal : Dict = {}
+            addressesFinal.update({"provider-address":addresses.get("provider-address")})
+            addressesFinal.update({"customer-address":addresses.get("customer-address")})
+            addressesFinal.update({"prefixe-length":addresses.get("prefixe-length")})
+            res.update({"addresses":addressesFinal})
+        
+        return res
+    
+    # @staticmethod
+    # def readSecurity(security : Dict) -> Dict:
+    #     res : Dict = {}
+        
+    #     res.update({"authentication" : security.get("authentication")})
+    #     encryption : Dict = security.get("encryption")
+    #     encryptionFinal : Dict = {}
+        
+        
+    #     return res
+    
+    #readService todavía me da error
+    @staticmethod
+    def readService(service : Dict) -> Dict :
+        res : Dict = {}
+        #try:                
+        res.update({"svc-mtu":service.get("svc-mtu")})
+        res.update({"svc-input-bandwidth":service.get("svc-input-bandwidth")})
+        res.update({"svc-output-bandwidth":service.get("svc-output-bandwidth")})
+        qos : Dict = service.get("qos")
+        
+        class_id = qos["qos-profile"]["classes"]["class"][0]["class-id"]
+        direction = qos["qos-profile"]["classes"]["class"][0]["direction"]
+        latency_boundary = qos["qos-profile"]["classes"]["class"][0]["latency"]["latency-boundary"]
+        guaranteed_bw_percent = qos["qos-profile"]["classes"]["class"][0]["bandwidth"]["guaranteed-bw-percent"]
+
+        res.update({"class-id":class_id})
+        res.update({"direction":direction})
+        res.update({"latency-boundary":latency_boundary})
+        res.update({"guaranteed-bw-percent":guaranteed_bw_percent})
+        
+        #Clase que lee el campo custom varias lineas mas adelante
+        
+        def readCustom (custom : Dict) -> Dict: #custom es un Dict que es: {classes: [class-id: *, ..., latency: {flavor:{lowest:*///boundary:*}}],[...],[...]}
+            res : Dict = {}
+                        
+            arrayClassesEntrada : List = custom.get("class") #un array con elementos indeterminados
+            arrayClassesFinal : List = []
+            
+            for elementEntrada in arrayClassesEntrada:
+                elementFinal : Dict = {}
+                elementFinal.update({"class-id":elementEntrada.get("class-id")})
+                elementFinal.update({"direction":elementEntrada.get("direction")})
+                elementFinal.update({"rate-limit":elementEntrada.get("rate-limit")})
+                #Campo latency--->
+                    #latency: {
+                    #     flavor:{
+                    #         lowest:*
+                    #         o quizás
+                    #         boundary:*
+                    #     }
+                    # }
+                                    
+                latency : Dict = elementEntrada.get("latency")
+                jitter : Dict = elementEntrada.get("jitter")
+                if latency is not None:
+                    if "use-lowest-latency" in latency:
+                        elementFinal.update({"latency-flavor-lowest":latency.get("use-lowest-latency")})
+                    elif "latency-boundary" in latency:
+                        elementFinal.update({"latency-flavor-boundary":latency.get("latency-boundary")})
+                elif jitter is not None:
+                    if "use-lowest-jitter" in jitter:
+                        elementFinal.update({"jitter-flavor-lowest":jitter.get("use-lowest-jiter")})
+                    elif "latency-boundary" in jitter:
+                        elementFinal.update({"jitter-flavor-boundary":jitter.get("latency-boundary")})
+                    else:
+                        print("Debe haber un campo lowest o boundary dentro de jitter>flavor")
+                else:
+                    print("FALTAN CAMPOS EN CUSTOM")
+                
+                bandwidthEntrada : Dict = elementEntrada.get("bandwidth")
+                bandwidthSalida : Dict = {}
+                bandwidthSalida.update({"guaranteed-bw-percent":bandwidthEntrada.get("guaranteed-bw-percent")})
+                bandwidthSalida.update({"end-to-end":bandwidthEntrada.get("end-to-end")})
+                elementFinal.update({"bandwidth":bandwidthSalida})
+                
+                arrayClassesFinal.append(elementFinal)
+                
+        #     res.update({"latency-flavor":arrayClassesFinal})
+        #     return res
+        #     #END READCUSTOM
+        
+        # qosProfileEntrada : Dict = qos.get("qos-profile")
+        # qosProfileFin : Dict = {}
+                
+        # if "profile" in qosProfileEntrada:
+        #     qosProfileFin.update({"standard-profile":qosProfileEntrada.get("standard")})
+        # elif "classes" in qosProfileEntrada:
+        #     qosProfileFin.update({"custom-classes":readCustom(qosProfileEntrada.get("classes"))})
+        # else:
+        #     print("falta campo en qos>qos-profile")
+        #     #raise NoJSONFieldException("qos>qos-profile")
+                    
+        # res.update({"qos-profile":qosProfileFin})
+        # res.update({"carrierscarrier":qos.get("carrierscarrier")})
+        # multicastEntrada : Dict = qos.get("multicast")
+        # multicastSalida : Dict = {}
+        # multicastSalida.update({"multicast-site-type":multicastEntrada.get("multicast-site-type")})
+        # multicastAddressSalida : Dict = {}
+        # multicastAddressSalida.update({"ipv4":multicastEntrada.get("multicast-address-family", {}).get("ipv4")})
+        # multicastAddressSalida.update({"ipv6":multicastEntrada.get("multicast-address-family", {}).get("ipv6")})
+        # multicastSalida.update({"multicast-address-family":multicastAddressSalida})
+        # multicastSalida.update({"protocol-type":multicastEntrada.get("protocol-type")})
+        
+        # res.update({"multicast":multicastSalida})
+            
+        # # except KeyError:
+        # #     print("falta alguna clave en service")
+        # # except NoJSONFieldException as e:
+        # #     print(e)        
+        # # finally:  
+        return res
+    
+    
\ No newline at end of file
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/WriteNetworkFunctionDefinition.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/WriteNetworkFunctionDefinition.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c22de56dee9140467fb747c4d43db2a3ab25bcc
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/WriteNetworkFunctionDefinition.py
@@ -0,0 +1,288 @@
+from typing import Dict, List
+import json, os
+
+class WriteNetworkFunctionDefinition:
+    
+    input_file_path : str ="/home/ubuntu/Documents/EstructuraIntermedia.json"
+    output_file_path = "/home/ubuntu/Documents/L3VPN_ntw.json"
+    
+    jsonEstructuraIntermedia = open(input_file_path, "r")
+    jsonEstructuraIntermediaData : str = jsonEstructuraIntermedia.read()
+    jsonEstIntDict : Dict = json.loads(jsonEstructuraIntermediaData)    
+    
+    @staticmethod
+    def numberVpnIDs() -> int :
+        vpnIDs : List = WriteNetworkFunctionDefinition.jsonEstIntDict["VPN_IDs"]
+        return len(vpnIDs)
+    
+    #Este metodo devuelve el numero de sites, y es necesario porque el numero de sites es el numero de nodes
+    @staticmethod
+    def numberSitesIDS() -> int :
+        sitesIDs : List = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sitesIDs"]
+        return len(sitesIDs)
+            
+    @staticmethod
+    def write() -> Dict: 
+        
+        jsonNet : Dict = {}
+        jsonNet.update({"vpn-service":{}}) #no tenemos en cuenta que puede haber más de 1 VPN. Codigo valido para 1 VPN solo
+        vpn_id : str = WriteNetworkFunctionDefinition.jsonEstIntDict["VPN_IDs"][0].split("vpn")[1] #obtengo el numero de vpn_id
+        jsonNet["vpn-service"]["vpn-id"] = vpn_id #de momento solo 1 vpn
+        # jsonNet["vpn-service"]["customer-name"] = "_"
+        # jsonNet["vpn-service"]["parent-service-id"] = "_"
+        jsonNet["vpn-service"]["vpn-type"] = "L3VRF"
+        jsonNet["vpn-service"]["vpn-service-topology"] = "custom" #modificar todos estos campos
+        jsonNet["vpn-service"]["vpn-nodes"] = {}
+        
+        arrayVpnNode : List = []
+        print(WriteNetworkFunctionDefinition.numberSitesIDS())
+        for numeroDeNode in range(WriteNetworkFunctionDefinition.numberSitesIDS()):
+          
+            sites : List = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sitesIDs"]
+            
+            site: str = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sitesIDs"][numeroDeNode]
+            service_access: str = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sites_Dict"][sites[numeroDeNode]]["site-network-accesses"]["site-network-accesses-IDs"][0]
+            
+            #con los siguientes ifs hago que las politicas cambien de lugar, uno si y uno no constantemente
+            if numeroDeNode % 2 is 0:
+                arrayVpnNode.append(WriteNetworkFunctionDefinition.writeNode(WriteNetworkFunctionDefinition.jsonEstIntDict,site,service_access,numeroDeNode, 0))
+            if numeroDeNode % 2 is not 0:
+                arrayVpnNode.append(WriteNetworkFunctionDefinition.writeNode(WriteNetworkFunctionDefinition.jsonEstIntDict,site,service_access,numeroDeNode, 1))
+
+        jsonNet["vpn-service"]["vpn-nodes"]["vpn-node"] = arrayVpnNode
+                
+        #Codigo para crear el archivo y escribir el json en el
+        with open(WriteNetworkFunctionDefinition.output_file_path, 'w') as json_file:
+            json.dump(jsonNet, json_file, indent = 3)
+            
+        return jsonNet
+    
+    def writeNode(jsonEstDict : Dict, site: str, service_access : str, numeroDeNode : int, importExport : int) -> Dict:
+        res: Dict = {}
+        res.update({"ne-id": "pe" + str(numeroDeNode+1)})
+        res.update({"vpn-node-id": "PE" + str(numeroDeNode+1)})
+        res.update({"local-as":"65000"})
+        res.update({"router-id":"5.5.5.5"})
+        
+        #active-vpn-instance-profiles
+        activeVpnInstanceProfiles: Dict = {}
+        vpnInstanceProfilesArray: List = []
+        vpnInstanceProfilesArrayObject: Dict = {}
+        
+        vpnInstanceProfilesArrayObject.update({"profile-id": "1"})
+        routerID : Dict = {}
+        
+        #Pregutnar sobre si route distinguisher debe cambiar por cada node
+        directlyAsigned : Dict = {"rd": str(res.get("local-as")) + ":100"}
+        routerID.update({"directly-assigned":directlyAsigned})
+        vpnInstanceProfilesArrayObject.update({"router-id":routerID})
+        
+        addressFamily : List = []
+        addressFamilyElement : Dict = {}
+        addressFamilyElement.update({"address-family":""})
+        
+        vpnTargets: Dict = {}
+        vpnTarget: List = []
+        vpnTargetObject: Dict = {}
+        vpnTargetObject.update({"id": str(numeroDeNode+1)})
+        
+        routeTargets: List = []
+        routeTargetsElement: Dict = {}
+        routeTargetsElement.update({"route-target": str(res.get("local-as"))+":222"}) #Any to any
+        routeTargets.append(routeTargetsElement)
+        
+        vpnTargetObject.update({"route-targets": routeTargets})
+        vpnTargetObject.update({"route-target-type": "both"}) #Any to any
+        
+        vpnTarget.append(vpnTargetObject)      
+        vpnTargets.update({"vpn-target": vpnTarget})
+        
+        vpnPolicies: Dict = {}
+        #Codigo que en metodo write() hace que un nodo si y uno no vayan modificando el orden de politicas
+        if importExport is 0:
+            vpnPolicies.update({"import-policy":"politica 1"})
+            vpnPolicies.update({"export-policy":"politica 2"})
+        else:
+            vpnPolicies.update({"import-policy":"politica 2"})
+            vpnPolicies.update({"export-policy":"politica 1"})
+        vpnTargets.update({"vpn-policies":vpnPolicies})
+        
+        addressFamilyElement.update({"vpn-targets":vpnTargets})
+        addressFamily.append(addressFamilyElement)
+
+        vpnInstanceProfilesArrayObject.update({"address-family": addressFamily})
+        vpnInstanceProfilesArray.append(vpnInstanceProfilesArrayObject)        
+        activeVpnInstanceProfiles.update({"vpn-instance-profile": vpnInstanceProfilesArray})        
+        res.update({"active-vpn-instance-profiles": activeVpnInstanceProfiles})
+        
+        #vpn-network-accesses
+        vpnNetworkAccesses : Dict = {}
+        vpnNetworkAccessArray : List = []
+        vpnNetworkAccessElement : Dict = {}
+        vpnNetworkAccessElement.update({"id":"1-1-1"})
+        vpnNetworkAccessElement.update({"interface-id":"1-1-1"})
+        vpnNetworkAccessElement.update({"vpn-network-access-type":"l3ipvlan"})
+        vpnNetworkAccessElement.update({"vpn-instance-profiles":""})
+        
+        status : Dict = {
+            "admin-status":{
+                "status":"admin-up",
+                "last-change":""
+            },
+            "oper-status":{
+                "status":"",
+                "last-change":""
+            }           
+        }
+        vpnNetworkAccessElement.update({"status":status})
+        vpnNetworkAccessElement.update({"connection":WriteNetworkFunctionDefinition.writeConnection()})
+                      
+        vpn_id : str = jsonEstDict["VPN_IDs"][0]#valido cuando solo hay una VPN.
+                                                                                                                                                                            #de momento no IPv6, campo vacio, solo IPv4
+        provider_address: str = jsonEstDict["VPNs_Dict"][vpn_id]["sites_Dict"][site]["site-network-accesses"]["site-network-accesses-Dict"][service_access]["ip-connection"]["ipv4"]["addresses"]["provider-address"]
+        prefixe_length : int = jsonEstDict["VPNs_Dict"][vpn_id]["sites_Dict"][site]["site-network-accesses"]["site-network-accesses-Dict"][service_access]["ip-connection"]["ipv4"]["addresses"]["prefixe-length"]
+        customer_address : str = jsonEstDict["VPNs_Dict"][vpn_id]["sites_Dict"][site]["site-network-accesses"]["site-network-accesses-Dict"][service_access]["ip-connection"]["ipv4"]["addresses"]["customer-address"]
+        
+        ipConnectionAndRoutingProtocols : Dict = {
+            "ip-connection":{
+                "l3-termination-point":"1-1-1.222",
+                "ipv4":{
+                    "local-address":provider_address,
+                    "prefixe-length":prefixe_length,
+                    "address-allocation-type":"static-address",
+                    "static-addresses":{
+                        "primary-addresses":"1",
+                        "address":[
+                            {
+                                "address-id":"1",
+                                "customer-address":customer_address
+                            }
+                        ]
+                    }
+                },
+                "ipv6":{
+                    "local-address":"",
+                    "prefixe-lenght":"",
+                    "address-allocation-type":"static-address",
+                    "static-addresses":{
+                        "primary-addresses":"1",
+                        "address":[
+                            {
+                                "address-id":"1",
+                                "customer-address":""
+                            }
+                        ]
+                    }
+                }
+            },
+            "routing-protocols":{
+                "routing-protocol":[
+                    {
+                        "id":"ver con oscar",
+                        "type":"static-routing",
+                        "routing-profiles":[
+                            {
+                                "id":"",
+                                "type":""
+                                
+                            }
+                        ],
+                        "static":{
+                            "cascaded-lan-prefixes":{
+                                "ipv4-lan-prefixes":[
+                                   {
+                                    "lan":"",
+                                    "next-hop":"",
+                                    "lan-tag":"",
+                                    "bfd-enable":"",
+                                    "metric":"",
+                                    "preference":"",
+                                    "status":{
+                                        "admin-status":{
+                                            "status":"",
+                                            "last-change":""
+                                        },
+                                        "oper-status":{
+                                            "status":"",
+                                            "last-change":""
+                                        }
+                                    },
+                                    "tef-l3vpn-ntw:description":""
+                                   }                     
+                                ],
+                                "ipv6-lan-prefixes":[
+                                   {
+                                    "lan":"",
+                                    "next-hop":"",
+                                    "lan-tag":"",
+                                    "bfd-enable":"",
+                                    "metric":"",
+                                    "preference":"",
+                                    "status":{
+                                        "admin-status":{
+                                            "status":"",
+                                            "last-change":""
+                                        },
+                                        "oper-status":{
+                                            "status":"",
+                                            "last-change":""
+                                        }
+                                    },
+                                    "tef-l3vpn-ntw:description":""
+                                   }                     
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+        
+        vpnNetworkAccessElement.update(ipConnectionAndRoutingProtocols)
+        
+        vpnNetworkAccessArray.append(vpnNetworkAccessElement)
+        vpnNetworkAccesses.update({"vpn-network-access":vpnNetworkAccessArray})
+        res.update({"vpn-network-accesses":vpnNetworkAccesses})        
+        return res
+
+
+    #Falta "l2vpn-id":"", que es la otra choice entre l2vpn-id y l2-tunnel-service
+    def writeConnection() -> Dict:
+        res : Dict = {            
+            "encapsulation":{
+                "type":"untagged"
+                # ,
+                # "dot1q":{
+                #     "tag-type":"cvlan",
+                #     "cvlan-id":"222"
+                # }
+                # ,
+                # "priotity-tagged":{
+                #     "tag-type":""
+                # },
+                # "quinq":{
+                #     "tag-type":"",
+                #     "svlan-id":"",
+                #     "cvlan-id":""
+                # }                                    
+            }
+            # ,            
+            # "l2-tunnel-service":{
+            #     "type":"",
+            #     "pseudowire":{
+            #         "vcid":"",
+            #         "far-end":""
+            #     },
+            #     "vpls":{
+            #         "vcid":"",
+            #         "far-end":""
+            #     },
+            #     "vxlan":{
+            #         "vni-id":"",
+            #         "peer-mode":"",
+            #         "peer-ip-address":""
+            #     }
+            # }
+        }
+        
+        return res
\ No newline at end of file
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/orignal_L3VPN_Services b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/orignal_L3VPN_Services
new file mode 100644
index 0000000000000000000000000000000000000000..6bd57c8238c1af63ed3f504593f3c70cf8a68cc6
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/orignal_L3VPN_Services
@@ -0,0 +1,80 @@
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+from typing import Dict, List
+from flask import request
+from flask.json import jsonify
+from flask_restful import Resource
+from werkzeug.exceptions import UnsupportedMediaType
+from nbi.service.rest_server.nbi_plugins.tools.HttpStatusCodes import HTTP_CREATED, HTTP_SERVERERROR
+from nbi.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH
+from .Handlers import process_site, process_vpn_service
+from .YangValidator import YangValidator
+
+LOGGER = logging.getLogger(__name__)
+
+class L3VPN_Services(Resource):
+    @HTTP_AUTH.login_required
+    def get(self):
+        return {}
+
+    @HTTP_AUTH.login_required
+    def post(self):
+        if not request.is_json: raise UnsupportedMediaType('JSON payload is required')
+        request_data : Dict = request.json
+        LOGGER.debug('Request: {:s}'.format(str(request_data)))
+
+        errors = list()
+        if 'ietf-l3vpn-svc:l3vpn-services' in request_data:
+            # processing multiple L3VPN service requests formatted as:
+            #{
+            #  "ietf-l3vpn-svc:l3vpn-services": {
+            #    "l3vpn-svc": [
+            #      {
+            #        "service-id": "vpn1",
+            #        "vpn-services": {
+            #          "vpn-service": [
+            for l3vpn_svc in request_data['ietf-l3vpn-svc:l3vpn-services']['l3vpn-svc']:
+                l3vpn_svc.pop('service-id', None)
+                l3vpn_svc_request_data = {'ietf-l3vpn-svc:l3vpn-svc': l3vpn_svc}
+                errors.extend(self._process_l3vpn(l3vpn_svc_request_data))
+        elif 'ietf-l3vpn-svc:l3vpn-svc' in request_data:
+            # processing single (standard) L3VPN service request formatted as:
+            #{
+            #  "ietf-l3vpn-svc:l3vpn-svc": {
+            #    "vpn-services": {
+            #      "vpn-service": [
+            errors.extend(self._process_l3vpn(request_data))
+        else:
+            errors.append('unexpected request: {:s}'.format(str(request_data)))
+
+        response = jsonify(errors)
+        response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR
+        return response
+
+    def _process_l3vpn(self, request_data : Dict) -> List[Dict]:
+        yang_validator = YangValidator('ietf-l3vpn-svc')
+        request_data = yang_validator.parse_to_dict(request_data)
+        yang_validator.destroy()
+
+        errors = list()
+
+        for vpn_service in request_data['l3vpn-svc']['vpn-services']['vpn-service']:
+            process_vpn_service(vpn_service, errors)
+
+        for site in request_data['l3vpn-svc']['sites']['site']:
+            process_site(site, errors)
+
+        return errors
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/run_tests_locally-nbi-ietf-l3vpn.sh b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/run_tests_locally-nbi-ietf-l3vpn.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0bd133e16caac438c8da1a5795fce89e99230c9a
--- /dev/null
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/run_tests_locally-nbi-ietf-l3vpn.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+PROJECTDIR=`pwd`
+
+cd $PROJECTDIR/src
+RCFILE=$PROJECTDIR/coverage/.coveragerc
+
+# Run unitary tests and analyze coverage of code at same time
+# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0
+coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \
+    nbi/tests/test_ietf_l3vpn.py
diff --git a/src/nbi/tests/test_ietf_l3vpn.py b/src/nbi/tests/test_ietf_l3vpn.py
index 5c77744a6024803157f210212ee071eba3a3fbf1..95fdf095f9516be7c2fe924dbc35928e2f3cba07 100644
--- a/src/nbi/tests/test_ietf_l3vpn.py
+++ b/src/nbi/tests/test_ietf_l3vpn.py
@@ -12,7 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json, logging, pytest
+import json, logging
+import pytest
 from typing import Dict
 from common.Constants import DEFAULT_CONTEXT_NAME
 from common.proto.context_pb2 import ContextId
diff --git a/src/service/service/service_handler_api/SettingsHandler.py b/src/service/service/service_handler_api/SettingsHandler.py
index 293de54aa84be11f3c31bc1b47fce852df19a16a..2afa4cbbf4a8b2418aec16df99615e26a8ecbfba 100644
--- a/src/service/service/service_handler_api/SettingsHandler.py
+++ b/src/service/service/service_handler_api/SettingsHandler.py
@@ -47,6 +47,12 @@ class SettingsHandler:
             ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/index[{:d}]/acl_ruleset[{:s}]'
             key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_name,endpoint_index, acl_ruleset_name)
             value = grpc_message_to_json(config_rule.acl)
+        elif kind == 'ip_link':
+            device_uuid = config_rule.ip_link.device1
+            endpoint_uuid = config_rule.ip_link.interface
+            IP_LINK_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/ip_link'
+            key_or_path = IP_LINK_KEY_TEMPLATE.format(device_uuid, endpoint_uuid,)
+            value = config_rule.ip_link
         else:
             MSG = 'Unsupported Kind({:s}) in ConfigRule({:s})'
             LOGGER.warning(MSG.format(str(kind), grpc_message_to_json_string(config_rule)))
@@ -100,6 +106,25 @@ class SettingsHandler:
                     if not 'index[{:d}]'.format(acl_index) in res_key: continue
                     acl_rules.append((res_key, res_value))
         return acl_rules
+    
+    def get_endpoint_ip_link(self, device : Device, endpoint : EndPoint) -> List [Tuple]:
+        endpoint_name = endpoint.name
+        device_keys   = device.device_id.device_uuid.uuid,       device.name
+        endpoint_keys = endpoint.endpoint_id.endpoint_uuid.uuid, endpoint.name
+        ip_links = []
+        for device_key in device_keys:
+            for endpoint_key in endpoint_keys:
+                endpoint_settings_uri = '/device[{:s}]/endpoint[{:s}]'.format(device_key, endpoint_key)
+                endpoint_settings = self.get(endpoint_settings_uri)
+                if endpoint_settings is None: continue  
+                IP_LINK_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/'.format(device_key, endpoint_name)
+                
+                results = dump_subtree(endpoint_settings)
+                for res_key, res_value in results: 
+                    if not res_key.startswith(IP_LINK_KEY_TEMPLATE): continue
+                    if not "ip_link" in res_key: continue
+                    ip_links.append((res_key, res_value))
+        return None
 
     def set(self, key_or_path : Union[str, List[str]], value : Any) -> None:
         set_subnode_value(self.__resolver, self.__config, key_or_path, value)
diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py
index 52944a31c439472055d65e9e75249465dcbca7f7..84c561737d7582bdf6b7fe1a4b622de1b24a2f93 100644
--- a/src/webui/service/main/routes.py
+++ b/src/webui/service/main/routes.py
@@ -48,7 +48,7 @@ def process_descriptors(descriptors):
     descriptor_loader = DescriptorLoader(descriptors, num_workers=DESCRIPTOR_LOADER_NUM_WORKERS)
     results = descriptor_loader.process()
     for message,level in compose_notifications(results):
-        if level == 'error': LOGGER.warning('ERROR message={:s}'.format(str(message)))
+        if level == 'error': LOGGER.warning('ERROR message servicio ={:s}, {:s}'.format(str(message), str(descriptors)))
         flash(message, level)
 
 @main.route('/', methods=['GET', 'POST'])