GET /debug (Basic auth, QR_TOKEN) devuelve el estado de la conexión de WhatsApp y un anillo de los últimos mensajes entrantes (remoteJid real, fromMe, tipo) y el resultado del matching lead↔teléfono. Para diagnosticar por qué el bot no responde (conexión zombi vs formato de jid @lid vs no llega el mensaje). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 |
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
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_KEYreemplazan a la antiguaDATABASE_URL. El bot ya no escribe a Postgres directamente.WEBHOOK_PORTdefine 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
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
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_audiousando 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_urlusando Claude Sonnet. - Además se envía a
/ingestade 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
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 con API key
- App Reformix corriendo con
FUNNEL_API_KEYconfigurada
Desarrollado para Reformix © 2026