Initial commit: Documentación completa del proyecto WhatsApp Centralizado
- README principal con descripción del proyecto - Documento de diseño completo (arquitectura, DB, flujos) - Documentación de API REST y WebSocket - Guía del Flow Builder (30+ tipos de nodos) - Documentación de integración con Odoo - Guía de despliegue con Docker - Esquema de base de datos - Estructura de carpetas del proyecto - Archivo .env.example con todas las variables Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
452
docs/database/README.md
Normal file
452
docs/database/README.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# Esquema de Base de Datos
|
||||
|
||||
## Diagrama ER
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ users │ │ queues │ │ whatsapp_accounts│
|
||||
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||
│ email │ │ name │ │ phone_number │
|
||||
│ password_hash │ │ description │ │ name │
|
||||
│ name │ │ assignment_method │ status │
|
||||
│ role │ │ max_per_agent │ │ session_data │
|
||||
│ status │ │ sla_first_resp │ │ qr_code │
|
||||
│ is_active │ │ sla_resolution │ │ created_at │
|
||||
│ created_at │ │ business_hours │ └────────┬────────┘
|
||||
│ updated_at │ │ fallback_flow_id│ │
|
||||
└────────┬────────┘ │ is_active │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────┼─────────────────────────┤
|
||||
│ │ │ │
|
||||
│ ▼ ▼ ▼
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ │ queue_agents │ │ conversations │ │ contacts │
|
||||
│ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ │ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||
│ │ queue_id (FK)───┼──│ queue_id (FK)───│ │ phone_number │
|
||||
├──│ user_id (FK) │ │ whatsapp_acc_id─┼──│ name │
|
||||
│ │ is_supervisor │ │ contact_id (FK)─┼──│ email │
|
||||
│ │ skills │ │ assigned_to (FK)│ │ company │
|
||||
│ └─────────────────┘ │ status │ │ metadata │
|
||||
│ │ priority │ │ tags │
|
||||
│ │ current_flow_id │ │ odoo_partner_id │
|
||||
│ │ flow_context │ │ created_at │
|
||||
│ │ sla_* │ └─────────────────┘
|
||||
│ │ csat_* │
|
||||
│ │ created_at │
|
||||
│ └────────┬────────┘
|
||||
│ │
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌─────────────────┐
|
||||
│ │ messages │
|
||||
│ ├─────────────────┤
|
||||
│ │ id (PK) │
|
||||
│ │ conversation_id │
|
||||
│ │ direction │
|
||||
│ │ type │
|
||||
│ │ content │
|
||||
└───────────────────────│ sent_by (FK) │
|
||||
│ is_internal_note│
|
||||
│ status │
|
||||
│ created_at │
|
||||
└─────────────────┘
|
||||
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ flows │ │ message_templates│ │ quick_replies │
|
||||
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||
│ name │ │ name │ │ shortcut │
|
||||
│ description │ │ content │ │ content │
|
||||
│ trigger_type │ │ attachments │ │ attachments │
|
||||
│ trigger_value │ │ variables │ │ queue_id (FK) │
|
||||
│ nodes (JSONB) │ │ created_at │ │ created_by (FK) │
|
||||
│ variables │ └─────────────────┘ │ created_at │
|
||||
│ is_active │ └─────────────────┘
|
||||
│ version │
|
||||
│ created_at │ ┌─────────────────┐ ┌─────────────────┐
|
||||
└─────────────────┘ │ odoo_config │ │odoo_automations │
|
||||
├─────────────────┤ ├─────────────────┤
|
||||
┌─────────────────┐ │ id (PK) │ │ id (PK) │
|
||||
│ tags │ │ url │ │ name │
|
||||
├─────────────────┤ │ database │ │ odoo_model │
|
||||
│ id (PK) │ │ username │ │ odoo_trigger │
|
||||
│ name │ │ api_key_enc │ │ odoo_condition │
|
||||
│ color │ │ is_active │ │ template_id (FK)│
|
||||
│ created_at │ │ last_sync_at │ │ is_active │
|
||||
└─────────────────┘ └─────────────────┘ │ created_at │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tablas Detalladas
|
||||
|
||||
### users
|
||||
|
||||
Usuarios del sistema (admins, supervisores, agentes).
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| email | VARCHAR(255) | Email único |
|
||||
| password_hash | VARCHAR(255) | Hash bcrypt |
|
||||
| name | VARCHAR(100) | Nombre completo |
|
||||
| role | ENUM | admin, supervisor, agent |
|
||||
| status | ENUM | online, offline, away, busy, lunch, on_call |
|
||||
| is_active | BOOLEAN | Usuario activo |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
| updated_at | TIMESTAMP | Última modificación |
|
||||
|
||||
**Índices:**
|
||||
- `idx_users_email` (email)
|
||||
- `idx_users_role` (role)
|
||||
- `idx_users_status` (status)
|
||||
|
||||
---
|
||||
|
||||
### whatsapp_accounts
|
||||
|
||||
Números de WhatsApp conectados.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| phone_number | VARCHAR(20) | Número con código país |
|
||||
| name | VARCHAR(100) | Alias (ej: "Ventas") |
|
||||
| status | ENUM | connected, disconnected, banned |
|
||||
| session_data | JSONB | Datos de sesión Baileys |
|
||||
| qr_code | TEXT | QR para reconexión |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
**Índices:**
|
||||
- `idx_wa_phone` (phone_number)
|
||||
- `idx_wa_status` (status)
|
||||
|
||||
---
|
||||
|
||||
### contacts
|
||||
|
||||
Contactos (clientes que escriben).
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| phone_number | VARCHAR(20) | Teléfono único |
|
||||
| name | VARCHAR(100) | Nombre |
|
||||
| email | VARCHAR(255) | Email |
|
||||
| company | VARCHAR(100) | Empresa |
|
||||
| metadata | JSONB | Datos personalizados |
|
||||
| tags | VARCHAR[] | Array de etiquetas |
|
||||
| odoo_partner_id | INTEGER | ID en Odoo |
|
||||
| created_at | TIMESTAMP | Primer contacto |
|
||||
|
||||
**Índices:**
|
||||
- `idx_contacts_phone` UNIQUE (phone_number)
|
||||
- `idx_contacts_email` (email)
|
||||
- `idx_contacts_odoo` (odoo_partner_id)
|
||||
- `idx_contacts_tags` GIN (tags)
|
||||
|
||||
---
|
||||
|
||||
### queues
|
||||
|
||||
Colas de atención.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| name | VARCHAR(100) | Nombre de cola |
|
||||
| description | TEXT | Descripción |
|
||||
| assignment_method | ENUM | round_robin, least_busy, skill_based, sticky |
|
||||
| max_per_agent | INTEGER | Máx. conversaciones por agente |
|
||||
| sla_first_response | INTEGER | Segundos para primera respuesta |
|
||||
| sla_resolution | INTEGER | Segundos para resolución |
|
||||
| business_hours | JSONB | Horario de atención |
|
||||
| fallback_flow_id | UUID | Flujo fuera de horario |
|
||||
| is_active | BOOLEAN | Cola activa |
|
||||
|
||||
---
|
||||
|
||||
### queue_agents
|
||||
|
||||
Relación agentes-colas.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| queue_id | UUID | FK a queues |
|
||||
| user_id | UUID | FK a users |
|
||||
| is_supervisor | BOOLEAN | Es supervisor de cola |
|
||||
| skills | VARCHAR[] | Habilidades del agente |
|
||||
|
||||
**Índices:**
|
||||
- `idx_qa_queue` (queue_id)
|
||||
- `idx_qa_user` (user_id)
|
||||
- `idx_qa_unique` UNIQUE (queue_id, user_id)
|
||||
|
||||
---
|
||||
|
||||
### conversations
|
||||
|
||||
Conversaciones.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| whatsapp_account_id | UUID | FK a whatsapp_accounts |
|
||||
| contact_id | UUID | FK a contacts |
|
||||
| queue_id | UUID | FK a queues (nullable) |
|
||||
| assigned_to | UUID | FK a users (nullable) |
|
||||
| status | ENUM | bot, waiting, active, resolved |
|
||||
| priority | ENUM | low, normal, high, urgent |
|
||||
| current_flow_id | UUID | Flujo en ejecución |
|
||||
| flow_context | JSONB | Estado del flujo |
|
||||
| sla_first_response_at | TIMESTAMP | Hora primera respuesta |
|
||||
| sla_first_response_met | BOOLEAN | SLA cumplido |
|
||||
| resolved_at | TIMESTAMP | Hora resolución |
|
||||
| csat_score | INTEGER | Calificación 1-5 |
|
||||
| csat_feedback | TEXT | Feedback del cliente |
|
||||
| last_message_at | TIMESTAMP | Último mensaje |
|
||||
| created_at | TIMESTAMP | Inicio conversación |
|
||||
|
||||
**Índices:**
|
||||
- `idx_conv_contact` (contact_id)
|
||||
- `idx_conv_status` (status)
|
||||
- `idx_conv_assigned` (assigned_to)
|
||||
- `idx_conv_queue` (queue_id)
|
||||
- `idx_conv_last_msg` (last_message_at)
|
||||
- `idx_conv_created` (created_at)
|
||||
|
||||
---
|
||||
|
||||
### messages
|
||||
|
||||
Mensajes de conversaciones.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| conversation_id | UUID | FK a conversations |
|
||||
| direction | ENUM | inbound, outbound |
|
||||
| type | ENUM | text, image, audio, video, document, buttons, list, location, contact, sticker |
|
||||
| content | TEXT | Contenido del mensaje |
|
||||
| media_url | VARCHAR(500) | URL de media |
|
||||
| metadata | JSONB | Datos adicionales (botones, etc.) |
|
||||
| sent_by | UUID | FK a users (si fue agente) |
|
||||
| is_internal_note | BOOLEAN | Nota interna |
|
||||
| status | ENUM | pending, sent, delivered, read, failed |
|
||||
| created_at | TIMESTAMP | Fecha envío/recepción |
|
||||
|
||||
**Índices:**
|
||||
- `idx_msg_conv` (conversation_id)
|
||||
- `idx_msg_created` (created_at)
|
||||
- `idx_msg_direction` (direction)
|
||||
- `idx_msg_status` (status)
|
||||
|
||||
**Particionamiento:** Por fecha (mensual) para alto volumen.
|
||||
|
||||
---
|
||||
|
||||
### flows
|
||||
|
||||
Flujos de chatbot.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| name | VARCHAR(100) | Nombre del flujo |
|
||||
| description | TEXT | Descripción |
|
||||
| trigger_type | ENUM | welcome, keyword, fallback, event, schedule, manual |
|
||||
| trigger_value | VARCHAR(255) | Valor del trigger |
|
||||
| nodes | JSONB | Definición del flujo |
|
||||
| variables | JSONB | Variables del flujo |
|
||||
| is_active | BOOLEAN | Flujo activo |
|
||||
| version | INTEGER | Versión del flujo |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
**Índices:**
|
||||
- `idx_flows_trigger` (trigger_type, is_active)
|
||||
- `idx_flows_active` (is_active)
|
||||
|
||||
---
|
||||
|
||||
### message_templates
|
||||
|
||||
Templates de mensajes reutilizables.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| name | VARCHAR(100) | Nombre del template |
|
||||
| content | TEXT | Contenido con variables |
|
||||
| attachments | JSONB | Archivos adjuntos |
|
||||
| variables | VARCHAR[] | Variables usadas |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
---
|
||||
|
||||
### quick_replies
|
||||
|
||||
Respuestas rápidas para agentes.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| shortcut | VARCHAR(50) | Atajo (/saludo) |
|
||||
| content | TEXT | Contenido |
|
||||
| attachments | JSONB | Archivos adjuntos |
|
||||
| queue_id | UUID | FK a queues (opcional) |
|
||||
| created_by | UUID | FK a users |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
**Índices:**
|
||||
- `idx_qr_shortcut` (shortcut)
|
||||
- `idx_qr_queue` (queue_id)
|
||||
|
||||
---
|
||||
|
||||
### tags
|
||||
|
||||
Etiquetas para contactos.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| name | VARCHAR(50) | Nombre único |
|
||||
| color | VARCHAR(7) | Color hex (#FF5733) |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
---
|
||||
|
||||
### odoo_config
|
||||
|
||||
Configuración de conexión Odoo.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| url | VARCHAR(255) | URL de Odoo |
|
||||
| database | VARCHAR(100) | Nombre de BD |
|
||||
| username | VARCHAR(100) | Usuario API |
|
||||
| api_key_encrypted | TEXT | API key encriptada |
|
||||
| is_active | BOOLEAN | Conexión activa |
|
||||
| last_sync_at | TIMESTAMP | Última sincronización |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
---
|
||||
|
||||
### odoo_automations
|
||||
|
||||
Automatizaciones Odoo → WhatsApp.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador único |
|
||||
| name | VARCHAR(100) | Nombre |
|
||||
| odoo_model | VARCHAR(100) | Modelo Odoo (sale.order) |
|
||||
| odoo_trigger | VARCHAR(100) | Evento trigger |
|
||||
| odoo_condition | JSONB | Condiciones |
|
||||
| message_template_id | UUID | FK a templates |
|
||||
| is_active | BOOLEAN | Automatización activa |
|
||||
| created_at | TIMESTAMP | Fecha creación |
|
||||
|
||||
---
|
||||
|
||||
## Migraciones
|
||||
|
||||
### Crear migración
|
||||
|
||||
```bash
|
||||
docker compose exec api-gateway alembic revision --autogenerate -m "descripcion"
|
||||
```
|
||||
|
||||
### Aplicar migraciones
|
||||
|
||||
```bash
|
||||
docker compose exec api-gateway alembic upgrade head
|
||||
```
|
||||
|
||||
### Revertir migración
|
||||
|
||||
```bash
|
||||
docker compose exec api-gateway alembic downgrade -1
|
||||
```
|
||||
|
||||
### Ver historial
|
||||
|
||||
```bash
|
||||
docker compose exec api-gateway alembic history
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Índices Recomendados
|
||||
|
||||
```sql
|
||||
-- Búsqueda full-text en mensajes
|
||||
CREATE INDEX idx_messages_content_fts ON messages
|
||||
USING gin(to_tsvector('spanish', content));
|
||||
|
||||
-- Búsqueda en metadata JSONB
|
||||
CREATE INDEX idx_contacts_metadata ON contacts
|
||||
USING gin(metadata);
|
||||
|
||||
-- Queries frecuentes
|
||||
CREATE INDEX idx_conv_active ON conversations (status, assigned_to)
|
||||
WHERE status IN ('active', 'waiting');
|
||||
|
||||
CREATE INDEX idx_msg_recent ON messages (conversation_id, created_at DESC);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mantenimiento
|
||||
|
||||
### Vacuum
|
||||
|
||||
```sql
|
||||
-- Vacuum manual
|
||||
VACUUM ANALYZE conversations;
|
||||
VACUUM ANALYZE messages;
|
||||
|
||||
-- Configurar autovacuum agresivo para messages
|
||||
ALTER TABLE messages SET (
|
||||
autovacuum_vacuum_scale_factor = 0.05,
|
||||
autovacuum_analyze_scale_factor = 0.02
|
||||
);
|
||||
```
|
||||
|
||||
### Archivado de mensajes antiguos
|
||||
|
||||
```sql
|
||||
-- Mover mensajes > 1 año a tabla de archivo
|
||||
INSERT INTO messages_archive
|
||||
SELECT * FROM messages
|
||||
WHERE created_at < NOW() - INTERVAL '1 year';
|
||||
|
||||
DELETE FROM messages
|
||||
WHERE created_at < NOW() - INTERVAL '1 year';
|
||||
```
|
||||
|
||||
### Estadísticas
|
||||
|
||||
```sql
|
||||
-- Tamaño de tablas
|
||||
SELECT
|
||||
relname as table,
|
||||
pg_size_pretty(pg_total_relation_size(relid)) as size
|
||||
FROM pg_catalog.pg_statio_user_tables
|
||||
ORDER BY pg_total_relation_size(relid) DESC;
|
||||
|
||||
-- Mensajes por día
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count
|
||||
FROM messages
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date DESC
|
||||
LIMIT 30;
|
||||
```
|
||||
Reference in New Issue
Block a user