# Reformix Luisa Bot Agente de WhatsApp **Luisa** para **Reformix** — cualifica leads de reforma de forma conversacional siguiendo una máquina de estados de 7 pasos. Toda la persistencia va por **API HTTP** contra la app principal (`POST /api/leads/:id/perfil`, `conversacion`, etc.), no escribe a Postgres directamente. --- ## Stack | Capa | Tecnología | |------|-----------| | **Framework** | NestJS 10 | | **WhatsApp** | Baileys 7 (`@whiskeysockets/baileys`) + `baileys-antiban` | | **Persistencia** | API HTTP contra `REFORMIX_API_URL` con `Authorization: Bearer` | | **LLM** | Claude 4.5 Sonnet/Haiku + Gemini 2.5 Flash via **OpenRouter** | | **Logging** | Pino | | **QR** | `qrcode-terminal` | --- ## Estructura del proyecto ``` / ├── auth_info_baileys/ ← Estado de sesión de WhatsApp (se genera automáticamente) ├── dist/ ← Compilación ├── node_modules/ ├── prompts/ ← Prompts del sistema para Claude │ ├── luisa_core.md ← Identidad, personalidad y máquina de estados │ ├── luisa_flujo.md ← Flujo de cualificación paso a paso │ └── luisa_casos.md ← Casos edge y ejemplos ├── src/ │ ├── main.ts ← Punto de entrada │ ├── app.module.ts ← Módulo raíz │ ├── api/ │ │ ├── api-client.service.ts ← Cliente HTTP para endpoints de la app Reformix │ │ └── api.module.ts │ ├── whatsapp/ │ │ ├── whatsapp.module.ts │ │ ├── whatsapp.service.ts ← Conexión Baileys, recepción/envío │ │ └── whatsapp-debounce.service.ts ← Debounce de 3s para coalescer mensajes rápidos │ ├── leads/ │ │ ├── leads.module.ts │ │ └── leads.service.ts ← Máquina de estados, viabilidad (sin BD) │ ├── conversacion/ │ │ ├── conversacion.module.ts │ │ └── conversacion.service.ts ← Historial via API HTTP │ ├── claude/ │ │ ├── claude.module.ts │ │ └── claude.service.ts ← Arquitectura de 4 capas con Claude │ ├── media/ │ │ ├── media.module.ts │ │ └── media.service.ts ← Transcripción de audio + análisis de imagen │ └── webhook/ │ ├── webhook.module.ts │ └── webhook-listener.ts ← Servidor HTTP para recibir señales de la app ├── .env.example ├── nest-cli.json ├── package.json ├── tsconfig.json └── tsconfig.build.json ``` --- ## Arquitectura de procesamiento (4 capas con Claude) Cada mensaje entrante pasa por 4 capas antes de responder: ``` Mensaje entrante (texto / audio / imagen) ↓ ┌───────────────────────────────┐ │ PREPROCESAMIENTO │ │ • Verificar lead en sesión │ │ (llega via webhook, no por │ │ teléfono) │ │ • Si audio → transcripción │ │ (Gemini 2.5 Flash via │ │ OpenRouter) │ │ • Si imagen → Vision │ │ (Claude Sonnet via │ │ OpenRouter) + enviar a │ │ /ingesta │ │ • Si texto → directo │ │ • Guardar mensaje en │ │ /conversacion (API HTTP) │ └───────────┬───────────────────┘ ↓ ┌───────────────────────────────┐ │ CAPA 1: CLASIFICADOR (Haiku) │ └───────────┬───────────────────┘ ↓ ┌───────────────────────────────┐ │ CAPA 2: VALIDADOR (código) │ └───────────┬───────────────────┘ ↓ ┌───────────────────────────────┐ │ CAPA 3: GENERADOR (Sonnet) │ └───────────┬───────────────────┘ ↓ ┌───────────────────────────────┐ │ CAPA 4: REGLAS (Haiku) │ └───────────┬───────────────────┘ ↓ Guardar respuesta en /conversacion (API HTTP) ↓ Persistir datos en /perfil (API HTTP) ↓ Enviar por Baileys ``` --- ## Variables de entorno ### `.env.example` ```env OPENROUTER_API_KEY= # (REQUERIDA) API key de OpenRouter MODEL_GENERADOR=anthropic/claude-sonnet-4-5 # Modelo para generar respuestas (Capa 3) MODEL_CLASIFICADOR=anthropic/claude-haiku-4-5 # Modelo para clasificar mensajes (Capa 1) MODEL_REGLAS=anthropic/claude-haiku-4-5 # Modelo para aplicar reglas (Capa 4) MODEL_TRANSCRIPCION=google/gemini-2.5-flash # Modelo para transcripción de audio MODEL=anthropic/claude-sonnet-4-5 # Fallback general API_BASE_URL=https://reformix.dv3.com.es # (REQUERIDA) URL de la app Reformix FUNNEL_API_KEY= # (REQUERIDA) API key compartida WEBHOOK_PORT=3001 # (OPCIONAL) Puerto para webhooks entrantes ALLOWED_NUMBER= # (OPCIONAL) Restringe el bot a un solo número ``` **Notas:** - `API_BASE_URL` + `FUNNEL_API_KEY` reemplazan a la antigua `DATABASE_URL`. El bot ya no escribe a Postgres directamente. - `WEBHOOK_PORT` define dónde escucha el servidor HTTP para recibir señales de la app (`/whatsapp-start`, `/whatsapp-pdf`). - Una vez escaneado el QR, Luisa queda en espera. La app le enviará leads vía `WHATSAPP_START_WEBHOOK_URL`. --- ## Configuración rápida ### 1. Variables de entorno ```bash cp .env.example .env ``` Edita `.env` con tus valores reales. ### 2. Prompts de Luisa Los 3 archivos de prompts ya están creados y contienen la configuración completa. Puedes modificarlos para ajustar el tono o comportamiento de Luisa. ### 3. Arrancar ```bash npm install npm run start:dev ``` Aparecerá un **código QR** en la terminal. Escanéalo con WhatsApp → **WhatsApp Web**. Luisa queda conectada y escuchando webhooks de la app (por defecto en puerto 3001). --- ## Máquina de estados del lead | Estado | Descripción | |--------|-------------| | `nuevo` | Lead creado, aún no contactado | | `apertura` | Luisa se presenta y pregunta disponibilidad | | `espacio` | Pregunta: ¿qué espacio quieres reformar? | | `tamano` | Pregunta: ¿rango de metros cuadrados? | | `estilo` | Pregunta: ¿tipo de acabado? | | `urgencia` | Pregunta: ¿cuándo quieres empezar? | | `presupuesto` | Pregunta: ¿presupuesto aproximado? | | `fin_viable` | Lead viable (presupuesto >= 5000€) | | `fin_no_viable` | Lead no viable (presupuesto < 5000€) | ### Datos recolectados por estado | Estado | Campo perfil | Valores válidos | |--------|-------------|-----------------| | `espacio` | `espacio` | `cocina`, `bano`, `salon`, `comedor`, `integral`, `otro` | | `tamano` | `rangoM2` | `menos10`, `10a20`, `20a40`, `mas40` | | `estilo` | `estilo` | `funcional`, `cuidado`, `exclusivo` | | `urgencia` | `urgencia` | `alta`, `media`, `baja` | | `presupuesto` | `presupuestoDeclarado` | Cifra o rango en euros | --- ## Cómo se conecta con la app ``` App Reformix Bot (este proyecto) │ │ │ POST /webhook/whatsapp-start │ │ { leadId, telefono, nombre, empresa }────►│ Guarda sesión │ │ │ │ Cliente escribe a Luisa │ │ │ ◄── POST /api/leads/:id/conversacion ──── │ Guarda turno │ ◄── POST /api/leads/:id/perfil ────────── │ Actualiza datos │ ◄── POST /api/leads/:id/intento ───────── │ Registra contacto │ ◄── POST /api/leads/:id/ingesta ───────── │ Sube fotos del lead │ │ │ POST /webhook/whatsapp-pdf │ │ { leadId, telefono, pdfBase64 }──────────►│ Envía PDF al cliente ``` ## Flujo de webhooks | Webhook | Dirección | Puerto por defecto | |---------|-----------|-------------------| | `WHATSAPP_START_WEBHOOK_URL` | App → Bot | `http://bot:3001/whatsapp-start` | | `WHATSAPP_WEBHOOK_URL` | App → Bot | `http://bot:3001/whatsapp-pdf` | Configurar en el `.env` de la app Reformix. --- ## Debounce de mensajes El servicio `WhatsappDebounceService` agrupa mensajes rápidos de un mismo usuario en una ventana de **3 segundos**. Si el usuario envía varios mensajes cortos seguidos, se concatenan en un solo texto antes de procesarlos. --- ## Soporte multimedia ### Audio - Se descarga el buffer del mensaje de audio via Baileys. - Se detecta el formato real por magic bytes (Ogg, MP3, WAV). - Se envía a OpenRouter con `input_audio` usando **Gemini 2.5 Flash**. - La transcripción conserva coloquialismos y jerga madrileña. ### Imagen - Se descarga el buffer y se envía a OpenRouter con `image_url` usando **Claude Sonnet**. - Además se envía a `/ingesta` de la app Reformix para persistirla. - Si la imagen tiene caption, se combina con la inferencia. --- ## Manejo de errores y reconexión - Reconexión automática a WhatsApp tras 5 segundos (excepto logout). - Cada mensaje se procesa en un bloque `try/catch`. - Si Claude falla al clasificar, se usa un fallback conservador. - Las llamadas a la API de Reformix son **best-effort** (nunca lanzan error, loguean y continúan). --- ## Scripts disponibles ```bash npm run build # Compilar con NestJS npm run start # Iniciar en producción npm run start:dev # Iniciar en desarrollo con watch npm run lint # Ejecutar ESLint npm run test # Ejecutar tests con Jest ``` --- ## Desarrollo ### Requisitos - Node.js >= 20 - Cuenta en [OpenRouter](https://openrouter.ai) con API key - App Reformix corriendo con `FUNNEL_API_KEY` configurada --- Desarrollado para Reformix © 2026