12 KiB
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:
- Reciba el mensaje desde WhatsApp.
- Extraiga el teléfono y el texto del mensaje.
- Consulte en Supabase si el cliente existe.
- Cree el cliente si es nuevo.
- Maneje el estado del bot:
NEWAWAITING_NAMEACTIVE
- Responda por WhatsApp según el estado del cliente.
- Entregue una URL de Wallet / pase digital.
2. Arquitectura general
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
-- 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:
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:
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
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_DEMOpor 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:
{
"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:
{{ $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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
apikey: TU_SUPABASE_ANON_KEY
Authorization: Bearer TU_SUPABASE_ANON_KEY
Parte 5 — Importación del workflow
- Entra a n8n.
- Importa el archivo
n8n/plan-de-incentivo.template.json. - Asigna manualmente las credenciales de WhatsApp y Supabase.
- Reemplaza los placeholders:
TU_PROYECTOTU_PHONE_NUMBER_IDCOMPANY_ID_DEMOTU_PORTAL
- Ejecuta una prueba con un número autorizado de WhatsApp.
- Verifica que el cliente se cree en Supabase.
- 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
webhookIdinstanceId- 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_DEMOreemplazado en n8n.- Credenciales de WhatsApp configuradas manualmente.
- Credenciales de Supabase configuradas manualmente.
- Workflow importado en n8n.
pinDataeliminado del JSON antes de subirlo.- IDs, tokens y teléfonos reales eliminados.
- Prueba end-to-end ejecutada correctamente.