# Interoperabilidad Multi-Agente con AfexFields ## Resumen El sistema de *afexFields* permite que una transferencia sea procesada por múltiples agentes mediante transformación dinámica de campos. Cada agente puede tener sus propios formatos y requisitos específicos. Como concepto, cuando hablamos de **afexFields** nos referimos al uso de los campos `afexId` y `afexValue` en las configuraciones de los agentes, que permiten mapear los valores estándar de AFEX a los formatos específicos requeridos por cada agente. ## Componentes - **TransferAPI** - Maneja transferencias y solicita transformaciones - **AgentAPI** - Especializado en transformar campos por agente - **AgentFieldsService** - Servicio contenido en **AgentAPI** responsable de ejecutar la lógica de transformación ### Flujo de Datos ``` Transfer API → Agent API → AgentFieldsService → Transformación → Respuesta ``` --- ## Configuración de Campos Cada agente define sus propias configuraciones, basadas en cada país y método de pago. Estas configuraciones se almacenan en la tabla `agent-fields-[STAGE]` de cada ambiente en *DynamoDB* y es poblada mediante el *seeds* (actualmente en `agent-fields.json` en proyecto **transfer-api**). ```typescript { id: "agentId-countryId-methodPaymentId", fields: [ { id: "receiverBankCode", // ID del campo para el agente afexId: "receiverProviderCode", // ID estándar de AFEX type: "select", options: [ { value: "001", // Valor esperado por el agente afexValue: "BANK_XYZ", // Valor estándar de AFEX label: "Banco XYZ" } ] } ] } ``` ## Transformación de Campos ### Conceptos Clave **`afexId`**: Identificador estándar usado internamente por AFEX **`afexValue`**: Valor estándar usado internamente por AFEX **`id`**: Identificador específico requerido por el agente **`value`**: Valor específico esperado por el agente ### Proceso de Transformación El proceso de transformación es el **núcleo del sistema de interoperabilidad**. Convierte automáticamente los datos desde el formato estándar de AFEX hacia el formato específico que requiere cada agente. #### ¿Por qué es necesaria la transformación? **Problema:** Cada agente financiero tiene sus propios códigos y formatos: - **Agente A** espera banco "001" para Banco XYZ - **Agente B** espera banco "BXY" para el mismo Banco XYZ - **AFEX** internamente usa "BANK_XYZ" como identificador estándar **Solución:** El transformador actúa como traductor universal. #### Flujo Completo de Transformación ```mermaid flowchart TD A[Usuario selecciona: Banco XYZ] --> B[Frontend envía: afexValue = BANK_XYZ] B --> C[TransferAPI recibe agentFields] C --> D[TransferAPI solicita transformación] D --> E[AgentFieldsService ejecuta transformación] E --> F{¿Configuración existe para agente?} F -->|SÍ| G[Buscar campo por afexId] F -->|NO| H[Usar valores originales] G --> I{¿Campo encontrado?} I -->|SÍ| J{¿Es tipo select?} I -->|NO| K[Buscar por ID directo - Fallback] J -->|SÍ| L[Buscar opción por afexValue] J -->|NO| M[Usar valor directo] L --> N{¿Opción encontrada?} N -->|SÍ| O[Mapear: afexValue → value del agente] N -->|NO| P[Buscar opción por value] P --> Q{¿Opción encontrada?} Q -->|SÍ| R[Mapear: value → value del agente] Q -->|NO| S[Valor inválido - Error] K --> T{¿Encontrado por ID?} T -->|SÍ| U[Usar valor original] T -->|NO| V[Campo no configurado - Preservar] O --> W[Agente recibe: 001] R --> W M --> W U --> W H --> W V --> W style A fill:#e3f2fd style W fill:#c8e6c9 style S fill:#ffcdd2 style E fill:#fff3e0 ``` #### Pasos Detallados del Proceso **1. Recepción de Datos** ```typescript { agentFields: { "receiverProviderCode": "BANK_XYZ", // Valor estándar AFEX "receiverProviderCode_label": "Banco XYZ", // Label para UI "receiverAccountNumber": "123456789" // Campo de texto } } ``` **2. Búsqueda de Configuración** El sistema busca la configuración específica usando la clave: ``` "agentId-countryId-methodPaymentId" // Ejemplo: "EY-CO-1" (Terrapay, Colombia, Depósito) ``` **3. Transformación Campo por Campo** Para cada campo en `agentFields`: ```typescript // Campo: receiverProviderCode = "BANK_XYZ" // PASO A: Buscar por afexId configuracion = { id: "receiverBankCode", // ← ID que espera el agente afexId: "receiverProviderCode", // ← ID estándar de AFEX type: "select", options: [ { value: "001", afexValue: "BANK_XYZ" // ← Mapeo de valores } ] } // PASO B: Mapear valor "BANK_XYZ" (afexValue) → "001" (value del agente) // PASO C: Resultado { id: "receiverBankCode", // ID transformado value: "001" // Valor transformado } ``` **4. Resultado Final** ```typescript // Antes de transformación (formato AFEX) { "receiverProviderCode": "BANK_XYZ", "receiverAccountNumber": "123456789" } // Después de transformación (formato Agente) { "receiverBankCode": "001", // ← Transformado "receiverAccountNumber": "123456789" // ← Sin cambios (no configurado) } ``` #### Casos Especiales **Caso 1: Campo sin configuración (Retrocompatibilidad)** ```typescript // Si el campo no existe en la configuración del agente // Se preserva exactamente como llegó { "customField": "originalValue" // ← Permanece igual } ``` **Caso 2: Transformación fallida** ```typescript // Si afexValue no existe en las opciones del agente // El campo se omite del resultado (undefined) { "receiverProviderCode": "INVALID_BANK" // ← No se incluye en resultado } ``` **Caso 3: Campos de texto** ```typescript // Para campos tipo "input", solo se cambia el ID { id: "accountNumber", afexId: "receiverAccountNumber", type: "input" } // "receiverAccountNumber": "123" → "accountNumber": "123" ``` ### Retrocompatibilidad e implementación gradual Actualmente el sistema realiza la transformación de cada campo en los valores de *agentFields* basado en el proceso descrito con anterioridad, asegurando que los datos se ajusten a las expectativas de cada agente. Esta lógica convierte la transformación en un proceso **totalmente retrocompatible** con el modo utilizado actualmente al guardar/consultar los *agentFields* en las transferencias, retornando el mismo campo que se envió originalmente si no se encuentra en la configuración del agente. De este modo, se asegura que la transformación no afecte a los agentes que aún no utilizan el sistema de *afexFields* para sus *agentFields*, permitiendo a cada uno de los flujos que los utilizan poder hacer una migración gradual hacia esta nueva configuración bajo su propia necesidad o disponibilidad, por cada campo e incluso por cada agente. ### Lógica de Transformación #### Diagrama de Flujo ```mermaid flowchart TD A[Entrada: Campo AFEX] --> B{¿Existe campo con afexId?} B -->|SÍ| C{¿Es tipo select?} B -->|NO| D[Buscar por id directo - Fallback] C -->|SÍ| E[Buscar opción por afexValue] C -->|NO| F[Usar valor directo] E --> G{¿Opción encontrada?} G -->|SÍ| H[Mapear: afexValue → value] G -->|NO| I[Buscar opción por value] I --> J{¿Opción encontrada?} J -->|SÍ| K[Mapear: value → value] J -->|NO| L[Campo nulo - Valor inválido] D --> M{¿Encontrado?} M -->|SÍ| N[Usar valor original] M -->|NO| L F --> O[Campo transformado] H --> O K --> O N --> O L --> P[Error: Campo no encontrado] style A fill:#e1f5fe style O fill:#c8e6c9 style P fill:#ffcdd2 style B fill:#fff3e0 style C fill:#fff3e0 style G fill:#fff3e0 style J fill:#fff3e0 style M fill:#fff3e0 ``` #### Algoritmo en Pseudocódigo ``` FUNCIÓN transformarCampo(campoAFEX, configuracionAgente): // PASO 1: Buscar configuración por afexId campo = buscarPor(configuracionAgente, "afexId", campoAFEX.id) SI campo existe ENTONCES: SI campo.tipo == "select" ENTONCES: // PASO 2: Mapear valor para campos de selección por afexValue opcion = buscarPor(campo.opciones, "afexValue", campoAFEX.afexValue) SI opcion existe ENTONCES: RETORNAR { id: campo.id, value: opcion.value } SINO: // PASO 3: Mapear valor para campos de selección por value opcion = buscarPor(campo.opciones, "value", campoAFEX.value) SI opcion existe ENTONCES: RETORNAR { id: campo.id, value: opcion.value } SINO: RETORNAR null // Valor no válido FIN SI FIN SI SINO: // PASO 4: Usar valor directo para campos de texto RETORNAR { id: campo.id, value: campoAFEX.value } FIN SI SINO: // PASO 5: Fallback - buscar por id directo (retrocompatibilidad) campoFallback = buscarPor(configuracionAgente, "id", campoAFEX.id) SI campoFallback existe ENTONCES: RETORNAR { id: campoFallback.id, value: campoAFEX.value } SINO: RETORNAR null // Campo no encontrado FIN SI FIN SI FIN FUNCIÓN ``` #### Ejemplo Paso a Paso **Configuración del Agente:** ```typescript { fields: [ { id: "receiverBankCode", afexId: "receiverProviderCode", type: "select", options: [ { value: "001", afexValue: "BANK_XYZ", label: "Banco XYZ" }, { value: "002", afexValue: "BANK_ABC", label: "Banco ABC" } ] } ] } ``` **Transformación:** ``` 1. Entrada: { id: "receiverProviderCode", value: "BANK_XYZ" } 2. Buscar por afexId: "receiverProviderCode" ✓ Encontrado 3. ¿Es tipo select? ✓ SÍ 4. Buscar opción con afexValue: "BANK_XYZ" ✓ Encontrado 5. Resultado: { id: "receiverBankCode", value: "001" } ``` #### Manejo de Labels En el proceso de obtención de *agentFields* algunos campos obtienen el valor de `label` que luego se inyecta internamente con el sufijo `_label` en la lista a modo de ser utilizado posteriormente como un valor de texto en la presentación de datos, ya sea en formularios o en los propios *payloads* para realizar el envío hacia los agentes. Esto también fue considerado en el transformador y se incluye un mecanismo especial para preservar y transformar *labels* asociados a los campos mediante los *afexFields*, sin dejar fuera la retrocompatibilidad descrita anteriormente para campos que aún no hacen uso de ellos. Es por este motivo que los campos con el sufijo `_label` no son procesados directamente como los otros campos y son utilizados como complementos de ellos en su procesamiento. **Convención de Labels:** - Los *labels* se almacenan con el sufijo `_label` - Ejemplo: `receiverBankCode_label` contiene el texto descriptivo de `receiverBankCode` **Proceso de Transformación de Labels:** ```mermaid flowchart TD A[agentFieldsValues: receiverProviderCode] --> B[Intentar transformar campo] B --> C{¿Transformación exitosa?} C -->|SÍ| D[Usar campo transformado: receiverBankCode] C -->|NO| E[Usar campo original: receiverProviderCode] D --> H{¿Existe receiverProviderCode_label?} E --> I{¿Existe receiverProviderCode_label?} H -->|SÍ| J[receiverBankCode_label = valor original] H -->|NO| K[Sin label] I -->|SÍ| L[receiverProviderCode_label = valor original] I -->|NO| M[Sin label] J --> N[Resultado: Campo transformado + Label] K --> O[Resultado: Solo campo transformado] L --> P[Resultado: Campo original + Label] M --> Q[Resultado: Solo campo original] style A fill:#e1f5fe style N fill:#c8e6c9 style O fill:#c8e6c9 style P fill:#fff3e0 style Q fill:#fff3e0 ``` **Algoritmo de Labels:** ``` PARA cada campo en agentFieldsValues: SI campo NO termina en "_label" ENTONCES: // 1. Intentar transformar el campo principal campoTransformado = transformarCampo(campo, configuracion) // 2. Buscar si existe el label asociado en datos originales labelOriginal = agentFieldsValues[campo.id + "_label"] SI campoTransformado !== undefined ENTONCES: // TRANSFORMACIÓN EXITOSA resultado[campoTransformado.id] = campoTransformado.value SI labelOriginal existe ENTONCES: // Preservar label con el NUEVO ID transformado resultado[campoTransformado.id + "_label"] = labelOriginal FIN SI SINO: // TRANSFORMACIÓN FALLIDA - Usar campo original resultado[campo.id] = campo.value SI labelOriginal existe ENTONCES: // Preservar label con el ID ORIGINAL resultado[campo.id + "_label"] = labelOriginal FIN SI FIN SI FIN SI // Los campos "_label" se procesan automáticamente // junto con su campo principal, por eso se saltan aquí FIN PARA ``` **Ejemplo Práctico:** ```typescript // Entrada { "receiverProviderCode": "BANK_XYZ", "receiverProviderCode_label": "Banco XYZ - Sucursal Centro" } // Configuración del agente { id: "receiverBankCode", afexId: "receiverProviderCode", type: "select", options: [ { value: "001", afexValue: "BANK_XYZ", label: "Banco XYZ" } ] } // Salida transformada { "receiverBankCode": "001", "receiverBankCode_label": "Banco XYZ - Sucursal Centro" // Label preservado } ``` **Beneficios del Sistema de Labels:** - **Preservación de contexto**: Los labels descriptivos se mantienen tras la transformación - **Experiencia de usuario**: Las interfaces pueden mostrar texto legible en lugar de códigos - **Trazabilidad**: Se conserva la información original seleccionada por el usuario - **Flexibilidad**: Los labels pueden contener información adicional específica del contexto --- ## Invocación del Servicio de Transformación ### Endpoint de la Agent API El servicio de transformación se expone a través de un endpoint REST en la **Agent API**: ``` POST /v1/agents/:id/fields/transform ``` Donde `:id` corresponde al **ID del agente** que realizará la transformación. Considerar que este servicio permite el envío de lotes de transferencias para optimizar las invocaciones del recurso al momento de solicitar las transformaciones. ### Estructura de la Petición **URL de ejemplo:** ``` POST /v1/agents/EY/fields/transform ``` **Payload:** ```typescript { transfers: [ { id: "transfer_123", countryId: "CO", methodPaymentId: 1, agentFieldsValues: { "receiverProviderCode": "BANK_XYZ", "receiverProviderCode_label": "Banco XYZ", "receiverAccountNumber": "123456789" } }, { id: "transfer_456", countryId: "CO", methodPaymentId: 1, agentFieldsValues: { "receiverProviderCode": "BANK_ABC", "receiverAccountNumber": "987654321" } } ] } ``` ### Respuesta del Servicio ```typescript { "success": true, "data": [ { "id": "transfer_123", "agentFieldsValues": { "receiverBankCode": "001", "receiverBankCode_label": "Banco XYZ", "receiverAccountNumber": "123456789" } }, { "id": "transfer_456", "agentFieldsValues": { "receiverBankCode": "002", "receiverAccountNumber": "987654321" } } ] } ``` ### Casos de Uso del Endpoint 1. **Creación de transferencia**: Transforma campos antes de guardar al Core 2. **Consulta de pendientes**: Adapta campos almacenados al formato del agente 3. **Validación previa**: Verifica que los campos sean válidos para el agente 4. **Migración de datos**: Convierte transferencias existentes al nuevo formato --- ## Casos de Uso ### Caso 1: Campo de Selección Multi-Agente **Agente A** espera: - Campo: `bankType` - Valores: `COMMERCIAL`, `SAVINGS` **Agente B** espera: - Campo: `institutionType` - Valores: `BANK`, `CREDIT_UNION` **Configuración unificada:** ```typescript // Para Agente A { id: "bankType", afexId: "receiverAccountType", options: [ { value: "COMMERCIAL", afexValue: "CHECKING", label: "Cuenta Corriente" }, { value: "SAVINGS", afexValue: "SAVINGS", label: "Cuenta de Ahorros" } ] } // Para Agente B { id: "institutionType", afexId: "receiverAccountType", options: [ { value: "BANK", afexValue: "CHECKING", label: "Banco" }, { value: "CREDIT_UNION", afexValue: "SAVINGS", label: "Cooperativa" } ] } ``` **Ejemplos de Transformación:** **Escenario 1: Usuario selecciona "Cuenta Corriente"** ```typescript // Entrada (formato AFEX estándar) { agentFields: { "receiverAccountType": "CHECKING", "receiverAccountType_label": "Cuenta Corriente" } } // Transformación para Agente A { "bankType": "COMMERCIAL", "bankType_label": "Cuenta Corriente" } // Transformación para Agente B { "institutionType": "BANK", "institutionType_label": "Cuenta Corriente" } ``` **Escenario 2: Usuario selecciona "Cuenta de Ahorros"** ```typescript // Entrada (formato AFEX estándar) { agentFields: { "receiverAccountType": "SAVINGS", "receiverAccountType_label": "Cuenta de Ahorros" } } // Transformación para Agente A { "bankType": "SAVINGS", "bankType_label": "Cuenta de Ahorros" } // Transformación para Agente B { "institutionType": "CREDIT_UNION", "institutionType_label": "Cuenta de Ahorros" } ``` **Llamadas al servicio:** ```typescript // Para Agente A const transformedForAgentA = await agentFieldsService .transformTransfersAgentFieldsValuesForAgent( "AGENT_A", [ { id: "transfer123", countryId: "CO", methodPaymentId: 1, agentFieldsValues: { "receiverAccountType": "CHECKING" } } ] ); // Para Agente B const transformedForAgentB = await agentFieldsService .transformTransfersAgentFieldsValuesForAgent( "AGENT_B", [ { id: "transfer123", countryId: "CO", methodPaymentId: 1, agentFieldsValues: { "receiverAccountType": "CHECKING" } } ] ); ``` **Resultado: Una sola transferencia, múltiples formatos** - El **usuario** ve una interfaz unificada con "Cuenta Corriente" y "Cuenta de Ahorros" - **Agente A** recibe `bankType: "COMMERCIAL"` o `bankType: "SAVINGS"` - **Agente B** recibe `institutionType: "BANK"` o `institutionType: "CREDIT_UNION"` - **AFEX** mantiene consistencia interna con `receiverAccountType: "CHECKING"/"SAVINGS"` ### Caso 2: Transformación con Lógica Personalizada **Entrada:** ```typescript { receiverBankCode: "001", receiverBankSubCode: "123" } ``` **Salida para Terrapay Canadá:** ```typescript { receiverBankCode: "001", originalBankSubCode: "123", receiverBankSubCode: "001123" // Código concatenado } ``` ## Flujos integrados ### Creación de giro (createTransfer) ```mermaid sequenceDiagram title Creación de giro (nuevo) participant AFEX as AFEX+ participant TransferAPI as TransferAPI participant AgentAPI as AgentAPI participant Core as Core AFEX ->> TransferAPI: createTransfer (request) TransferAPI ->> TransferAPI: validateTransfer() TransferAPI ->> TransferAPI: validateCompliance() TransferAPI ->> AgentAPI: transformAgentFieldsForAgent (agentId, transfer) AgentAPI ->> TransferAPI: transfer: Transfer TransferAPI ->> TransferAPI: validateAgentFields() TransferAPI ->> Core: createTransfer(transfer) Core ->> TransferAPI: TransferAPI ->> TransferAPI: tranferIntegrations() TransferAPI ->> TransferAPI: saveTransfer TransferAPI ->> AFEX: transfer: Transfer ``` ### Obtención de giros pendiente de envío desde PulpoPlus (getPendingTransfersToSend) Observación: En el siguiente diagrama se presentan 2 columnas dentro de **AgentAPI** para hacer la separación entre ejecuciones externas e internas de la propia *API*. ```mermaid sequenceDiagram title Obtener giros pendiente de envío (nuevo) participant PulpoPlus as PulpoPlus participant TransferAPI as TransferAPI participant AgentAPI as AgentAPI participant AgentAPI(internal) as AgentAPI(internal) PulpoPlus ->> TransferAPI: getPendingTransfersToSend(agentId) TransferAPI ->> AgentAPI: transformAgentFieldsForAgent (agentId, transfers) AgentAPI ->> AgentAPI(internal): getAgentFields(agentId) AgentAPI(internal) ->> AgentAPI: agentFields: AgentField[] AgentAPI ->> AgentAPI(internal): [for each transfer] transformAgentFields(agentField, transfer) AgentAPI(internal) ->> AgentAPI: transfers: Transfer[] AgentAPI ->> TransferAPI: transfers: Transfer[] TransferAPI ->> PulpoPlus: transfers: Transfer[] ``` ### Lista de AfexFields La implementación actual en la configuración de agentes disponibiliza los siguientes campos para ser utilizados mediante *afexFields*: - **receiverProviderCode**: Código de banco para Depósito o *Wallet* --- ## Beneficios - **Interoperabilidad**: Una transferencia funciona con múltiples agentes - **Mantenimiento**: Lógica centralizada y configuración declarativa - **Escalabilidad**: Agregar nuevos agentes sin cambiar código base - **Consistencia**: Validación y transformación uniforme - **Experiencia de usuario**: Formulario único independiente del agente --- ## Conclusión El sistema de **afexFields** representa una solución robusta y escalable para la interoperabilidad multi-agente en el ecosistema de transferencias financieras. Su diseño modular y configuración declarativa permite adaptarse dinámicamente a los requisitos específicos de cada agente, mientras mantiene la consistencia y simplicidad para los usuarios finales. Esta arquitectura facilita la expansión del negocio a nuevos mercados y agentes, reduciendo significativamente el tiempo de integración y los costos de mantenimiento, mientras garantiza la calidad y confiabilidad de las operaciones financieras.