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:
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
|
||||
Reference in New Issue
Block a user