Esta página describe cómo el orquestador conecta el core de InsureHero con sistemas externos (aseguradoras y APIs), alineada con el diagrama de arquitectura del equipo y con el código en apps/next/src/integrations/. El objeto de negocio que se serializa hacia el orquestador es el risk item vía StandardRiskItem; contexto amplio en Risk item.
Abajo está la imagen de referencia del equipo. Haz clic en la imagen o en la barra para desplegar la versión vectorial (Mermaid) que mantenemos en el handbook: misma idea de arquitectura, texto nítido al hacer zoom y fuente editable en el repo.
Referencia visual del lienzo (PNG). Clic aquí para ver la versión Mermaid del handbook debajo. Coloca el archivo en static/img/arquitectura-orquestador-integraciones.png si aún no está en el repo.
Versión generada desde el código de documentación (comparar con la imagen de arriba):
Fuente: diagrams/arquitectura-plataforma-integraciones.mmd — yarn diagrams:build. Clic en el diagrama para ampliarlo (zoom nítido, SVG).
Los bloques “Adaptador X / Y / Z” en el diagrama representan proveedores adicionales que se pueden registrar en el mismo patrón; en el repositorio actual el registro incluye Phoenix, AMA y MAWDY Mail (ver Registro de adaptadores).
La función orchestrateInsuranceEmission (integrations/orchestrator/engine.ts) es la única puerta desde el core hacia la emisión en sistemas externos:
Recibe un StandardRiskItem ya normalizado (mismo contrato que consume Shield/APIs y el dispatch).
Resuelve el proveedor (options.provider, p. ej. PHOENIX, AMA).
Obtiene el adaptador con getInsuranceAdapter(provider) desde el registro (integrations/registry.ts).
Ejecuta adapter.emit(riskItem, { accessToken }).
Devuelve un OrchestratorResult: SUCCESS con datos del adaptador, o FAILED con código/mensaje estable (incluido INTERNAL_ORCHESTRATOR_ERROR si explota el adaptador).
El comentario en código resume el diseño: el core permanece agnóstico de la aseguradora concreta; la variación vive en los adaptadores y en las capas de mapeo hacia cada API externa.
Definido en integrations/contracts/insurance-adapter.contract.ts. Es el modelo canónico que sale del core (risk item + paquete + sujeto asegurado, beneficiarios, reclamantes autorizados, metadata).
Campos relevantes para integraciones:
uid: referencia externa estable para trazas y logs.
package_id: enlaza con configuración de integración en el paquete (sales_integration_slug / post_sales_integration_slug en flujos distintos).
insured_subject, beneficiaries, authorized_claimants: datos de negocio que cada adaptador mapea a su payload.
Cualquier nueva integración debe rellenar o transformar hacia este contrato antes de llamar al orquestador.
registry.ts mapea slug en mayúsculas → clase adaptadora:
Slug
Clase
Notas
PHOENIX
PhoenixAdapter
Lee auth_config de integrations donde slug = 'PHOENIX'.
AMA
AmaAdapter
Lee auth_config donde slug = 'AMA'; multi-canal vía auth_config.channels[] (ver AMA).
MAWDY_MAIL
MawdyMailAdapter
Envía correo transaccional (p. ej. welcome pack) vía la API de MAWDY con Cognito client_credentials. No emite pólizas: renderiza una plantilla con datos del StandardRiskItem. Config por canal en auth_config.
Añadir Adapter X / Y en el diagrama equivale a: implementar InsuranceAdapter, registrar en ADAPTERS y configurar filas en la tabla integrations en Supabase.
PhoenixAdapter elige un cliente HTTP según la variante del producto (MIA_TRAVEL, MIA_HEALTH, MIA_HOME, MIA_LIFESTYLE) y usa mappers dedicados (mapToPhoenixHealthContract, etc.) — esto es la “Mapping Layer” del bloque Phoenix del diagrama.
Autenticación y catálogo se apoyan en auth_config y datos resueltos desde BD dentro del adaptador.
AmaAdapter usa mapBeneficiariesToAma y tipos propios (AmaCreateHolderRequest, etc.) para construir las peticiones al cliente AMA.
En ambos casos, el adaptador traduce StandardRiskItem → API externa y EmissionResponse → resultado unificado (éxito, externalId, errores tipados, requestPayload para depuración).
Persiste en integration_emissions: éxito, fallo reintentable o abandonado (ABANDONED), con error_history, next_retry_at, intentos.
Notifica a Discord en fallos reintentables y en abandonados (notifyIntegrationEmissionDiscord).
Si el fallo es reintentable y existe QSTASH_TOKEN, encola un reintento diferido hacia /api/integrations/retry (Upstash QStash, delay configurable).
Actualiza risk_items.metadata.integration con estado, externalId, emissionId y error.
Esto materializa en código el esquema del diagrama: Orquestador → Integraciones → Contrato emitido o Manejador de errores → Discord + pila de fallidos / reintentos.
Junto al dispatch de ventas (disparado por el risk item), existe POST /api/integrations/dispatch-v2, para emisiones ligadas a la creación de una orden. Un trigger AFTER INSERT ON orders publica (vía pg_net) hacia este endpoint cuando la orden debe disparar una integración —por ejemplo el correo de bienvenida (MAWDY_MAIL) en el evento POLICY_CREATED—. El endpoint tiene dos modos, inicial (por order_id) y reintento (por emission_id), y en ambos delega en orchestrateInsuranceEmission.
Para permitir varias emisiones por risk item según el hecho que las dispara, integration_emissions incorpora una columna event y su unicidad pasa a ser (risk_item_id, provider, event) (el dispatch v1 y post-sales resuelven conflictos por esa misma clave).
POST /api/integrations/post-sales: valida JWT post-sales, obtiene post_sales_integration_slug del paquete y llama al mismo orquestador. Mismo contrato StandardRiskItem, distinto campo de configuración en el paquete.
integrationEmissions: consulta emisiones por risk item, retryEmissions y syncBeneficiaries delegan en lógica de reintento/emisión (retryFromBackoffice, executeEmission) para gestionar reintentos desde el dashboard (coherente con la “pila” del diagrama).
Estados y reintentos (tabla integration_emissions)
Estado en código
Significado aproximado
SUCCESS
Emisión aceptada por el adaptador; se guardan external_id y respuesta.
FAILED (reintentable)
Error de red/5xx/etc.; next_retry_at y posible cola QStash.
ABANDONED
Error no reintentable; se notifica a Discord; sin reintento automático.
La lógica isRetryableError en dispatch/route.ts define qué errores pueden volver a la cola.