Vía SSH al VPS + docker logs: la conexión Baileys está en bucle de reconexión (conflict:replaced + 503); persistirTurno funciona (→ ok) pero solo se llama en algunos turnos y la máquina de estados se descuadra; y el bot NUNCA llama a ingesta/perfilCompleto, así que la generación de presupuesto/render/entrega no se dispara (Problema C, el que rompe el end-to-end). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
116 lines
7.9 KiB
Markdown
116 lines
7.9 KiB
Markdown
# Handoff runtime del bot WhatsApp (Luisa) — para Simón
|
|
|
|
Estado tras la sesión de depuración del 09-jun. El **flujo conversacional funciona end-to-end**;
|
|
quedan **dos problemas de runtime del bot** que se diagnostican/cierran desde tu lado (tienes acceso
|
|
a los logs en Dokploy). La parte de la app (EPs) está verificada y no es el problema.
|
|
|
|
---
|
|
|
|
## 1. Lo que está PROBADO funcionando
|
|
|
|
- **EPs de la app** (en `mvp/b2c`): `conversacion`, `perfil`, `calificacion`, `intento`, `ingesta`,
|
|
+ `GET /api/leads/:id` y `GET /api/leads/:id/conversacion` y `GET /api/leads/by-phone`. Todos OK.
|
|
Verificado: `POST /perfil` con el payload exacto del bot (`{espacio,rangoM2,estilo,botStep}`) →
|
|
`200 {ok:true, actualizado:[...]}` y el lead se actualiza. **El EP no es el cuello de botella.**
|
|
- **Bot:** apertura proactiva, **resolución del `@lid`**, matching del lead por teléfono, y la
|
|
**conversación de cualificación de Luisa** (7 turnos reales: cocina → tamaño → estilo → urgencia).
|
|
- **Worker de render** (`mvp/image-worker`): genera renders con `google/gemini-2.5-flash-image`.
|
|
|
|
## 2. Cambios que hice hoy en el bot (`mvp/Whatsapp-bot`) — para que no te pillen por sorpresa
|
|
|
|
- **Apertura proactiva** al recibir `/whatsapp-start`: `WhatsappService` escucha un `startEmitter` y
|
|
envía el primer mensaje (antes solo registraba la sesión y esperaba al cliente). Persiste
|
|
`estadoWa/botStep` + intento.
|
|
- **Resolución `@lid`** ([`resolverTelefono`](../mvp/Whatsapp-bot/src/whatsapp/whatsapp.service.ts)):
|
|
WhatsApp entrega los mensajes desde una dirección `@lid` (p.ej. `239225534443615@lid`), no desde el
|
|
número. Se resuelve a número vía `msg.key.remoteJidAlt` o el mapa LID→PN de Baileys. **Esto era la
|
|
causa de que el bot ignorara los mensajes entrantes.**
|
|
- **Recuperación del lead por teléfono:** si la sesión no está en memoria (reinicio), `getOrCreateContext`
|
|
busca el lead en la BD vía `GET /api/leads/by-phone` y re-registra la sesión.
|
|
- **`markOnlineOnConnect: true`**: con `false`, tras reconectar el dispositivo quedaba "no disponible"
|
|
y WhatsApp **no entregaba** los mensajes. Con `true` empezó a recibir (la conversación de 7 turnos
|
|
lo demuestra).
|
|
- **Modelos de Claude corregidos** en el env: eran `claude-haiku-4-5` (guion) → inválidos en
|
|
OpenRouter; ahora `anthropic/claude-haiku-4.5` y `anthropic/claude-sonnet-4.5` (punto).
|
|
- **`BAILEYS_AUTH_DIR`** configurable (subcarpeta del volumen) para empezar una **sesión limpia**
|
|
sin perder persistencia. Hoy apunta a `/app/auth_info_baileys/v2`.
|
|
- **Endpoints de operación** (servidor de webhooks, puerto 3001):
|
|
- `GET /qr` — QR de vinculación como **imagen** (HTTP Basic; usuario cualquiera, contraseña =
|
|
`QR_TOKEN`, que está en el env del bot en Dokploy).
|
|
- `GET /debug` — estado de conexión + anillo de los últimos eventos entrantes (mismo auth). Útil
|
|
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 — conexión Baileys inestable (bucle de reconexión)
|
|
|
|
**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`
|
|
pero `inbound` no crece cuando el cliente escribe → estás en el estado "zombi".
|
|
- Para volver a recibir: sesión nueva → sube `BAILEYS_AUTH_DIR` a `/app/auth_info_baileys/v3`,
|
|
redeploy, escanea el QR fresco en `/qr`. **Y evita redeployar después** (cada reconexión arriesga
|
|
la recepción).
|
|
|
|
**Camino robusto (acordado con Carlos): migrar el transporte de WhatsApp del bot a Evolution API**
|
|
(ya está como primaria en el stack — NO la WhatsApp Cloud API oficial). Evolution gestiona la
|
|
conexión y entrega los mensajes por **webhook**, sin un socket Baileys en-proceso que se vuelva
|
|
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 se persiste solo a medias + máquina de estados errática
|
|
|
|
**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.
|
|
|
|
**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).
|
|
|
|
## 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)
|
|
|
|
| Servicio | App Dokploy | Dominio |
|
|
| --- | --- | --- |
|
|
| Bot | `reformix-bot` (`wY4F14fyEslU-4za_JIbi`) | `reformix-bot.dv3.com.es` (puerto 3001) |
|
|
| Worker | `reformix-worker` (`sMQd9zwoyV14q1vm8Vs8U`) | `reformix-worker.dv3.com.es` |
|
|
| App | `reformix-b2c` (`lzHDAuPuubbJu94OrkNS_`) | `reformix.dv3.com.es` |
|
|
|
|
- Build Dockerfile desde Gitea, autodeploy en push a `main`. Volumen del bot: `/app/auth_info_baileys`
|
|
(sesión WhatsApp). `OPENROUTER_API_KEY` y `FUNNEL_API_KEY` en el env de cada app.
|
|
- Contrato de los EPs y enums: [`mvp/b2c/api-docs/README.md`](../mvp/b2c/api-docs/README.md).
|
|
|
|
---
|
|
|
|
**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.
|