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? | SÍ — 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? | SÍ — 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
hackneydirectamente (nodkv_telemed_http:sendpara evitar token-retry overhead de ES) spawn/1— fire-and-forget, no bloquea- Feature flag
rabbitmq_events_enabled=falsepor defecto - Config key
pet_flows_events_urlpara 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 |