Saltar a contenido

MVP — Dinamización de Conversaciones Abandonadas

Estado: ✅ Implementado — AMQP Gateway integrado en dkv-pet-flows-api
Fecha: 4 de abril de 2026 (actualizado)
Tipo: Arquitectura técnica


1. Resumen del Requisito

"Incorporar un sistema de notificaciones para dinamización de conversaciones abandonadas, adaptando el cierre automático para procesos de continuidad (prevención, coach) e introduciendo notificaciones proactivas que animen al cliente a retomar la interacción."

Los 4 Pilares

# Pilar Descripción
1 Identificar Detectar conversaciones abandonadas en servicios de continuidad
2 Prorrogar Incrementar tiempo de cierre automático para estos servicios
3 Notificar Enviar push proactivos durante la prórroga
4 Resolver Si retoma → continúa. Si no → cierra tras prórroga

2. Arquitectura: Event-Driven via RabbitMQ

Decisión final

Se descartaron Dapr Timers (problemas con Business Hours) y Polling REST (latencia 5min + presión ES) a favor de un Event Bus en tiempo real utilizando RabbitMQ.

Flujo completo

sequenceDiagram
    participant P as 👤 Paciente
    participant NC as 🔧 netcomp/pet-cloud
    participant DTM as ⏰ DTM (Quartz)
    participant PF as 🔮 Pet Flows API
    participant RMQ as 🐰 RabbitMQ
    participant WH as 🔗 Webhook Subscribers
    participant NOT as 📱 dkv-notifications

    Note over P,NC: El paciente abandona la conversación

    NC->>DTM: Programar TMIPW (24h)
    Note over DTM: ⏱️ Pasan 24 horas...
    DTM->>NC: Callback TMIPW expirado

    NC->>NC: create_encounter_event("tmipw_expired")
    NC->>NC: Crear mensaje warning en chat (i18n)
    NC->>PF: POST /api/v1/events [spawn, fire-and-forget]
    PF->>RMQ: AMQP publish (pet.events, encounter.tmipw_expired)
    PF-->>NC: 202 Accepted

    RMQ->>PF: AMQP consume (SmallRye @Incoming)
    PF->>PF: Evaluar reglas del Flow configurado
    PF->>NOT: POST /api/v1/push (mensaje personalizado + deep link)
    PF->>WH: POST webhook (para cada suscriptor dinámico)
    NOT->>P: 📲 "¡Hola María! Tu coach te dejó..."

    Note over P: María toca el deep link → retoma la consulta ✨

3. Decisiones Técnicas Confirmadas

# Decisión Respuesta
1 ¿Tocamos netcomp Erlang? — hook en create_event/2 + publisher HTTP fire-and-forget
2 ¿Dónde inyectamos? create_event/2 en dkv_telemed_util.erl:10809 — embudo universal de encounter events
3 ¿Protocolo publicación? HTTP POST a dkv-pet-flows REST (/api/v1/events) — JSON plano, sin Management API
4 ¿Protocolo consumo? SmallRye RabbitMQ nativo (AMQP 0.9.1) — reemplaza Dapr binding
5 ¿Polling o Event-Driven? Event-Driven — sub-segundo vía RabbitMQ
6 ¿Push duplicado? No es problema: pet-cloud tiene // TODO en push nativo (DtmService.java:621)
7 ¿DTM integrado en netcomp? NO — servicio Java independiente (Quartz + PostgreSQL propio, puerto 8787)
8 ¿Microservicio proxy separado? NO — todo integrado en dkv-pet-flows-api (lección aprendida de pet-stomp-proxy)
9 ¿Suscripción dinámica? — REST subscribe/unsubscribe con webhook forwarding

4. Componentes Implementados

Para la documentación completa del AMQP Gateway, véase AMQP Gateway — Arquitectura Técnica.

4.1 Netcomp (Erlang) — Hook en create_event

Inyección en dkv_telemed_util:create_event/2 (línea 10809). Publica a dkv-pet-flows REST:

case nktelemed_event_obj:create(?DKV_DOMAIN, Opts3) of
    {ok, EvId, EvPID} ->
        maybe_publish_to_rabbitmq(EvId, Opts3),
        {ok, EvId, EvPID};
    Other -> Other
end.

%% Fire-and-forget: feature flag + spawn
maybe_publish_to_rabbitmq(EventId, EventData) ->
    case dkv_telemed_app:get(rabbitmq_events_enabled, false) of
        true -> spawn(fun() -> publish_to_rabbitmq(EventId, EventData) end);
        false -> ok
    end.

%% HTTP POST a dkv-pet-flows REST (JSON plano, sin envelope Management API)
publish_to_rabbitmq(EventId, EventData) ->
    Url = dkv_telemed_app:get(pet_flows_events_url,
        <<"http://localhost:8080/api/v1/events">>),
    Payload = build_rabbitmq_payload(EventId, EventData),
    Body = nklib_json:encode(Payload),
    Headers = [{<<"Content-Type">>, <<"application/json">>}],
    hackney:request(post, Url, Headers, Body,
        [{pool, default}, {connect_timeout, 5000}, {recv_timeout, 5000}, with_body]).

