Panel de monitorización en tiempo real del estado de extensiones de la centralita Salescare. Expone una API REST para que QueueMetrics libere canales bloqueados automáticamente.
Conexión persistente al AMI de Asterisk Salescare. Estado de extensiones sincronizado en memoria vía eventos AMI. UI actualizada en tiempo real por WebSocket. API REST para integración con QueueMetrics.
Conexión persistente con reconexión automática. Escucha ExtensionStatus, DeviceStateChange, Newchannel, Hangup. Rastrea canales activos por extensión.
Sin base de datos. Estado completo en dict Python: extensión → {state, since, channels[]}. Se reconstruye completamente desde AMI al arrancar. Cero I/O de disco.
Cada cambio de estado AMI se propaga a todos los clientes WebSocket conectados. La UI de monitorización refleja el estado en <100ms sin polling.
QueueMetrics detecta extensiones bloqueadas y las libera automáticamente via la API REST del panel.
state: inuse en /api/extensionsX-API-Key: <key>Events: off, ejecuta Action: Hangup para cada canal activo de la extensiónExtensionStatus con estado NOT_INUSEstate: available, agente listo para nuevas llamadasAPI REST consumida por QueueMetrics y la UI de monitorización.
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
| GET | /api/extensions |
Estado de todas las extensiones + canales activos | — |
| POST | /api/extensions/{exten}/hangup |
Cuelga todos los canales activos de una extensión | X-API-Key |
| GET | /api/health |
Health check + estado de conexión AMI | — |
| WS | /ws |
WebSocket — estado en tiempo real | — |
Respuesta ejemplo de GET /api/extensions:
{
"1200": { "state": "inuse", "since": 1717594123.4, "channels": ["SIP/1200-00001abc"], "context": "from-internal" },
"1201": { "state": "available", "since": 1717594000.0, "channels": [], "context": "from-internal" }
}Cinco estados posibles derivados de los eventos AMI ExtensionStatus y DeviceStateChange.
| Estado | Descripción | Color UI |
|---|---|---|
| available | Extensión libre, sin llamada activa | Verde |
| inuse | En llamada. Puede haber uno o varios canales activos | Naranja |
| ringing | Sonando, entrante sin responder | Cyan |
| onhold | Llamada en espera | Púrpura |
| unavailable | Extensión no registrada o sin dispositivo | Gris |
Cuatro módulos Python asincrónos con responsabilidades bien delimitadas.
| Módulo | Responsabilidad |
|---|---|
backend/ami_client.py |
Conexión AMI persistente, suscripción a eventos, tracking de canales activos por extensión, ami_hangup() con conexión dedicada |
backend/server.py |
PanelServer: endpoints REST + WebSocket, broadcast de cambios de estado, integración con AMI client |
backend/config.py |
Loader INI para secciones [ami], [web], [auth], [names] |
backend/main.py |
Entry point: arranque, bucle de reconexión AMI con backoff exponencial |
[ami]
host = <IP_ASTERISK_SALESCARE>
port = 5038
username = salescare-panel
secret = <SECRET>
[web]
host = 0.0.0.0
port = 8090
[auth]
api_key = <API_KEY_PARA_QUEUEMETRICS>
[names]
1200 = Nombre Agente
1201 = Otro AgenteDeploy en servidor Linux Asterisk Salescare.
systemd — salescare-panel/opt/jmfernandez/salescare-panelbash deploy.shLos eventos Newchannel/Hangup del AMI son eventuales. Hay una pequeña ventana desde que la extensión entra en llamada hasta que el canal queda registrado en el dict. Si QueueMetrics pide el hangup durante esa ventana, la API devuelve 400 y QM debe reintentar.
El endpoint POST /hangup abre una segunda conexión AMI temporal con Events: off. No interfiere con la conexión principal de monitorización. La respuesta indica qué canales se colgaron exitosamente.
Todo el estado está en memoria. En caso de reinicio del servicio, el estado se reconstruye desde el AMI (los eventos de reconexión lo rellenan). No hay CDR, no hay exportaciones, no hay persistencia.
La sección [names] del config.ini mapea extensión a nombre para mostrar en la UI. Para añadir o modificar nombres hay que editar el fichero y reiniciar el servicio.