Files
reformix-hackaton/mvp/b2c/api-docs
Carlos Narro 35ba2f28fe Documenta el EP de ingesta y los webhooks salientes
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>
2026-06-03 19:09:59 +02:00
..

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: true si perfilCompleto disparó el webhook y respondió ok.
  • finalizado: null si 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 foto con momento:"despues" por POST /api/leads/:id/ingesta, y cierra con finalizar: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 en lead_notas. Ver mvp/b2c/db-schema/.
  • Email: la entrega por email es real vía SMTP (SMTP_* + EMAIL_FROM). Sin configurar, finalizado.emailEnviado será false y el evento queda marcado como simulado.
  • Variables de entorno: ver mvp/b2c/.env.example.