# Handoff WhatsApp (Luisa) — para Simón Cómo integra el bot de WhatsApp con la app Reformix. **Una sola base de datos** (la de la app; Postgres). El **lead se crea siempre desde el form web**, así que cuando el cliente elige WhatsApp el lead **ya existe** y te pasamos su `leadId`. No creas leads tú. > **Modelo de integración (decidido):** el bot **no toca Postgres directamente**. Toda la escritura > va por **endpoints HTTP** autenticados (no necesitas credenciales de BD ni estar en la red de > Dokploy). Ya están **desplegados y probados** en `https://reformix.dv3.com.es`. --- ## 1. Cómo arranca tu flujo Cuando el cliente elige "WhatsApp" en el funnel, la app hace `POST` al webhook **`WHATSAPP_START_WEBHOOK_URL`** (lo configuras tú y nos lo pasas) con: ```json { "leadId": "uuid", "telefono": "+34...", "nombre": "...", "empresa": "Reformas Ejemplo" } ``` A partir de ahí Luisa escribe al `telefono` y trabaja **siempre con ese `leadId`**. ## 2. Cómo escribes en la BD: por API, no SQL | Qué guardas | Endpoint | | --- | --- | | Cada turno del chat (+ estado del mensaje y paso del bot) | `POST /api/leads/:id/conversacion` | | Lo que vas extrayendo del lead (espacio, m², estilo, urgencia, presupuesto, viabilidad…) | `POST /api/leads/:id/perfil` | | Calificación del lead (score/nivel/criterios) | `POST /api/leads/:id/calificacion` | | Intento de contacto (resultado de cada intento) | `POST /api/leads/:id/intento` | | **Fotos** del cliente + **notas/datos** por zona | `POST /api/leads/:id/ingesta` | | Señalar "perfil completo" / devolver renders / cerrar y entregar | `POST /api/leads/:id/ingesta` (flags `perfilCompleto` / `finalizar`) | **Auth (todos):** header `Authorization: Bearer ` (te paso la clave aparte; es la misma para todos los EPs). `Content-Type: application/json`. `:id` = el `leadId` del §1. **Por qué por EP y no SQL:** así se dispara nuestra lógica (motor de presupuesto, PDF, email, señales) y el esquema queda blindado (validación + tipos). Si escribieras las tablas a mano, esa lógica no corre y un valor inválido rompería la fila. **Doc completa con campos y ejemplos curl:** [`mvp/b2c/api-docs/README.md`](../mvp/b2c/api-docs/README.md). > `visitas` y `worker_jobs` quedan **fuera** de tu integración por ahora (son cola interna / panel > del reformista). Si los necesitas, lo hablamos y abrimos EP. ## 3. Resumen de los 4 EPs del bot Campos completos y curls en api-docs; aquí el mínimo de cada uno. - **`conversacion`** — `{ rol: user|assistant|system, mensaje, [mediaType, mediaUrl, transcripcionAudio, estadoWa, botStep] }` → `{ ok, id }`. - **`perfil`** (update parcial, solo lo que mandes) — cualquier subconjunto de: `botStep, estadoWa, canalOrigen, viable, espacio, rangoM2, estilo, presupuestoDeclarado, fotosSolicitadasAt, tipoReforma, m2Suelo, calidadGlobal, urgencia, presupuestoTarget, tasteText, estructural` → `{ ok, actualizado:[...] }`. - **`calificacion`** (upsert, 1 por lead) — `{ [score 0-100, nivel A|B|C|D, criterios{}, notasAgente] }` → `{ ok }`. - **`intento`** — `{ canal: formulario|whatsapp|llamada, numeroIntento, [resultado, completado, duracionSeg, notas, metadata{}] }` → `{ ok, id }`. Errores comunes a todos: `401` (sin Bearer o clave mala), `404` (lead no existe), `422` (JSON o validación). Body de error: `{ ok:false, error:"..." }`. ## 4. Enums y tipos: usa los valores de la API (esto es lo que hay que alinear en el bot) Tu esquema `reformix-full` tenía otros valores. En la API mandan estos (el EP rechaza con `422` lo que no encaje): **`tipoReforma`** → `cocina · bano · salon · comedor · integral · otro` `oficina`/`local`/`otros` → usa `otro`. **`urgencia`** → `alta · media · baja` (tu `inmediata` → `alta`). **`estadoWa`** (entrega del **mensaje**) → `sin_enviar · enviado · entregado · leido · fallido`. **`canalOrigen`** → `formulario_web · whatsapp · llamada · referido · anuncio`. **`calificacion.nivel`** → `A · B · C · D`. **`intento.canal`** → `formulario · whatsapp · llamada`. **`intento.resultado`** → `exitoso · no_contesta · ocupado · rechaza · error_tecnico`. **Tipos:** `estructural` = **boolean** (no texto). `calidadGlobal` = enum **`basica`/`media`/`premium`** (no 1-10; la extracción cruda de calidad va en `estilo`/`tasteText`). `m2Suelo` = número (>0). `presupuestoTarget` = entero en **céntimos**. `fotosSolicitadasAt` = string ISO datetime. **`pipeline_stage` / `estado`** → **no los escribas**. Los gestiona nuestro funnel/EP. ## 5. `bot_step` (estado de la conversación de Luisa) — persistido Texto libre (lo mandas en `conversacion.botStep` o `perfil.botStep`). Lo guardamos en `leads.bot_step` para verlo en el panel y poder retomar si el chat se corta. Valores sugeridos (puedes ajustar el vocabulario, es TEXT): `apertura → espacio → tamano → estilo → urgencia → presupuesto → pide_fotos → fotos_recibidas → completado` Terminales: `no_viable`, `abandonado`. > Ojo: `estadoWa` es la **entrega del mensaje** (enviado/leído…), **no** el paso de la conversación. > El paso es `botStep`. ## 6. Webhooks salientes de la app (los recibes/encadenas tú) - `WHATSAPP_START_WEBHOOK_URL` — inicio (§1). - `PERFIL_WEBHOOK_URL` — cuando marcas `perfilCompleto` en ingesta, te llega toda la data por zona para generar renders/agente (payload en api-docs §webhooks). - `WHATSAPP_WEBHOOK_URL` — entrega: cuando el PDF está listo (`finalizar`), te llega `{ pdfBase64, telefono, ... }` para mandarlo por WhatsApp. Pásanos las **3 URLs** y las ponemos en producción (Dokploy). ## 7. Lo que hace la app sola (no lo dupliques) `pipeline_stage`, cálculo del **presupuesto** orientativo, generación del **PDF** y envío del **email** los hace la app cuando llamas a ingesta con `finalizar`. Tú aportas fotos/notas, el historial del chat y el estado de la conversación; la app produce el entregable. ## 8. Estado: probado y en producción Los 4 EPs están desplegados en `https://reformix.dv3.com.es` y verificados end-to-end (lead real → `200` con id de fila insertada, perfil reflejado en el panel, upsert de calificación correcto). Smoke test reutilizable: [`mvp/b2c/api-docs/smoke-bot-eps.mjs`](../mvp/b2c/api-docs/smoke-bot-eps.mjs). --- **Resumen de lo que necesito de ti (Simón):** (1) las **3 URLs de webhook** (§6), (2) confirmar que el bot usa nuestros **enums/tipos** (§4). La conexión a la BD ya no hace falta: trabajas solo con la URL pública + `FUNNEL_API_KEY`.