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:
746
docs/api/README.md
Normal file
746
docs/api/README.md
Normal file
@@ -0,0 +1,746 @@
|
||||
# API Reference
|
||||
|
||||
## Información General
|
||||
|
||||
- **Base URL:** `https://tu-dominio.com/api`
|
||||
- **Autenticación:** Bearer Token (JWT)
|
||||
- **Content-Type:** `application/json`
|
||||
|
||||
## Autenticación
|
||||
|
||||
### Login
|
||||
```http
|
||||
POST /auth/login
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"email": "usuario@empresa.com",
|
||||
"password": "tu_password"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"email": "usuario@empresa.com",
|
||||
"name": "Usuario",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Refresh Token
|
||||
```http
|
||||
POST /auth/refresh
|
||||
```
|
||||
|
||||
### Logout
|
||||
```http
|
||||
POST /auth/logout
|
||||
```
|
||||
|
||||
### Usuario Actual
|
||||
```http
|
||||
GET /auth/me
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usuarios
|
||||
|
||||
### Listar Usuarios
|
||||
```http
|
||||
GET /api/users
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
| Parámetro | Tipo | Descripción |
|
||||
|-----------|------|-------------|
|
||||
| role | string | Filtrar por rol (admin, supervisor, agent) |
|
||||
| status | string | Filtrar por estado (online, offline, away) |
|
||||
| page | int | Página (default: 1) |
|
||||
| limit | int | Límite por página (default: 20) |
|
||||
|
||||
### Crear Usuario
|
||||
```http
|
||||
POST /api/users
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"email": "nuevo@empresa.com",
|
||||
"password": "password123",
|
||||
"name": "Nuevo Usuario",
|
||||
"role": "agent"
|
||||
}
|
||||
```
|
||||
|
||||
### Obtener Usuario
|
||||
```http
|
||||
GET /api/users/{user_id}
|
||||
```
|
||||
|
||||
### Actualizar Usuario
|
||||
```http
|
||||
PUT /api/users/{user_id}
|
||||
```
|
||||
|
||||
### Eliminar Usuario
|
||||
```http
|
||||
DELETE /api/users/{user_id}
|
||||
```
|
||||
|
||||
### Cambiar Estado
|
||||
```http
|
||||
PUT /api/users/{user_id}/status
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"status": "away"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cuentas WhatsApp
|
||||
|
||||
### Listar Cuentas
|
||||
```http
|
||||
GET /api/whatsapp/accounts
|
||||
```
|
||||
|
||||
### Crear Cuenta (Iniciar conexión)
|
||||
```http
|
||||
POST /api/whatsapp/accounts
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "Ventas Principal"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Ventas Principal",
|
||||
"status": "disconnected",
|
||||
"qr_code": null
|
||||
}
|
||||
```
|
||||
|
||||
### Obtener QR Code
|
||||
```http
|
||||
GET /api/whatsapp/accounts/{account_id}/qr
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"qr_code": "data:image/png;base64,..."
|
||||
}
|
||||
```
|
||||
|
||||
### Estado de Cuenta
|
||||
```http
|
||||
GET /api/whatsapp/accounts/{account_id}/status
|
||||
```
|
||||
|
||||
### Desconectar Cuenta
|
||||
```http
|
||||
POST /api/whatsapp/accounts/{account_id}/disconnect
|
||||
```
|
||||
|
||||
### Reconectar Cuenta
|
||||
```http
|
||||
POST /api/whatsapp/accounts/{account_id}/reconnect
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contactos
|
||||
|
||||
### Listar Contactos
|
||||
```http
|
||||
GET /api/contacts
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
| Parámetro | Tipo | Descripción |
|
||||
|-----------|------|-------------|
|
||||
| search | string | Buscar por nombre, teléfono o email |
|
||||
| tags | string[] | Filtrar por etiquetas |
|
||||
| has_odoo | boolean | Filtrar sincronizados con Odoo |
|
||||
| page | int | Página |
|
||||
| limit | int | Límite |
|
||||
|
||||
### Crear Contacto
|
||||
```http
|
||||
POST /api/contacts
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"phone_number": "+525551234567",
|
||||
"name": "Juan Pérez",
|
||||
"email": "juan@email.com",
|
||||
"company": "Empresa ABC",
|
||||
"tags": ["lead", "interesado"],
|
||||
"metadata": {
|
||||
"producto_interes": "Laptop Pro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Obtener Contacto
|
||||
```http
|
||||
GET /api/contacts/{contact_id}
|
||||
```
|
||||
|
||||
### Actualizar Contacto
|
||||
```http
|
||||
PUT /api/contacts/{contact_id}
|
||||
```
|
||||
|
||||
### Agregar Etiquetas
|
||||
```http
|
||||
POST /api/contacts/{contact_id}/tags
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"tags": ["vip", "cliente"]
|
||||
}
|
||||
```
|
||||
|
||||
### Sincronizar con Odoo
|
||||
```http
|
||||
POST /api/contacts/{contact_id}/sync-odoo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conversaciones
|
||||
|
||||
### Listar Conversaciones
|
||||
```http
|
||||
GET /api/conversations
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
| Parámetro | Tipo | Descripción |
|
||||
|-----------|------|-------------|
|
||||
| status | string | bot, waiting, active, resolved |
|
||||
| queue_id | uuid | Filtrar por cola |
|
||||
| assigned_to | uuid | Filtrar por agente |
|
||||
| priority | string | low, normal, high, urgent |
|
||||
| page | int | Página |
|
||||
| limit | int | Límite |
|
||||
|
||||
### Obtener Conversación
|
||||
```http
|
||||
GET /api/conversations/{conversation_id}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"contact": {
|
||||
"id": "uuid",
|
||||
"name": "Juan Pérez",
|
||||
"phone_number": "+525551234567"
|
||||
},
|
||||
"whatsapp_account": {
|
||||
"id": "uuid",
|
||||
"name": "Ventas Principal"
|
||||
},
|
||||
"status": "active",
|
||||
"priority": "normal",
|
||||
"assigned_to": {
|
||||
"id": "uuid",
|
||||
"name": "María García"
|
||||
},
|
||||
"queue": {
|
||||
"id": "uuid",
|
||||
"name": "Ventas"
|
||||
},
|
||||
"current_flow": null,
|
||||
"sla_first_response_met": true,
|
||||
"last_message_at": "2024-01-15T14:30:00Z",
|
||||
"created_at": "2024-01-15T14:25:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Asignar Conversación
|
||||
```http
|
||||
POST /api/conversations/{conversation_id}/assign
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"user_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### Transferir a Cola
|
||||
```http
|
||||
POST /api/conversations/{conversation_id}/transfer
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"queue_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### Resolver Conversación
|
||||
```http
|
||||
POST /api/conversations/{conversation_id}/resolve
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"resolution_note": "Problema resuelto"
|
||||
}
|
||||
```
|
||||
|
||||
### Reabrir Conversación
|
||||
```http
|
||||
POST /api/conversations/{conversation_id}/reopen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mensajes
|
||||
|
||||
### Listar Mensajes de Conversación
|
||||
```http
|
||||
GET /api/conversations/{conversation_id}/messages
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
| Parámetro | Tipo | Descripción |
|
||||
|-----------|------|-------------|
|
||||
| before | uuid | Mensajes antes de este ID |
|
||||
| limit | int | Límite (default: 50) |
|
||||
|
||||
### Enviar Mensaje
|
||||
```http
|
||||
POST /api/conversations/{conversation_id}/messages
|
||||
```
|
||||
|
||||
**Request (texto):**
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Hola, ¿en qué te puedo ayudar?"
|
||||
}
|
||||
```
|
||||
|
||||
**Request (imagen):**
|
||||
```json
|
||||
{
|
||||
"type": "image",
|
||||
"media_url": "https://example.com/image.jpg",
|
||||
"content": "Caption opcional"
|
||||
}
|
||||
```
|
||||
|
||||
**Request (botones):**
|
||||
```json
|
||||
{
|
||||
"type": "buttons",
|
||||
"content": "¿Qué necesitas?",
|
||||
"metadata": {
|
||||
"buttons": [
|
||||
{"id": "ventas", "text": "Ventas"},
|
||||
{"id": "soporte", "text": "Soporte"},
|
||||
{"id": "otro", "text": "Otro"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Request (lista):**
|
||||
```json
|
||||
{
|
||||
"type": "list",
|
||||
"content": "Selecciona una opción:",
|
||||
"metadata": {
|
||||
"button_text": "Ver opciones",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Productos",
|
||||
"rows": [
|
||||
{"id": "laptop", "title": "Laptops", "description": "Ver laptops disponibles"},
|
||||
{"id": "telefono", "title": "Teléfonos", "description": "Ver teléfonos"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Enviar Nota Interna
|
||||
```http
|
||||
POST /api/conversations/{conversation_id}/notes
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"content": "Cliente interesado, dar seguimiento"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujos
|
||||
|
||||
### Listar Flujos
|
||||
```http
|
||||
GET /api/flows
|
||||
```
|
||||
|
||||
### Crear Flujo
|
||||
```http
|
||||
POST /api/flows
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "Bienvenida",
|
||||
"description": "Flujo de bienvenida para nuevos contactos",
|
||||
"trigger_type": "welcome",
|
||||
"trigger_value": null,
|
||||
"nodes": {
|
||||
"nodes": [...],
|
||||
"edges": [...],
|
||||
"viewport": {...}
|
||||
},
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
### Obtener Flujo
|
||||
```http
|
||||
GET /api/flows/{flow_id}
|
||||
```
|
||||
|
||||
### Actualizar Flujo
|
||||
```http
|
||||
PUT /api/flows/{flow_id}
|
||||
```
|
||||
|
||||
### Duplicar Flujo
|
||||
```http
|
||||
POST /api/flows/{flow_id}/duplicate
|
||||
```
|
||||
|
||||
### Activar/Desactivar Flujo
|
||||
```http
|
||||
PUT /api/flows/{flow_id}/toggle
|
||||
```
|
||||
|
||||
### Probar Flujo
|
||||
```http
|
||||
POST /api/flows/{flow_id}/test
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"phone_number": "+525551234567",
|
||||
"initial_message": "Hola"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Colas
|
||||
|
||||
### Listar Colas
|
||||
```http
|
||||
GET /api/queues
|
||||
```
|
||||
|
||||
### Crear Cola
|
||||
```http
|
||||
POST /api/queues
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "Ventas",
|
||||
"description": "Cola de atención de ventas",
|
||||
"assignment_method": "least_busy",
|
||||
"max_per_agent": 8,
|
||||
"sla_first_response": 180,
|
||||
"sla_resolution": 3600,
|
||||
"business_hours": {
|
||||
"monday": {"start": "09:00", "end": "18:00"},
|
||||
"tuesday": {"start": "09:00", "end": "18:00"},
|
||||
"wednesday": {"start": "09:00", "end": "18:00"},
|
||||
"thursday": {"start": "09:00", "end": "18:00"},
|
||||
"friday": {"start": "09:00", "end": "17:00"},
|
||||
"saturday": {"start": "10:00", "end": "14:00"},
|
||||
"sunday": null
|
||||
},
|
||||
"fallback_flow_id": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### Obtener Cola
|
||||
```http
|
||||
GET /api/queues/{queue_id}
|
||||
```
|
||||
|
||||
### Actualizar Cola
|
||||
```http
|
||||
PUT /api/queues/{queue_id}
|
||||
```
|
||||
|
||||
### Agregar Agente a Cola
|
||||
```http
|
||||
POST /api/queues/{queue_id}/agents
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"user_id": "uuid",
|
||||
"is_supervisor": false,
|
||||
"skills": ["ventas", "ingles"]
|
||||
}
|
||||
```
|
||||
|
||||
### Remover Agente de Cola
|
||||
```http
|
||||
DELETE /api/queues/{queue_id}/agents/{user_id}
|
||||
```
|
||||
|
||||
### Estadísticas de Cola
|
||||
```http
|
||||
GET /api/queues/{queue_id}/stats
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"waiting": 5,
|
||||
"active": 12,
|
||||
"resolved_today": 47,
|
||||
"avg_first_response": 142,
|
||||
"avg_resolution": 1823,
|
||||
"sla_compliance": 0.91,
|
||||
"agents": [
|
||||
{
|
||||
"user_id": "uuid",
|
||||
"name": "María García",
|
||||
"status": "online",
|
||||
"active_conversations": 5,
|
||||
"resolved_today": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Respuestas Rápidas
|
||||
|
||||
### Listar Respuestas Rápidas
|
||||
```http
|
||||
GET /api/quick-replies
|
||||
```
|
||||
|
||||
### Crear Respuesta Rápida
|
||||
```http
|
||||
POST /api/quick-replies
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"shortcut": "/saludo",
|
||||
"content": "¡Hola {{contact.name}}! Soy {{agent.name}}, ¿en qué te puedo ayudar?",
|
||||
"queue_id": null
|
||||
}
|
||||
```
|
||||
|
||||
### Actualizar Respuesta Rápida
|
||||
```http
|
||||
PUT /api/quick-replies/{id}
|
||||
```
|
||||
|
||||
### Eliminar Respuesta Rápida
|
||||
```http
|
||||
DELETE /api/quick-replies/{id}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Odoo
|
||||
|
||||
### Probar Conexión
|
||||
```http
|
||||
POST /api/odoo/test-connection
|
||||
```
|
||||
|
||||
### Sincronizar Contactos
|
||||
```http
|
||||
POST /api/odoo/sync-contacts
|
||||
```
|
||||
|
||||
### Buscar Partner
|
||||
```http
|
||||
GET /api/odoo/partners/search
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
| Parámetro | Tipo | Descripción |
|
||||
|-----------|------|-------------|
|
||||
| phone | string | Buscar por teléfono |
|
||||
| email | string | Buscar por email |
|
||||
| name | string | Buscar por nombre |
|
||||
|
||||
### Buscar Pedidos
|
||||
```http
|
||||
GET /api/odoo/orders
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
| Parámetro | Tipo | Descripción |
|
||||
|-----------|------|-------------|
|
||||
| partner_id | int | ID del partner en Odoo |
|
||||
| state | string | Estado del pedido |
|
||||
| limit | int | Límite |
|
||||
|
||||
### Consultar Stock
|
||||
```http
|
||||
GET /api/odoo/products/{product_id}/stock
|
||||
```
|
||||
|
||||
### Crear Lead
|
||||
```http
|
||||
POST /api/odoo/leads
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "Interés en Producto X",
|
||||
"contact_name": "Juan Pérez",
|
||||
"phone": "+525551234567",
|
||||
"email": "juan@email.com",
|
||||
"description": "Cliente interesado en laptops",
|
||||
"expected_revenue": 5000
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSocket
|
||||
|
||||
### Conexión
|
||||
```javascript
|
||||
const socket = io('wss://tu-dominio.com', {
|
||||
auth: {
|
||||
token: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Eventos del Servidor
|
||||
|
||||
**Nuevo mensaje:**
|
||||
```javascript
|
||||
socket.on('new_message', (data) => {
|
||||
// data: { conversation_id, message }
|
||||
});
|
||||
```
|
||||
|
||||
**Estado de mensaje:**
|
||||
```javascript
|
||||
socket.on('message_status', (data) => {
|
||||
// data: { message_id, status: 'delivered' | 'read' }
|
||||
});
|
||||
```
|
||||
|
||||
**Conversación asignada:**
|
||||
```javascript
|
||||
socket.on('conversation_assigned', (data) => {
|
||||
// data: { conversation_id, assigned_to }
|
||||
});
|
||||
```
|
||||
|
||||
**QR Code:**
|
||||
```javascript
|
||||
socket.on('qr_code', (data) => {
|
||||
// data: { account_id, qr_code }
|
||||
});
|
||||
```
|
||||
|
||||
**Estado de conexión WhatsApp:**
|
||||
```javascript
|
||||
socket.on('connection_status', (data) => {
|
||||
// data: { account_id, status: 'connected' | 'disconnected' }
|
||||
});
|
||||
```
|
||||
|
||||
### Eventos del Cliente
|
||||
|
||||
**Unirse a conversación:**
|
||||
```javascript
|
||||
socket.emit('join_conversation', { conversation_id: 'uuid' });
|
||||
```
|
||||
|
||||
**Escribiendo:**
|
||||
```javascript
|
||||
socket.emit('typing', { conversation_id: 'uuid' });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Códigos de Error
|
||||
|
||||
| Código | Descripción |
|
||||
|--------|-------------|
|
||||
| 400 | Bad Request - Datos inválidos |
|
||||
| 401 | Unauthorized - Token inválido o expirado |
|
||||
| 403 | Forbidden - Sin permisos |
|
||||
| 404 | Not Found - Recurso no encontrado |
|
||||
| 409 | Conflict - Conflicto de datos |
|
||||
| 422 | Unprocessable Entity - Validación fallida |
|
||||
| 429 | Too Many Requests - Rate limit excedido |
|
||||
| 500 | Internal Server Error |
|
||||
|
||||
**Formato de error:**
|
||||
```json
|
||||
{
|
||||
"detail": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "El campo email es requerido",
|
||||
"field": "email"
|
||||
}
|
||||
}
|
||||
```
|
||||
227
docs/architecture/README.md
Normal file
227
docs/architecture/README.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Arquitectura del Sistema
|
||||
|
||||
## Visión General
|
||||
|
||||
WhatsApp Centralizado está diseñado como una arquitectura de microservicios, donde cada componente tiene una responsabilidad específica y se comunica con los demás a través de APIs REST y WebSocket.
|
||||
|
||||
## Diagrama de Arquitectura
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ NGINX │
|
||||
│ (Reverse Proxy)│
|
||||
│ :80 / :443 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────────────────────┼────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ Frontend │ │ API Gateway │ │ WhatsApp Core │
|
||||
│ (React) │ │ (FastAPI) │ │ (Node.js) │
|
||||
│ :3000 │ │ :8000 │ │ :3001 │
|
||||
└───────────────┘ └───────┬───────┘ └───────┬───────┘
|
||||
│ │
|
||||
│ │
|
||||
┌──────────────────┼──────────────────┐ │
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ │
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐
|
||||
│ Flow Engine │ │ Integrations │ │ Redis │
|
||||
│ (Python) │ │ (Python) │ │ (Cache/PubSub)│
|
||||
│ :8001 │ │ :8002 │ │ :6379 │
|
||||
└────────┬────────┘ └────────┬────────┘ └──────────────────┘
|
||||
│ │ ▲
|
||||
│ │ │
|
||||
└─────────┬─────────┘ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│ PostgreSQL │────────────────────┘
|
||||
│ (Database) │
|
||||
│ :5432 │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Componentes
|
||||
|
||||
### Frontend (React + TypeScript)
|
||||
- **Puerto:** 3000
|
||||
- **Responsabilidad:** Interfaz de usuario
|
||||
- **Tecnologías:**
|
||||
- React 18
|
||||
- TypeScript
|
||||
- React Flow (editor visual)
|
||||
- Ant Design / shadcn/ui
|
||||
- TanStack Query
|
||||
- Zustand (estado)
|
||||
- Socket.io-client
|
||||
|
||||
**Módulos:**
|
||||
- Dashboard con métricas
|
||||
- Inbox de conversaciones
|
||||
- Flow Builder visual
|
||||
- Gestión de números WhatsApp
|
||||
- Panel de supervisor
|
||||
- Configuración
|
||||
|
||||
### API Gateway (Python + FastAPI)
|
||||
- **Puerto:** 8000
|
||||
- **Responsabilidad:** Punto de entrada único, autenticación, orquestación
|
||||
- **Tecnologías:**
|
||||
- FastAPI
|
||||
- SQLAlchemy
|
||||
- Pydantic
|
||||
- python-jose (JWT)
|
||||
- Celery (tareas async)
|
||||
|
||||
**Endpoints principales:**
|
||||
- `/auth/*` - Autenticación
|
||||
- `/api/conversations/*` - Conversaciones
|
||||
- `/api/messages/*` - Mensajes
|
||||
- `/api/flows/*` - Flujos de chatbot
|
||||
- `/api/contacts/*` - Contactos
|
||||
- `/api/users/*` - Usuarios
|
||||
- `/api/queues/*` - Colas
|
||||
- `/api/whatsapp/*` - Gestión WhatsApp
|
||||
- `/ws/*` - WebSocket
|
||||
|
||||
### WhatsApp Core (Node.js + TypeScript)
|
||||
- **Puerto:** 3001
|
||||
- **Responsabilidad:** Conexión con WhatsApp via Baileys
|
||||
- **Tecnologías:**
|
||||
- Node.js 20
|
||||
- TypeScript
|
||||
- Baileys
|
||||
- Socket.io
|
||||
|
||||
**Funcionalidades:**
|
||||
- Gestión de sesiones multi-número
|
||||
- Generación de QR
|
||||
- Envío/recepción de mensajes
|
||||
- Manejo de media
|
||||
- Reconexión automática
|
||||
- Estados de mensajes
|
||||
|
||||
### Flow Engine (Python)
|
||||
- **Puerto:** 8001 (interno)
|
||||
- **Responsabilidad:** Ejecutar flujos de chatbot
|
||||
- **Tecnologías:**
|
||||
- Python
|
||||
- Motor de ejecución propio
|
||||
|
||||
**Funcionalidades:**
|
||||
- Parsear definición de flujos (JSONB)
|
||||
- Ejecutar nodos secuencialmente
|
||||
- Manejar bifurcaciones y condiciones
|
||||
- Gestionar variables y contexto
|
||||
- Integrar con IA (OpenAI, Claude, Ollama)
|
||||
|
||||
### Integrations (Python)
|
||||
- **Puerto:** 8002 (interno)
|
||||
- **Responsabilidad:** Conexión con sistemas externos
|
||||
- **Tecnologías:**
|
||||
- Python
|
||||
- xmlrpc.client (Odoo)
|
||||
- httpx (webhooks)
|
||||
|
||||
**Integraciones:**
|
||||
- Odoo via XML-RPC
|
||||
- Webhooks entrantes/salientes
|
||||
- Email (futuro)
|
||||
- SMS (futuro)
|
||||
|
||||
### PostgreSQL
|
||||
- **Puerto:** 5432
|
||||
- **Responsabilidad:** Persistencia de datos
|
||||
- **Datos almacenados:**
|
||||
- Usuarios y roles
|
||||
- Conversaciones y mensajes
|
||||
- Contactos
|
||||
- Flujos de chatbot
|
||||
- Configuración
|
||||
- Logs de auditoría
|
||||
|
||||
### Redis
|
||||
- **Puerto:** 6379
|
||||
- **Responsabilidad:** Cache, colas, pub/sub
|
||||
- **Usos:**
|
||||
- Sesiones de usuario
|
||||
- Cache de datos frecuentes
|
||||
- Cola de mensajes salientes
|
||||
- Pub/Sub para tiempo real
|
||||
- Rate limiting
|
||||
|
||||
## Comunicación entre Servicios
|
||||
|
||||
### HTTP/REST
|
||||
- Frontend → API Gateway
|
||||
- API Gateway → Flow Engine
|
||||
- API Gateway → Integrations
|
||||
- WhatsApp Core → API Gateway
|
||||
|
||||
### WebSocket
|
||||
- Frontend ↔ API Gateway (tiempo real)
|
||||
- API Gateway ↔ WhatsApp Core (eventos)
|
||||
|
||||
### Pub/Sub (Redis)
|
||||
- Eventos de nuevos mensajes
|
||||
- Cambios de estado
|
||||
- Notificaciones
|
||||
|
||||
## Flujo de Datos
|
||||
|
||||
### Mensaje Entrante
|
||||
```
|
||||
WhatsApp → Baileys → WhatsApp Core → API Gateway → Flow Engine
|
||||
↓
|
||||
PostgreSQL
|
||||
↓
|
||||
Frontend (via WebSocket)
|
||||
```
|
||||
|
||||
### Mensaje Saliente (Bot)
|
||||
```
|
||||
Flow Engine → API Gateway → WhatsApp Core → Baileys → WhatsApp
|
||||
↓
|
||||
PostgreSQL
|
||||
```
|
||||
|
||||
### Mensaje Saliente (Agente)
|
||||
```
|
||||
Frontend → API Gateway → WhatsApp Core → Baileys → WhatsApp
|
||||
↓
|
||||
PostgreSQL
|
||||
```
|
||||
|
||||
## Escalabilidad
|
||||
|
||||
### Horizontal
|
||||
- WhatsApp Core: Múltiples instancias para más números
|
||||
- API Gateway: Load balancer con múltiples instancias
|
||||
- Flow Engine: Workers independientes
|
||||
|
||||
### Vertical
|
||||
- PostgreSQL: Aumentar recursos según carga
|
||||
- Redis: Cluster para alta disponibilidad
|
||||
|
||||
## Seguridad
|
||||
|
||||
### Autenticación
|
||||
- JWT con access tokens (1h) y refresh tokens (7d)
|
||||
- Refresh tokens en httpOnly cookies
|
||||
- Bcrypt para passwords (cost 12)
|
||||
|
||||
### Autorización
|
||||
- RBAC: Admin, Supervisor, Agente
|
||||
- Permisos granulares por endpoint
|
||||
|
||||
### Red
|
||||
- HTTPS obligatorio
|
||||
- Rate limiting
|
||||
- Validación de entrada con Pydantic
|
||||
|
||||
### Datos
|
||||
- Encriptación en reposo (PostgreSQL)
|
||||
- Secrets en variables de entorno
|
||||
- No logs de datos sensibles
|
||||
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;
|
||||
```
|
||||
539
docs/deployment/README.md
Normal file
539
docs/deployment/README.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# Guía de Despliegue
|
||||
|
||||
## Requisitos del Servidor
|
||||
|
||||
### Hardware Mínimo
|
||||
- **CPU:** 2 cores
|
||||
- **RAM:** 4 GB
|
||||
- **Disco:** 50 GB SSD
|
||||
|
||||
### Hardware Recomendado
|
||||
- **CPU:** 4 cores
|
||||
- **RAM:** 8 GB
|
||||
- **Disco:** 100 GB SSD
|
||||
|
||||
### Software
|
||||
- Ubuntu 22.04 LTS (recomendado) o Debian 12
|
||||
- Docker 24.0+
|
||||
- Docker Compose 2.20+
|
||||
- Git
|
||||
|
||||
---
|
||||
|
||||
## Instalación
|
||||
|
||||
### 1. Preparar Servidor
|
||||
|
||||
```bash
|
||||
# Actualizar sistema
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Instalar Docker
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
|
||||
# Agregar usuario al grupo docker
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Instalar Docker Compose
|
||||
sudo apt install docker-compose-plugin -y
|
||||
|
||||
# Verificar instalación
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### 2. Clonar Repositorio
|
||||
|
||||
```bash
|
||||
cd /opt
|
||||
sudo git clone https://git.consultoria-as.com/tu-usuario/WhatsAppCentralizado.git
|
||||
sudo chown -R $USER:$USER WhatsAppCentralizado
|
||||
cd WhatsAppCentralizado
|
||||
```
|
||||
|
||||
### 3. Configurar Variables de Entorno
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
**Variables requeridas:**
|
||||
|
||||
```bash
|
||||
# Base de datos
|
||||
DB_USER=whatsapp_admin
|
||||
DB_PASSWORD=<password_seguro_32_caracteres>
|
||||
|
||||
# JWT Secret (generar aleatorio)
|
||||
JWT_SECRET=<secreto_aleatorio_64_caracteres>
|
||||
|
||||
# Dominio
|
||||
DOMAIN=chat.tuempresa.com
|
||||
|
||||
# Odoo (opcional, configurar después)
|
||||
ODOO_URL=https://odoo.tuempresa.com
|
||||
ODOO_DB=production
|
||||
ODOO_USER=api-whatsapp@tuempresa.com
|
||||
ODOO_API_KEY=<api_key_de_odoo>
|
||||
|
||||
# OpenAI (opcional, para AI Response)
|
||||
OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
**Generar secrets:**
|
||||
```bash
|
||||
# Generar password DB
|
||||
openssl rand -base64 32
|
||||
|
||||
# Generar JWT secret
|
||||
openssl rand -base64 64
|
||||
```
|
||||
|
||||
### 4. Construir e Iniciar
|
||||
|
||||
```bash
|
||||
# Construir imágenes
|
||||
docker compose build
|
||||
|
||||
# Iniciar servicios
|
||||
docker compose up -d
|
||||
|
||||
# Ver logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### 5. Inicializar Base de Datos
|
||||
|
||||
```bash
|
||||
# Aplicar migraciones
|
||||
docker compose exec api-gateway alembic upgrade head
|
||||
|
||||
# Crear usuario admin
|
||||
docker compose exec api-gateway python scripts/create_admin.py
|
||||
```
|
||||
|
||||
### 6. Configurar SSL con Let's Encrypt
|
||||
|
||||
```bash
|
||||
# Instalar certbot
|
||||
sudo apt install certbot -y
|
||||
|
||||
# Obtener certificado
|
||||
sudo certbot certonly --standalone -d chat.tuempresa.com
|
||||
|
||||
# Los certificados quedan en:
|
||||
# /etc/letsencrypt/live/chat.tuempresa.com/fullchain.pem
|
||||
# /etc/letsencrypt/live/chat.tuempresa.com/privkey.pem
|
||||
|
||||
# Copiar a directorio del proyecto
|
||||
sudo cp /etc/letsencrypt/live/chat.tuempresa.com/fullchain.pem nginx/ssl/cert.pem
|
||||
sudo cp /etc/letsencrypt/live/chat.tuempresa.com/privkey.pem nginx/ssl/key.pem
|
||||
|
||||
# Reiniciar nginx
|
||||
docker compose restart nginx
|
||||
```
|
||||
|
||||
### 7. Configurar Renovación Automática
|
||||
|
||||
```bash
|
||||
# Crear script de renovación
|
||||
sudo nano /etc/cron.d/certbot-renew
|
||||
```
|
||||
|
||||
```
|
||||
0 3 * * * root certbot renew --quiet --post-hook "cp /etc/letsencrypt/live/chat.tuempresa.com/*.pem /opt/WhatsAppCentralizado/nginx/ssl/ && docker compose -f /opt/WhatsAppCentralizado/docker-compose.yml restart nginx"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuración de Nginx
|
||||
|
||||
### nginx/nginx.conf
|
||||
|
||||
```nginx
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream frontend {
|
||||
server frontend:80;
|
||||
}
|
||||
|
||||
upstream api {
|
||||
server api-gateway:8000;
|
||||
}
|
||||
|
||||
upstream websocket {
|
||||
server whatsapp-core:3001;
|
||||
}
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name chat.tuempresa.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS Server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name chat.tuempresa.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
proxy_pass http://frontend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# API
|
||||
location /api {
|
||||
proxy_pass http://api;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Timeouts para operaciones largas
|
||||
proxy_read_timeout 300;
|
||||
proxy_connect_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
}
|
||||
|
||||
# Auth endpoints
|
||||
location /auth {
|
||||
proxy_pass http://api;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# WebSocket
|
||||
location /ws {
|
||||
proxy_pass http://websocket;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
# Webhook de Odoo
|
||||
location /api/odoo/webhook {
|
||||
proxy_pass http://api;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Media files
|
||||
location /media {
|
||||
alias /var/www/media;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backups
|
||||
|
||||
### Script de Backup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# /opt/WhatsAppCentralizado/scripts/backup.sh
|
||||
|
||||
BACKUP_DIR="/opt/backups/whatsapp"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
RETENTION_DAYS=30
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Backup PostgreSQL
|
||||
docker compose exec -T postgres pg_dump -U $DB_USER whatsapp_central | gzip > $BACKUP_DIR/db_$DATE.sql.gz
|
||||
|
||||
# Backup sesiones WhatsApp
|
||||
tar -czf $BACKUP_DIR/sessions_$DATE.tar.gz -C /var/lib/docker/volumes whatsapp_sessions
|
||||
|
||||
# Backup configuración
|
||||
tar -czf $BACKUP_DIR/config_$DATE.tar.gz .env docker-compose.yml nginx/
|
||||
|
||||
# Eliminar backups antiguos
|
||||
find $BACKUP_DIR -type f -mtime +$RETENTION_DAYS -delete
|
||||
|
||||
echo "Backup completado: $DATE"
|
||||
```
|
||||
|
||||
### Programar Backup
|
||||
|
||||
```bash
|
||||
# Editar crontab
|
||||
crontab -e
|
||||
```
|
||||
|
||||
```
|
||||
# Backup diario a las 3 AM
|
||||
0 3 * * * /opt/WhatsAppCentralizado/scripts/backup.sh >> /var/log/whatsapp-backup.log 2>&1
|
||||
```
|
||||
|
||||
### Restaurar Backup
|
||||
|
||||
```bash
|
||||
# Restaurar base de datos
|
||||
gunzip -c backup/db_20240115.sql.gz | docker compose exec -T postgres psql -U $DB_USER whatsapp_central
|
||||
|
||||
# Restaurar sesiones
|
||||
docker compose down
|
||||
tar -xzf backup/sessions_20240115.tar.gz -C /var/lib/docker/volumes
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoreo
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# Ver todos los logs
|
||||
docker compose logs -f
|
||||
|
||||
# Ver logs de servicio específico
|
||||
docker compose logs -f api-gateway
|
||||
docker compose logs -f whatsapp-core
|
||||
|
||||
# Ver últimas 100 líneas
|
||||
docker compose logs --tail=100 api-gateway
|
||||
```
|
||||
|
||||
### Healthchecks
|
||||
|
||||
```bash
|
||||
# Estado de contenedores
|
||||
docker compose ps
|
||||
|
||||
# Uso de recursos
|
||||
docker stats
|
||||
```
|
||||
|
||||
### Verificar Servicios
|
||||
|
||||
```bash
|
||||
# API
|
||||
curl -s https://chat.tuempresa.com/api/health | jq
|
||||
|
||||
# Frontend
|
||||
curl -I https://chat.tuempresa.com
|
||||
|
||||
# WebSocket
|
||||
wscat -c wss://chat.tuempresa.com/ws
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Actualización
|
||||
|
||||
### Proceso de Actualización
|
||||
|
||||
```bash
|
||||
cd /opt/WhatsAppCentralizado
|
||||
|
||||
# Hacer backup primero
|
||||
./scripts/backup.sh
|
||||
|
||||
# Obtener cambios
|
||||
git pull origin main
|
||||
|
||||
# Reconstruir imágenes
|
||||
docker compose build
|
||||
|
||||
# Aplicar migraciones
|
||||
docker compose exec api-gateway alembic upgrade head
|
||||
|
||||
# Reiniciar servicios
|
||||
docker compose up -d
|
||||
|
||||
# Verificar logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# Volver a versión anterior
|
||||
git checkout <commit_anterior>
|
||||
|
||||
# Reconstruir
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
|
||||
# Restaurar base de datos si es necesario
|
||||
gunzip -c backup/db_<fecha>.sql.gz | docker compose exec -T postgres psql -U $DB_USER whatsapp_central
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problemas Comunes
|
||||
|
||||
#### Contenedor no inicia
|
||||
```bash
|
||||
# Ver logs detallados
|
||||
docker compose logs <servicio>
|
||||
|
||||
# Verificar configuración
|
||||
docker compose config
|
||||
```
|
||||
|
||||
#### Error de conexión a PostgreSQL
|
||||
```bash
|
||||
# Verificar que postgres está corriendo
|
||||
docker compose ps postgres
|
||||
|
||||
# Verificar credenciales
|
||||
docker compose exec postgres psql -U $DB_USER -d whatsapp_central
|
||||
```
|
||||
|
||||
#### WhatsApp no conecta
|
||||
```bash
|
||||
# Verificar logs de whatsapp-core
|
||||
docker compose logs whatsapp-core
|
||||
|
||||
# Verificar sesiones
|
||||
ls -la volumes/whatsapp_sessions/
|
||||
|
||||
# Reiniciar servicio
|
||||
docker compose restart whatsapp-core
|
||||
```
|
||||
|
||||
#### Frontend no carga
|
||||
```bash
|
||||
# Verificar build
|
||||
docker compose logs frontend
|
||||
|
||||
# Reconstruir frontend
|
||||
docker compose build frontend
|
||||
docker compose up -d frontend
|
||||
```
|
||||
|
||||
### Comandos Útiles
|
||||
|
||||
```bash
|
||||
# Reiniciar todo
|
||||
docker compose restart
|
||||
|
||||
# Reiniciar servicio específico
|
||||
docker compose restart api-gateway
|
||||
|
||||
# Reconstruir sin cache
|
||||
docker compose build --no-cache
|
||||
|
||||
# Limpiar volúmenes (¡CUIDADO!)
|
||||
docker compose down -v
|
||||
|
||||
# Ver uso de disco
|
||||
docker system df
|
||||
|
||||
# Limpiar imágenes no usadas
|
||||
docker image prune -a
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Seguridad
|
||||
|
||||
### Firewall
|
||||
|
||||
```bash
|
||||
# Instalar ufw
|
||||
sudo apt install ufw
|
||||
|
||||
# Configurar reglas
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow ssh
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
|
||||
# Activar
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### Fail2ban
|
||||
|
||||
```bash
|
||||
# Instalar
|
||||
sudo apt install fail2ban
|
||||
|
||||
# Configurar para nginx
|
||||
sudo nano /etc/fail2ban/jail.local
|
||||
```
|
||||
|
||||
```ini
|
||||
[nginx-http-auth]
|
||||
enabled = true
|
||||
port = http,https
|
||||
logpath = /var/log/nginx/error.log
|
||||
maxretry = 3
|
||||
bantime = 3600
|
||||
```
|
||||
|
||||
### Actualizaciones de Seguridad
|
||||
|
||||
```bash
|
||||
# Habilitar actualizaciones automáticas
|
||||
sudo apt install unattended-upgrades
|
||||
sudo dpkg-reconfigure unattended-upgrades
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Escalamiento
|
||||
|
||||
### Múltiples Números WhatsApp
|
||||
|
||||
Para manejar muchos números, escalar whatsapp-core:
|
||||
|
||||
```yaml
|
||||
# docker-compose.override.yml
|
||||
services:
|
||||
whatsapp-core:
|
||||
deploy:
|
||||
replicas: 3
|
||||
volumes:
|
||||
- whatsapp_sessions:/app/sessions
|
||||
```
|
||||
|
||||
### Alta Disponibilidad
|
||||
|
||||
Para producción de alta disponibilidad:
|
||||
|
||||
1. **Load Balancer** externo (HAProxy, AWS ALB)
|
||||
2. **PostgreSQL** en cluster (Patroni) o managed (RDS)
|
||||
3. **Redis** en cluster o managed (ElastiCache)
|
||||
4. **Storage** compartido para sesiones (NFS, EFS)
|
||||
|
||||
---
|
||||
|
||||
## Contacto
|
||||
|
||||
Para soporte técnico, contactar al equipo de desarrollo.
|
||||
786
docs/flow-builder/README.md
Normal file
786
docs/flow-builder/README.md
Normal file
@@ -0,0 +1,786 @@
|
||||
# Flow Builder - Guía Completa
|
||||
|
||||
## Introducción
|
||||
|
||||
El Flow Builder es el editor visual para crear chatbots sin código. Permite diseñar flujos de conversación arrastrando y conectando nodos.
|
||||
|
||||
## Interfaz
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Flujo: Bienvenida [Guardar] [Probar] [Activar] │
|
||||
├───────────┬─────────────────────────────────────────────────────────┤
|
||||
│ NODOS │ CANVAS │
|
||||
│ ───────── │ │
|
||||
│ 📨 Mensaje│ [Nodos conectados con líneas] │
|
||||
│ 🔘 Botones│ │
|
||||
│ 📋 Lista │ │
|
||||
│ ❓ Input │ │
|
||||
│ ⑂ Condición │
|
||||
│ ... │ │
|
||||
├───────────┼─────────────────────────────────────────────────────────┤
|
||||
│ │ PANEL DE PROPIEDADES (al seleccionar un nodo) │
|
||||
└───────────┴─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tipos de Nodos
|
||||
|
||||
### Nodos de Trigger (Inicio)
|
||||
|
||||
#### welcome
|
||||
Inicia el flujo cuando un contacto escribe por primera vez.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "welcome",
|
||||
"id": "trigger_1",
|
||||
"data": {
|
||||
"label": "Primer mensaje"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### keyword
|
||||
Inicia el flujo cuando el mensaje contiene palabras clave.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "keyword",
|
||||
"id": "trigger_2",
|
||||
"data": {
|
||||
"keywords": ["precio", "costo", "cuanto"],
|
||||
"match_type": "contains"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Match types:**
|
||||
- `exact` - Coincidencia exacta
|
||||
- `contains` - Contiene la palabra
|
||||
- `starts_with` - Empieza con
|
||||
- `regex` - Expresión regular
|
||||
|
||||
#### fallback
|
||||
Se activa cuando ningún otro flujo coincide.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "fallback",
|
||||
"id": "trigger_3",
|
||||
"data": {
|
||||
"label": "No entendido"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### event
|
||||
Se activa por eventos externos (webhook, Odoo).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "event",
|
||||
"id": "trigger_4",
|
||||
"data": {
|
||||
"event_type": "odoo_order_confirmed",
|
||||
"filters": {
|
||||
"amount_total": { "gt": 1000 }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nodos de Mensaje
|
||||
|
||||
#### text
|
||||
Envía un mensaje de texto.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"id": "msg_1",
|
||||
"data": {
|
||||
"content": "¡Hola {{contact.name}}! 👋\n\n¿En qué te puedo ayudar?",
|
||||
"typing_delay": 1500
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables disponibles:**
|
||||
- `{{contact.name}}` - Nombre del contacto
|
||||
- `{{contact.phone}}` - Teléfono
|
||||
- `{{agent.name}}` - Nombre del agente
|
||||
- `{{variables.xxx}}` - Variables capturadas
|
||||
|
||||
#### image
|
||||
Envía una imagen.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "image",
|
||||
"id": "msg_2",
|
||||
"data": {
|
||||
"url": "https://example.com/producto.jpg",
|
||||
"caption": "Nuestro producto estrella"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### video
|
||||
Envía un video.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "video",
|
||||
"id": "msg_3",
|
||||
"data": {
|
||||
"url": "https://example.com/demo.mp4",
|
||||
"caption": "Video demostrativo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### document
|
||||
Envía un documento (PDF, Excel, etc).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "document",
|
||||
"id": "msg_4",
|
||||
"data": {
|
||||
"url": "https://example.com/catalogo.pdf",
|
||||
"filename": "Catálogo 2024.pdf"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### location
|
||||
Envía una ubicación.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "location",
|
||||
"id": "msg_5",
|
||||
"data": {
|
||||
"latitude": 19.4326,
|
||||
"longitude": -99.1332,
|
||||
"name": "Nuestra Oficina",
|
||||
"address": "Av. Reforma 123, CDMX"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### buttons
|
||||
Envía mensaje con botones (máximo 3).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "buttons",
|
||||
"id": "msg_6",
|
||||
"data": {
|
||||
"content": "¿Qué necesitas?",
|
||||
"buttons": [
|
||||
{ "id": "ventas", "text": "Ventas" },
|
||||
{ "id": "soporte", "text": "Soporte" },
|
||||
{ "id": "otro", "text": "Otro" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### list
|
||||
Envía mensaje con lista de opciones (máximo 10).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "list",
|
||||
"id": "msg_7",
|
||||
"data": {
|
||||
"content": "Selecciona una categoría:",
|
||||
"button_text": "Ver opciones",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Productos",
|
||||
"rows": [
|
||||
{ "id": "laptops", "title": "Laptops", "description": "Computadoras portátiles" },
|
||||
{ "id": "phones", "title": "Teléfonos", "description": "Smartphones" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Servicios",
|
||||
"rows": [
|
||||
{ "id": "soporte", "title": "Soporte Técnico" },
|
||||
{ "id": "garantia", "title": "Garantías" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### template
|
||||
Usa un mensaje guardado como template.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "template",
|
||||
"id": "msg_8",
|
||||
"data": {
|
||||
"template_id": "uuid-del-template",
|
||||
"variables": {
|
||||
"producto": "{{variables.producto_seleccionado}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nodos de Lógica
|
||||
|
||||
#### condition
|
||||
Bifurca según condiciones.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "condition",
|
||||
"id": "cond_1",
|
||||
"data": {
|
||||
"conditions": [
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"field": "{{message.text}}",
|
||||
"operator": "contains",
|
||||
"value": "precio"
|
||||
}
|
||||
],
|
||||
"logic": "AND"
|
||||
}
|
||||
],
|
||||
"outputs": ["true", "false"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Operadores:**
|
||||
- Texto: `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`, `regex`
|
||||
- Número: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `between`
|
||||
- Lista: `in`, `not_in`, `is_empty`, `is_not_empty`
|
||||
- General: `exists`, `not_exists`, `is_null`, `is_not_null`
|
||||
|
||||
#### switch
|
||||
Múltiples salidas según valor.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "switch",
|
||||
"id": "switch_1",
|
||||
"data": {
|
||||
"field": "{{variables.departamento}}",
|
||||
"cases": [
|
||||
{ "value": "ventas", "output": "ventas" },
|
||||
{ "value": "soporte", "output": "soporte" },
|
||||
{ "value": "cobranza", "output": "cobranza" }
|
||||
],
|
||||
"default_output": "default"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### wait_input
|
||||
Espera respuesta del usuario y la guarda.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wait_input",
|
||||
"id": "input_1",
|
||||
"data": {
|
||||
"variable": "nombre_cliente",
|
||||
"timeout_seconds": 300,
|
||||
"timeout_output": "timeout",
|
||||
"validation": {
|
||||
"type": "text",
|
||||
"min_length": 2,
|
||||
"max_length": 100
|
||||
},
|
||||
"error_message": "Por favor ingresa un nombre válido."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### delay
|
||||
Pausa antes de continuar.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "delay",
|
||||
"id": "delay_1",
|
||||
"data": {
|
||||
"seconds": 3,
|
||||
"random_range": [2, 5]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### set_variable
|
||||
Asigna o modifica una variable.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "set_variable",
|
||||
"id": "set_1",
|
||||
"data": {
|
||||
"variable": "contador",
|
||||
"operation": "increment",
|
||||
"value": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Operaciones:**
|
||||
- `set` - Asignar valor
|
||||
- `increment` - Incrementar
|
||||
- `decrement` - Decrementar
|
||||
- `append` - Agregar a lista
|
||||
- `concat` - Concatenar texto
|
||||
|
||||
#### random
|
||||
A/B Testing - bifurca aleatoriamente.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "random",
|
||||
"id": "ab_1",
|
||||
"data": {
|
||||
"test_name": "Test saludo",
|
||||
"variants": [
|
||||
{ "name": "A", "weight": 50, "output": "variant_a" },
|
||||
{ "name": "B", "weight": 50, "output": "variant_b" }
|
||||
],
|
||||
"track_metric": "response_rate"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### loop
|
||||
Repetir bloque.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "loop",
|
||||
"id": "loop_1",
|
||||
"data": {
|
||||
"max_iterations": 3,
|
||||
"condition": {
|
||||
"field": "{{variables.confirmado}}",
|
||||
"operator": "not_equals",
|
||||
"value": "si"
|
||||
},
|
||||
"outputs": ["continue", "exit"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### go_to
|
||||
Salta a otro flujo o nodo.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "go_to",
|
||||
"id": "goto_1",
|
||||
"data": {
|
||||
"target_type": "flow",
|
||||
"target_id": "uuid-del-flujo",
|
||||
"pass_context": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### sub_flow
|
||||
Ejecuta un sub-flujo y retorna.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "sub_flow",
|
||||
"id": "sub_1",
|
||||
"data": {
|
||||
"flow_id": "uuid-del-subflujo",
|
||||
"input_variables": {
|
||||
"producto_id": "{{variables.producto}}"
|
||||
},
|
||||
"output_variable": "resultado_subflujo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### javascript
|
||||
Ejecuta código JavaScript personalizado.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "javascript",
|
||||
"id": "js_1",
|
||||
"data": {
|
||||
"code": "const total = variables.precio * variables.cantidad;\nreturn { total, descuento: total > 1000 ? 0.1 : 0 };",
|
||||
"output_variable": "calculo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nodos de Validación
|
||||
|
||||
#### validate_email
|
||||
Valida formato de email.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "validate_email",
|
||||
"id": "val_1",
|
||||
"data": {
|
||||
"input": "{{message.text}}",
|
||||
"output_variable": "email_valido",
|
||||
"outputs": ["valid", "invalid"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### validate_phone
|
||||
Valida formato de teléfono.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "validate_phone",
|
||||
"id": "val_2",
|
||||
"data": {
|
||||
"input": "{{message.text}}",
|
||||
"country": "MX",
|
||||
"output_variable": "telefono_valido",
|
||||
"outputs": ["valid", "invalid"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### validate_number
|
||||
Valida número en rango.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "validate_number",
|
||||
"id": "val_3",
|
||||
"data": {
|
||||
"input": "{{message.text}}",
|
||||
"min": 1,
|
||||
"max": 100,
|
||||
"output_variable": "cantidad",
|
||||
"outputs": ["valid", "invalid"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### validate_date
|
||||
Valida formato de fecha.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "validate_date",
|
||||
"id": "val_4",
|
||||
"data": {
|
||||
"input": "{{message.text}}",
|
||||
"format": "DD/MM/YYYY",
|
||||
"min_date": "today",
|
||||
"max_date": "+30days",
|
||||
"output_variable": "fecha",
|
||||
"outputs": ["valid", "invalid"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### validate_options
|
||||
Valida contra lista de opciones.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "validate_options",
|
||||
"id": "val_5",
|
||||
"data": {
|
||||
"input": "{{message.text}}",
|
||||
"options": ["1", "2", "3", "uno", "dos", "tres"],
|
||||
"case_insensitive": true,
|
||||
"output_variable": "opcion",
|
||||
"outputs": ["valid", "invalid"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### retry_input
|
||||
Reintenta captura con mensaje de error.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "retry_input",
|
||||
"id": "retry_1",
|
||||
"data": {
|
||||
"max_retries": 3,
|
||||
"error_message": "No entendí tu respuesta. Por favor intenta de nuevo.",
|
||||
"final_fallback": "transfer",
|
||||
"outputs": ["continue", "max_retries"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nodos de Acción
|
||||
|
||||
#### transfer
|
||||
Transfiere a agente humano.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "transfer",
|
||||
"id": "act_1",
|
||||
"data": {
|
||||
"target_type": "queue",
|
||||
"target_id": "uuid-de-cola",
|
||||
"priority": "high",
|
||||
"message": "Te comunico con un agente. Por favor espera."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### close
|
||||
Cierra la conversación.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "close",
|
||||
"id": "act_2",
|
||||
"data": {
|
||||
"resolution": "resolved",
|
||||
"send_csat": true,
|
||||
"message": "¡Gracias por contactarnos! ¿Hay algo más en que pueda ayudarte?"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### tag
|
||||
Agrega o quita etiquetas.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tag",
|
||||
"id": "act_3",
|
||||
"data": {
|
||||
"add": ["lead", "interesado"],
|
||||
"remove": ["nuevo"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### note
|
||||
Agrega nota interna.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "note",
|
||||
"id": "act_4",
|
||||
"data": {
|
||||
"content": "Cliente interesado en {{variables.producto}}. Presupuesto: {{variables.presupuesto}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### notify
|
||||
Envía notificación.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "notify",
|
||||
"id": "act_5",
|
||||
"data": {
|
||||
"type": "email",
|
||||
"recipients": ["admin@empresa.com"],
|
||||
"subject": "Nuevo lead VIP",
|
||||
"message": "El cliente {{contact.name}} está interesado en {{variables.producto}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### webhook
|
||||
Llama API externa.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "webhook",
|
||||
"id": "act_6",
|
||||
"data": {
|
||||
"url": "https://api.example.com/leads",
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {{secrets.api_key}}"
|
||||
},
|
||||
"body": {
|
||||
"name": "{{contact.name}}",
|
||||
"phone": "{{contact.phone}}",
|
||||
"product": "{{variables.producto}}"
|
||||
},
|
||||
"output_variable": "api_response",
|
||||
"retry": {
|
||||
"max_attempts": 3,
|
||||
"delay_seconds": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ai_response
|
||||
Genera respuesta con IA.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ai_response",
|
||||
"id": "act_7",
|
||||
"data": {
|
||||
"provider": "openai",
|
||||
"model": "gpt-4",
|
||||
"system_prompt": "Eres un asistente de ventas amable. Productos: {{odoo.products}}",
|
||||
"include_context": {
|
||||
"last_messages": 10,
|
||||
"contact_info": true,
|
||||
"odoo_data": true
|
||||
},
|
||||
"allowed_actions": ["transfer", "create_lead"],
|
||||
"output_variable": "ai_response",
|
||||
"fallback_on_error": "transfer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nodos de Odoo
|
||||
|
||||
Ver [Integración Odoo](../odoo-integration/README.md) para documentación completa.
|
||||
|
||||
---
|
||||
|
||||
## Variables del Sistema
|
||||
|
||||
```
|
||||
# Contacto
|
||||
{{contact.id}}
|
||||
{{contact.name}}
|
||||
{{contact.phone}}
|
||||
{{contact.email}}
|
||||
{{contact.company}}
|
||||
{{contact.tags}}
|
||||
{{contact.odoo_id}}
|
||||
{{contact.created_at}}
|
||||
{{contact.custom.xxx}}
|
||||
|
||||
# Conversación
|
||||
{{conversation.id}}
|
||||
{{conversation.status}}
|
||||
{{conversation.assigned_to}}
|
||||
{{conversation.queue}}
|
||||
{{conversation.started_at}}
|
||||
|
||||
# Mensaje actual
|
||||
{{message.id}}
|
||||
{{message.text}}
|
||||
{{message.type}}
|
||||
{{message.media_url}}
|
||||
|
||||
# Sistema
|
||||
{{system.date}}
|
||||
{{system.time}}
|
||||
{{system.datetime}}
|
||||
{{system.day_of_week}}
|
||||
{{system.business_hours}}
|
||||
{{system.timestamp}}
|
||||
|
||||
# Agente (si asignado)
|
||||
{{agent.id}}
|
||||
{{agent.name}}
|
||||
{{agent.email}}
|
||||
|
||||
# Odoo (si conectado)
|
||||
{{odoo.partner.id}}
|
||||
{{odoo.partner.name}}
|
||||
{{odoo.partner.email}}
|
||||
{{odoo.partner.balance}}
|
||||
{{odoo.last_order.name}}
|
||||
{{odoo.last_order.total}}
|
||||
{{odoo.last_order.state}}
|
||||
|
||||
# Variables capturadas
|
||||
{{variables.xxx}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estructura de Flujo (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Bienvenida",
|
||||
"description": "Flujo de bienvenida",
|
||||
"trigger_type": "welcome",
|
||||
"trigger_value": null,
|
||||
"nodes": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "trigger_1",
|
||||
"type": "welcome",
|
||||
"position": { "x": 100, "y": 100 },
|
||||
"data": { "label": "Inicio" }
|
||||
},
|
||||
{
|
||||
"id": "msg_1",
|
||||
"type": "text",
|
||||
"position": { "x": 100, "y": 200 },
|
||||
"data": { "content": "¡Hola!" }
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "e1",
|
||||
"source": "trigger_1",
|
||||
"target": "msg_1",
|
||||
"sourceHandle": "default"
|
||||
}
|
||||
],
|
||||
"viewport": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"zoom": 1
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"producto": { "type": "string", "default": "" },
|
||||
"cantidad": { "type": "number", "default": 1 }
|
||||
},
|
||||
"is_active": true,
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mejores Prácticas
|
||||
|
||||
1. **Nombra los nodos claramente** para fácil identificación
|
||||
2. **Usa sub-flujos** para lógica repetida (validaciones, etc.)
|
||||
3. **Siempre incluye fallback** para respuestas no entendidas
|
||||
4. **Valida inputs** antes de usarlos en acciones
|
||||
5. **Limita reintentos** para evitar loops infinitos
|
||||
6. **Usa A/B testing** para optimizar mensajes
|
||||
7. **Transfiere a humano** cuando el bot no puede resolver
|
||||
8. **Guarda contexto** para conversaciones que se retoman después
|
||||
572
docs/odoo-integration/README.md
Normal file
572
docs/odoo-integration/README.md
Normal file
@@ -0,0 +1,572 @@
|
||||
# Integración con Odoo
|
||||
|
||||
## Visión General
|
||||
|
||||
La integración con Odoo es bidireccional:
|
||||
- **WhatsApp → Odoo**: Crear leads, consultar pedidos, verificar stock, etc.
|
||||
- **Odoo → WhatsApp**: Notificaciones automáticas de eventos (pedido enviado, factura vencida, etc.)
|
||||
|
||||
## Configuración
|
||||
|
||||
### 1. Crear Usuario API en Odoo
|
||||
|
||||
1. En Odoo, ir a **Configuración → Usuarios y Compañías → Usuarios**
|
||||
2. Crear nuevo usuario:
|
||||
- Nombre: `API WhatsApp`
|
||||
- Email: `api-whatsapp@tuempresa.com`
|
||||
- Tipo de usuario: `Usuario interno`
|
||||
3. Asignar permisos según módulos a usar:
|
||||
- CRM: Usuario
|
||||
- Ventas: Usuario
|
||||
- Inventario: Usuario (lectura)
|
||||
- Helpdesk: Usuario
|
||||
- Facturación: Contable (lectura)
|
||||
4. Generar API Key:
|
||||
- Ir a Preferencias del usuario
|
||||
- En "Claves API", crear nueva clave
|
||||
- Guardar la clave de forma segura
|
||||
|
||||
### 2. Configurar en WhatsApp Central
|
||||
|
||||
```bash
|
||||
# .env
|
||||
ODOO_URL=https://odoo.tuempresa.com
|
||||
ODOO_DB=production
|
||||
ODOO_USER=api-whatsapp@tuempresa.com
|
||||
ODOO_API_KEY=tu_api_key_aqui
|
||||
```
|
||||
|
||||
### 3. Probar Conexión
|
||||
|
||||
```http
|
||||
POST /api/odoo/test-connection
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Módulos Soportados
|
||||
|
||||
### Contactos (res.partner)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Buscar | `odoo_search_partner` | Buscar por teléfono, email o nombre |
|
||||
| Crear | `odoo_create_partner` | Crear nuevo contacto |
|
||||
| Actualizar | `odoo_update_partner` | Modificar datos |
|
||||
| Sincronizar | Automático | Sync bidireccional |
|
||||
|
||||
**Ejemplo: Buscar cliente**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_search_partner",
|
||||
"data": {
|
||||
"search_by": "phone",
|
||||
"value": "{{contact.phone}}",
|
||||
"output_variable": "odoo_partner"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{odoo_partner.id}}
|
||||
{{odoo_partner.name}}
|
||||
{{odoo_partner.email}}
|
||||
{{odoo_partner.phone}}
|
||||
{{odoo_partner.street}}
|
||||
{{odoo_partner.city}}
|
||||
{{odoo_partner.credit}} // Saldo a favor
|
||||
{{odoo_partner.debit}} // Deuda
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CRM (crm.lead)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Crear lead | `odoo_create_lead` | Nueva oportunidad |
|
||||
| Actualizar etapa | `odoo_update_lead_stage` | Mover en pipeline |
|
||||
| Agregar nota | `odoo_add_lead_note` | Agregar actividad/nota |
|
||||
|
||||
**Ejemplo: Crear lead**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_create_lead",
|
||||
"data": {
|
||||
"name": "Interés en {{variables.producto}}",
|
||||
"contact_name": "{{contact.name}}",
|
||||
"phone": "{{contact.phone}}",
|
||||
"email": "{{variables.email}}",
|
||||
"description": "Cliente contactó por WhatsApp.\nProducto: {{variables.producto}}\nPresupuesto: {{variables.presupuesto}}",
|
||||
"expected_revenue": "{{variables.presupuesto}}",
|
||||
"team_id": 1,
|
||||
"user_id": 5,
|
||||
"output_variable": "lead"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo: Mover etapa**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_update_lead_stage",
|
||||
"data": {
|
||||
"lead_id": "{{lead.id}}",
|
||||
"stage": "qualified"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Etapas disponibles:
|
||||
- `new` - Nuevo
|
||||
- `qualified` - Calificado
|
||||
- `proposition` - Propuesta
|
||||
- `negotiation` - Negociación
|
||||
- `won` - Ganado
|
||||
- `lost` - Perdido
|
||||
|
||||
---
|
||||
|
||||
### Ventas (sale.order)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Buscar pedidos | `odoo_search_orders` | Pedidos del cliente |
|
||||
| Crear cotización | `odoo_create_quotation` | Nueva cotización |
|
||||
| Confirmar pedido | `odoo_confirm_order` | Cotización → Pedido |
|
||||
| Enviar PDF | `odoo_send_quotation_pdf` | Enviar por WhatsApp |
|
||||
|
||||
**Ejemplo: Buscar pedidos**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_search_orders",
|
||||
"data": {
|
||||
"partner_id": "{{odoo_partner.id}}",
|
||||
"states": ["sale", "done"],
|
||||
"limit": 5,
|
||||
"output_variable": "orders"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{orders}} // Array de pedidos
|
||||
{{orders[0].id}}
|
||||
{{orders[0].name}} // SO-2024-001
|
||||
{{orders[0].state}}
|
||||
{{orders[0].amount_total}}
|
||||
{{orders[0].date_order}}
|
||||
{{orders[0].commitment_date}} // Fecha compromiso
|
||||
```
|
||||
|
||||
**Ejemplo: Crear cotización**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_create_quotation",
|
||||
"data": {
|
||||
"partner_id": "{{odoo_partner.id}}",
|
||||
"lines": [
|
||||
{
|
||||
"product_id": "{{variables.producto_id}}",
|
||||
"quantity": "{{variables.cantidad}}"
|
||||
}
|
||||
],
|
||||
"validity_days": 30,
|
||||
"output_variable": "quotation"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo: Enviar PDF**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_send_quotation_pdf",
|
||||
"data": {
|
||||
"order_id": "{{quotation.id}}",
|
||||
"message": "Aquí está tu cotización {{quotation.name}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Inventario (stock)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Verificar stock | `odoo_check_stock` | Disponibilidad |
|
||||
| Estado envío | `odoo_tracking_status` | Tracking de pedido |
|
||||
|
||||
**Ejemplo: Verificar stock**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_check_stock",
|
||||
"data": {
|
||||
"search_by": "name",
|
||||
"value": "{{variables.producto}}",
|
||||
"location_id": 8,
|
||||
"output_variable": "stock"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{stock.product_id}}
|
||||
{{stock.product_name}}
|
||||
{{stock.qty_available}} // Stock disponible
|
||||
{{stock.virtual_available}} // Stock proyectado
|
||||
{{stock.incoming_qty}} // Por recibir
|
||||
{{stock.outgoing_qty}} // Por enviar
|
||||
```
|
||||
|
||||
**Ejemplo: Estado de envío**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_tracking_status",
|
||||
"data": {
|
||||
"order_id": "{{order.id}}",
|
||||
"output_variable": "tracking"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{tracking.state}} // assigned, done, cancel
|
||||
{{tracking.carrier}} // Nombre paquetería
|
||||
{{tracking.tracking_ref}} // Número de guía
|
||||
{{tracking.tracking_url}} // URL de rastreo
|
||||
{{tracking.scheduled_date}}
|
||||
{{tracking.date_done}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Helpdesk (helpdesk.ticket)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Crear ticket | `odoo_create_ticket` | Nuevo ticket soporte |
|
||||
| Estado ticket | `odoo_ticket_status` | Consultar estado |
|
||||
|
||||
**Ejemplo: Crear ticket**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_create_ticket",
|
||||
"data": {
|
||||
"name": "{{variables.asunto}}",
|
||||
"description": "Reporte desde WhatsApp:\n\n{{variables.descripcion}}\n\n---\nConversación: {{conversation.id}}",
|
||||
"partner_id": "{{odoo_partner.id}}",
|
||||
"team_id": 1,
|
||||
"priority": "2",
|
||||
"attach_conversation": true,
|
||||
"output_variable": "ticket"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{ticket.id}}
|
||||
{{ticket.number}} // TICKET-001
|
||||
{{ticket.name}}
|
||||
{{ticket.stage}} // Nombre de etapa
|
||||
{{ticket.user_id}} // Asignado a
|
||||
{{ticket.priority}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Facturación (account.move)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Saldo cliente | `odoo_check_balance` | Deuda/crédito |
|
||||
| Enviar factura | `odoo_send_invoice_pdf` | PDF por WhatsApp |
|
||||
|
||||
**Ejemplo: Verificar saldo**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_check_balance",
|
||||
"data": {
|
||||
"partner_id": "{{odoo_partner.id}}",
|
||||
"output_variable": "balance"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{balance.total_due}} // Total adeudado
|
||||
{{balance.total_overdue}} // Vencido
|
||||
{{balance.credit}} // A favor
|
||||
{{balance.last_payment_date}}
|
||||
{{balance.last_payment_amount}}
|
||||
{{balance.invoices}} // Lista de facturas pendientes
|
||||
```
|
||||
|
||||
**Ejemplo: Enviar factura**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_send_invoice_pdf",
|
||||
"data": {
|
||||
"invoice_id": "{{variables.invoice_id}}",
|
||||
"message": "Aquí está tu factura. Total: ${{invoice.amount_total}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Productos (product)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Buscar productos | `odoo_search_products` | Catálogo |
|
||||
| Detalles producto | `odoo_product_details` | Info completa |
|
||||
|
||||
**Ejemplo: Buscar productos**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_search_products",
|
||||
"data": {
|
||||
"search": "{{message.text}}",
|
||||
"category_id": null,
|
||||
"available_only": true,
|
||||
"limit": 5,
|
||||
"output_variable": "products"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{products}} // Array
|
||||
{{products[0].id}}
|
||||
{{products[0].name}}
|
||||
{{products[0].list_price}}
|
||||
{{products[0].qty_available}}
|
||||
{{products[0].image_url}}
|
||||
{{products[0].description}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Calendario (calendar.event)
|
||||
|
||||
| Acción | Nodo | Descripción |
|
||||
|--------|------|-------------|
|
||||
| Disponibilidad | `odoo_check_availability` | Horarios libres |
|
||||
| Crear cita | `odoo_create_appointment` | Agendar evento |
|
||||
|
||||
**Ejemplo: Verificar disponibilidad**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_check_availability",
|
||||
"data": {
|
||||
"user_id": 5,
|
||||
"date_from": "{{system.date}}",
|
||||
"date_to": "{{system.date}}+7days",
|
||||
"duration_hours": 1,
|
||||
"output_variable": "slots"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables resultantes:**
|
||||
```
|
||||
{{slots}} // Array de horarios
|
||||
{{slots[0].date}}
|
||||
{{slots[0].start_time}}
|
||||
{{slots[0].end_time}}
|
||||
```
|
||||
|
||||
**Ejemplo: Crear cita**
|
||||
```json
|
||||
{
|
||||
"type": "odoo_create_appointment",
|
||||
"data": {
|
||||
"name": "Cita con {{contact.name}}",
|
||||
"start": "{{variables.fecha}} {{variables.hora}}",
|
||||
"duration": 1,
|
||||
"partner_id": "{{odoo_partner.id}}",
|
||||
"user_id": 5,
|
||||
"description": "Cita agendada desde WhatsApp",
|
||||
"send_invitation": true,
|
||||
"output_variable": "appointment"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automatizaciones Odoo → WhatsApp
|
||||
|
||||
### Configuración de Webhooks
|
||||
|
||||
1. Instalar módulo de webhooks en Odoo o usar Automated Actions
|
||||
2. Configurar URL de webhook:
|
||||
```
|
||||
https://chat.tuempresa.com/api/odoo/webhook
|
||||
```
|
||||
3. Configurar secret para validación
|
||||
|
||||
### Eventos Disponibles
|
||||
|
||||
#### Pedido Confirmado
|
||||
```json
|
||||
{
|
||||
"event": "sale.order.confirmed",
|
||||
"trigger": "state changed to 'sale'",
|
||||
"template": {
|
||||
"message": "✅ ¡Tu pedido ha sido confirmado!\n\nPedido: {{order.name}}\nTotal: ${{order.amount_total}}\n\nTe notificaremos cuando sea enviado.",
|
||||
"attach_pdf": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Pedido Enviado
|
||||
```json
|
||||
{
|
||||
"event": "stock.picking.done",
|
||||
"trigger": "state changed to 'done'",
|
||||
"template": {
|
||||
"message": "🚚 ¡Tu pedido está en camino!\n\nNúmero de guía: {{picking.tracking_ref}}\nPaquetería: {{picking.carrier}}\n\nRastrear: {{picking.tracking_url}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Pago Recibido
|
||||
```json
|
||||
{
|
||||
"event": "account.payment.posted",
|
||||
"trigger": "state changed to 'posted'",
|
||||
"template": {
|
||||
"message": "💰 ¡Gracias por tu pago!\n\nMonto: ${{payment.amount}}\nReferencia: {{payment.name}}\n\nTu saldo actual es: ${{partner.balance}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Factura Vencida
|
||||
```json
|
||||
{
|
||||
"event": "account.move.overdue",
|
||||
"trigger": "invoice_date_due < today AND state = 'posted'",
|
||||
"delay": "3 days",
|
||||
"template": {
|
||||
"message": "⚠️ Recordatorio de pago\n\nFactura: {{invoice.name}}\nMonto: ${{invoice.amount_residual}}\nVencimiento: {{invoice.invoice_date_due}}\n\n¿Necesitas ayuda con el pago?"
|
||||
},
|
||||
"retry": {
|
||||
"interval_days": 7,
|
||||
"max_attempts": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Recordatorio de Cita
|
||||
```json
|
||||
{
|
||||
"event": "calendar.event.reminder",
|
||||
"trigger": "start - 24 hours",
|
||||
"template": {
|
||||
"message": "📅 Recordatorio: Tienes una cita mañana\n\n📍 {{event.location}}\n🕐 {{event.start_time}}\n\n¿Confirmas tu asistencia?",
|
||||
"buttons": [
|
||||
{"id": "confirm", "text": "Confirmar"},
|
||||
{"id": "reschedule", "text": "Reagendar"},
|
||||
{"id": "cancel", "text": "Cancelar"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ticket Resuelto
|
||||
```json
|
||||
{
|
||||
"event": "helpdesk.ticket.closed",
|
||||
"trigger": "stage.is_close = true",
|
||||
"template": {
|
||||
"message": "✅ Tu ticket #{{ticket.number}} ha sido resuelto.\n\n¿Cómo calificarías la atención recibida?",
|
||||
"send_csat": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sincronización de Contactos
|
||||
|
||||
### WhatsApp → Odoo
|
||||
- Al recibir mensaje de número nuevo, buscar en Odoo
|
||||
- Si no existe, opcionalmente crear partner
|
||||
- Vincular `contact.odoo_partner_id`
|
||||
|
||||
### Odoo → WhatsApp
|
||||
- Importar partners con campo `mobile` lleno
|
||||
- Filtrar por categoría/etiqueta si se desea
|
||||
- Ejecutar sync programado o manual
|
||||
|
||||
### Mapeo de Campos
|
||||
|
||||
| WhatsApp | Odoo |
|
||||
|----------|------|
|
||||
| phone_number | mobile |
|
||||
| name | name |
|
||||
| email | email |
|
||||
| company | parent_id.name |
|
||||
| tags | category_id |
|
||||
| custom.rfc | vat |
|
||||
| custom.direccion | street + city |
|
||||
|
||||
---
|
||||
|
||||
## Módulo Odoo
|
||||
|
||||
### Instalación
|
||||
|
||||
```bash
|
||||
# Copiar módulo a addons
|
||||
cp -r odoo_whatsapp_hub /odoo/addons/
|
||||
|
||||
# Actualizar lista de apps
|
||||
# En Odoo: Apps → Actualizar lista de aplicaciones
|
||||
|
||||
# Instalar módulo
|
||||
# Buscar "WhatsApp Hub" e instalar
|
||||
```
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
1. **Tab WhatsApp en Partner**
|
||||
- Ver conversaciones del cliente
|
||||
- Historial de mensajes
|
||||
- Enviar mensaje rápido
|
||||
|
||||
2. **Widget de Chat**
|
||||
- Chat en tiempo real desde Odoo
|
||||
- Ver estado de conversación
|
||||
- Transferir a agente
|
||||
|
||||
3. **Acciones Automáticas**
|
||||
- Enviar WhatsApp al confirmar pedido
|
||||
- Notificar cambios de estado
|
||||
- Recordatorios automáticos
|
||||
|
||||
4. **Envío Masivo**
|
||||
- Seleccionar partners
|
||||
- Usar templates
|
||||
- Programar envío
|
||||
|
||||
---
|
||||
|
||||
## Mejores Prácticas
|
||||
|
||||
1. **Usa cache** para consultas frecuentes (productos, stock)
|
||||
2. **Limita llamadas** a Odoo para no saturar
|
||||
3. **Maneja errores** con fallback a agente humano
|
||||
4. **Valida permisos** del usuario API
|
||||
5. **Loguea transacciones** para auditoría
|
||||
6. **Sincroniza en horarios** de baja demanda
|
||||
706
docs/plans/2026-01-29-whatsapp-centralizado-design.md
Normal file
706
docs/plans/2026-01-29-whatsapp-centralizado-design.md
Normal file
@@ -0,0 +1,706 @@
|
||||
# WhatsApp Centralizado - Documento de Diseño
|
||||
|
||||
**Fecha:** 2026-01-29
|
||||
**Versión:** 1.0
|
||||
**Estado:** Aprobado para implementación
|
||||
|
||||
---
|
||||
|
||||
## 1. Resumen Ejecutivo
|
||||
|
||||
Plataforma de mensajería centralizada con automatización de chatbots, gestión multi-agente e integración profunda con Odoo. Similar a Kommo, Wasapi, ManyChat y Brevo pero con enfoque en integración empresarial.
|
||||
|
||||
### Decisiones Clave
|
||||
|
||||
| Aspecto | Decisión |
|
||||
|---------|----------|
|
||||
| **Enfoque** | Chatbots + Multi-agente + Multi-canal |
|
||||
| **Canal inicial** | WhatsApp (Baileys) |
|
||||
| **Stack backend** | Node.js (WhatsApp) + Python/FastAPI (API) |
|
||||
| **Stack frontend** | React + TypeScript |
|
||||
| **Base de datos** | PostgreSQL + Redis |
|
||||
| **Editor chatbot** | Visual drag & drop (React Flow) |
|
||||
| **Modelo** | Single-tenant |
|
||||
| **Despliegue** | Docker Compose en servidores propios |
|
||||
| **Integración principal** | Odoo (bidireccional) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Arquitectura General
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ FRONTEND (React) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Dashboard │ │ Inbox Chat │ │ Flow Builder (React │ │
|
||||
│ │ │ │ (Agentes) │ │ Flow) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ API GATEWAY (Python/FastAPI) │
|
||||
│ • Autenticación JWT • REST API • WebSocket para tiempo real │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────────┐ ┌──────────────┐ ┌──────────────────────────┐
|
||||
│ WHATSAPP CORE │ │ FLOW ENGINE │ │ INTEGRATION SERVICE │
|
||||
│ (Node.js + │ │ (Python) │ │ (Python) │
|
||||
│ Baileys) │ │ Motor de │ │ • Webhooks │
|
||||
│ • Multi-número │ │ chatbot │ │ • Odoo XML-RPC │
|
||||
│ • Sesiones │ │ │ │ • APIs externas │
|
||||
└──────────────────┘ └──────────────┘ └──────────────────────────┘
|
||||
│ │ │
|
||||
└───────────────┼───────────────┘
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ PostgreSQL+Redis │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Estructura del Proyecto
|
||||
|
||||
```
|
||||
WhatsAppCentralizado/
|
||||
├── docker-compose.yml
|
||||
├── .env.example
|
||||
│
|
||||
├── services/
|
||||
│ ├── whatsapp-core/ # Node.js + Baileys
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── package.json
|
||||
│ │ └── src/
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── sessions/ # Gestión de sesiones WhatsApp
|
||||
│ │ ├── handlers/ # Procesamiento de mensajes
|
||||
│ │ └── api/ # API interna
|
||||
│ │
|
||||
│ ├── api-gateway/ # Python + FastAPI
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── requirements.txt
|
||||
│ │ └── app/
|
||||
│ │ ├── main.py
|
||||
│ │ ├── routers/
|
||||
│ │ ├── models/
|
||||
│ │ ├── schemas/
|
||||
│ │ └── services/
|
||||
│ │
|
||||
│ ├── flow-engine/ # Python - Motor de chatbot
|
||||
│ │ ├── Dockerfile
|
||||
│ │ └── app/
|
||||
│ │ ├── engine.py
|
||||
│ │ ├── nodes/
|
||||
│ │ └── context.py
|
||||
│ │
|
||||
│ └── integrations/ # Python - Integraciones
|
||||
│ ├── Dockerfile
|
||||
│ └── app/
|
||||
│ ├── odoo.py
|
||||
│ └── webhooks.py
|
||||
│
|
||||
├── frontend/ # React + TypeScript
|
||||
│ ├── Dockerfile
|
||||
│ ├── package.json
|
||||
│ └── src/
|
||||
│ ├── pages/
|
||||
│ │ ├── Dashboard/
|
||||
│ │ ├── Inbox/
|
||||
│ │ └── FlowBuilder/
|
||||
│ ├── components/
|
||||
│ └── hooks/
|
||||
│
|
||||
└── database/
|
||||
└── migrations/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Modelo de Datos
|
||||
|
||||
### 4.1 Tablas Principales
|
||||
|
||||
```sql
|
||||
-- Usuarios y Autenticación
|
||||
users
|
||||
├── id (UUID, PK)
|
||||
├── email (UNIQUE)
|
||||
├── password_hash
|
||||
├── name
|
||||
├── role (admin, supervisor, agent)
|
||||
├── status (online, away, busy, offline)
|
||||
├── is_active
|
||||
├── created_at
|
||||
└── updated_at
|
||||
|
||||
-- Números de WhatsApp conectados
|
||||
whatsapp_accounts
|
||||
├── id (UUID, PK)
|
||||
├── phone_number
|
||||
├── name (alias)
|
||||
├── status (connected, disconnected, banned)
|
||||
├── session_data (JSONB)
|
||||
├── qr_code (TEXT)
|
||||
└── created_at
|
||||
|
||||
-- Contactos
|
||||
contacts
|
||||
├── id (UUID, PK)
|
||||
├── phone_number (UNIQUE)
|
||||
├── name
|
||||
├── email
|
||||
├── company
|
||||
├── metadata (JSONB)
|
||||
├── tags (ARRAY)
|
||||
├── odoo_partner_id (INT)
|
||||
└── created_at
|
||||
|
||||
-- Colas de atención
|
||||
queues
|
||||
├── id (UUID, PK)
|
||||
├── name
|
||||
├── description
|
||||
├── assignment_method (round_robin, least_busy, skill_based)
|
||||
├── max_per_agent (INT)
|
||||
├── sla_first_response (INT, segundos)
|
||||
├── sla_resolution (INT, segundos)
|
||||
├── business_hours (JSONB)
|
||||
├── fallback_flow_id (FK)
|
||||
└── is_active
|
||||
|
||||
-- Agentes en colas
|
||||
queue_agents
|
||||
├── id (UUID, PK)
|
||||
├── queue_id (FK)
|
||||
├── user_id (FK)
|
||||
├── is_supervisor
|
||||
└── skills (ARRAY)
|
||||
|
||||
-- Conversaciones
|
||||
conversations
|
||||
├── id (UUID, PK)
|
||||
├── whatsapp_account_id (FK)
|
||||
├── contact_id (FK)
|
||||
├── queue_id (FK, nullable)
|
||||
├── assigned_to (FK → users, nullable)
|
||||
├── status (bot, waiting, active, resolved)
|
||||
├── priority (low, normal, high, urgent)
|
||||
├── current_flow_id (FK, nullable)
|
||||
├── flow_context (JSONB)
|
||||
├── sla_first_response_at (TIMESTAMP)
|
||||
├── sla_first_response_met (BOOLEAN)
|
||||
├── resolved_at (TIMESTAMP)
|
||||
├── csat_score (INT, 1-5)
|
||||
├── csat_feedback (TEXT)
|
||||
├── last_message_at
|
||||
└── created_at
|
||||
|
||||
-- Mensajes
|
||||
messages
|
||||
├── id (UUID, PK)
|
||||
├── conversation_id (FK)
|
||||
├── direction (inbound, outbound)
|
||||
├── type (text, image, audio, video, document, buttons, list, location, contact)
|
||||
├── content (TEXT)
|
||||
├── media_url
|
||||
├── metadata (JSONB)
|
||||
├── sent_by (FK → users, nullable)
|
||||
├── is_internal_note (BOOLEAN)
|
||||
├── status (pending, sent, delivered, read, failed)
|
||||
└── created_at
|
||||
|
||||
-- Flujos de chatbot
|
||||
flows
|
||||
├── id (UUID, PK)
|
||||
├── name
|
||||
├── description
|
||||
├── trigger_type (keyword, welcome, fallback, manual, event)
|
||||
├── trigger_value
|
||||
├── nodes (JSONB)
|
||||
├── variables (JSONB)
|
||||
├── is_active
|
||||
├── version (INT)
|
||||
└── created_at
|
||||
|
||||
-- Templates de mensajes
|
||||
message_templates
|
||||
├── id (UUID, PK)
|
||||
├── name
|
||||
├── content (TEXT)
|
||||
├── attachments (JSONB)
|
||||
├── variables (ARRAY)
|
||||
└── created_at
|
||||
|
||||
-- Respuestas rápidas
|
||||
quick_replies
|
||||
├── id (UUID, PK)
|
||||
├── shortcut
|
||||
├── content
|
||||
├── attachments (JSONB)
|
||||
├── queue_id (FK, nullable)
|
||||
├── created_by (FK)
|
||||
└── created_at
|
||||
|
||||
-- Etiquetas
|
||||
tags
|
||||
├── id (UUID, PK)
|
||||
├── name
|
||||
├── color
|
||||
└── created_at
|
||||
|
||||
-- Configuración Odoo
|
||||
odoo_config
|
||||
├── id (UUID, PK)
|
||||
├── url
|
||||
├── database
|
||||
├── username
|
||||
├── api_key_encrypted
|
||||
├── is_active
|
||||
├── last_sync_at
|
||||
└── created_at
|
||||
|
||||
-- Automatizaciones Odoo → WhatsApp
|
||||
odoo_automations
|
||||
├── id (UUID, PK)
|
||||
├── name
|
||||
├── odoo_model (sale.order, stock.picking, etc.)
|
||||
├── odoo_trigger (state change, field change)
|
||||
├── odoo_condition (JSONB)
|
||||
├── message_template_id (FK)
|
||||
├── is_active
|
||||
└── created_at
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Flow Builder
|
||||
|
||||
### 5.1 Tipos de Nodos
|
||||
|
||||
#### Nodos de Trigger
|
||||
- `welcome` - Primer mensaje del contacto
|
||||
- `keyword` - Palabras clave específicas
|
||||
- `fallback` - Cuando ningún otro flujo coincide
|
||||
- `event` - Evento externo (webhook, Odoo)
|
||||
- `schedule` - Horario programado
|
||||
|
||||
#### Nodos de Mensaje
|
||||
- `text` - Texto con variables y formateo
|
||||
- `image` - Imagen con caption
|
||||
- `video` - Video con caption
|
||||
- `audio` - Audio/nota de voz
|
||||
- `document` - PDF, Excel, Word
|
||||
- `location` - Ubicación
|
||||
- `contact` - Tarjeta de contacto
|
||||
- `sticker` - Stickers
|
||||
- `buttons` - Hasta 3 botones
|
||||
- `list` - Lista con secciones (hasta 10 opciones)
|
||||
- `template` - Mensaje reutilizable
|
||||
- `carousel` - Múltiples tarjetas
|
||||
|
||||
#### Nodos de Lógica
|
||||
- `condition` - Condiciones múltiples (AND/OR/NOT)
|
||||
- `switch` - Múltiples ramas según valor
|
||||
- `wait_input` - Esperar respuesta con timeout
|
||||
- `wait_event` - Esperar evento externo
|
||||
- `delay` - Delay fijo o aleatorio
|
||||
- `schedule` - Ejecutar en horario específico
|
||||
- `random` - A/B testing
|
||||
- `loop` - Repetir bloque
|
||||
- `set_variable` - Asignar/modificar variables
|
||||
- `javascript` - Código custom
|
||||
- `go_to` - Saltar a otro flujo/nodo
|
||||
- `sub_flow` - Ejecutar sub-flujo
|
||||
|
||||
#### Nodos de Validación
|
||||
- `validate_email` - Formato email
|
||||
- `validate_phone` - Formato teléfono
|
||||
- `validate_number` - Rango numérico
|
||||
- `validate_date` - Fecha/hora
|
||||
- `validate_regex` - Expresión regular
|
||||
- `validate_options` - Lista de opciones
|
||||
- `retry_input` - Reintentar con mensaje error
|
||||
|
||||
#### Nodos de Acción
|
||||
- `transfer` - Transferir a agente/cola
|
||||
- `close` - Cerrar conversación
|
||||
- `tag` - Agregar/quitar etiquetas
|
||||
- `note` - Nota interna
|
||||
- `assign` - Asignar a agente
|
||||
- `notify` - Notificar a agente/admin
|
||||
- `webhook` - HTTP request
|
||||
- `odoo_action` - Acciones Odoo
|
||||
- `email` - Enviar email
|
||||
- `sms` - Enviar SMS (futuro)
|
||||
- `ai_response` - Respuesta con IA
|
||||
- `human_takeover` - Pausar bot
|
||||
|
||||
### 5.2 Sistema de Variables
|
||||
|
||||
```
|
||||
{{contact.name}} - Nombre del contacto
|
||||
{{contact.phone}} - Teléfono
|
||||
{{contact.email}} - Email
|
||||
{{contact.tags}} - Etiquetas
|
||||
{{contact.odoo_id}} - ID en Odoo
|
||||
{{contact.custom.*}} - Campos personalizados
|
||||
|
||||
{{conversation.id}} - ID conversación
|
||||
{{conversation.channel}} - Canal
|
||||
{{conversation.agent}} - Agente asignado
|
||||
{{conversation.status}} - Estado actual
|
||||
|
||||
{{message.text}} - Texto del último mensaje
|
||||
{{message.type}} - Tipo de mensaje
|
||||
|
||||
{{system.date}} - Fecha actual
|
||||
{{system.time}} - Hora actual
|
||||
{{system.day_of_week}} - Día de la semana
|
||||
{{system.business_hours}} - Si está en horario
|
||||
|
||||
{{odoo.partner.*}} - Datos del partner
|
||||
{{odoo.last_order.*}} - Último pedido
|
||||
{{odoo.balance}} - Saldo del cliente
|
||||
```
|
||||
|
||||
### 5.3 A/B Testing
|
||||
|
||||
- Distribución configurable entre variantes
|
||||
- Métricas: tasa de respuesta, conversión, CSAT
|
||||
- Auto-selección de ganador
|
||||
- Duración por tiempo o número de contactos
|
||||
|
||||
---
|
||||
|
||||
## 6. Inbox y Gestión de Agentes
|
||||
|
||||
### 6.1 Sistema de Colas
|
||||
|
||||
- **Round-robin**: Distribuir equitativamente
|
||||
- **Least-busy**: Al agente con menos conversaciones
|
||||
- **Skill-based**: Según habilidades del agente
|
||||
- **Sticky**: Mismo agente si contacto fue atendido antes
|
||||
- **Priority**: VIPs primero, luego por tiempo
|
||||
|
||||
### 6.2 Configuración de Cola
|
||||
|
||||
- Nombre y descripción
|
||||
- Método de asignación
|
||||
- Máximo conversaciones por agente
|
||||
- Re-asignación por timeout
|
||||
- Horario de atención
|
||||
- Flujo fuera de horario
|
||||
- SLA primera respuesta
|
||||
- SLA resolución
|
||||
|
||||
### 6.3 Estados de Agente
|
||||
|
||||
- 🟢 **Online** - Recibe nuevas conversaciones
|
||||
- 🟡 **Ausente** - No recibe nuevas, mantiene activas
|
||||
- 🔴 **Ocupado** - Atendiendo urgente
|
||||
- 🍽️ **Almuerzo** - Pausa temporal
|
||||
- 📞 **En llamada** - No recibe nuevas
|
||||
- ⚫ **Offline** - Desconectado
|
||||
|
||||
### 6.4 Panel de Supervisor
|
||||
|
||||
- Estado de todos los agentes
|
||||
- Conversaciones en cola
|
||||
- SLA en tiempo real
|
||||
- Conversaciones críticas (alerta)
|
||||
- Métricas por agente
|
||||
- Re-asignación manual
|
||||
- Actividad en tiempo real
|
||||
|
||||
### 6.5 CSAT
|
||||
|
||||
- Encuesta después de resolución
|
||||
- Escala 1-5 con emojis
|
||||
- Pregunta de feedback si negativo
|
||||
- Notificación a supervisor
|
||||
- Métricas por agente/cola
|
||||
|
||||
---
|
||||
|
||||
## 7. Integración Odoo
|
||||
|
||||
### 7.1 Módulos Soportados
|
||||
|
||||
| Módulo | Modelo | Acciones |
|
||||
|--------|--------|----------|
|
||||
| Contactos | res.partner | Buscar, crear, actualizar, sincronizar |
|
||||
| CRM | crm.lead | Crear lead, actualizar etapa, agregar nota |
|
||||
| Ventas | sale.order | Consultar, crear cotización, confirmar, enviar PDF |
|
||||
| Inventario | stock.* | Consultar stock, disponibilidad |
|
||||
| Helpdesk | helpdesk.ticket | Crear ticket, estado, asignar |
|
||||
| Facturación | account.move | Consultar, enviar PDF, estado pago |
|
||||
| Calendario | calendar.event | Disponibilidad, crear cita |
|
||||
| Productos | product.* | Buscar, detalles, precio |
|
||||
| Envíos | stock.picking | Estado, tracking |
|
||||
|
||||
### 7.2 Nodos Odoo en Flow Builder
|
||||
|
||||
**Contactos:**
|
||||
- `odoo_search_partner` - Buscar cliente
|
||||
- `odoo_create_partner` - Crear cliente
|
||||
- `odoo_update_partner` - Actualizar cliente
|
||||
|
||||
**CRM:**
|
||||
- `odoo_create_lead` - Crear oportunidad
|
||||
- `odoo_update_lead_stage` - Mover en pipeline
|
||||
- `odoo_add_lead_note` - Agregar nota
|
||||
|
||||
**Ventas:**
|
||||
- `odoo_search_orders` - Buscar pedidos
|
||||
- `odoo_create_quotation` - Crear cotización
|
||||
- `odoo_confirm_order` - Confirmar pedido
|
||||
- `odoo_send_quotation_pdf` - Enviar PDF
|
||||
|
||||
**Inventario:**
|
||||
- `odoo_check_stock` - Consultar disponibilidad
|
||||
- `odoo_tracking_status` - Estado de envío
|
||||
|
||||
**Helpdesk:**
|
||||
- `odoo_create_ticket` - Crear ticket
|
||||
- `odoo_ticket_status` - Estado de ticket
|
||||
|
||||
**Productos:**
|
||||
- `odoo_search_products` - Buscar productos
|
||||
- `odoo_product_details` - Información detallada
|
||||
|
||||
**Facturación:**
|
||||
- `odoo_check_balance` - Saldo del cliente
|
||||
- `odoo_send_invoice_pdf` - Enviar factura
|
||||
|
||||
**Calendario:**
|
||||
- `odoo_check_availability` - Disponibilidad
|
||||
- `odoo_create_appointment` - Crear cita
|
||||
|
||||
### 7.3 Automatizaciones Odoo → WhatsApp
|
||||
|
||||
- Pedido confirmado → Mensaje de confirmación
|
||||
- Pedido enviado → Mensaje con tracking
|
||||
- Pago recibido → Agradecimiento
|
||||
- Factura vencida → Recordatorio
|
||||
- Recordatorio de cita → 24h antes
|
||||
- Ticket resuelto → Encuesta satisfacción
|
||||
|
||||
### 7.4 Módulo Odoo
|
||||
|
||||
```
|
||||
odoo_whatsapp_hub/
|
||||
├── __manifest__.py
|
||||
├── models/
|
||||
│ ├── res_partner.py # Campos WhatsApp en partner
|
||||
│ ├── whatsapp_account.py
|
||||
│ ├── whatsapp_conversation.py
|
||||
│ └── whatsapp_message.py
|
||||
├── controllers/
|
||||
│ ├── webhook.py # Eventos de WhatsApp Central
|
||||
│ └── api.py
|
||||
├── wizards/
|
||||
│ ├── send_whatsapp.py # Enviar mensaje
|
||||
│ └── mass_whatsapp.py # Envío masivo
|
||||
├── views/
|
||||
│ ├── res_partner_views.xml # Tab WhatsApp
|
||||
│ └── whatsapp_*.xml
|
||||
├── static/src/
|
||||
│ ├── js/chat_widget.js # Widget de chat
|
||||
│ └── css/whatsapp.css
|
||||
└── security/ir.model.access.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Autenticación y Roles
|
||||
|
||||
### 8.1 Roles
|
||||
|
||||
| Rol | Permisos |
|
||||
|-----|----------|
|
||||
| **Admin** | Todo acceso: config, flujos, usuarios, integraciones |
|
||||
| **Supervisor** | Ver todas las conversaciones, reasignar, reportes equipo |
|
||||
| **Agente** | Solo conversaciones asignadas, responder, transferir |
|
||||
|
||||
### 8.2 JWT
|
||||
|
||||
- Access token: 1 hora
|
||||
- Refresh token: 7 días (httpOnly cookie)
|
||||
- Permisos en payload
|
||||
|
||||
---
|
||||
|
||||
## 9. Docker Compose
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: whatsapp_central
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
whatsapp-core:
|
||||
build: ./services/whatsapp-core
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
API_GATEWAY_URL: http://api-gateway:8000
|
||||
REDIS_URL: redis://redis:6379
|
||||
volumes:
|
||||
- whatsapp_sessions:/app/sessions
|
||||
ports:
|
||||
- "3001:3001"
|
||||
|
||||
api-gateway:
|
||||
build: ./services/api-gateway
|
||||
environment:
|
||||
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/whatsapp_central
|
||||
REDIS_URL: redis://redis:6379
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
flow-engine:
|
||||
build: ./services/flow-engine
|
||||
environment:
|
||||
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/whatsapp_central
|
||||
REDIS_URL: redis://redis:6379
|
||||
|
||||
integrations:
|
||||
build: ./services/integrations
|
||||
environment:
|
||||
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/whatsapp_central
|
||||
ODOO_URL: ${ODOO_URL}
|
||||
ODOO_DB: ${ODOO_DB}
|
||||
ODOO_USER: ${ODOO_USER}
|
||||
ODOO_PASSWORD: ${ODOO_PASSWORD}
|
||||
|
||||
frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
whatsapp_sessions:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Roadmap de Implementación
|
||||
|
||||
### Fase 1: Fundación
|
||||
- Estructura proyecto + Docker Compose
|
||||
- whatsapp-core: Baileys, sesiones, multi-número
|
||||
- api-gateway: Auth JWT, roles
|
||||
- Frontend: Login, Dashboard, gestión números
|
||||
- Inbox básico: Lista conversaciones, chat
|
||||
|
||||
### Fase 2: Flow Engine Básico
|
||||
- Motor de ejecución de flujos
|
||||
- Nodos básicos: mensaje, botones, wait_input, condición
|
||||
- Sistema de variables
|
||||
- Frontend: Flow Builder con React Flow
|
||||
- Triggers: welcome, keyword, fallback
|
||||
|
||||
### Fase 3: Inbox Avanzado + Multi-agente
|
||||
- Sistema de colas
|
||||
- Asignación inteligente
|
||||
- Transferencia bot → humano → bot
|
||||
- Estados de agente
|
||||
- Respuestas rápidas
|
||||
- Notas internas
|
||||
- Panel supervisor
|
||||
- SLA tracking
|
||||
|
||||
### Fase 4: Flow Engine Avanzado
|
||||
- Nodos avanzados: switch, loop, random, sub-flow
|
||||
- Nodos validación
|
||||
- Templates reutilizables
|
||||
- Variables globales
|
||||
- Nodo JavaScript
|
||||
- A/B Testing
|
||||
- Nodo AI Response
|
||||
|
||||
### Fase 5: Integración Odoo Completa
|
||||
- Conexión XML-RPC
|
||||
- Sincronización contactos
|
||||
- Todos los nodos Odoo
|
||||
- Eventos Odoo → WhatsApp
|
||||
- Envío de PDFs
|
||||
|
||||
### Fase 6: Módulo Odoo
|
||||
- Módulo odoo_whatsapp_hub
|
||||
- Widget de chat en partner
|
||||
- Historial conversaciones
|
||||
- Enviar WhatsApp desde Odoo
|
||||
- Acciones automáticas
|
||||
- Envío masivo
|
||||
|
||||
### Fase 7: Reportes y Analytics
|
||||
- Dashboard analytics
|
||||
- Métricas por agente/cola/flujo
|
||||
- CSAT
|
||||
- Exportación
|
||||
- Reportes programados
|
||||
|
||||
### Fase 8: Multi-canal (Futuro)
|
||||
- Email (SMTP/IMAP)
|
||||
- SMS (Twilio)
|
||||
- Inbox unificado
|
||||
- WhatsApp Business API oficial
|
||||
|
||||
---
|
||||
|
||||
## 11. Requisitos de Servidor
|
||||
|
||||
- **RAM**: 4GB mínimo, 8GB recomendado
|
||||
- **CPU**: 2 cores mínimo
|
||||
- **Disco**: SSD, 50GB+
|
||||
- **SO**: Linux (Ubuntu 22.04 recomendado)
|
||||
- **Docker**: 24.0+
|
||||
- **Docker Compose**: 2.20+
|
||||
|
||||
---
|
||||
|
||||
## 12. Consideraciones de Seguridad
|
||||
|
||||
- Passwords con bcrypt (cost 12)
|
||||
- JWT con refresh tokens en httpOnly cookies
|
||||
- Rate limiting en auth
|
||||
- HTTPS obligatorio (Let's Encrypt)
|
||||
- Secrets en variables de entorno
|
||||
- No commitear .env
|
||||
- Backup automatizado de PostgreSQL y sesiones
|
||||
- Futuro: 2FA para admins
|
||||
|
||||
---
|
||||
|
||||
*Documento generado durante sesión de brainstorming.*
|
||||
*Aprobado para iniciar implementación.*
|
||||
Reference in New Issue
Block a user