Skip to main content

Orquestador y arquitectura de integraciones

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.

Diagrama de arquitectura

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.

Diagrama de arquitectura InsureHero (referencia del equipo): partners, canales, core, orquestador, integraciones

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):
Arquitectura: partners, canales, Supabase, Shield, core, orquestador, adaptadores, contrato emitido, errores, Discord y pila de reintentos

Fuente: diagrams/arquitectura-plataforma-integraciones.mmdyarn 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).

Secuencia resumida (ventas → dispatch)

Este diagrama muestra el camino desde que el core tiene un risk item hasta emisión o cola de reintentos (simplificado):

Flujo: dispatch → sales_integration_slug → orquestador → éxito, abandonado o reintento con QStash

Vista gráfica del flujo. Fuente editable: diagrams/dispatch-orquestador.mmd — regenerar con yarn diagrams:build. Clic en el diagrama para ampliarlo.

Rol del orquestador

La función orchestrateInsuranceEmission (integrations/orchestrator/engine.ts) es la única puerta desde el core hacia la emisión en sistemas externos:

  1. Recibe un StandardRiskItem ya normalizado (mismo contrato que consume Shield/APIs y el dispatch).
  2. Resuelve el proveedor (options.provider, p. ej. PHOENIX, AMA).
  3. Obtiene el adaptador con getInsuranceAdapter(provider) desde el registro (integrations/registry.ts).
  4. Ejecuta adapter.emit(riskItem, { accessToken }).
  5. 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.

Contrato de entrada: StandardRiskItem

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.

Registro de adaptadores

registry.ts mapea slug en mayúsculas → clase adaptadora:

SlugClaseNotas
PHOENIXPhoenixAdapterLee auth_config de integrations donde slug = 'PHOENIX'.
AMAAmaAdapterLee auth_config donde slug = 'AMA'; multi-canal vía auth_config.channels[] (ver AMA).
MAWDY_MAILMawdyMailAdapterEnví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.

Capa de mapeo por proveedor (integraciones)

Phoenix

  • 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.

AMA

  • 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).

Cómo entra el flujo desde el core

Emisión en ventas (webhook / dispatch)

POST /api/integrations/dispatch (autenticación con service role):

  1. Recibe el record del risk item (p. ej. desde trigger Supabase).
  2. Lee sales_integration_slug del paquete; si no hay, termina con skipped.
  3. Construye un StandardRiskItem desde el registro.
  4. Llama orchestrateInsuranceEmission(riskItem, { provider: salesIntegrationSlug }).
  5. Persiste en integration_emissions: éxito, fallo reintentable o abandonado (ABANDONED), con error_history, next_retry_at, intentos.
  6. Notifica a Discord en fallos reintentables y en abandonados (notifyIntegrationEmissionDiscord).
  7. Si el fallo es reintentable y existe QSTASH_TOKEN, encola un reintento diferido hacia /api/integrations/retry (Upstash QStash, delay configurable).
  8. 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.

Dispatch v2 (disparo por orden creada)

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).

Postventa (JWT post-sales)

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.

Backoffice (tRPC)

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ódigoSignificado aproximado
SUCCESSEmisió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.
ABANDONEDError 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.

Relación con otros documentos