api-docs/README.md: método/URL, auth Bearer, esquema del cuerpo (items foto/texto, zona, momento, flags), respuesta y errores (401/404/422), ejemplos curl de los 5 casos, y los payloads de los 3 webhooks salientes (perfil, entrega WhatsApp, arranque WhatsApp) con cómo el flujo externo devuelve las "después" por el mismo EP. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
API — Ingesta del perfil del lead
Endpoint único y asíncrono para enriquecer el perfil de un lead del funnel B2C. Cada llamada puede traer imágenes, imagen+texto o solo texto, etiquetado por zona y, en fotos, por momento (antes/después). Lo usan tanto el formulario web como los flujos externos (agente de llamada, bot de WhatsApp, generador de renders).
Endpoint
POST /api/leads/:id/ingesta
:id= UUID del lead (el que crea el funnel al capturar nombre/teléfono/email).- Auth: header
Authorization: Bearer <FUNNEL_API_KEY>. Sin él, o con clave incorrecta →401. - Content-Type:
application/json.
Cuerpo (JSON)
| Campo | Tipo | Notas |
|---|---|---|
items |
array | Lista de items foto o texto. Por defecto []. |
perfilCompleto |
boolean | Opcional. true señala al flujo externo que genere renders/agente. |
finalizar |
boolean | Opcional. true construye el PDF y lo entrega (email + señal WhatsApp). |
Debe llegar al menos un item o un flag; una llamada totalmente vacía → 422.
Item foto
| Campo | Tipo | Notas |
|---|---|---|
tipo |
"foto" |
Obligatorio. |
imagen |
string | Obligatorio. Data URI (data:image/...;base64,...) o URL http(s). |
zona |
enum | Opcional: cocina,bano,salon,comedor,integral,otro. Si falta, se asume el tipo de reforma del lead. |
momento |
"antes"|"despues" |
Por defecto "antes". Los renders del flujo externo se mandan como despues. |
orden |
number | Opcional. Si falta, continúa el máximo actual del lead. |
Item texto
| Campo | Tipo | Notas |
|---|---|---|
tipo |
"texto" |
Obligatorio. |
texto |
string | Obligatorio, no vacío. Ej. "suelo premium". |
zona |
enum | Opcional (mismo enum que en foto). |
Respuesta
200:
{
"ok": true,
"fotos": 1,
"notas": 1,
"perfilSenalado": false,
"finalizado": null
}
fotos/notas: cuántos items de cada tipo se guardaron.perfilSenalado:truesiperfilCompletodisparó el webhook y respondió ok.finalizado:nullsi no se pidiófinalizar; si sí,{ ok, emailEnviado, whatsappSenal }.
Códigos de error
| Código | Cuándo |
|---|---|
401 |
Falta Authorization: Bearer, o la clave no coincide con FUNNEL_API_KEY. |
404 |
El lead :id no existe. |
422 |
JSON inválido, item mal formado, o llamada vacía (sin items ni flag). |
Ejemplos (curl)
Sustituye LEAD, KEY y el host según tu entorno.
# 1) Solo texto: añade un dato a la zona baño
curl -X POST "$HOST/api/leads/$LEAD/ingesta" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"items":[{"tipo":"texto","zona":"bano","texto":"suelo premium"}]}'
# 2) Solo imagen (antes)
curl -X POST "$HOST/api/leads/$LEAD/ingesta" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"items":[{"tipo":"foto","zona":"bano","imagen":"https://cdn/ejemplo/bano-antes.jpg"}]}'
# 3) Imagen + texto en la misma llamada
curl -X POST "$HOST/api/leads/$LEAD/ingesta" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"items":[
{"tipo":"foto","zona":"cocina","imagen":"data:image/jpeg;base64,..."},
{"tipo":"texto","zona":"cocina","texto":"encimera de cuarzo"}
]}'
# 4) Perfil completo: que el flujo externo genere renders / corra el agente
curl -X POST "$HOST/api/leads/$LEAD/ingesta" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"perfilCompleto":true}'
# 5) Devolver renders "después" y finalizar (genera PDF + email + señal WhatsApp)
curl -X POST "$HOST/api/leads/$LEAD/ingesta" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"items":[{"tipo":"foto","zona":"cocina","momento":"despues","imagen":"https://cdn/render-cocina.jpg"}],"finalizar":true}'
Webhooks salientes (hacia el flujo externo)
La app emite estas señales; el flujo externo (n8n/generador) las recibe y, cuando toca,
devuelve los resultados llamando de nuevo a este mismo EP (las "después" con finalizar:true).
Cada webhook es opcional: sin su URL en el entorno, la señal simplemente no se manda.
PERFIL_WEBHOOK_URL — perfil completo
Disparado por perfilCompleto:true. Payload:
{
"leadId": "uuid",
"cliente": { "nombre": "...", "telefono": "...", "email": "...", "provincia": "..." },
"reforma": { "tipo": "cocina", "m2Suelo": 12, "calidad": "media",
"estructural": false, "urgencia": "media", "presupuestoTarget": 800000 },
"empresa": { "tenantId": "uuid", "nombre": "Reformas Ejemplo" },
"zonas": [
{ "zona": "cocina", "notas": ["encimera de cuarzo"],
"fotos": { "antes": ["url", "..."], "despues": [] } }
]
}
El flujo externo genera los renders y los devuelve como items
fotoconmomento:"despues"porPOST /api/leads/:id/ingesta, y cierra confinalizar:true.
WHATSAPP_WEBHOOK_URL — entrega del PDF
Disparado por finalizar:true. Payload:
{
"leadId": "uuid",
"telefono": "+34...",
"nombre": "...",
"empresa": "Reformas Ejemplo",
"pdfBase64": "JVBERi0xLj...",
"filename": "presupuesto-nombre.pdf"
}
WHATSAPP_START_WEBHOOK_URL — arranque de conversación
Disparado cuando el lead elige continuar por WhatsApp en el funnel. Payload:
{ "leadId": "uuid", "telefono": "+34...", "nombre": "...", "empresa": "Reformas Ejemplo" }
Notas
- Storage: las imágenes se guardan tal cual se reciben (data URI o URL) en
lead_fotos.url; las notas enlead_notas. Vermvp/b2c/db-schema/. - Email: la entrega por email es real vía SMTP (
SMTP_*+EMAIL_FROM). Sin configurar,finalizado.emailEnviadoseráfalsey el evento queda marcado como simulado. - Variables de entorno: ver
mvp/b2c/.env.example.