Files
reformix-hackaton/mvp/Whatsapp-bot
Carlos Narro a8b6d62dd6 Bot responde a mensajes entrantes: recupera lead por teléfono + fix matching
El bot no respondía a las réplicas del cliente: la sesión lead↔teléfono vivía
solo en memoria y no casaba (se pierde al reiniciar el contenedor). Ahora si no
está en memoria, getOrCreateContext busca el lead en la BD por teléfono vía un
EP nuevo GET /api/leads/by-phone (match por últimos 9 dígitos) y re-registra la
sesión. Aparte, los ids de modelo de Claude del bot estaban mal (-4-5 vs 4.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 17:28:56 +02:00
..
2026-06-02 23:07:34 -04:00
2026-06-01 23:24:43 -04:00

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

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

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_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

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_KEY configurada

Desarrollado para Reformix © 2026