El bot de Luisa puebla la BD vía los 4 EPs HTTP (no SQL directo): conversacion, perfil, calificacion, intento. Actualiza el handoff de Simón en consecuencia (qué EP usa para cada cosa, enums/tipos a alinear, ya no necesita acceso a BD). Añade api-docs/smoke-bot-eps.mjs: crea un lead de prueba, ejerce los 4 EPs por HTTP, verifica en BD y limpia. Verificado end-to-end en produccion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.4 KiB
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:
{ "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 <FUNNEL_API_KEY> (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.
visitasyworker_jobsquedan 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:
estadoWaes la entrega del mensaje (enviado/leído…), no el paso de la conversación. El paso esbotStep.
6. Webhooks salientes de la app (los recibes/encadenas tú)
WHATSAPP_START_WEBHOOK_URL— inicio (§1).PERFIL_WEBHOOK_URL— cuando marcasperfilCompletoen 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.
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.