# SOP: Sistema de Asistente Virtual AI (SNH)
Guía para entender, replicar y alimentar el sistema de asistentes virtuales de Sierra Nevada Holdings.
Este documento es **auto-contenido**: permite construir el mismo sistema para un nuevo cliente/marca
con otra persona, colores y proyectos.
> **Público:** desarrolladores y equipos que van a replicar/operar el sistema.
> **Base:** repositorio `hogar-pro-lab` (Vite + React + TypeScript + Supabase).
---
## 1. Resumen ejecutivo
El sistema NO es un solo bot: son **tres asistentes independientes** que comparten la misma
base de datos y la misma "persona" (un asesor de inversiones llamado **Santiago**), más un
**sistema de aprendizaje automático** que mejora los bots con cada conversación real de los brokers.
| Componente | Canal | Usuario | Modelo LLM | Función backend |
|------------|-------|---------|------------|-----------------|
| **Web Chat AI** | Chat flotante del sitio público | Visitantes anónimos | DeepSeek (`deepseek-chat`) | `web-chat-ai` |
| **Social Chat AI** | WhatsApp / Instagram / Facebook | Leads de redes sociales | DeepSeek (`deepseek-chat`) | `social-chat-ai` |
| **Investor AI** | Chat dentro de portales (inversionista) | Usuarios autenticados | Gemini 2.5 Flash (Lovable Gateway) | `ai-broker-conversation` |
| **Learning loop** | Automático (cron diario) | — | DeepSeek (`deepseek-chat`) | `ai-learn-from-chats` |
**Lo que logra el sistema en conjunto:**
- Vende proyectos inmobiliarios en 3 canales (web, redes, portales) 24/7.
- Captura leads con datos de contacto y los califica.
- Agenda citas de llamada con el equipo comercial.
- Permite intervención humana (el broker toma control y pausa la IA).
- Aprende de las conversaciones de los brokers y mejora solo.
---
## 2. Qué hace el bot actual (funcionalidad)
### 2.1 Bot del chat web público ("Santiago") — el principal
Aparece como **botón flotante naranja en todas las páginas públicas**
(montado en la raíz de la app, `src/App.tsx` → ``).
Al abrirlo:
1. **Captura el lead primero**: pide nombre, teléfono (con selector de código de país) y email
antes de permitir chatear.
2. **Responde en español o inglés** (según el idioma seleccionado en la web) sobre:
- **Proyectos**: precios **en COP y USD** (convierte ÷ 4,200), ubicación, tipo, ROI esperado.
- **Lotes disponibles**: número, área en m², precio en COP + conversión USD.
- **Índice de ocupación** (lo traduce a "puedes construir el X% del terreno").
- **Cotizaciones de villa** (lote + construcción) con la misma fórmula de la calculadora del sitio.
- **Plan de pagos**: reserva 2%, saldo de terreno en 6 cuotas, construcción en 20 cuotas.
- Brochures, videos, ubicación en mapa, landing page del proyecto.
3. **Manda "action cards"** (botones) cuando corresponde:
Descargar Brochure · Ver Video · Ver en Mapa · Agendar Llamada · Ver Página del Proyecto.
4. **Agenda citas** con calendario/horario integrado (8:00 AM – 6:00 PM) → crea `appointments`
y envía email de confirmación (Resend).
5. **Crea el lead automáticamente** en `social_leads` con el primer mensaje.
6. **Persiste el chat en localStorage** → el visitante reanuda la conversación al volver.
### 2.2 Bot de redes sociales (WhatsApp / Instagram / Facebook)
Mismo personaje, pero **respondiendo solo** por Meta Graph API:
- Cuando llega un mensaje por WhatsApp o Instagram/Facebook, el webhook crea el lead y el bot responde solo.
- Gestiona **agendamiento por conversación**: "¿Qué día te viene bien?" → parsea la fecha →
"¿A qué hora?" → parsea la hora → crea la cita en `appointments`.
- Si el **broker interviene**, la IA se pausa 24h y luego se reactiva.
### 2.3 Bot del portal del inversionista (autenticado)
Aparece dentro de los portales (`src/components/Layout.tsx` → ``) y en la página
de detalle de cada proyecto (`src/pages/ProjectDetail.tsx` → ``):
- Se **personaliza con el perfil** del inversionista (del diagnóstico: presupuesto, objetivo,
tolerancia al riesgo, compatibilidad turismo/residencial).
- Es un **vendedor consultivo**: descubre → educa → maneja objeciones → cierra hacia reserva.
- **Califica el lead** (score SQL 0–100) y crea `investment_inquiries` con todos los datos del prospecto.
- Agenda llamadas con selector de día/horario.
### 2.4 Sistema de aprendizaje (el "cerebro" que se alimenta solo)
- Cuando un broker responde un chat web, **la conversación completa se guarda** en `ai_conversation_logs`.
- Un **cron diario (3:00 AM UTC)** hace que DeepSeek analice conversaciones y **extraiga patrones**
(objeciones bien manejadas, argumentos de ROI, tips de cierre) → `ai_knowledge_base`.
- Esos patrones se **inyectan en el prompt** de los bots como "CONOCIMIENTO APRENDIDO DE BROKERS
EXITOSOS" → cada vez que los brokers venden mejor, el bot aprende.
### 2.5 Panel del broker (control humano)
En `/broker/leads` el broker ve todos los chats web + leads sociales con la conversación, y puede:
- **Tomar control** (pausa la IA 12h en web / 24h en redes), responder como humano, y **reactivar la IA**.
- Archivar chats, destacarlos, ver leads nuevos.
---
## 3. Arquitectura
### 3.1 Flujo general del Web Chat AI
```
Visitante abre widget (App.tsx)
└─> Form captura (nombre/tel/email) → INSERT web_chats + web_chat_messages (bienvenida)
└─> Cada mensaje → supabase.functions.invoke("web-chat-ai")
├─ 1. Lee web_chats (¿intervino humano en 12h? → respondo pasivamente)
├─ 2. Lee historial (últimos 50 mensajes)
├─ 3. Lee investment_projects activos + project_documents + project_lots
├─ 4. Lee ai_knowledge_base (effectiveness_score ≥ 5)
├─ 5. Arma SYSTEM PROMPT dinámico (persona + datos reales)
├─ 6. Llama a DeepSeek (deepseek-chat, temperature 0.7)
├─ 7. Post-procesa: detecta idioma + intención → agrega marcadores [BROCHURE_URL=...]
│ [VIDEO_URL=...] [LOCATION_URL=...] [SCHEDULE_CALL] [LANDING_URL=...]
└─ 8. Devuelve { reply, actions } → widget renderiza texto + action cards
└─ 9. Crea social_leads si no existe
```
### 3.2 Flujo general del Social Chat AI
```
Mensaje entrante (WhatsApp / IG / FB)
└─> Meta Graph API → whatsapp-webhook | meta-instagram-webhook
├─ 1. Crea/encuentra social_leads por platform_lead_id
├─ 2. Guarda social_messages (role=visitor)
└─ 3. Llama a social-chat-ai
├─ 4. Si scheduling_state = awaiting_date/time → parsea fecha/hora → crea appointments
├─ 5. Si no, arma prompt dinámico (igual que web) → DeepSeek
├─ 6. Envía respuesta por Meta Graph API (sendPlatformReply)
└─ 7. Guarda respuesta en social_messages y actualiza status del lead
```
### 3.3 Mapa de archivos canónicos
| Capa | Archivo | Rol |
|------|---------|-----|
| Frontend widget | `src/components/chat/WebChatWidget.tsx` | Chat flotante público (form, mensajes, action cards, scheduler) |
| Frontend portal | `src/components/chat/AIChatWidget.tsx` | Chat del inversionista autenticado |
| Frontend proyecto | `src/components/chat/ProjectChatModal.tsx` | Chat contextual por proyecto |
| Frontend broker | `src/pages/broker/Leads.tsx` | Panel de leads, chats y control de la IA |
| Función web | `supabase/functions/web-chat-ai/index.ts` | Brain del chat web público |
| Función social | `supabase/functions/social-chat-ai/index.ts` | Brain del chat de redes |
| Función inversionista | `supabase/functions/ai-broker-conversation/index.ts` | Brain del chat del inversionista |
| Función learning | `supabase/functions/ai-learn-from-chats/index.ts` | Extrae conocimiento de conversaciones |
| Webhook WA | `supabase/functions/whatsapp-webhook/index.ts` | Entrada de WhatsApp |
| Webhook IG | `supabase/functions/meta-instagram-webhook/index.ts` | Entrada de Instagram/Facebook |
| Emails citas | `supabase/functions/send-appointment-email/index.ts` | Confirmaciones y recordatorios (Resend) |
| Migraciones | `supabase/migrations/20260817*.sql`, `20260818*.sql`, `20260825*.sql` | Tablas, RLS, RPC, cron |
---
## 4. Modelo de datos
### 4.1 Tablas nuevas del sistema de chat
| Tabla | Propósito | Migración fuente |
|-------|-----------|------------------|
| `web_chats` | Sesiones de chat web (visitor_name/email/phone, status, human_intervened_at, broker_id) | `20260817100000_web_chat_tables.sql` |
| `web_chat_messages` | Mensajes del chat web (role: visitor/assistant/human, metadata jsonb) | ídem |
| `social_leads` | Leads de redes + web (platform, platform_lead_id, name/email/phone, status) | `20260813003641_*.sql` |
| `social_messages` | Mensajes sociales (role: visitor/assistant/human) | `20260817180000_social_messages.sql` |
| `campaigns` | Campañas (plataforma, broker asignado) | `20260813003641_*.sql` |
| `social_connections` | Conexiones de plataformas (Instagram/WhatsApp) | `20260813003641_*.sql` |
| `ai_knowledge_base` | Conocimiento aprendido (category, content, effectiveness_score, is_active) | `20260817160000_ai_knowledge_base.sql` |
| `ai_conversation_logs` | Conversaciones crudas de brokers (para aprender) | ídem |
| `ai_conversations` | Conversaciones del Investor AI + extracted_data + conversion_status | preexistente |
| `appointments` | Citas agendadas (preferred_date/time, status, broker_id) | `20260817120000_appointments_table.sql` |
| `appointment_notifications` | Rastro de emails enviados (previene duplicados) | `20260817140000_appointment_notifications.sql` |
### 4.2 Columnas clave en `social_leads` (más allá del schema base)
```sql
human_intervened_at timestamptz -- pausa la IA 24h
scheduling_state text -- 'awaiting_date' | 'awaiting_time'
scheduling_data jsonb -- { preferred_date, ... }
chat_id uuid REFERENCES web_chats(id) -- vincula lead web ↔ chat
```
### 4.3 Columnas clave en `web_chats`
```sql
status text -- 'active' | 'bot_paused' | 'closed'
human_intervened_at -- pausa la IA 12h
human_resumed_at
broker_id -- broker que tomó el chat
lead_id -- lead asociado
```
### 4.4 RPC públicas (acceso anónimo seguro)
Definidas en `20260825001857_*.sql`:
- `get_web_chat_public(p_chat_id)` → datos del chat para el propio visitante.
- `get_web_chat_messages_public(p_chat_id)` → mensajes (límite 500).
- `get_appointment_public(p_id)` / `reschedule_appointment_public(...)` → re-agendar cita pública.
> Usan `SECURITY DEFINER` y se otorgan solo a `anon, authenticated`. No expongas la tabla completa.
### 4.5 RLS esencial
- Web chats/mensajes y social messages usan políticas **scoped**: super_admin o broker asignado
(o `broker_id IS NULL` para los sin asignar).
- `ai_knowledge_base` es legible por todos (`USING (true)`) — es la que alimenta los prompts.
- Insertar chats/mensajes por anónimos está permitido con políticas `FOR INSERT WITH CHECK (true)`.
---
## 5. Edge Functions (backend)
Todas son funciones **Deno** (Supabase Edge Functions). Usan `createClient` con
`SUPABASE_SERVICE_ROLE_KEY` (bypass de RLS) y `corsHeaders` estándar.
### 5.1 `web-chat-ai`
- **Endpoint:** `POST /functions/v1/web-chat-ai`
- **Body:** `{ chat_id, message, language, visitor_name }`
- **Modelo:** DeepSeek `deepseek-chat` (max_tokens 800, temperature 0.7)
- **Secrets:** `DEEPSEEK_API_KEY`
- **Rol:** responde el chat web público, agrega action markers, crea `social_leads`.
### 5.2 `social-chat-ai`
- **Endpoint:** `POST /functions/v1/social-chat-ai`
- **Body:** `{ lead_id, message, platform, language }`
- **Modelo:** DeepSeek `deepseek-chat` (max_tokens 800, temperature 0.7)
- **Secrets:** `DEEPSEEK_API_KEY`, `META_PAGE_ACCESS_TOKEN`, `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`
- **Rol:** responde por redes, maneja el flujo de agendamiento (estado máquina), responde por Meta Graph API.
### 5.3 `ai-broker-conversation`
- **Endpoint:** `POST /functions/v1/ai-broker-conversation`
- **Body:** `{ message, conversationHistory, sessionId, projectContext?, investorContext? }`
- **Modelo:** Gemini 2.5 Flash vía Lovable Gateway (`ai.gateway.lovable.dev`)
- **Secrets:** `LOVABLE_API_KEY`
- **Rol:** asesor consultivo para inversionistas autenticados; calcula score SQL;
crea `investment_inquiries`; persiste en `ai_conversations` con `conversion_status`.
### 5.4 `ai-learn-from-chats`
- **Endpoint:** `POST /functions/v1/ai-learn-from-chats` (invocado por cron)
- **Modelo:** DeepSeek `deepseek-chat` (max_tokens 1000, temperature 0.3)
- **Secrets:** `DEEPSEEK_API_KEY`
- **Rol:** analiza hasta 5 `ai_conversation_logs` sin analizar, extrae patrones y los inserta
en `ai_knowledge_base` (máx. 3 items por conversación).
### 5.5 Webhooks de entrada (Meta)
- **`whatsapp-webhook`**: verificación GET (`hub.verify_token`) + eventos POST.
Crea lead y llama a `social-chat-ai`.
- **`meta-instagram-webhook`**: verificación GET + eventos POST de `page` messaging.
Detecta plataforma (`meta_instagram`), crea/encuentra lead, guarda mensaje y llama a `social-chat-ai`.
- **Secrets:** `WHATSAPP_WEBHOOK_VERIFY_TOKEN`, `META_PAGE_ACCESS_TOKEN`, etc.
### 5.6 `send-appointment-email`
- **Rol:** envía confirmaciones y recordatorios de citas (Resend).
- **Secrets:** `RESEND_API_KEY`.
- Tipos de notificación: `client_confirmation`, `client_reminder_24h`, `client_reminder_1h`,
`broker_confirmation`, `broker_reminder_24h`, `broker_reminder_30min`.
### 5.7 `twilio-sms`
- **Rol:** enviar SMS a leads (usado desde el panel del broker).
- **Secrets:** `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_PHONE_NUMBER`.
### 5.8 Configuración `verify_jwt` (⚠️ IMPORTANTE)
En `supabase/config.toml` se define `verify_jwt`. **Los webhooks de Meta (WhatsApp/Instagram)
NO envían JWT** → deben tener `verify_jwt = false` o Meta recibirá 401.
```toml
[functions.whatsapp-webhook]
verify_jwt = false
[functions.meta-instagram-webhook]
verify_jwt = false
# web-chat-ai / social-chat-ai / ai-broker-conversation / ai-learn-from-chats
# se invocan con anon key o service role (JWT válidos) → pueden quedarse en verify_jwt = true
```
> ⚠️ **Gotcha detectado:** en el repo actual, `config.toml` NO incluye estas entradas.
> Verifica al desplegar que las funciones de webhook queden con `verify_jwt = false`.
---
## 6. Secrets / Variables de entorno
Lista completa de secretos por función (configura en Supabase Dashboard → Edge Functions → Secrets
o con `supabase secrets set`):
| Variable | Para | ¿Obligatoria? |
|----------|------|----------------|
| `DEEPSEEK_API_KEY` | web-chat-ai, social-chat-ai, ai-learn-from-chats | sí (para el chat) |
| `LOVABLE_API_KEY` | ai-broker-conversation, analyze-investment-url, personalize-project-description | sí (portal) |
| `META_PAGE_ACCESS_TOKEN` | social-chat-ai, meta-instagram-webhook | sí (redes) |
| `WHATSAPP_PHONE_NUMBER_ID` | social-chat-ai | sí (WhatsApp) |
| `WHATSAPP_ACCESS_TOKEN` | social-chat-ai | sí (WhatsApp) |
| `WHATSAPP_WEBHOOK_VERIFY_TOKEN` | whatsapp-webhook | sí (verificación) |
| `RESEND_API_KEY` | send-appointment-email, notify-contact-submission, etc. | sí (emails) |
| `TWILIO_ACCOUNT_SID` / `TWILIO_AUTH_TOKEN` / `TWILIO_PHONE_NUMBER` | twilio-sms | opcional |
| `HUBSPOT_WEBHOOK_SECRET` / `Hubspot_SNH` / `HS_SNH` | integraciones HubSpot | opcional |
| `SUPABASE_URL` / `SUPABASE_SERVICE_ROLE_KEY` / `SUPABASE_ANON_KEY` | todas | automáticas (injectadas) |
---
## 7. Frontend del widget (nueva marca)
### 7.1 Archivos a crear/adaptar
| Archivo | Nota |
|---------|------|
| `src/components/chat/WebChatWidget.tsx` | Copiar tal cual y ajustar branding |
| `src/components/chat/AIChatWidget.tsx` | Si se replica el portal del inversionista |
| `src/components/chat/ProjectChatModal.tsx` | Si se replica el chat por proyecto |
| `src/App.tsx` | Montar `` al final de la app (fuera de ``) |
### 7.2 Cambios de marca (checklist)
1. **Colores:** el widget usa clases Tailwind `snh-orange`, `snh-dark`, `snh-orange/10`, etc.
Definidas en `tailwind.config.ts`. Para una nueva marca, redefine los tokens
(o agrega `brand-primary`, `brand-dark`) y reemplaza las clases `snh-*`.
2. **Textos:** el widget tiene textos hardcodeados con ternarios `lang === "en" ? ... : ...`
(ej. "Iniciar Chat", "Agendar Llamada"). Puedes dejarlos así o migrarlos al hook `useLanguage()`.
3. **Países/telefonía:** la lista `COUNTRIES` en `WebChatWidget.tsx` define códigos de país
(default `+57`). Ajusta el default al mercado del cliente.
4. **Logo/nombre:** el header del widget muestra "Sierra Nevada Holdings" hardcodeado → cambia
por el nombre de la nueva marca.
### 7.3 Action cards (botones que renderiza el widget)
Se renderizan desde `metadata.actions` de cada mensaje del asistente:
| `action.type` | Render | Acción |
|---------------|--------|--------|
| `brochure` | "Descargar Brochure" | abre `url` |
| `video` | "Ver Video" | abre `url` |
| `location` | "Ver en Mapa" | abre `url` |
| `schedule` | "Agendar Llamada" | abre el scheduler integrado |
| `landing` | "Ver Página del Proyecto" | abre `url` |
Los markers se generan en el backend (`[BROCHURE_URL=...]`, `[VIDEO_URL=...]`,
`[LOCATION_URL=...]`, `[SCHEDULE_CALL]`, `[LANDING_URL=...]`) y se parsean a `actions`.
---
## 8. Personalizar la persona del asistente
La personalidad vive **casi íntegramente en el system prompt** de cada función. No es RAG ni
base de conocimiento separada: es un prompt largo con reglas de negocio.
### 8.1 Dónde editar
| Persona | Archivo |
|---------|---------|
| Chat web público | `supabase/functions/web-chat-ai/index.ts` (variable `systemPrompt`) |
| Chat de redes | `supabase/functions/social-chat-ai/index.ts` (variable `systemPrompt`) |
| Chat del inversionista | `supabase/functions/ai-broker-conversation/index.ts` (variable `systemPrompt`) |
> ⚠️ **Gotcha:** los prompts de web y social están **duplicados casi idénticos**.
> Si mantienes ambos, edita en los dos (o extrae a un archivo compartido / una tabla).
### 8.2 Checklist de personalización
- [ ] **Nombre y rol** (SOBRE TI): "Soy Santiago, asesor de inversiones..." → nuevo nombre y contexto.
- [ ] **Mensaje de bienvenida** (primer mensaje del chat).
- [ ] **Reglas de moneda**: tasa de conversión (hoy fija `4200` COP/USD) y heurística
"precio > 1M = COP".
- [ ] **Regla de videos por idioma**: exige 2 videos por proyecto ("Video del proyecto (Español)"
e "(Inglés)") y regla de cuál enviar según el idioma del cliente.
- [ ] **Regla de lotes**: cómo mostrar lotes (2-3 más económicos primero, conversión a USD).
- [ ] **Regla de índice de ocupación**: cómo traducirlo para el cliente ("puedes construir X%").
- [ ] **Fórmula de cálculo de inversión**: pasos 1-6 (proyecto → lote → pisos → área construible →
costo construcción → total) + ejemplo con cifras reales.
- [ ] **Plan de pagos**: reserva %, número de cuotas (hoy 2% / 6 / 20).
- [ ] **Manejo de objeciones**: 4-5 frases/respuestas ("Es caro", "Tengo que pensarlo", ...).
- [ ] **Regla de acciones**: cuándo adjuntar brochure/video/ubicación/landing y cuándo no.
- [ ] **Regla de hablar con asesor**: siempre incluir `[SCHEDULE_CALL]`.
- [ ] **Regla de longitud**: máx. 2-4 oraciones, máx. 1 emoji, nunca decir "soy IA".
- [ ] **Idiomas soportados**: hoy es/es-en. El prompt inyecta `Lenguaje: Español/English`.
---
## 9. Cómo alimentar al asistente
El bot se alimenta de **5 fuentes**. Las primeras 4 son datos; la quinta es el aprendizaje automático.
### 9.1 Proyectos (`investment_projects`)
El bot lee automáticamente los proyectos `status = 'active'` (máx. 10) y usa:
`name, project_type, min_investment, expected_roi_min/max, city, department, short_description,
land_price, land_with_construction_price, brochure_url, google_maps_url, occupancy_estimate,
sim_allowed_floors, sim_cost_per_sqm_1floor, sim_cost_per_sqm_2floors, snh_markup_pct`.
**Reglas de calidad:**
- Precios completos y coherentes (COP vs USD).
- `short_description` en español (o bilingüe).
- `occupancy_estimate` correcto (afecta las cotizaciones).
- `snh_markup_pct` correcto (el bot aplica el mismo markup que las landing pages:
`precio / (1 - markup/100)`).
### 9.2 Documentos (`project_documents`)
El bot usa brochures y videos. **Los nombres y `document_type` son críticos**:
| document_type | Nombre esperado | Uso |
|---------------|-----------------|-----|
| `brochure_es` | — | brochure en español |
| `brochure_en` | — | brochure en inglés |
| `video` / `cover_video` | `Video del proyecto (Español)` | video ES (se busca **por nombre exacto**) |
| `video` / `cover_video` | `Video del proyecto (Inglés)` | video EN (se busca **por nombre exacto**) |
> ⚠️ **Gotcha:** el backend busca los videos por `name` EXACTO. Si el nombre difiere,
> el bot no encontrará el video y dirá "No disponible". Mantén la convención de nombres.
### 9.3 Lotes (`project_lots`)
El bot lee los lotes con `is_available = true` de cada proyecto activo:
`lot_number, area_sqm, price_cop`. Se muestran en las respuestas de "lotes disponibles".
### 9.4 Knowledge base manual (`ai_knowledge_base`)
Inserta conocimiento curado directamente (SQL o desde el panel):
```sql
INSERT INTO public.ai_knowledge_base (category, content, effectiveness_score, is_active, language)
VALUES
('faq', 'La inversión mínima es 2% de reserva...', 8, true, 'es'),
('objection', 'Si dicen "es caro", comparar con ROI 8-12% anual...', 9, true, 'es'),
('roi_argument', 'En Barú la ocupación promedio es X%...', 7, true, 'es');
```
**Categorías válidas** (check constraint):
`objection | technique | faq | roi_argument | closing_tip | project_insight | general`
El bot solo inyecta items con `is_active = true` y `effectiveness_score >= 5`.
### 9.5 Learning loop automático (no requiere acción manual)
1. Un broker responde un chat web en `Leads.tsx` → la conversación completa se guarda en
`ai_conversation_logs` (con `outcome: pending`, `project_id`, `broker_id`).
2. El cron diario **`ai-learn-from-chats`** (pg_cron, `0 3 * * *`) llama a la función
`ai-learn-from-chats` vía `net.http_post`.
3. DeepSeek analiza hasta 5 conversaciones no analizadas y extrae patrones reutilizables
(máx. 3 por conversación) → los inserta en `ai_knowledge_base` con `effectiveness_score`.
4. Los bots web/social los inyectan en el prompt bajo "CONOCIMIENTO APRENDIDO DE BROKERS EXITOSOS".
**Para activar el cron** (requiere config manual, no está en la migración):
```sql
-- En Supabase SQL Editor (usa el project_id real):
ALTER DATABASE postgres SET app.settings.supabase_url = 'https://TU_PROYECTO.supabase.co';
ALTER DATABASE postgres SET app.settings.service_role_key = 'TU_SERVICE_ROLE_KEY';
-- Verificar el schedule:
SELECT * FROM cron.job;
```
> La migración `20260817170000_ai_learning_cron.sql` crea `pg_cron` + `net` y el schedule.
> El `service_role_key` NO debe ir hardcodeado en código; se lee vía `app.settings`.
---
## 10. Despliegue paso a paso (nuevo proyecto)
### 10.1 Requisitos previos
- [ ] Supabase CLI (`supabase --version`).
- [ ] Cuenta DeepSeek API (obtener `DEEPSEEK_API_KEY`).
- [ ] (Opcional) Llave de Lovable Gateway para el Investor AI (`LOVABLE_API_KEY`).
- [ ] (Opcional) Cuenta Meta para WhatsApp/IG con tokens.
- [ ] (Opcional) Cuenta Resend para emails.
### 10.2 Pasos
```bash
# 1. Vincular el proyecto Supabase (nuevo o existente)
supabase login
supabase link --project-ref TU_PROJECT_ID
# 2. Aplicar migraciones (crea tablas, RLS, RPC, cron)
supabase db push
# 3. Aplicar configuración verify_jwt
# Edita supabase/config.toml (agrega verify_jwt=false a los webhooks)
supabase functions deploy --no-verify-jwt whatsapp-webhook
supabase functions deploy --no-verify-jwt meta-instagram-webhook
# 4. Desplegar funciones restantes
supabase functions deploy web-chat-ai social-chat-ai ai-learn-from-chats ai-broker-conversation
supabase functions deploy send-appointment-email twilio-sms
# 5. Configurar secrets
supabase secrets set DEEPSEEK_API_KEY=sk-... LOVABLE_API_KEY=...
supabase secrets set META_PAGE_ACCESS_TOKEN=... WHATSAPP_PHONE_NUMBER_ID=...
supabase secrets set WHATSAPP_ACCESS_TOKEN=... WHATSAPP_WEBHOOK_VERIFY_TOKEN=...
supabase secrets set RESEND_API_KEY=...
# 6. Configurar cron (SQL Editor)
ALTER DATABASE postgres SET app.settings.supabase_url = 'https://TU_PROYECTO.supabase.co';
ALTER DATABASE postgres SET app.settings.service_role_key = 'TU_SERVICE_ROLE_KEY';
```
### 10.3 Conexión de webhooks de Meta
- **WhatsApp (Meta Graph):** crea una app, obtén `WHATSAPP_PHONE_NUMBER_ID` y `WHATSAPP_ACCESS_TOKEN`.
Configura el webhook con URL `https://TU_PROYECTO.supabase.co/functions/v1/whatsapp-webhook`,
y el mismo `WHATSAPP_WEBHOOK_VERIFY_TOKEN` en "Verify token".
- **Instagram/Facebook:** URL
`https://TU_PROYECTO.supabase.co/functions/v1/meta-instagram-webhook`,
verify token `snh_meta_verify_2026` (o cambia el valor en el código) y suscríbete al campo `messages`.
---
## 11. Verificación
### 11.1 Chat web
1. Abre el sitio → botón flotante → completa el form (nombre/tel/email).
2. Pregunta: *"¿qué proyectos tienen?"* → debe listar proyectos con precios en COP y USD.
3. Pide *"el brochure de [proyecto]"* → debe aparecer la action card "Descargar Brochure".
4. Pide *"quiero hablar con un asesor"* → debe aparecer "Agendar Llamada" → agenda y valida
que se cree en `appointments` y llegue el email de confirmación.
5. Verifica que el lead quede en `social_leads` (platform = website).
### 11.2 Chat social
1. Envía un mensaje por WhatsApp al número conectado → debe responder "Santiago".
2. Escribe *"quiero agendar una llamada"* → sigue el flujo fecha → hora → confirma cita.
3. Revisa `social_messages` y `appointments` creados.
### 11.3 Learning
1. En `Leads.tsx`, responde un chat web como broker.
2. Verifica que se cree `ai_conversation_logs` con `analyzed = false`.
3. Invoca manualmente `ai-learn-from-chats` (o espera el cron) → valida que aparezcan items en
`ai_knowledge_base`.
### 11.4 Logs
Cada función loguea con prefijos (`[Web Chat AI]`, `[Social AI]`, `[AI Learn]`, `[WEBHOOK]`,
`[AI Broker]`). Revisa en Supabase Dashboard → Edge Functions → Logs.
---
## 12. Gotchas y troubleshooting
| # | Problema | Causa | Solución |
|---|----------|-------|----------|
| 1 | Meta webhook devuelve 401 | `verify_jwt = true` por defecto en `config.toml` | `verify_jwt = false` para `whatsapp-webhook` y `meta-instagram-webhook` |
| 2 | El bot dice "Video: No disponible" | El nombre del documento no coincide exactamente con `Video del proyecto (Español)` / `(Inglés)` | Corrige el `name` en `project_documents` |
| 3 | Función `send-social-message` no encontrada | `Leads.tsx` la invoca pero NO existe en el repo (solo existe `twilio-sms`) | Deployarla por separado o reemplazar la llamada |
| 4 | Precios errados en USD | Tasa fija `4200` y heurística `>1M = COP` hardcodeadas | Revisa si el dólar cambió; ideal: almacenar tasa en DB |
| 5 | Prompts duplicados web/social | `systemPrompt` copiado en dos funciones | Editar en ambos o extraer a un archivo compartido |
| 6 | El cron no corre | `app.settings.supabase_url` / `service_role_key` no configurados en la DB | Ejecutar los `ALTER DATABASE` del punto 10.2 |
| 7 | La IA no responde tras intervención humana | `human_intervened_at` pausa el bot (12h web / 24h social) | Es comportamiento esperado; reactivar en el panel del broker |
| 8 | El widget no aparece | `` no está montado en `App.tsx` | Agregarlo fuera de `` |
---
## 13. Resumen para un nuevo cliente
**Plan de acción de 30 minutos (si ya existe el repo base):**
1. Copia el repo (o crea una rama/cliente).
2. Cambia marca: colores `snh-*` en `tailwind.config.ts`, textos del widget, nombre de la persona
en los 3 system prompts.
3. Crea un nuevo proyecto Supabase → `supabase link` → `supabase db push`.
4. Despliega funciones + secrets + cron (sección 10).
5. Conecta los webhooks de Meta (sección 10.3).
6. Alimenta: proyectos activos + documentos (convención de nombres) + knowledge base manual (sección 9).
7. Verifica (sección 11).
> **Recordatorio:** el sistema aprende solo de las conversaciones de los brokers. Mientras más
> conversaciones reales se registren en `ai_conversation_logs`, mejor responderá el bot.