Seguridad del cambio:

  • Usa hackney directamente (no dkv_telemed_http:send para evitar token-retry overhead de ES)
  • spawn/1 — fire-and-forget, no bloquea
  • Feature flag rabbitmq_events_enabled = false por defecto
  • Config key pet_flows_events_url para apuntar al host correcto

4.2 Pet Flows (Quarkus) — AMQP Gateway nativo

Cambio arquitectónico: se eliminó el Dapr RabbitMQ binding y se reemplazó por SmallRye RabbitMQ nativo (quarkus-messaging-rabbitmq). Esto proporciona:

  • Publicación y consumo AMQP nativos (sin Dapr sidecar para messaging)
  • Reconexión automática con backoff exponencial
  • Suscripciones dinámicas vía REST con webhook forwarding

API REST expuesta por dkv-pet-flows-api:

Método Ruta Descripción
POST /api/v1/events Publicar un evento (netcomp → RabbitMQ)
POST /api/v1/subscriptions Registrar suscripción webhook dinámica
GET /api/v1/subscriptions Listar todas las suscripciones
GET /api/v1/subscriptions/{id} Obtener suscripción por ID
DELETE /api/v1/subscriptions/{id} Cancelar suscripción

4.3 Pet-cloud (Java) — Publisher nativo (futuro)

// Cuando pet-cloud sea LEADER, publicará directamente:
rabbitTemplate.convertAndSend("pet.events",
    "encounter." + eventSubtype, eventPayload);

4.4 Ejecución del Orquestador (Patrón Intérprete)

A nivel de negocio, una vez que el consumer recibe el evento, Pet Flows inicializa un "Motor Intérprete". Esto significa que no ejecuta una clase fija de código, sino que lee en milisegundos el gráfico visual (JSON) que Marketing o Producto delineó en la interfaz web (Jaraxa Flow Builder), y lo convierte dinámicamente en acciones (esperas, notificaciones, e-mails).

Dicho de otro modo: Un cambio en la interfaz gráfica del publicador por parte de negocio afecta de forma inmediata a la inyección de notificaciones sin requerir desarrollo backend posterior.


5. Hallazgos de la Investigación

5.1 El DTM (Distributed Task Manager)

  • Qué es: Servicio Java independiente (ghcr.io/jaraxasoftware/distributed-task-manager:latest)
  • Qué tiene: PostgreSQL propio (dkv-dtm-postgres-legacy, puerto 5433), tablas Quartz
  • Qué hace: Recibe peticiones de planificación (create jobs), las almacena, y cuando expiran devuelve un webhook callback
  • Qué NO hace: No tiene lógica de negocio. No pausa timers en fines de semana

5.2 TMIPW/TMIP en ambos codebases

Acción netcomp (Erlang) pet-cloud (Java)
Crear evento tmipw_expired ✅ L488 ✅ L592
Buscar mensaje i18n por brand/product/queue/service ✅ L495-526 ✅ L602
Crear mensaje en conversación (warning) ✅ L531-545 ✅ L604-618
Enviar push nativo al paciente ✅ L548-553 ⚠️ L621 // TODO
Programar timer TMIP (cierre) ✅ L555-562 ✅ L622-628

5.3 RabbitMQ en el ecosistema

Config Detalle
Spring config application.yaml:143-148 (host, port, user, pass)
Colas AMQP queue.simple.task, topic.clustered.tasks.node, fanout.cluster.notifications
STOMP Habilitado (tests E2E: rabbitmq:3.12-management)
Management API Disponible en :15672

5.4 Campos de inactividad en la entidad Cola

Campo Tipo Para MVP
max_inactive_patient_warning_time int (min) Controla cuándo salta TMIPW
max_inactive_patient_time int (min) Controla cuándo salta TMIP (cierre)
close_inactive_encounters bool Activa/desactiva el motor de cierre

6. Coexistencia Leader/Follower

graph LR
    subgraph "Ahora → Julio 2026"
        NC["🦎 netcomp<br/>LEADER<br/>ES 6"]
        PC["🐾 pet-cloud<br/>FOLLOWER<br/>PG + ES 7"]
    end

    subgraph "Post GoLive"
        PC2["🐾 pet-cloud<br/>LEADER"]
        NC2["🦎 netcomp<br/>FOLLOWER"]
    end

    NC -->|"GoLive"| NC2
    PC -->|"GoLive"| PC2

    style NC fill:#ef4444,color:#fff
    style PC fill:#009BE0,color:#fff
    style NC2 fill:#f97316,color:#fff
    style PC2 fill:#059669,color:#fff

Regla: Solo el Leader publica al Event Bus. Esto evita eventos duplicados.


Documentos Relacionados

Documento Enlace
AMQP Gateway — Arquitectura Técnica mvp_amqp_gateway.md
Arquitectura del Sistema arquitectura_sistema.md
Investigación DKV Pet Cloud investigacion_dkv_pet_cloud.md
Spike Dapr Workflows dapr_workflows_spike.md