833 lines
26 KiB
Markdown
833 lines
26 KiB
Markdown
# DDL Specification: messaging Schema
|
|
|
|
## Identificacion
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **Schema** | messaging |
|
|
| **Modulo** | MGN-017 WhatsApp Business Integration |
|
|
| **Version** | 1.0 |
|
|
| **Fecha** | 2025-12-05 |
|
|
| **Estado** | Ready |
|
|
|
|
---
|
|
|
|
## Diagrama ER
|
|
|
|
```mermaid
|
|
erDiagram
|
|
tenants ||--o{ whatsapp_accounts : has
|
|
whatsapp_accounts ||--o{ whatsapp_templates : defines
|
|
whatsapp_accounts ||--o{ whatsapp_conversations : hosts
|
|
whatsapp_conversations ||--o{ whatsapp_messages : contains
|
|
whatsapp_conversations }o--|| contacts : with
|
|
whatsapp_conversations }o--|| users : assigned_to
|
|
tenants ||--o{ chatbot_flows : creates
|
|
chatbot_flows ||--o{ chatbot_nodes : contains
|
|
whatsapp_conversations ||--o{ chatbot_sessions : runs
|
|
|
|
whatsapp_accounts {
|
|
uuid id PK
|
|
uuid tenant_id FK
|
|
string phone_number UK
|
|
string display_name
|
|
string waba_id
|
|
string status
|
|
boolean is_active
|
|
}
|
|
|
|
whatsapp_templates {
|
|
uuid id PK
|
|
uuid account_id FK
|
|
string name
|
|
string category
|
|
string language
|
|
string status
|
|
jsonb components
|
|
}
|
|
|
|
whatsapp_conversations {
|
|
uuid id PK
|
|
uuid account_id FK
|
|
uuid contact_id FK
|
|
uuid assigned_user_id FK
|
|
string wa_conversation_id
|
|
string status
|
|
timestamp window_expires_at
|
|
}
|
|
|
|
whatsapp_messages {
|
|
uuid id PK
|
|
uuid conversation_id FK
|
|
string wa_message_id UK
|
|
string direction
|
|
string message_type
|
|
jsonb content
|
|
string status
|
|
timestamp sent_at
|
|
}
|
|
|
|
chatbot_flows {
|
|
uuid id PK
|
|
uuid tenant_id FK
|
|
string name
|
|
boolean is_active
|
|
jsonb trigger_config
|
|
}
|
|
|
|
chatbot_nodes {
|
|
uuid id PK
|
|
uuid flow_id FK
|
|
string node_type
|
|
jsonb config
|
|
jsonb connections
|
|
}
|
|
|
|
chatbot_sessions {
|
|
uuid id PK
|
|
uuid conversation_id FK
|
|
uuid flow_id FK
|
|
uuid current_node_id FK
|
|
jsonb context
|
|
string status
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Tablas
|
|
|
|
### 1. whatsapp_accounts
|
|
|
|
Cuentas de WhatsApp Business conectadas.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.whatsapp_accounts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES core_tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- WhatsApp Business Account (WABA)
|
|
waba_id VARCHAR(50) NOT NULL,
|
|
phone_number_id VARCHAR(50) NOT NULL,
|
|
phone_number VARCHAR(20) NOT NULL,
|
|
display_name VARCHAR(100) NOT NULL,
|
|
verified_name VARCHAR(100),
|
|
|
|
-- Calidad y limites
|
|
quality_rating VARCHAR(20) DEFAULT 'unknown',
|
|
messaging_limit VARCHAR(20) DEFAULT 'tier_1',
|
|
-- tier_1: 1K/day, tier_2: 10K/day, tier_3: 100K/day, unlimited
|
|
|
|
-- Configuracion
|
|
webhook_url VARCHAR(500),
|
|
webhook_verify_token VARCHAR(100),
|
|
|
|
-- Credenciales (encriptadas via integrations schema)
|
|
access_token_encrypted BYTEA,
|
|
token_expires_at TIMESTAMPTZ,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
last_synced_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID,
|
|
|
|
CONSTRAINT uq_whatsapp_accounts UNIQUE (tenant_id, phone_number),
|
|
CONSTRAINT chk_whatsapp_accounts_status CHECK (status IN (
|
|
'pending', 'connected', 'disconnected', 'suspended', 'error'
|
|
)),
|
|
CONSTRAINT chk_whatsapp_accounts_quality CHECK (quality_rating IN (
|
|
'unknown', 'green', 'yellow', 'red'
|
|
)),
|
|
CONSTRAINT chk_whatsapp_accounts_phone CHECK (phone_number ~ '^\+[1-9]\d{6,14}$')
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_whatsapp_accounts_tenant ON messaging.whatsapp_accounts(tenant_id);
|
|
CREATE INDEX idx_whatsapp_accounts_phone ON messaging.whatsapp_accounts(phone_number);
|
|
CREATE INDEX idx_whatsapp_accounts_active ON messaging.whatsapp_accounts(tenant_id, is_active)
|
|
WHERE is_active = true;
|
|
|
|
-- Trigger para updated_at
|
|
CREATE TRIGGER trg_whatsapp_accounts_updated_at
|
|
BEFORE UPDATE ON messaging.whatsapp_accounts
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- RLS
|
|
ALTER TABLE messaging.whatsapp_accounts ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY whatsapp_accounts_tenant_isolation ON messaging.whatsapp_accounts
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.tenant_id')::uuid);
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.whatsapp_accounts IS 'Cuentas de WhatsApp Business conectadas (MGN-017)';
|
|
COMMENT ON COLUMN messaging.whatsapp_accounts.waba_id IS 'WhatsApp Business Account ID de Meta';
|
|
COMMENT ON COLUMN messaging.whatsapp_accounts.quality_rating IS 'Calidad de la cuenta: green, yellow, red';
|
|
```
|
|
|
|
---
|
|
|
|
### 2. whatsapp_templates
|
|
|
|
Templates de mensajes aprobados por Meta.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.whatsapp_templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id UUID NOT NULL REFERENCES messaging.whatsapp_accounts(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
external_id VARCHAR(50), -- ID en Meta
|
|
name VARCHAR(100) NOT NULL,
|
|
language VARCHAR(10) NOT NULL DEFAULT 'es',
|
|
|
|
-- Categoria
|
|
category VARCHAR(30) NOT NULL,
|
|
-- MARKETING, UTILITY, AUTHENTICATION
|
|
|
|
-- Componentes del template
|
|
components JSONB NOT NULL,
|
|
-- Ejemplo: [
|
|
-- {"type": "HEADER", "format": "TEXT", "text": "Hola {{1}}"},
|
|
-- {"type": "BODY", "text": "Tu pedido #{{1}} está en camino"},
|
|
-- {"type": "FOOTER", "text": "Gracias por tu compra"},
|
|
-- {"type": "BUTTONS", "buttons": [{"type": "QUICK_REPLY", "text": "OK"}]}
|
|
-- ]
|
|
|
|
-- Estado de aprobacion
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
rejection_reason VARCHAR(500),
|
|
|
|
-- Metadata
|
|
quality_score VARCHAR(20),
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
submitted_at TIMESTAMPTZ,
|
|
approved_at TIMESTAMPTZ,
|
|
created_by UUID,
|
|
|
|
CONSTRAINT uq_whatsapp_templates UNIQUE (account_id, name, language),
|
|
CONSTRAINT chk_whatsapp_templates_category CHECK (category IN (
|
|
'MARKETING', 'UTILITY', 'AUTHENTICATION'
|
|
)),
|
|
CONSTRAINT chk_whatsapp_templates_status CHECK (status IN (
|
|
'pending', 'approved', 'rejected', 'disabled', 'paused'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_whatsapp_templates_account ON messaging.whatsapp_templates(account_id);
|
|
CREATE INDEX idx_whatsapp_templates_status ON messaging.whatsapp_templates(status);
|
|
CREATE INDEX idx_whatsapp_templates_category ON messaging.whatsapp_templates(category);
|
|
|
|
-- Trigger para updated_at
|
|
CREATE TRIGGER trg_whatsapp_templates_updated_at
|
|
BEFORE UPDATE ON messaging.whatsapp_templates
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.whatsapp_templates IS 'Templates de mensajes aprobados por Meta';
|
|
COMMENT ON COLUMN messaging.whatsapp_templates.components IS 'Estructura del template en formato Meta';
|
|
```
|
|
|
|
---
|
|
|
|
### 3. whatsapp_conversations
|
|
|
|
Conversaciones con contactos.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.whatsapp_conversations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id UUID NOT NULL REFERENCES messaging.whatsapp_accounts(id) ON DELETE CASCADE,
|
|
contact_id UUID REFERENCES core_catalogs.contacts(id),
|
|
|
|
-- WhatsApp IDs
|
|
wa_conversation_id VARCHAR(50),
|
|
contact_wa_id VARCHAR(50) NOT NULL, -- Phone number o WA ID
|
|
|
|
-- Asignacion
|
|
assigned_user_id UUID REFERENCES core_users.users(id),
|
|
assigned_at TIMESTAMPTZ,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) NOT NULL DEFAULT 'open',
|
|
unread_count INT NOT NULL DEFAULT 0,
|
|
|
|
-- Ventana de 24 horas
|
|
window_expires_at TIMESTAMPTZ,
|
|
is_window_open BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
-- Ultima actividad
|
|
last_message_at TIMESTAMPTZ,
|
|
last_message_preview VARCHAR(200),
|
|
last_message_direction VARCHAR(10),
|
|
|
|
-- Chatbot
|
|
chatbot_active BOOLEAN NOT NULL DEFAULT false,
|
|
chatbot_session_id UUID,
|
|
|
|
-- Tags y categorias
|
|
tags TEXT[] DEFAULT '{}',
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
closed_at TIMESTAMPTZ,
|
|
|
|
CONSTRAINT uq_whatsapp_conversations UNIQUE (account_id, contact_wa_id),
|
|
CONSTRAINT chk_whatsapp_conversations_status CHECK (status IN (
|
|
'open', 'pending', 'resolved', 'closed', 'spam'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_whatsapp_conversations_account ON messaging.whatsapp_conversations(account_id);
|
|
CREATE INDEX idx_whatsapp_conversations_contact ON messaging.whatsapp_conversations(contact_id);
|
|
CREATE INDEX idx_whatsapp_conversations_assigned ON messaging.whatsapp_conversations(assigned_user_id);
|
|
CREATE INDEX idx_whatsapp_conversations_status ON messaging.whatsapp_conversations(status);
|
|
CREATE INDEX idx_whatsapp_conversations_last_message ON messaging.whatsapp_conversations(last_message_at DESC);
|
|
CREATE INDEX idx_whatsapp_conversations_open ON messaging.whatsapp_conversations(account_id, status)
|
|
WHERE status IN ('open', 'pending');
|
|
|
|
-- Trigger para updated_at
|
|
CREATE TRIGGER trg_whatsapp_conversations_updated_at
|
|
BEFORE UPDATE ON messaging.whatsapp_conversations
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.whatsapp_conversations IS 'Conversaciones de WhatsApp con contactos';
|
|
COMMENT ON COLUMN messaging.whatsapp_conversations.window_expires_at IS 'Expiracion de ventana de 24h para mensajes libres';
|
|
```
|
|
|
|
---
|
|
|
|
### 4. whatsapp_messages
|
|
|
|
Mensajes enviados y recibidos.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.whatsapp_messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
conversation_id UUID NOT NULL REFERENCES messaging.whatsapp_conversations(id) ON DELETE CASCADE,
|
|
|
|
-- WhatsApp IDs
|
|
wa_message_id VARCHAR(100) NOT NULL,
|
|
wa_context_id VARCHAR(100), -- Mensaje al que responde
|
|
|
|
-- Direccion
|
|
direction VARCHAR(10) NOT NULL, -- inbound, outbound
|
|
|
|
-- Tipo y contenido
|
|
message_type VARCHAR(20) NOT NULL,
|
|
-- text, image, document, audio, video, sticker, location, contacts, interactive, template
|
|
content JSONB NOT NULL,
|
|
-- Ejemplo text: {"body": "Hola!"}
|
|
-- Ejemplo image: {"id": "...", "mime_type": "image/jpeg", "caption": "..."}
|
|
-- Ejemplo template: {"name": "order_confirmation", "language": "es", "components": [...]}
|
|
|
|
-- Estado
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
error_code VARCHAR(50),
|
|
error_message VARCHAR(500),
|
|
|
|
-- Timestamps
|
|
sent_at TIMESTAMPTZ,
|
|
delivered_at TIMESTAMPTZ,
|
|
read_at TIMESTAMPTZ,
|
|
failed_at TIMESTAMPTZ,
|
|
|
|
-- Metadata
|
|
is_from_chatbot BOOLEAN NOT NULL DEFAULT false,
|
|
is_from_ai BOOLEAN NOT NULL DEFAULT false,
|
|
ai_agent_id UUID,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT uq_whatsapp_messages UNIQUE (conversation_id, wa_message_id),
|
|
CONSTRAINT chk_whatsapp_messages_direction CHECK (direction IN ('inbound', 'outbound')),
|
|
CONSTRAINT chk_whatsapp_messages_type CHECK (message_type IN (
|
|
'text', 'image', 'document', 'audio', 'video', 'sticker',
|
|
'location', 'contacts', 'interactive', 'template', 'reaction', 'unknown'
|
|
)),
|
|
CONSTRAINT chk_whatsapp_messages_status CHECK (status IN (
|
|
'pending', 'sent', 'delivered', 'read', 'failed'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_whatsapp_messages_conversation ON messaging.whatsapp_messages(conversation_id);
|
|
CREATE INDEX idx_whatsapp_messages_wa_id ON messaging.whatsapp_messages(wa_message_id);
|
|
CREATE INDEX idx_whatsapp_messages_direction ON messaging.whatsapp_messages(direction);
|
|
CREATE INDEX idx_whatsapp_messages_date ON messaging.whatsapp_messages(created_at DESC);
|
|
CREATE INDEX idx_whatsapp_messages_status ON messaging.whatsapp_messages(status)
|
|
WHERE status IN ('pending', 'failed');
|
|
|
|
-- Particionamiento por mes (recomendado para alto volumen)
|
|
-- CREATE TABLE messaging.whatsapp_messages_y2025m01 PARTITION OF messaging.whatsapp_messages
|
|
-- FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.whatsapp_messages IS 'Mensajes de WhatsApp enviados y recibidos';
|
|
COMMENT ON COLUMN messaging.whatsapp_messages.content IS 'Contenido del mensaje en formato Meta';
|
|
```
|
|
|
|
---
|
|
|
|
### 5. chatbot_flows
|
|
|
|
Flujos de chatbot.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.chatbot_flows (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES core_tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
name VARCHAR(100) NOT NULL,
|
|
description VARCHAR(500),
|
|
|
|
-- Trigger
|
|
trigger_type VARCHAR(30) NOT NULL,
|
|
-- keyword, first_message, menu_option, always
|
|
trigger_config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
-- Ejemplo keyword: {"keywords": ["hola", "menu", "ayuda"]}
|
|
-- Ejemplo menu: {"option_id": "support"}
|
|
|
|
-- Cuentas donde aplica
|
|
account_ids UUID[], -- NULL = todas las cuentas del tenant
|
|
|
|
-- Estado
|
|
is_active BOOLEAN NOT NULL DEFAULT false,
|
|
is_draft BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
-- AI Integration (MGN-018)
|
|
use_ai_fallback BOOLEAN NOT NULL DEFAULT false,
|
|
ai_agent_id UUID, -- FK a ai_agents.agents
|
|
|
|
-- Estadisticas
|
|
total_sessions INT NOT NULL DEFAULT 0,
|
|
completed_sessions INT NOT NULL DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
published_at TIMESTAMPTZ,
|
|
created_by UUID,
|
|
|
|
CONSTRAINT uq_chatbot_flows UNIQUE (tenant_id, name),
|
|
CONSTRAINT chk_chatbot_flows_trigger CHECK (trigger_type IN (
|
|
'keyword', 'first_message', 'menu_option', 'always', 'scheduled', 'manual'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_chatbot_flows_tenant ON messaging.chatbot_flows(tenant_id);
|
|
CREATE INDEX idx_chatbot_flows_active ON messaging.chatbot_flows(tenant_id, is_active)
|
|
WHERE is_active = true;
|
|
CREATE INDEX idx_chatbot_flows_trigger ON messaging.chatbot_flows(trigger_type);
|
|
|
|
-- Trigger para updated_at
|
|
CREATE TRIGGER trg_chatbot_flows_updated_at
|
|
BEFORE UPDATE ON messaging.chatbot_flows
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- RLS
|
|
ALTER TABLE messaging.chatbot_flows ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY chatbot_flows_tenant_isolation ON messaging.chatbot_flows
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.tenant_id')::uuid);
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.chatbot_flows IS 'Flujos de chatbot automatizado';
|
|
COMMENT ON COLUMN messaging.chatbot_flows.trigger_type IS 'Cuando se activa: keyword, first_message, menu_option, always';
|
|
```
|
|
|
|
---
|
|
|
|
### 6. chatbot_nodes
|
|
|
|
Nodos de flujos de chatbot.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.chatbot_nodes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
flow_id UUID NOT NULL REFERENCES messaging.chatbot_flows(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
node_key VARCHAR(50) NOT NULL, -- ID interno para referencias
|
|
name VARCHAR(100),
|
|
|
|
-- Tipo de nodo
|
|
node_type VARCHAR(30) NOT NULL,
|
|
-- send_message, ask_question, menu, condition, action, delay, handoff, ai_response
|
|
|
|
-- Configuracion del nodo
|
|
config JSONB NOT NULL,
|
|
-- Ejemplo send_message: {"message": "Hola, bienvenido!", "type": "text"}
|
|
-- Ejemplo ask_question: {"question": "Cual es tu email?", "variable": "email", "validation": "email"}
|
|
-- Ejemplo menu: {"text": "Selecciona:", "options": [{"id": "1", "text": "Soporte"}, ...]}
|
|
-- Ejemplo condition: {"variable": "email", "operator": "contains", "value": "@"}
|
|
-- Ejemplo action: {"type": "create_lead", "params": {"source": "whatsapp"}}
|
|
|
|
-- Conexiones a otros nodos
|
|
connections JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
-- Ejemplo: [{"from_output": "default", "to_node": "node_2"}, {"from_output": "option_1", "to_node": "node_3"}]
|
|
|
|
-- Posicion visual (para editor)
|
|
position JSONB DEFAULT '{"x": 0, "y": 0}'::jsonb,
|
|
|
|
-- Flags
|
|
is_start BOOLEAN NOT NULL DEFAULT false,
|
|
is_end BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT uq_chatbot_nodes UNIQUE (flow_id, node_key),
|
|
CONSTRAINT chk_chatbot_nodes_type CHECK (node_type IN (
|
|
'send_message', 'ask_question', 'menu', 'condition',
|
|
'action', 'delay', 'handoff', 'ai_response', 'webhook'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_chatbot_nodes_flow ON messaging.chatbot_nodes(flow_id);
|
|
CREATE INDEX idx_chatbot_nodes_type ON messaging.chatbot_nodes(node_type);
|
|
CREATE INDEX idx_chatbot_nodes_start ON messaging.chatbot_nodes(flow_id, is_start)
|
|
WHERE is_start = true;
|
|
|
|
-- Trigger para updated_at
|
|
CREATE TRIGGER trg_chatbot_nodes_updated_at
|
|
BEFORE UPDATE ON messaging.chatbot_nodes
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.chatbot_nodes IS 'Nodos de flujos de chatbot';
|
|
COMMENT ON COLUMN messaging.chatbot_nodes.node_key IS 'Identificador interno para referencias entre nodos';
|
|
```
|
|
|
|
---
|
|
|
|
### 7. chatbot_sessions
|
|
|
|
Sesiones activas de chatbot.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.chatbot_sessions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
conversation_id UUID NOT NULL REFERENCES messaging.whatsapp_conversations(id) ON DELETE CASCADE,
|
|
flow_id UUID NOT NULL REFERENCES messaging.chatbot_flows(id),
|
|
|
|
-- Nodo actual
|
|
current_node_id UUID REFERENCES messaging.chatbot_nodes(id),
|
|
waiting_for_response BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
-- Contexto y variables
|
|
context JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
-- Ejemplo: {"name": "Juan", "email": "juan@email.com", "order_number": "12345"}
|
|
|
|
-- Estado
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
completed_nodes TEXT[] DEFAULT '{}',
|
|
|
|
-- Estadisticas
|
|
messages_processed INT NOT NULL DEFAULT 0,
|
|
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_activity_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
completed_at TIMESTAMPTZ,
|
|
timeout_at TIMESTAMPTZ,
|
|
|
|
CONSTRAINT chk_chatbot_sessions_status CHECK (status IN (
|
|
'active', 'waiting', 'completed', 'handoff', 'timeout', 'error'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_chatbot_sessions_conversation ON messaging.chatbot_sessions(conversation_id);
|
|
CREATE INDEX idx_chatbot_sessions_flow ON messaging.chatbot_sessions(flow_id);
|
|
CREATE INDEX idx_chatbot_sessions_status ON messaging.chatbot_sessions(status);
|
|
CREATE INDEX idx_chatbot_sessions_active ON messaging.chatbot_sessions(conversation_id, status)
|
|
WHERE status IN ('active', 'waiting');
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.chatbot_sessions IS 'Sesiones activas de chatbot por conversacion';
|
|
COMMENT ON COLUMN messaging.chatbot_sessions.context IS 'Variables recopiladas durante el flujo';
|
|
```
|
|
|
|
---
|
|
|
|
### 8. whatsapp_campaigns
|
|
|
|
Campanas de marketing masivo.
|
|
|
|
```sql
|
|
CREATE TABLE messaging.whatsapp_campaigns (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES core_tenants.tenants(id) ON DELETE CASCADE,
|
|
account_id UUID NOT NULL REFERENCES messaging.whatsapp_accounts(id),
|
|
|
|
-- Identificacion
|
|
name VARCHAR(100) NOT NULL,
|
|
description VARCHAR(500),
|
|
|
|
-- Template a usar
|
|
template_id UUID NOT NULL REFERENCES messaging.whatsapp_templates(id),
|
|
template_params JSONB DEFAULT '{}'::jsonb,
|
|
-- Mapeo de variables: {"1": "contact.name", "2": "custom.promo_code"}
|
|
|
|
-- Audiencia
|
|
audience_type VARCHAR(30) NOT NULL,
|
|
-- all_contacts, tagged, segment, custom_list
|
|
audience_config JSONB NOT NULL,
|
|
-- Ejemplo tagged: {"tags": ["vip", "newsletter"]}
|
|
-- Ejemplo segment: {"filter": {"country": "MX", "last_order_days": 30}}
|
|
|
|
-- Programacion
|
|
scheduled_at TIMESTAMPTZ,
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
|
|
-- Estado
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
|
|
|
-- Estadisticas
|
|
total_recipients INT NOT NULL DEFAULT 0,
|
|
sent_count INT NOT NULL DEFAULT 0,
|
|
delivered_count INT NOT NULL DEFAULT 0,
|
|
read_count INT NOT NULL DEFAULT 0,
|
|
failed_count INT NOT NULL DEFAULT 0,
|
|
opt_out_count INT NOT NULL DEFAULT 0,
|
|
|
|
-- Costo estimado
|
|
estimated_cost DECIMAL(10,2),
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_by UUID,
|
|
|
|
CONSTRAINT chk_whatsapp_campaigns_status CHECK (status IN (
|
|
'draft', 'scheduled', 'sending', 'completed', 'paused', 'cancelled'
|
|
)),
|
|
CONSTRAINT chk_whatsapp_campaigns_audience CHECK (audience_type IN (
|
|
'all_contacts', 'tagged', 'segment', 'custom_list'
|
|
))
|
|
);
|
|
|
|
-- Indices
|
|
CREATE INDEX idx_whatsapp_campaigns_tenant ON messaging.whatsapp_campaigns(tenant_id);
|
|
CREATE INDEX idx_whatsapp_campaigns_account ON messaging.whatsapp_campaigns(account_id);
|
|
CREATE INDEX idx_whatsapp_campaigns_status ON messaging.whatsapp_campaigns(status);
|
|
CREATE INDEX idx_whatsapp_campaigns_scheduled ON messaging.whatsapp_campaigns(scheduled_at)
|
|
WHERE status = 'scheduled';
|
|
|
|
-- RLS
|
|
ALTER TABLE messaging.whatsapp_campaigns ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY whatsapp_campaigns_tenant_isolation ON messaging.whatsapp_campaigns
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.tenant_id')::uuid);
|
|
|
|
-- Comentarios
|
|
COMMENT ON TABLE messaging.whatsapp_campaigns IS 'Campanas de marketing masivo por WhatsApp';
|
|
```
|
|
|
|
---
|
|
|
|
## Funciones de Utilidad
|
|
|
|
### Procesar Mensaje Entrante
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION messaging.process_incoming_message(
|
|
p_account_id UUID,
|
|
p_wa_message_id VARCHAR,
|
|
p_from_number VARCHAR,
|
|
p_message_type VARCHAR,
|
|
p_content JSONB
|
|
) RETURNS UUID AS $$
|
|
DECLARE
|
|
v_conversation_id UUID;
|
|
v_message_id UUID;
|
|
v_contact_id UUID;
|
|
BEGIN
|
|
-- Buscar o crear conversacion
|
|
SELECT id, contact_id INTO v_conversation_id, v_contact_id
|
|
FROM messaging.whatsapp_conversations
|
|
WHERE account_id = p_account_id AND contact_wa_id = p_from_number;
|
|
|
|
IF v_conversation_id IS NULL THEN
|
|
-- Buscar contacto existente
|
|
SELECT c.id INTO v_contact_id
|
|
FROM core_catalogs.contacts c
|
|
JOIN messaging.whatsapp_accounts wa ON wa.tenant_id = c.tenant_id
|
|
WHERE wa.id = p_account_id
|
|
AND c.whatsapp_number = p_from_number
|
|
AND c.deleted_at IS NULL
|
|
LIMIT 1;
|
|
|
|
-- Crear conversacion
|
|
INSERT INTO messaging.whatsapp_conversations (
|
|
account_id, contact_id, contact_wa_id, status,
|
|
is_window_open, window_expires_at
|
|
) VALUES (
|
|
p_account_id, v_contact_id, p_from_number, 'open',
|
|
true, NOW() + INTERVAL '24 hours'
|
|
)
|
|
RETURNING id INTO v_conversation_id;
|
|
ELSE
|
|
-- Actualizar ventana
|
|
UPDATE messaging.whatsapp_conversations
|
|
SET is_window_open = true,
|
|
window_expires_at = NOW() + INTERVAL '24 hours',
|
|
updated_at = NOW()
|
|
WHERE id = v_conversation_id;
|
|
END IF;
|
|
|
|
-- Insertar mensaje
|
|
INSERT INTO messaging.whatsapp_messages (
|
|
conversation_id, wa_message_id, direction,
|
|
message_type, content, status, sent_at
|
|
) VALUES (
|
|
v_conversation_id, p_wa_message_id, 'inbound',
|
|
p_message_type, p_content, 'delivered', NOW()
|
|
)
|
|
RETURNING id INTO v_message_id;
|
|
|
|
-- Actualizar conversacion
|
|
UPDATE messaging.whatsapp_conversations
|
|
SET last_message_at = NOW(),
|
|
last_message_preview = LEFT(p_content->>'body', 200),
|
|
last_message_direction = 'inbound',
|
|
unread_count = unread_count + 1,
|
|
updated_at = NOW()
|
|
WHERE id = v_conversation_id;
|
|
|
|
RETURN v_message_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### Verificar Ventana de Conversacion
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION messaging.is_window_open(
|
|
p_conversation_id UUID
|
|
) RETURNS BOOLEAN AS $$
|
|
DECLARE
|
|
v_window_open BOOLEAN;
|
|
v_window_expires TIMESTAMPTZ;
|
|
BEGIN
|
|
SELECT is_window_open, window_expires_at
|
|
INTO v_window_open, v_window_expires
|
|
FROM messaging.whatsapp_conversations
|
|
WHERE id = p_conversation_id;
|
|
|
|
IF v_window_expires IS NOT NULL AND v_window_expires < NOW() THEN
|
|
-- Ventana expirada, actualizar
|
|
UPDATE messaging.whatsapp_conversations
|
|
SET is_window_open = false
|
|
WHERE id = p_conversation_id;
|
|
RETURN false;
|
|
END IF;
|
|
|
|
RETURN COALESCE(v_window_open, false);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
---
|
|
|
|
## Vistas
|
|
|
|
### Vista: Inbox de Conversaciones
|
|
|
|
```sql
|
|
CREATE VIEW messaging.vw_inbox AS
|
|
SELECT
|
|
c.id,
|
|
c.account_id,
|
|
wa.phone_number AS account_phone,
|
|
c.contact_id,
|
|
ct.name AS contact_name,
|
|
ct.whatsapp_number AS contact_phone,
|
|
c.assigned_user_id,
|
|
u.full_name AS assigned_user_name,
|
|
c.status,
|
|
c.unread_count,
|
|
c.is_window_open,
|
|
c.window_expires_at,
|
|
c.last_message_at,
|
|
c.last_message_preview,
|
|
c.last_message_direction,
|
|
c.chatbot_active,
|
|
c.tags,
|
|
c.created_at
|
|
FROM messaging.whatsapp_conversations c
|
|
JOIN messaging.whatsapp_accounts wa ON wa.id = c.account_id
|
|
LEFT JOIN core_catalogs.contacts ct ON ct.id = c.contact_id
|
|
LEFT JOIN core_users.users u ON u.id = c.assigned_user_id
|
|
WHERE c.status NOT IN ('closed', 'spam')
|
|
ORDER BY c.last_message_at DESC NULLS LAST;
|
|
```
|
|
|
|
### Vista: Metricas de WhatsApp
|
|
|
|
```sql
|
|
CREATE VIEW messaging.vw_whatsapp_metrics AS
|
|
SELECT
|
|
wa.tenant_id,
|
|
wa.id AS account_id,
|
|
wa.phone_number,
|
|
wa.display_name,
|
|
wa.quality_rating,
|
|
(SELECT COUNT(*) FROM messaging.whatsapp_conversations c
|
|
WHERE c.account_id = wa.id AND c.created_at > CURRENT_DATE - 30) AS conversations_30d,
|
|
(SELECT COUNT(*) FROM messaging.whatsapp_messages m
|
|
JOIN messaging.whatsapp_conversations c ON c.id = m.conversation_id
|
|
WHERE c.account_id = wa.id AND m.direction = 'outbound' AND m.created_at > CURRENT_DATE - 30) AS messages_sent_30d,
|
|
(SELECT COUNT(*) FROM messaging.whatsapp_messages m
|
|
JOIN messaging.whatsapp_conversations c ON c.id = m.conversation_id
|
|
WHERE c.account_id = wa.id AND m.direction = 'inbound' AND m.created_at > CURRENT_DATE - 30) AS messages_received_30d,
|
|
(SELECT COUNT(*) FROM messaging.whatsapp_conversations c
|
|
WHERE c.account_id = wa.id AND c.status = 'open') AS open_conversations
|
|
FROM messaging.whatsapp_accounts wa
|
|
WHERE wa.is_active = true;
|
|
```
|
|
|
|
---
|
|
|
|
## Resumen de Tablas
|
|
|
|
| Tabla | Columnas | Descripcion |
|
|
|-------|----------|-------------|
|
|
| whatsapp_accounts | 20 | Cuentas de WhatsApp Business |
|
|
| whatsapp_templates | 15 | Templates aprobados por Meta |
|
|
| whatsapp_conversations | 20 | Conversaciones con contactos |
|
|
| whatsapp_messages | 18 | Mensajes enviados/recibidos |
|
|
| chatbot_flows | 17 | Flujos de chatbot |
|
|
| chatbot_nodes | 13 | Nodos de flujos |
|
|
| chatbot_sessions | 12 | Sesiones activas |
|
|
| whatsapp_campaigns | 21 | Campanas de marketing |
|
|
|
|
**Total: 8 tablas, 136 columnas**
|
|
|
|
---
|
|
|
|
## Historial
|
|
|
|
| Version | Fecha | Autor | Cambios |
|
|
|---------|-------|-------|---------|
|
|
| 1.0 | 2025-12-05 | System | Creacion inicial (MGN-017) |
|