From 35ba2f28fea52b1a64223333e94220aba95556e6 Mon Sep 17 00:00:00 2001 From: Carlos Narro Date: Wed, 3 Jun 2026 19:09:59 +0200 Subject: [PATCH] Documenta el EP de ingesta y los webhooks salientes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- mvp/b2c/api-docs/README.md | 163 +++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 mvp/b2c/api-docs/README.md diff --git a/mvp/b2c/api-docs/README.md b/mvp/b2c/api-docs/README.md new file mode 100644 index 0000000..61f73ca --- /dev/null +++ b/mvp/b2c/api-docs/README.md @@ -0,0 +1,163 @@ +# 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 `. 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`: + +```json +{ + "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. + +```bash +# 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: + +```json +{ + "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: + +```json +{ + "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: + +```json +{ "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`.