# Configuración — Lealtad Demo n8n Este documento resume la configuración del demo de sistema de lealtad usando **Supabase**, **n8n** y **WhatsApp Business API / Meta Graph**. > **Nota de seguridad:** esta versión está preparada para subirse a Gitea. > No contiene tokens, teléfonos reales, credenciales, IDs reales de producción ni datos personales. --- ## 1. Objetivo del proyecto El flujo permite que un cliente escriba por WhatsApp y que n8n: 1. Reciba el mensaje desde WhatsApp. 2. Extraiga el teléfono y el texto del mensaje. 3. Consulte en Supabase si el cliente existe. 4. Cree el cliente si es nuevo. 5. Maneje el estado del bot: - `NEW` - `AWAITING_NAME` - `ACTIVE` 6. Responda por WhatsApp según el estado del cliente. 7. Entregue una URL de Wallet / pase digital. --- ## 2. Arquitectura general ```mermaid flowchart LR A[WhatsApp / Meta] --> B[n8n WhatsApp Trigger] B --> C[Code Node: parsear mensaje] C --> D[Supabase RPC: process_wa_customer] D --> E{Switch por estado} E -->|NEW| F[Actualizar a AWAITING_NAME] F --> G[Enviar mensaje solicitando nombre] E -->|AWAITING_NAME| H[Guardar nombre y activar cliente] H --> I[Enviar link de Wallet] E -->|ACTIVE| J[Responder saldo, tier y link de Wallet] ``` --- ## 3. Valores que deben reemplazarse Antes de usar este proyecto en una demo real o producción, reemplaza los siguientes placeholders: | Placeholder | Descripción | |---|---| | `TU_PROYECTO` | Subdominio del proyecto en Supabase | | `TU_SUPABASE_ANON_KEY` | Supabase anon key configurada en credenciales de n8n | | `TU_TOKEN_DE_META` | Token de Meta / WhatsApp Business API | | `TU_PHONE_NUMBER_ID` | ID del número de WhatsApp en Meta | | `COMPANY_ID_DEMO` | UUID de la empresa creada en la tabla `companies` | | `TU_PORTAL` | Dominio del portal donde se consulta/genera el Wallet | --- # Parte 1 — Configuración en Supabase En Supabase, abre el **SQL Editor**, pega el siguiente script y ejecútalo. Este script crea: - Tabla `companies` - Tabla `customers` - Tabla `transactions` - Trigger de gamificación automática - RPC `process_wa_customer` ```sql -- 1. Habilitar extensión para IDs seguros (UUID) CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- 2. TABLA: Empresas CREATE TABLE companies ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, wa_phone_id TEXT -- ID del número de WhatsApp en Meta ); -- 3. TABLA: Clientes finales CREATE TABLE customers ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), company_id UUID REFERENCES companies(id) ON DELETE CASCADE, phone TEXT NOT NULL, name TEXT, balance NUMERIC(10,2) DEFAULT 0.00, tier TEXT DEFAULT 'Member', bot_state TEXT DEFAULT 'NEW', pass_id TEXT UNIQUE DEFAULT uuid_generate_v4(), created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(company_id, phone) ); -- 4. TABLA: Transacciones CREATE TABLE transactions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), customer_id UUID REFERENCES customers(id) ON DELETE CASCADE, amount_spent NUMERIC(10,2) NOT NULL, cashback_earned NUMERIC(10,2) NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); -- 5. TRIGGER: Gamificación automática CREATE OR REPLACE FUNCTION update_customer_stats() RETURNS TRIGGER AS $$ DECLARE new_balance NUMERIC; BEGIN -- Sumar cashback al cliente UPDATE customers SET balance = balance + NEW.cashback_earned WHERE id = NEW.customer_id RETURNING balance INTO new_balance; -- Lógica de estatus dinámico IF new_balance >= 100 THEN UPDATE customers SET tier = 'Black' WHERE id = NEW.customer_id; ELSIF new_balance >= 50 THEN UPDATE customers SET tier = 'Gold' WHERE id = NEW.customer_id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER after_transaction_insert AFTER INSERT ON transactions FOR EACH ROW EXECUTE FUNCTION update_customer_stats(); -- 6. RPC: función que n8n llama para consultar/crear cliente CREATE OR REPLACE FUNCTION process_wa_customer( p_phone TEXT, p_company_id UUID ) RETURNS TABLE ( customer_id UUID, state TEXT, c_name TEXT, pass_id TEXT, c_balance NUMERIC, c_tier TEXT ) AS $$ DECLARE v_id UUID; v_state TEXT; v_name TEXT; v_pass TEXT; v_bal NUMERIC; v_tier TEXT; BEGIN -- Buscar cliente existente SELECT id, bot_state, name, customers.pass_id, balance, tier INTO v_id, v_state, v_name, v_pass, v_bal, v_tier FROM customers WHERE phone = p_phone AND company_id = p_company_id; -- Si es la primera vez que escribe, crearlo en estado NEW IF v_id IS NULL THEN INSERT INTO customers (phone, company_id, bot_state) VALUES (p_phone, p_company_id, 'NEW') RETURNING id, bot_state, name, customers.pass_id, balance, tier INTO v_id, v_state, v_name, v_pass, v_bal, v_tier; END IF; RETURN QUERY SELECT v_id, v_state, v_name, v_pass, v_bal, v_tier; END; $$ LANGUAGE plpgsql; ``` --- ## 4. Crear empresa demo Después de crear las tablas, inserta una empresa de prueba en `companies`. Ejemplo: ```sql INSERT INTO companies (name, wa_phone_id) VALUES ('Empresa Demo', 'TU_PHONE_NUMBER_ID'); ``` Luego copia el `id` generado por Supabase. Ese valor será usado en n8n como: ```text COMPANY_ID_DEMO ``` > No subas a Git/Gitea el UUID real de producción si este repositorio se usará como plantilla. --- # Parte 2 — Flujo en n8n El flujo en n8n procesa mensajes de WhatsApp, consulta Supabase y responde según el estado del cliente. --- ## Nodo 1 — WhatsApp Trigger Configura el nodo de entrada con las credenciales de WhatsApp / Meta. Responsabilidad: - Escuchar mensajes entrantes. - Recibir payloads de WhatsApp. - Disparar el flujo cuando llega un mensaje. --- ## Nodo 2 — Code Node: Parsear JSON de Meta Este nodo limpia el payload de Meta y devuelve solo los campos necesarios: - Teléfono del cliente - Texto recibido - ID del número de WhatsApp - ID de empresa demo ```javascript const body = $input.first().json; // Evitar procesar payloads sin mensajes reales if (!body?.messages?.length) { return []; } const msg = body.messages[0]; const phone_id = body.metadata?.phone_number_id; return [ { json: { phone: msg.from, text: msg.type === 'text' ? (msg.text?.body || '').trim() : '', phone_id, company_id: 'COMPANY_ID_DEMO' } } ]; ``` > Reemplaza `COMPANY_ID_DEMO` por el UUID real de la empresa creada en Supabase, o configura este valor mediante variables/credenciales según tu estándar interno. --- ## Nodo 3 — HTTP Request: Consultar a Supabase RPC Este nodo consulta la función `process_wa_customer` en Supabase. Configuración sugerida: | Campo | Valor | |---|---| | Method | `POST` | | URL | `https://TU_PROYECTO.supabase.co/rest/v1/rpc/process_wa_customer` | | Authentication | Header Auth / Custom Auth | | Header `apikey` | `TU_SUPABASE_ANON_KEY` | | Header `Authorization` | `Bearer TU_SUPABASE_ANON_KEY` | | Send Body | Activado | | Body Type | JSON | Body: ```json { "p_phone": "{{ $json.phone }}", "p_company_id": "{{ $json.company_id }}" } ``` --- ## Nodo 4 — Switch Node: Enrutador por estado Conecta la salida del HTTP Request de Supabase al Switch. El Switch debe evaluar el campo: ```text {{ $json.state }} ``` Rutas: | Ruta | Condición | Descripción | |---|---|---| | Ruta 0 | `state == NEW` | Cliente nuevo | | Ruta 1 | `state == AWAITING_NAME` | Cliente está dando su nombre | | Ruta 2 | `state == ACTIVE` | Cliente ya está activo | --- # Parte 3 — Ramas del Switch --- ## Rama 0 — Estado `NEW` Objetivo: pedir el nombre del cliente. ### Paso 1 — Actualizar estado en Supabase Nodo: HTTP Request | Campo | Valor | |---|---| | Method | `PATCH` | | URL | `https://TU_PROYECTO.supabase.co/rest/v1/customers?id=eq.{{ $json.customer_id }}` | | Auth | Misma configuración de Supabase | | Header recomendado | `Prefer: return=minimal` | | Body | JSON | Body: ```json { "bot_state": "AWAITING_NAME" } ``` ### Paso 2 — Enviar WhatsApp solicitando nombre Nodo: HTTP Request | Campo | Valor | |---|---| | Method | `POST` | | URL | `https://graph.facebook.com/v19.0/{{ $('Code').item.json.phone_id }}/messages` | | Header `Authorization` | `Bearer TU_TOKEN_DE_META` | | Body | JSON | Body: ```json { "messaging_product": "whatsapp", "to": "{{ $('Code').item.json.phone }}", "type": "text", "text": { "body": "¡Hola! 👋 Bienvenido a nuestro Club VIP. Para personalizar tu Billetera Digital, ¿cómo te gusta que te llamen?" } } ``` --- ## Rama 1 — Estado `AWAITING_NAME` Objetivo: guardar el nombre del cliente y activar su perfil. ### Paso 1 — Guardar nombre y pasar a `ACTIVE` Nodo: HTTP Request | Campo | Valor | |---|---| | Method | `PATCH` | | URL | `https://TU_PROYECTO.supabase.co/rest/v1/customers?id=eq.{{ $json.customer_id }}` | | Auth | Misma configuración de Supabase | | Body | JSON | Body: ```json { "bot_state": "ACTIVE", "name": "{{ $('Code').item.json.text }}" } ``` ### Paso 2 — Enviar link del Wallet Nodo: HTTP Request | Campo | Valor | |---|---| | Method | `POST` | | URL | `https://graph.facebook.com/v19.0/{{ $('Code').item.json.phone_id }}/messages` | | Header `Authorization` | `Bearer TU_TOKEN_DE_META` | | Body | JSON | Body: ```json { "messaging_product": "whatsapp", "to": "{{ $('Code').item.json.phone }}", "type": "text", "text": { "body": "¡Listo, {{ $('Code').item.json.text }}! 🎉\n\nTu perfil está activo. Descarga tu pase VIP y agrégalo a tu Apple/Google Wallet tocando este enlace:\n\n👉 https://TU_PORTAL/wallet/{{ $('HTTP Request').item.json.pass_id }}" } } ``` --- ## Rama 2 — Estado `ACTIVE` Objetivo: responder saldo, nivel y link de recuperación del Wallet. Nodo: HTTP Request | Campo | Valor | |---|---| | Method | `POST` | | URL | `https://graph.facebook.com/v19.0/{{ $('Code').item.json.phone_id }}/messages` | | Header `Authorization` | `Bearer TU_TOKEN_DE_META` | | Body | JSON | Body: ```json { "messaging_product": "whatsapp", "to": "{{ $('Code').item.json.phone }}", "type": "text", "text": { "body": "¡Hola de nuevo, {{ $json.c_name }}! 💳\n\nEres nivel *{{ $json.c_tier }}* y tu saldo actual para gastar es de *$ {{ $json.c_balance }}*.\n\nSi perdiste tu tarjeta, recupérala aquí:\n👉 https://TU_PORTAL/wallet/{{ $json.pass_id }}" } } ``` --- # Parte 4 — Credenciales necesarias en n8n Configura manualmente las credenciales dentro de n8n. No deben versionarse en Gitea. ## WhatsApp / Meta - WhatsApp Trigger OAuth o credencial equivalente. - Token de Meta para llamadas a Graph API. - Phone Number ID de Meta. ## Supabase Configura headers para HTTP Request: ```text apikey: TU_SUPABASE_ANON_KEY Authorization: Bearer TU_SUPABASE_ANON_KEY ``` --- # Parte 5 — Importación del workflow 1. Entra a n8n. 2. Importa el archivo `n8n/plan-de-incentivo.template.json`. 3. Asigna manualmente las credenciales de WhatsApp y Supabase. 4. Reemplaza los placeholders: - `TU_PROYECTO` - `TU_PHONE_NUMBER_ID` - `COMPANY_ID_DEMO` - `TU_PORTAL` 5. Ejecuta una prueba con un número autorizado de WhatsApp. 6. Verifica que el cliente se cree en Supabase. 7. Verifica que el bot responda según el estado correcto. --- # Parte 6 — Seguridad antes de subir a Gitea Antes de hacer commit, verifica que no existan: - `pinData` - Teléfonos reales - Nombres reales de clientes/personas - Tokens de Meta - Supabase anon key real - Supabase service role key - Credential IDs reales de n8n - Credential names reales de n8n - `webhookId` - `instanceId` - Workflow ID real - `versionId` - UUID real de `company_id` - URLs reales internas o sensibles El workflow que se suba al repositorio debe funcionar como **template reutilizable**, no como exportación exacta de producción. --- # Parte 7 — Checklist rápido - [ ] Supabase schema creado. - [ ] Empresa demo creada en `companies`. - [ ] `COMPANY_ID_DEMO` reemplazado en n8n. - [ ] Credenciales de WhatsApp configuradas manualmente. - [ ] Credenciales de Supabase configuradas manualmente. - [ ] Workflow importado en n8n. - [ ] `pinData` eliminado del JSON antes de subirlo. - [ ] IDs, tokens y teléfonos reales eliminados. - [ ] Prueba end-to-end ejecutada correctamente.