diff --git a/docs/handoff-bot-runtime-simon.md b/docs/handoff-bot-runtime-simon.md index 713b15f..f9083df 100644 --- a/docs/handoff-bot-runtime-simon.md +++ b/docs/handoff-bot-runtime-simon.md @@ -41,12 +41,19 @@ a los logs en Dokploy). La parte de la app (EPs) está verificada y no es el pro para ver `remoteJid`/`remoteJidAlt`, si llega algo, y el resultado del matching. - Subido el límite de body del worker a 30 MB (las fotos en data URI rompían el 100kb por defecto). -## 3. Problema A — Baileys deja de recibir tras reconectar +## 3. Problema A — conexión Baileys inestable (bucle de reconexión) -**Síntoma:** justo tras un **escaneo fresco** del QR, el bot recibe mensajes (conversación OK). Pero -tras cualquier **reconexión** (redeploy, o auto-reconexión de Baileys al caerse el socket), reporta -`connection: "open"` pero **no recibe nada** (`/debug` → `inbound: []` aunque el cliente escriba). -`markOnlineOnConnect: true` ayudó al primer connect pero no lo blinda contra reconexiones. +**Evidencia en logs del contenedor (`docker logs`):** el socket se cae cada pocos minutos con +`stream:error → conflict {type:"replaced"}` y `stream:error code 503`, y reconecta en bucle +(`AwaitingInitialSync → Transitioning to Online → opened connection to WA → ✅ conectado`, repetido). +El `conflict: replaced` indica que **otra sesión reclama la misma cuenta** — típico del **solapamiento +en el deploy** (Swarm arranca el contenedor nuevo antes de matar el viejo y ambos usan la misma +sesión del volumen). `markOnlineOnConnect: true` mejoró la recepción pero no arregla una conexión que +se cae sola. + +**Mitigación parcial (Baileys):** configurar el deploy del bot como **stop-first / sin solapamiento** +(1 réplica, recreate) para evitar el `conflict`. Pero los `503` son de WhatsApp y seguirán. +**Vía robusta (acordada): Evolution API.** **Cómo reproducir/diagnosticar:** - `GET https://reformix-bot.dv3.com.es/debug` (Basic, contraseña `QR_TOKEN`): si `connection:open` @@ -61,24 +68,31 @@ conexión y entrega los mensajes por **webhook**, sin un socket Baileys en-proce zombi. El bot pasaría a: (1) recibir mensajes por webhook de Evolution, (2) enviar por su REST. La lógica de Claude + los EPs de la app se quedan igual; solo cambia la capa de transporte WhatsApp. -## 4. Problema B — el perfil extraído no se persiste en el lead durante la conversación +## 4. Problema B — el perfil se persiste solo a medias + máquina de estados errática -**Síntoma:** la conversación avanza (Luisa pregunta espacio→tamaño→estilo→urgencia) pero el lead -queda con `botStep` desfasado y `espacio/rangoM2/...` vacíos. +**Verificado en vivo:** `persistirTurno` SÍ funciona cuando se llama — +`Lead ... persistido via API: {"rangoM2":"10a20","botStep":"estilo"} → ok`. Pero en una conversación +completa **solo apareció UN `persistido`** (rangoM2); `espacio`, `urgencia` y `presupuesto` no se +guardaron. Y la conversación se descuadra (rechaza a 4500€ → con 8500€ vuelve a preguntar el tamaño → +"preparo presupuesto"). Causa probable: `claudeService.llamarClaude` solo devuelve `entidad`/`nuevoEstado` +en algunos turnos, y la lógica de estado/viabilidad no es determinista. -**Qué sé:** el EP `/perfil` **funciona** con el payload del bot (probado). Así que el fallo está en la -llamada del bot. [`LeadsService.persistirTurno`](../mvp/Whatsapp-bot/src/leads/leads.service.ts#L68) -ya loguea: `Lead X persistido via API: {...} → ok/fallo`. +**A revisar:** que cada turno con dato extraído llame a `persistirTurno`, que los valores encajen con +los enums de la app (`urgencia` alta|media|baja, etc., o `POST /perfil` da 422 y no guarda nada), y +endurecer la máquina de estados (no re-preguntar lo ya respondido; viabilidad estable). -**Qué mirar (logs del bot en Dokploy) durante una conversación:** -- Si aparece `→ fallo`: mira el payload logueado. Sospechosos: un valor de enum inválido (p.ej. - `urgencia` fuera de `alta|media|baja`, o `calidadGlobal` fuera de `basica|media|premium`) hace que - TODO el `POST /perfil` dé `422` y no persista nada de ese turno. Mapea los valores que extrae Claude - a los enums de la app antes de enviar. -- Si no aparece la línea: `persistirTurno` no se está llamando → revisa que `claudeService.llamarClaude` - devuelva `entidad`/`nuevoEstado`. -- Ojo también a si el bot se **reinició a media conversación** (Problema A): tras reiniciar deja de - recibir y el flujo se corta. +## 4bis. Problema C (el que rompe el end-to-end) — el bot NUNCA dispara la generación + +**Verificado en logs:** Luisa termina diciendo *"en un momento recibes tu presupuesto"* pero **no hay +ninguna llamada a `ingesta` / `perfilCompleto`** en toda la sesión (grep vacío). Es decir, al cerrar la +cualificación el bot **no dispara nada**: ni render, ni PDF, ni entrega. Es una promesa vacía. + +**Qué falta (lado bot):** cuando la cualificación se completa (estado `presupuesto`/`fin_viable`), el +bot debe (1) **pedir las fotos** del espacio por WhatsApp, (2) subirlas vía `POST /api/leads/:id/ingesta` +(items `foto`, `momento:"antes"`), y (3) marcar `perfilCompleto:true` (y/o `finalizar`). Eso dispara en +la app: `PERFIL_WEBHOOK` → worker genera renders → `ingesta finalizar` → PDF + email + entrega WhatsApp. +**La app y el worker ya están listos para esto; solo falta que el bot llame al EP.** Contrato: +[`mvp/b2c/api-docs/README.md`](../mvp/b2c/api-docs/README.md). ## 5. Infra (referencia rápida) @@ -94,6 +108,8 @@ ya loguea: `Lead X persistido via API: {...} → ok/fallo`. --- -**Resumen:** el flujo está demostrado funcionando; los 2 problemas son de la capa de transporte/runtime -del bot. Recomendación: cerrar el Problema B mirando los logs de `persistirTurno`, y para el Problema A -(estabilidad) migrar a **Evolution API**. Mientras tanto, para una demo: escaneo fresco + no redeployar. +**Resumen:** la conversación de Luisa funciona (recibe, resuelve `@lid`, cualifica, responde). Quedan 3 +cosas del bot: **A)** conexión inestable (`conflict`+`503`, bucle de reconexión) → Evolution API; +**B)** persistencia parcial del perfil + estado errático; **C)** el bot **nunca dispara la generación** +(no llama a `ingesta`/`perfilCompleto`), así que el presupuesto/render/entrega no llega — esto es lo que +rompe el end-to-end y lo que más conviene cerrar. App y worker ya están listos esperando esa llamada.