- 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>
453 lines
16 KiB
Markdown
453 lines
16 KiB
Markdown
# 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;
|
|
```
|