Análisis OMniLeads
Visión General
OMniLeads es un contact center open source que integra canales de mensajería, telefónica, y herramientas de gestión de campañas. Es el proyecto de referencia más completo en el ecosistema open source para contact center.
| Aspecto | Detalle |
|---|---|
| Repositorio | github.com/omnileads/omnileads |
| Stack | Python 3.9, Django 3.2, PostgreSQL, Redis, Kamailio |
| Licencia | GNU LGPL v3 |
| Estado | En desarrollo activo |
| Deploy | Docker Compose, Ansible |
Arquitectura
OMniLeads sigue una arquitectura de monolito Django con 13 aplicaciones internas, cada una responsable de un subdominio del negocio:
Aplicaciones Principales
| App | Responsabilidad |
|---|---|
omnileads | Core del sistema, configuración global |
campaigns | Gestión de campañas outbound |
contacts | Gestión de contactos y segmentación |
queues | Colas de distribución de llamadas |
ivr | Flujos IVR y encuestas |
dialer | Integración con discadores externos |
telephony | Integración con Asterisk/Kamailio |
webhooks | Notificaciones y eventos |
reports | Reporting y métricas |
audit | Auditoría de acciones |
users | Gestión de usuarios y permisos |
brands | Multi-tenancy por marca |
storage | Almacenamiento de grabaciones |
Flujo de Datos
WhatsApp API ─┐
Facebook API ─┤
Instagram API ─┤→ Channel Adapters → Django Views → Domain Logic → PostgreSQL
Email API ────┤ ↓
SIP Proxy ────┘ Redis (cache, events)
↓
WebSocket (realtime)Modelo de Dominio
Campaña (Campaign)
Las campañas son la unidad central del sistema. Cada campaña tiene un ciclo de vida bien definido:
Estados de la Campaña:
| Estado | Descripción |
|---|---|
manual | Configuración inicial, sin activación |
dialer | Modo discador outbound, activa llamadas |
incoming | Modo recepción, solo recibe llamadas |
preview | Modo preview, agente ve datos antes de conectar |
Atributos clave:
name: Nombre de la campañatype: manual | dialer | incoming | previewqueue: Cola asociadadialer_type: tipo de discador (si aplica)schedule: horario de operaciónmax_attempts: máximo de intentos por contactotime_zone: timezone de la campaña
Contacto (Contact)
Representa una persona externa que interactúa con la organización:
- Datos personales (nombre, email, teléfono)
- Campos personalizados (dynamic fields)
- Etiquetas y segmentación
- Historial de interacciones
- Fuente de origen (canal, campaña)
Perfil de Agente (AgentProfile)
Estado y configuración de cada agente del sistema:
- Estado actual:
available,pause,on_call,offline - Canales habilitados
- Skills y competencias
- Campañas asignadas
- Estadísticas de productividad
Cola (Queue)
Define cómo se distribuyen las interacciones:
- Estrategia de distribución:
ring_all,round_robin,least_idle - Horario de operación
- Overflow conditions
- Música de espera y mensajes
IVR (Inboundivr)
Flujos de respuesta de voz interactiva:
- nodos conectados en grafo
- Tipos de nodo: menú, input, transferencia, hangup,http
- Variables de sesión
- Integración con ARI para control de llamadas
Canales
OMniLeads soporta dos proveedores de WhatsApp Business API:
- GupShup: Proveedor indirecto, acceso rápido a API
- Meta Cloud API: API oficial directa de Meta
La integración se realiza mediante webhooks entrantes y polling de mensajes.
Facebook Messenger
Integración directa con la API de Facebook Messenger para páginas de negocio.
Soporte para Direct Messages de Instagram Business.
Recepción vía IMAP, envío vía SMTP. Soporte para plantillas HTML.
Webchat Widget
Widget JavaScript embeddable que permite聊天 desde cualquier sitio web.
Telefonía
Integración con Asterisk/Kamailio
OMniLeads utiliza un enfoque de dual stack telefónica:
- Kamailio: Proxy SIP para registro de agentes y routing de llamadas
- Asterisk: Media server para IVR, grabación, y bridging
Generación de Dialplan
El sistema genera dinámicamente configuración de Asterisk (dialplan) mediante conexiones SSH a los servidores telefónicos:
# Patrón simplificado (no código real)
def generate_dialplan(campaign):
config = render_dialplan_template(campaign)
ssh_connect(asterisk_host)
ssh_write(config, '/etc/asterisk/extensions_custom.conf')
ssh_command('asterisk -rx "dialplan reload"')Debilidad: Este patrón de generación vía SSH es frágil y difícil de testear. Un enfoque basado en ARI sería más limpio.
Integración con Discador
La integración con discadores externos (Wombat Dialer / Omnidialer) se realiza vía HTTP REST:
- El discador consulta la lista de contactos
- El discador inicia llamadas y notifica eventos
- OMniLeads actualiza el estado de la campaña
Dialer
Enfoque Actual
OMniLeads no implementa su propio discador. En su lugar, se integra con discadores externos:
- Wombat Dialer: Discador comercial (legacy)
- Omnidialer: Discador open source moderno
Interfaz de Integración
La integración se realiza mediante endpoints REST:
| Endpoint | Método | Descripción |
|---|---|---|
/api/v1/campaigns/{id}/contacts | GET | Obtener contactos para discar |
/api/v1/campaigns/{id}/status | POST | Notificar estado de llamada |
/api/v1/agents/{id}/status | POST | Notificar cambio de estado de agente |
Realtime
Django Channels WebSocket
OMniLeads utiliza Django Channels con Redis como capa de transporte para comunicación en tiempo real:
- Actualización de estado de agentes
- Nuevos mensajes entrantes
- Cambios de estado de campañas
- Notificaciones de SLA
Redis Streams
Los eventos de alto volumen (llamadas, mensajes) se procesan mediante Redis Streams, permitiendo:
- Procesamiento asíncrono
- Consumidores group
- Persistencia temporal de eventos
Fortalezas
- Modelo de dominio completo: Campañas con 4 estados, IVR con grafo, colas, contactos con campos dinámicos
- Producción probada: Utilizado en entornos reales de contact center en Latam
- Cobertura de canales: WhatsApp (2 proveedores), Facebook, Instagram, Email
- Integración telefónica: Kamailio + Asterisk, generación de dialplan
- Documentación: Deploy con Docker y Ansible, documentación operativa
Debilidades
1. Archivo God de 4170 líneas
El archivo principal de vista contiene lógica de presentación, dominio, y acceso a datos mezcladas. Esto dificulta:
- Testing unitario
- Mantenimiento
- Reutilización de lógica
2. Sin Multi-tenancy Real
OMniLeads opera como sistema single-tenant. No hay aislamiento de datos entre organizaciones不同. El concepto de brand existe pero no implementa separación real.
3. Mezcla de Síncrono/Asíncrono
La arquitectura mezcla operaciones síncronas (Django views) con asíncronas (Django Channels, Redis Streams) sin una capa clara de separación.
4. Django 3.2 (End of Life)
Django 3.2 alcanzó EOL en abril 2024. La migración a Django 4.x+ es necesaria pero no trivial.
5. Generación de Dialplan vía SSH
El patrón de generar configuración de Asterisk mediante SSH es frágil:
- Sin atomicidad en la escritura
- Sin rollback en caso de error
- Sin validación previa del config
- Dificulta el testing automatizado
6. Sin API REST Documentada
No existe documentación OpenAPI/Swagger de la API, dificultando integraciones de terceros.
Licencia: GNU LGPL v3
¿Qué Significa LGPL v3?
- LGPL (Lesser General Public License): Permite usar la biblioteca en productos proprietarios, pero con restricciones
- Si se modifica la biblioteca LGPL, las modificaciones deben开源
- El producto que usa la biblioteca puede ser proprietario, pero el vínculo debe ser dinámico
Implicaciones para Nuestro Producto
| Escenario | ¿Permitido? |
|---|---|
| Usar OMniLeads como referencia conceptual | Sí |
| Copiar fragmentos de código | No (obliga a LGPL) |
| Linkar dinámicamente contra OMniLeads | Sí, pero el producto debe ser LGPL |
| Incluir OMniLeads como dependencia | Sí, pero todo el stack debe ser LGPL |
Decisión
No reutilizar código de OMniLeads. Usar solo como referencia de diseño y modelo de dominio.
Conceptos Reutilizables (Como Referencia)
1. Estados de Campaña
El ciclo de vida de 4 estados (Manual → Dialer/Incoming/Preview) es un patrón sólido:
manual ──┬──→ dialer (outbound)
├──→ incoming (inbound)
└──→ preview (agente revisa antes de conectar)2. Modelo de Grafo IVR
La representación de flujos IVR como grafo de nodos conectados es el patrón estándar de la industria. Cada nodo tiene tipo, datos de configuración, y conexiones a otros nodos.
3. Interfaz Abstracta de Discador
La separación entre el sistema de campañas y el discador externo es un buen patrón de desacoplamiento.
4. Patrón de Adaptadores de Canal
Cada canal (WhatsApp, Facebook, Email) implementa una interfaz común de recepción/envío de mensajes.
Patrones de Referencia
Channel Adapter Paralela
Cada canal mantiene su propio adapter con lógica específica:
ChannelAdapter (interfaz)
├── WhatsAppAdapter (GupShup/Meta)
├── FacebookAdapter
├── InstagramAdapter
├── EmailAdapter
└── WebchatAdapterWebSocket Notification Dispatch
Notificaciones en tiempo real mediante patrón pub/sub sobre WebSocket:
Evento → Redis Stream → Consumer → WebSocket → Frontend