# 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.