Hasta ahora el render solo se condicionaba con tipo/m²/calidad + notas de texto
libre por zona; lo que el cliente decía hablando con Luisa o en la llamada
(estilo, colores, materiales) se guardaba en estilo/tasteText pero NO viajaba al
generador de imagen, así que el render no lo representaba.
- b2c (perfil.ts): el payload de PERFIL_WEBHOOK_URL incluye ahora
preferencias:{estilo, gustos} (gustos = tasteText). Claves vacías se omiten.
- worker (webhook.dto): nuevo PreferenciasDto opcional.
- worker (prompt-builder): construirUserContent (función pura) inyecta el estilo
y los gustos del cliente como bloque dedicado y omite el "modern" por defecto
cuando hay preferencias; el system prompt prioriza colores/materiales del
cliente sobre un estilo genérico.
- worker (pipeline): enhebra preferencias hasta generarPrompt.
- worker (sandbox): acepta estilo/gustos para poder probarlos.
- docs/arquitectura-integracion: documenta el campo preferencias.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
582 lines
28 KiB
Markdown
582 lines
28 KiB
Markdown
# Arquitectura de Integración — Reformix
|
|
|
|
## Índice
|
|
|
|
1. [Visión general del sistema](#1-visión-general-del-sistema)
|
|
2. [Landing pública y captura del lead (sitio web)](#2-landing-pública-y-captura-del-lead-sitio-web)
|
|
3. [El funnel B2C — pipeline de 7 pasos](#3-el-funnel-b2c--pipeline-de-7-pasos)
|
|
4. [Elección de canal: formulario, llamada o WhatsApp](#4-elección-de-canal-formulario-llamada-o-whatsapp)
|
|
5. [Sistema de webhooks salientes (app → bot/worker)](#5-sistema-de-webhooks-salientes-app--botworker)
|
|
6. [Sistema de endpoints de bot (app ← bot/worker)](#6-sistema-de-endpoints-de-bot-app--botworker)
|
|
7. [El agente de WhatsApp (Luisa)](#7-el-agente-de-whatsapp-luisa)
|
|
8. [Workers: render de imágenes y presupuesto](#8-workers-render-de-imágenes-y-presupuesto)
|
|
9. [El presupuesto como entregable final](#9-el-presupuesto-como-entregable-final)
|
|
10. [Diagrama de flujo completo](#10-diagrama-de-flujo-completo)
|
|
11. [Estado actual vs lo que falta](#11-estado-actual-vs-lo-que-falta)
|
|
12. [Guía de conexión: cómo integrar Luisa + Workers con la app](#12-guía-de-conexión-cómo-integrar-luisa--workers-con-la-app)
|
|
|
|
---
|
|
|
|
## 1. Visión general del sistema
|
|
|
|
Reformix es un SaaS multi-tenant para empresas de reformas. Tiene **dos caras**:
|
|
|
|
- **B2B (reformista):** el reformista se registra, configura su perfil, catálogo de materiales, precios, y pone un widget en su web.
|
|
- **B2C (cliente final):** el cliente llega a la landing del reformista, pide presupuesto, sube fotos, y recibe un presupuesto con render "antes/después" en < 7 minutos.
|
|
|
|
### Componentes del sistema
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ SITIO WEB (Next.js) │
|
|
│ Landing → Formulario → Elección canal → Pipeline → Entrega │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ App Reformix (mvp/b2c/) │ │
|
|
│ │ - Landing pública /[slug] │ │
|
|
│ │ - Funnel B2C /solicitud/[id]/* │ │
|
|
│ │ - Panel reformista /panel/* │ │
|
|
│ │ - API endpoints para bot (mvp/b2c/src/app/api/leads/) │ │
|
|
│ │ - Motor de presupuesto (mvp/b2c/src/budget/) │ │
|
|
│ │ - Generación de PDF (mvp/b2c/src/lib/pdf/) │ │
|
|
│ │ - Envío de email (mvp/b2c/src/lib/email/) │ │
|
|
│ │ - Webhooks salientes (mvp/b2c/src/lib/webhooks.ts) │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ AGENTE WHATSAPP (Luisa) — EXTERNO │
|
|
│ (mvp/Whatsapp-bot/) │
|
|
│ - Conexión WhatsApp via Baileys │
|
|
│ - Pipeline Claude 4-capas para cualificar leads │
|
|
│ - DEBE usar API HTTP de la app (hoy escribe directo a BD) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ WORKERS (Render + Análisis) — NO IMPLEMENTADO AÚN │
|
|
│ - Generación de renders "después" (Nano Banana 2 / Image 2) │
|
|
│ - Análisis de fotos con IA │
|
|
│ - DEBE recibir webhook PERFIL_WEBHOOK_URL y devolver vía API │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Landing pública y captura del lead (sitio web)
|
|
|
|
### Flujo de captura
|
|
|
|
```
|
|
Usuario llega a /[slug] (landing del reformista)
|
|
│
|
|
▼
|
|
Rellena formulario Hero:
|
|
- nombre + email + teléfono
|
|
- consentimiento privacidad + contratación (RGPD obligatorio)
|
|
│
|
|
▼
|
|
crearLead(slug, data) → Server Action
|
|
│
|
|
├── Valida con Zod schema
|
|
├── Busca tenant por slug
|
|
├── INSERT en leads (pipelineStage: 'form_completado', estado: 'nuevo')
|
|
└── INSERT en leadPipelineEventos (stage: 'form_completado')
|
|
```
|
|
|
|
### Lo que crea la base de datos
|
|
|
|
El lead se crea con:
|
|
|
|
- `id` (UUID) — **este es el leadId que usará todo el sistema**
|
|
- `tenantId` (UUID) — referencia al reformista
|
|
- `nombre`, `email`, `telefono` — datos del cliente
|
|
- `pipelineStage: 'form_completado'` — dónde está en el pipeline
|
|
- `estado: 'nuevo'` — estado comercial
|
|
|
|
**Importante:** El lead NO tiene aún `tipoReforma`, `m2Suelo`, `calidadGlobal`, etc. Esos se rellenan después, cuando el cliente pasa por el canal elegido.
|
|
|
|
---
|
|
|
|
## 3. El funnel B2C — pipeline de 7 pasos
|
|
|
|
Definido por el enum `pipelineStage` en `src/db/schema.ts`:
|
|
|
|
| # | Stage | Qué ocurre | Quién lo dispara |
|
|
| --- | ---------------------- | ---------------------------------------------- | ------------------------------------------------------- |
|
|
| 1 | `form_completado` | Lead creado con datos básicos | Server Action `crearLead()` |
|
|
| 2 | `fotos_subidas` | Cliente describe reforma + sube fotos por zona | Server Action `guardarDetallesYFotos()` o API `ingesta` |
|
|
| 3 | `prellamada_enviada` | Notificación SMS/WhatsApp previa a llamada | Orchestrator `procesarLead()` o bot |
|
|
| 4 | `llamada_completada` | Agente IA (Retell) cualifica al lead | Orchestrator (simulado) o webhook Retell (real) |
|
|
| 5 | `render_generado` | Render "después" generado por IA | Orchestrator (simulado con imagen demo) |
|
|
| 6 | `presupuesto_generado` | Presupuesto calculado con motor real | Orchestrator `procesarLead()` |
|
|
| 7 | `whatsapp_entregado` | PDF entregado al cliente | `finalizarYEntregar()` |
|
|
|
|
### Pipeline automático (`procesarLead`)
|
|
|
|
Cuando el cliente completa el formulario detallado (con zonas, fotos, etc.), la app ejecuta:
|
|
|
|
```typescript
|
|
guardarDetallesYFotos(leadId, formData)
|
|
├── Guarda fotos (momento: 'antes') en leadFotos
|
|
├── Guarda notas en leadNotas
|
|
├── Calcula tipoReforma, m2Suelo, calidadGlobal desde las zonas
|
|
├── UPDATE lead (pipelineStage: 'fotos_subidas')
|
|
│
|
|
└── procesarLead(leadId) ← ORQUESTRADOR
|
|
├── Paso 4: Evento prellamada_enviada
|
|
├── Paso 5: Llamada Retell (real si configurado, sino simulada con transcript ficticio)
|
|
├── Paso 6a: Render demo (imagen estática, NO IA real)
|
|
├── Paso 6b: Presupuesto REAL con motor (computeBudget)
|
|
├── UPDATE lead (pipelineStage: 'presupuesto_generado')
|
|
└── Paso 7: Si envio=automatico → lead pasa a 'whatsapp_entregado'
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Elección de canal: formulario, llamada o WhatsApp
|
|
|
|
Después de crear el lead, el cliente llega a `/solicitud/[id]/` donde elige cómo continuar:
|
|
|
|
### Canal formulario (`/solicitud/[id]/formulario`)
|
|
|
|
El cliente rellena un formulario multi-zona: tipo de reforma, m², calidad, notas, sube fotos. Esto dispara `guardarDetallesYFotos()` que ejecuta el pipeline completo (incluyendo llamada simulada, render demo, presupuesto real).
|
|
|
|
### Canal llamada (`/solicitud/[id]/llamada`)
|
|
|
|
El cliente pide que le llamen (ahora o programado). Se le envía un email con enlace para subir fotos después. Dispara `pedirLlamada()` que inicia llamada Retell saliente (si configurado). El cliente recibe la llamada del agente IA, y después puede subir fotos via el enlace del email.
|
|
|
|
### Canal WhatsApp (`/solicitud/[id]/whatsapp`)
|
|
|
|
El cliente elige continuar por WhatsApp. La app dispara `iniciarWhatsapp()` que:
|
|
|
|
```typescript
|
|
iniciarWhatsapp(leadId)
|
|
└── POST a WHATSAPP_START_WEBHOOK_URL
|
|
Payload: { leadId, telefono, nombre, empresa }
|
|
→ El bot de WhatsApp (Luisa) recibe esto y empieza la conversación
|
|
```
|
|
|
|
**Este es el punto de entrada del bot de WhatsApp.** El bot recibe el `leadId` y a partir de ahí debe escribir los datos extraídos de la conversación usando los endpoints de la app.
|
|
|
|
---
|
|
|
|
## 5. Sistema de webhooks salientes (app → bot/worker)
|
|
|
|
La app envía 3 señales HTTP a sistemas externos. Todas son **best-effort** (nunca lanzan error, devuelven boolean).
|
|
|
|
### 5.1 WHATSAPP_START_WEBHOOK_URL — Arranque de conversación WhatsApp
|
|
|
|
**Disparado por:** `iniciarWhatsapp()` (cuando el lead elige canal WhatsApp)
|
|
|
|
```json
|
|
POST {url}
|
|
{
|
|
"leadId": "uuid",
|
|
"telefono": "+34...",
|
|
"nombre": "...",
|
|
"empresa": "Reformas Ejemplo"
|
|
}
|
|
```
|
|
|
|
**La app espera que:** el bot de WhatsApp reciba esto y comience la conversación con el lead. El bot debe usar el `leadId` para escribir los datos vía los endpoints de la app.
|
|
|
|
### 5.2 PERFIL_WEBHOOK_URL — Perfil completo para generar renders
|
|
|
|
**Disparado por:**
|
|
|
|
- `señalarPerfilCompleto()` en `guardarDetallesYFotos()` (formulario)
|
|
- `señalarPerfilCompleto()` en `subirFotos()` (fotos por email)
|
|
- API `ingesta` con flag `perfilCompleto: true` (bot/worker)
|
|
|
|
```json
|
|
POST {url}
|
|
{
|
|
"leadId": "uuid",
|
|
"cliente": { "nombre": "...", "telefono": "...", "email": "...", "provincia": "..." },
|
|
"reforma": { "tipo": "cocina", "m2Suelo": 12, "calidad": "media",
|
|
"estructural": false, "urgencia": "media", "presupuestoTarget": 800000 },
|
|
"preferencias": { "estilo": "nórdico", "gustos": "tonos azules, muebles de madera, encimera clara" },
|
|
"empresa": { "tenantId": "uuid", "nombre": "Reformas Ejemplo" },
|
|
"zonas": [
|
|
{ "zona": "cocina",
|
|
"notas": ["encimera de cuarzo"],
|
|
"fotos": { "antes": ["data:image/..."], "despues": [] } }
|
|
]
|
|
}
|
|
```
|
|
|
|
**`preferencias`** (opcional): gustos estéticos del cliente capturados en la conversación (`estilo` =
|
|
campo `estilo` del lead; `gustos` = `tasteText`, resumen en texto libre de colores/materiales/acabados
|
|
que pidió). Cada clave se omite si está vacía. El worker los inyecta como bloque dedicado en el prompt
|
|
de imagen para que el render los represente; si no llegan, infiere un estilo neutro.
|
|
|
|
**La app espera que:** el worker externo genere renders "después" a partir de las fotos "antes"
|
|
respetando las `preferencias` del cliente, y los devuelva haciendo POST al endpoint
|
|
`/api/leads/:id/ingesta` con `momento: "despues"` y opcionalmente `finalizar: true`.
|
|
|
|
### 5.3 WHATSAPP_WEBHOOK_URL — Entrega del PDF
|
|
|
|
**Disparado por:** `finalizarYEntregar()` (cuando el PDF está listo)
|
|
|
|
```json
|
|
POST {url}
|
|
{
|
|
"leadId": "uuid",
|
|
"telefono": "+34...",
|
|
"nombre": "...",
|
|
"empresa": "Reformas Ejemplo",
|
|
"pdfBase64": "JVBERi0xLj...",
|
|
"filename": "presupuesto-nombre.pdf"
|
|
}
|
|
```
|
|
|
|
**La app espera que:** el bot de WhatsApp reciba esto y envíe el PDF al cliente por WhatsApp.
|
|
|
|
---
|
|
|
|
## 6. Sistema de endpoints de bot (app ← bot/worker)
|
|
|
|
La app expone 5 endpoints bajo `/api/leads/:id/`. Todos requieren `Authorization: Bearer <FUNNEL_API_KEY>`.
|
|
|
|
| Endpoint | Qué hace | Tabla que escribe |
|
|
| ---------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
| `POST /api/leads/:id/conversacion` | Guarda un turno del chat | `conversacion_whatsapp` |
|
|
| `POST /api/leads/:id/perfil` | Actualiza datos extraídos del lead | `leads` (campos: espacio, rangoM2, estilo, tipoReforma, m2Suelo, etc.) |
|
|
| `POST /api/leads/:id/calificacion` | Upsert de calificación | `lead_calificacion` |
|
|
| `POST /api/leads/:id/intento` | Registra intento de contacto | `intentos_contacto` |
|
|
| `POST /api/leads/:id/ingesta` | Sube fotos/notas + flags de perfilCompleto/finalizar | `lead_fotos`, `lead_notas` |
|
|
|
|
### Flujo de uso típico del bot
|
|
|
|
```
|
|
1. Bot recibe WHATSAPP_START_WEBHOOK → leadId, telefono, nombre
|
|
2. Bot inicia conversación por WhatsApp
|
|
3. Por cada interacción:
|
|
a. Bot llama POST /conversacion (guarda el turno)
|
|
b. Bot llama POST /perfil (actualiza datos extraídos: espacio, m2, estilo, etc.)
|
|
c. Cuando tiene datos suficientes, llama POST /calificacion
|
|
d. Cuando pide fotos, el cliente las envía, bot las guarda vía POST /ingesta
|
|
4. Cuando el perfil está completo:
|
|
- Bot marca perfilCompleto: true en POST /ingesta
|
|
- Esto dispara PERFIL_WEBHOOK_URL → worker genera renders
|
|
- Worker devuelve renders vía POST /ingesta con momento: "despues" + finalizar: true
|
|
- finalizar:true dispara WHATSAPP_WEBHOOK_URL → bot recibe PDF y lo envía al cliente
|
|
```
|
|
|
|
---
|
|
|
|
## 7. El agente de WhatsApp (Luisa)
|
|
|
|
### Estado actual (código en `mvp/Whatsapp-bot/`)
|
|
|
|
El bot de Luisa es un servicio NestJS independiente que:
|
|
|
|
1. **Se conecta a WhatsApp** usando la librería Baileys (WebSocket no oficial)
|
|
2. **Orquesta un pipeline Claude de 4 capas:**
|
|
- Capa 1 — Clasificador (Haiku): extrae intención y valor del mensaje
|
|
- Capa 2 — Validador: valida contra valores permitidos
|
|
- Capa 3 — Generador (Sonnet): produce el borrador de respuesta
|
|
- Capa 4 — Reglas (Haiku): corrige tono e identidad
|
|
3. **Mantiene una máquina de estados** de 7 pasos para cualificar al lead:
|
|
`nuevo → apertura → espacio → tamano → estilo → urgencia → presupuesto → fin`
|
|
4. **Tiene un scheduler** que cada 5 minutos busca leads "nuevos" en BD y les envía el mensaje de apertura
|
|
5. **Soporta multimedia:** transcripción de audio (Gemini), análisis de imágenes (Claude Vision)
|
|
|
|
### Problema actual
|
|
|
|
El bot **escribe directamente a Postgres** usando TypeORM con sus propias entidades (`Lead` y `Conversacion`), en lugar de usar los endpoints HTTP de la app. Esto causa:
|
|
|
|
- Incompatibilidad de IDs: el bot usa `id` numérico autoincremental, la app usa UUID
|
|
- Tablas duplicadas: el bot tiene su propia tabla `conversacion`, la app tiene `conversacion_whatsapp`
|
|
- Enums desalineados: el bot usa `urgente/medio_plazo/frio`, la app usa `alta/media/baja`
|
|
- `synchronize: true` en TypeORM puede alterar el schema de la BD real
|
|
- El scheduler crea leads desde cero, pero según la arquitectura los leads ya existen (creados desde el form web)
|
|
|
|
### Lo que DEBE hacer el bot
|
|
|
|
1. **No crear leads.** Recibirlos vía `WHATSAPP_START_WEBHOOK_URL` con el `leadId` UUID.
|
|
2. **No escribir a BD directamente.** Usar los 5 endpoints HTTP (`conversacion`, `perfil`, `calificacion`, `intento`, `ingesta`).
|
|
3. **No tener scheduler propio.** El arranque lo hace la app vía webhook.
|
|
4. **Usar los enums correctos** según la app (`alta/media/baja`, `cocina/bano/salon/comedor/integral/otro`, etc.).
|
|
5. **No mantener estado propio.** El estado de la conversación (`botStep`) se persiste vía `/perfil`.
|
|
|
|
### Dónde está el código
|
|
|
|
| Elemento | Ruta |
|
|
| ----------------------- | ---------------------------- |
|
|
| Código del bot (NestJS) | `mvp/Whatsapp-bot/src/` |
|
|
| Prompts de Luisa | `mvp/Whatsapp-bot/prompts/` |
|
|
| Configuración (.env) | `mvp/Whatsapp-bot/.env` |
|
|
| Documentación del bot | `mvp/Whatsapp-bot/README.md` |
|
|
|
|
---
|
|
|
|
## 8. Workers: render de imágenes y presupuesto
|
|
|
|
### Estado actual
|
|
|
|
Los workers **no están implementados**. Existe:
|
|
|
|
- La **tabla `worker_jobs`** en la BD (`mvp/b2c/src/db/schema.ts:515-537`) con tipos: `analisis_fotos`, `render`, `presupuesto_ia`
|
|
- El **webhook `PERFIL_WEBHOOK_URL`** listo para enviar el perfil completo al worker
|
|
- El **endpoint `ingesta`** listo para recibir los renders de vuelta
|
|
- **Renders simulados** con imágenes estáticas en `procesarLead()` (usa `/despues.webp`, `/despues-bano.webp`, etc.)
|
|
|
|
### Lo que DEBE hacer el worker de renders
|
|
|
|
```
|
|
1. Recibe POST a PERFIL_WEBHOOK_URL con:
|
|
{ leadId, cliente, reforma, empresa, zonas: [{ zona, notas, fotos: { antes: [...], despues: [] } }] }
|
|
|
|
2. Para cada zona:
|
|
a. Toma las fotos "antes" del cliente
|
|
b. Genera render "después" usando modelo de IA (Nano Banana 2, Image 2, Stable Diffusion, etc.)
|
|
c. Convierte el render a data URI base64
|
|
|
|
3. Devuelve los renders haciendo POST a /api/leads/:id/ingesta:
|
|
{ items: [{ tipo: "foto", zona: "cocina", momento: "despues", imagen: "data:image/..." }],
|
|
finalizar: true }
|
|
(finalizar:true dispara la construcción del PDF + email + señal WhatsApp)
|
|
```
|
|
|
|
### Stack planeado para renders
|
|
|
|
Según la documentación:
|
|
|
|
- **Nano Banana 2** o **Image 2** (Google Gemini) — modelos image-to-image
|
|
- Alternativa: **Replicate SDXL + ControlNet** (~0,02€/imagen)
|
|
- Alternativa: **DALL-E 3 HD** (~0,08€/imagen)
|
|
|
|
### Dónde implementar los workers
|
|
|
|
Los workers son servicios externos independientes. Pueden ser:
|
|
|
|
- **n8n workflows** (orquestación visual)
|
|
- **Un worker Node.js/Python** que escucha webhooks y se comunica con APIs de IA
|
|
- **Cloud Functions** (Vercel, Cloudflare Workers, AWS Lambda)
|
|
|
|
No hay código de workers en este repositorio. El repositorio solo define el contrato (webhooks + API).
|
|
|
|
---
|
|
|
|
## 9. El presupuesto como entregable final
|
|
|
|
El entregable final es un **PDF de presupuesto** que incluye:
|
|
|
|
1. **Cabecera** con datos del reformista (logo, nombre, CIF, dirección)
|
|
2. **Datos del cliente** y tipo de reforma
|
|
3. **Tabla de presupuesto** con partidas calculadas por el motor:
|
|
- Demolición, impermeabilización, alicatado, fontanería, electricidad, carpintería, mano de obra, extras, licencia
|
|
4. **Render "después"** generado por IA
|
|
5. **Galería por zona** con fotos "antes" y "después"
|
|
6. **Footer legal**
|
|
|
|
### El motor de presupuesto (`mvp/b2c/src/budget/`)
|
|
|
|
**Es real, no simulado.** Calcula presupuestos basado en:
|
|
|
|
- Catálogo de materiales del reformista (precios por calidad: básica/media/premium)
|
|
- Configuración de precios (factores por zona, mano de obra, extras fijos)
|
|
- Inputs del lead: tipo de reforma, m², calidad, urgencia, estructural, provincia
|
|
|
|
### Flujo de entrega
|
|
|
|
```
|
|
finalizarYEntregar(leadId)
|
|
├── construirPresupuestoPdf(leadId)
|
|
│ ├── Carga lead completo + fotos + notas + catálogo + config
|
|
│ ├── Renderiza PDF con @react-pdf
|
|
│ └── Devuelve buffer PDF + filename
|
|
│
|
|
├── UPDATE lead (pdfUrl)
|
|
│
|
|
├── enviarPresupuestoEmail() → SMTP (opcional)
|
|
│
|
|
├── notificarFlujoWhatsapp() → WHATSAPP_WEBHOOK_URL
|
|
│ └── Bot recibe pdfBase64 y lo envía por WhatsApp
|
|
│
|
|
└── UPDATE lead (pipelineStage: 'whatsapp_entregado', estado: 'presupuesto_enviado')
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Diagrama de flujo completo
|
|
|
|
```
|
|
LANDING PÚBLICA /[slug]
|
|
│
|
|
crearLead()
|
|
│
|
|
▼
|
|
┌───────────────────────┐
|
|
│ Lead CREADO │
|
|
│ pipelineStage: │
|
|
│ form_completado │
|
|
│ estado: nuevo │
|
|
└───────────┬───────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────┐
|
|
│ ELECCIÓN DE CANAL │
|
|
│ /solicitud/[id]/ │
|
|
└──────┬────────┬───────┘
|
|
│ │
|
|
┌────────┘ └────────┐
|
|
▼ ▼
|
|
┌──────────────┐ ┌──────────────┐
|
|
│ FORMULARIO │ │ WHATSAPP │
|
|
│ + fotos │ │ │
|
|
└──────┬───────┘ │ iniciar │
|
|
│ │ Whatsapp() │
|
|
│ └──────┬───────┘
|
|
│ guardarDetalles │
|
|
│ YFotos() │ POST WHATSAPP
|
|
│ │ START WEBHOOK
|
|
├── Guarda fotos │
|
|
├── Guarda notas ▼
|
|
│ ┌──────────────┐
|
|
└── procesarLead() │ BOT WHATSAPP │
|
|
│ │ (Luisa) │
|
|
│ │ │
|
|
├── Simula │ Inicia │
|
|
│ llamada │ conversación │
|
|
│ │ │
|
|
├── Render │ Por cada msg: │
|
|
│ demo │ POST /perfil │
|
|
│ (img fija)│ POST /conv. │
|
|
│ │ │
|
|
├── Presup. │ Cuando listo: │
|
|
│ REAL │ POST /ingesta │
|
|
│ │ perfilCompleto│
|
|
│ │ │
|
|
└── Estado └──────┬────────┘
|
|
intermedio │
|
|
│ │
|
|
▼ │
|
|
┌────────────────────┘
|
|
│ PERFIL_WEBHOOK_URL
|
|
▼
|
|
┌──────────────────┐
|
|
│ WORKER RENDER │
|
|
│ Genera imágenes │
|
|
│ "después" │
|
|
└────────┬─────────┘
|
|
│ POST /api/leads/:id/ingesta
|
|
│ { items: [{tipo:"foto", momento:"despues",...}],
|
|
│ finalizar: true }
|
|
▼
|
|
┌──────────────────────────────┐
|
|
│ finalizarYEntregar() │
|
|
│ - Construye PDF │
|
|
│ - Envía email │
|
|
│ - WHATSAPP_WEBHOOK_URL │
|
|
└────────┬─────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────┐
|
|
│ BOT WHATSAPP │
|
|
│ Recibe pdfBase64│
|
|
│ y lo envía al │
|
|
│ cliente │
|
|
└──────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Estado actual vs lo que falta
|
|
|
|
| Componente | Estado | Notas |
|
|
| ---------------------------- | ------------------------------- | -------------------------------------------------- |
|
|
| Landing pública / formulario | ✅ Implementado | Multi-zona, fotos, notas |
|
|
| Motor de presupuesto | ✅ Implementado | Real, con catálogo y config |
|
|
| PDF | ✅ Implementado | Con @react-pdf |
|
|
| Email | ✅ Implementado | SMTP, best-effort |
|
|
| Endpoints API del bot | ✅ Implementado y en producción | 5 endpoints en `dv3.com.es` |
|
|
| Webhooks salientes | ✅ Implementado | 3 webhooks listos |
|
|
| Autenticación bot | ✅ Implementado | Bearer token con FUNNEL_API_KEY |
|
|
| Bot WhatsApp (Luisa) | ❌ Por reconectar | Hoy escribe directo a BD, debe usar APIs HTTP |
|
|
| Workers render | ❌ No implementado | Existe solo tabla y webhook, sin worker real |
|
|
| Renders IA | ❌ Simulado | Usa imágenes estáticas `/despues.webp` |
|
|
| Llamada Retell real | ⚠️ Parcial | Código listo, depende de config de vars de entorno |
|
|
| n8n workflows | ❌ No existe | Mencionado en docs pero sin implementar |
|
|
|
|
---
|
|
|
|
## 12. Guía de conexión: cómo integrar Luisa + Workers con la app
|
|
|
|
### 12.1 Lo que necesita el bot de WhatsApp (Luisa)
|
|
|
|
El bot DEBE:
|
|
|
|
1. **Recibir leads por webhook** (`WHATSAPP_START_WEBHOOK_URL`), no buscarlos en BD.
|
|
2. **Usar los endpoints HTTP de la app** en lugar de TypeORM:
|
|
- `POST /api/leads/:id/conversacion` — para cada turno del chat
|
|
- `POST /api/leads/:id/perfil` — para actualizar datos extraídos
|
|
- `POST /api/leads/:id/calificacion` — para calificar al lead
|
|
- `POST /api/leads/:id/intento` — para registrar intentos
|
|
- `POST /api/leads/:id/ingesta` — para subir fotos y marcar perfil completo
|
|
3. **Usar los enums correctos** de la app:
|
|
- `urgencia`: `alta`, `media`, `baja`
|
|
- `tipoReforma`: `cocina`, `bano`, `salon`, `comedor`, `integral`, `otro`
|
|
- `calidadGlobal`: `basica`, `media`, `premium`
|
|
- `estadoWa`: `sin_enviar`, `enviado`, `entregado`, `leido`, `fallido`
|
|
- `canalOrigen`: `formulario_web`, `whatsapp`, `llamada`, `referido`, `anuncio`
|
|
4. **Trabajar con UUIDs** (el `leadId` que recibe del webhook).
|
|
5. **No tener scheduler interno** — la app controla cuándo arrancar.
|
|
|
|
### 12.2 Lo que necesita el worker de renders
|
|
|
|
El worker DEBE:
|
|
|
|
1. **Escuchar en la URL que configures como `PERFIL_WEBHOOK_URL`** (POST /perfil-completo).
|
|
2. **Recibir el payload** con `leadId`, `cliente`, `reforma`, `zonas` (con fotos "antes").
|
|
3. **Generar renders** para cada zona: Etapa 1 (Claude Haiku → prompt), Etapa 2 (Gemini Flash → imagen), Etapa 3 (Claude Haiku Vision → validación). Todo via OpenRouter.
|
|
4. **Devolver los renders** llamando a `POST /api/leads/:id/ingesta` con:
|
|
5. **Autenticarse** con `Authorization: Bearer <FUNNEL_API_KEY>`.
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"tipo": "foto",
|
|
"zona": "cocina",
|
|
"momento": "despues",
|
|
"imagen": "data:image/..."
|
|
}
|
|
],
|
|
"finalizar": true
|
|
}
|
|
```
|
|
5. **Autenticarse** con `Authorization: Bearer <FUNNEL_API_KEY>`.
|
|
|
|
### 12.3 Variables de entorno necesarias en la app
|
|
|
|
```
|
|
# Para que el bot pueda escribir en la BD:
|
|
FUNNEL_API_KEY=<clave-compartida>
|
|
|
|
# URLs donde escuchan el bot y el worker:
|
|
WHATSAPP_START_WEBHOOK_URL=https://url-del-bot/whatsapp-start
|
|
PERFIL_WEBHOOK_URL=https://url-del-worker/perfil-completo
|
|
WHATSAPP_WEBHOOK_URL=https://url-del-bot/whatsapp-pdf
|
|
```
|
|
|
|
### 12.4 Secuencia de integración recomendada
|
|
|
|
**Paso 1 — Reconectar Luisa a los endpoints HTTP (prioridad alta)**
|
|
|
|
- Eliminar TypeORM y las entidades propias del bot
|
|
- Implementar llamadas HTTP a los 5 endpoints de la app
|
|
- Ajustar enums y tipos para que coincidan con la app
|
|
- Eliminar el scheduler interno
|
|
- Configurar las 3 URLs de webhook en la app
|
|
|
|
**Paso 2 — Implementar worker de renders (prioridad media)**
|
|
|
|
- Crear servicio que escuche `PERFIL_WEBHOOK_URL`
|
|
- Elegir modelo de IA para image-to-image
|
|
- Integrar con el endpoint `ingesta` para devolver resultados
|
|
|
|
**Paso 3 — Conectar llamada Retell real (prioridad baja)**
|
|
|
|
- Configurar variables de entorno de Retell
|
|
- El bot de WhatsApp o el formulario pueden complementar la llamada
|