Nova aba 'Dialplan -> Rotas de Saída': abstração amigável sobre o dialplan bruto, no mesmo espírito das Outbound Routes do Issabel/FreePBX. O usuário informa (prepend) + prefix | match pattern (sintaxe de padrão do Asterisk: X/Z/N/faixas/coringas, sem o '_' inicial) e escolhe o tronco, sem escrever exten=>/Dial() à mão. - packages/database: model OutboundRoute + migration - apps/api/src/outbound-routes: gerador determinístico (contexto b2bcall-outbound-routes, incluído em b2bcall-agents — contexto padrão de Extension.context), service/controller CRUD reaproveitando as permissões dialplans.*, 6 testes unitários - apps/api/src/dialplan/dialplan.service.ts: publish() agora gera e verifica também o contexto das rotas, no mesmo pipeline de versionamento/rollback automático das entradas de dialplan brutas - apps/frontend: aba com formulário (prepend/prefix/padrão/tronco) e preview ao vivo da máscara resultante O prefixo é sempre removido do número antes de discar e substituído pelo prepend — o tronco nunca vê o prefixo digitado pelo agente, só o número já mascarado. Não usado pelo discador preditivo (campanhas já sabem o tronco via Campaign.trunkId diretamente). Build/lint/testes (api+frontend) verificados; teste end-to-end contra o Asterisk real ficou pendente porque a senha do super_admin foi trocada durante a sessão (acesso legítimo do usuário) — validação via UI delegada ao usuário.
116 lines
5.8 KiB
Markdown
116 lines
5.8 KiB
Markdown
# B2BCall — Asterisk
|
|
|
|
## Versão e módulos
|
|
|
|
Asterisk **22.10.1**, compilado do fonte (Debian trixie não empacota
|
|
`asterisk`) — `infrastructure/docker/asterisk.Dockerfile`. Módulos
|
|
habilitados: `chan_pjsip` (nunca `chan_sip`, removido/depreciado),
|
|
`res_odbc`/`res_config_odbc` (Realtime), `app_queue`, `cdr_adaptive_odbc`,
|
|
`cel_odbc`, AMI, ARI.
|
|
|
|
## Realtime (PJSIP via Postgres)
|
|
|
|
Objetos PJSIP (`ps_endpoints`, `ps_auths`, `ps_aors`, `ps_contacts`,
|
|
`ps_endpoint_id_ips`, `ps_registrations`) vivem no schema `asterisk` do
|
|
mesmo Postgres da aplicação — nunca em arquivo estático. Escritos
|
|
exclusivamente por `apps/api/src/telephony/pjsip-realtime.service.ts`
|
|
quando um Tronco/Ramal é criado/editado pela API.
|
|
|
|
- DSN ODBC: `infrastructure/asterisk/config/odbc.ini.tpl` → conexão nomeada
|
|
`asterisk`, `ConnSettings = SET search_path TO asterisk, public;`.
|
|
- `sorcery.conf`/`extconfig.conf` apontam os tipos PJSIP para essa conexão.
|
|
- Depois de criar/editar um objeto, a API dispara reload seletivo via AMI
|
|
(`pjsip reload`), nunca `core restart`.
|
|
|
|
## Dialplan e filas (gerados, versionados)
|
|
|
|
- `infrastructure/asterisk/config/extensions.conf` inclui
|
|
`/etc/asterisk-generated/b2bcall-dialplan.conf` — gerado por
|
|
`DialplanService` a partir de `DialplanEntry`/`DialplanVersion`
|
|
(Postgres). Toda alteração é uma nova `DialplanVersion` com validação
|
|
(`dialplan reload` + checagem de erro) e rollback automático se falhar.
|
|
- **Rotas de saída** (`OutboundRoute`, tela "Dialplan → Rotas de Saída"):
|
|
abstração amigável no estilo Issabel/FreePBX sobre o dialplan bruto — o
|
|
usuário informa `prepend` + `prefix` + `matchPattern` (sintaxe de padrão
|
|
do Asterisk: `X`/`Z`/`N`/faixas `[1-5]`/`.`/`!`, sem o `_` inicial) e
|
|
escolhe o tronco, sem escrever `exten =>`/`Dial()` à mão
|
|
(`apps/api/src/outbound-routes/outbound-route-generator.ts`). Gera o
|
|
contexto `b2bcall-outbound-routes`:
|
|
```
|
|
exten => _<prefix><matchPattern>,1,NoOp(...)
|
|
same => n,Dial(PJSIP/<prepend>${EXTEN:<len(prefix)>}@<tronco>,60)
|
|
same => n,Hangup()
|
|
```
|
|
e inclui esse contexto em `[b2bcall-agents]` (contexto padrão de
|
|
`Extension.context`), para que a discagem manual de um agente já passe
|
|
pelas rotas sem configuração extra. O prefixo é **sempre** removido do
|
|
número antes de discar e substituído pelo prepend — o tronco nunca vê o
|
|
prefixo que o agente digitou, só o número já mascarado. Publicado e
|
|
versionado junto com `DialplanEntry` pelo mesmo `DialplanService.publish()`
|
|
(mesma validação `dialplan show`/rollback automático). Não usado pelo
|
|
discador preditivo — campanhas já sabem o tronco diretamente via
|
|
`Campaign.trunkId`, sem precisar casar padrão.
|
|
- `infrastructure/asterisk/config/queues.conf` inclui
|
|
`/etc/asterisk-generated/b2bcall-queues.conf` — gerado a partir de
|
|
`Queue` (Postgres). **Membros nunca são estáticos** — adicionados/
|
|
removidos via AMI `QueueAdd`/`QueueRemove` no login/logout do agente
|
|
(`AgentConsoleService`), para nunca ter duas fontes de verdade
|
|
divergentes sobre quem está em qual fila.
|
|
- Ambos os arquivos gerados vivem no volume Docker `dialplan-generated`,
|
|
compartilhado entre `api` (escreve) e `asterisk` (lê + recarrega).
|
|
- **Nunca editar esses dois arquivos gerados manualmente** — qualquer
|
|
edição é sobrescrita na próxima publicação pela aplicação.
|
|
|
|
## AMI / ARI
|
|
|
|
- AMI: client próprio em `packages/telephony` (TCP raw, sem biblioteca de
|
|
terceiros — o protocolo de wire do Asterisk 22 para a action `Command`
|
|
usa headers `Output:` repetidos, não o formato legado
|
|
`Response: Follows`/`--END COMMAND--` documentado em versões antigas).
|
|
- Usado para: Originate, Hangup, QueueAdd/Remove, QueuePause,
|
|
QueueStatus, ExtensionState/DeviceState, PJSIP show endpoints/contacts,
|
|
reloads seletivos.
|
|
- ARI: reservado para controle fino de canais/bridges quando necessário —
|
|
não usado extensivamente nesta fase (a maior parte das operações
|
|
administrativas é feita via AMI).
|
|
- **Nunca expostos além de `127.0.0.1`/rede interna** — bloqueados da LAN
|
|
por nftables mesmo estando em `network_mode: host` (ver
|
|
`docs/ARCHITECTURE.md` §3.1 e `infrastructure/nftables/`).
|
|
|
|
## Comandos úteis de diagnóstico
|
|
|
|
```bash
|
|
docker exec b2bcall-asterisk asterisk -rx "core show uptime"
|
|
docker exec b2bcall-asterisk asterisk -rx "pjsip show endpoints"
|
|
docker exec b2bcall-asterisk asterisk -rx "pjsip show registrations"
|
|
docker exec b2bcall-asterisk asterisk -rx "queue show"
|
|
docker exec b2bcall-asterisk asterisk -rx "dialplan show b2bcall-healthcheck"
|
|
docker exec b2bcall-asterisk asterisk -rx "module show like odbc"
|
|
docker exec b2bcall-asterisk asterisk -rx "odbc show all"
|
|
```
|
|
|
|
Um subconjunto seguro e auditado desses comandos também é exposto via
|
|
`GET /api/asterisk/diagnostic/allowed-commands` +
|
|
`POST /api/asterisk/diagnostic` (allowlist explícita — nunca shell livre,
|
|
seção 18) para uso pela interface web sem precisar SSH no servidor.
|
|
|
|
## Rede
|
|
|
|
`network_mode: host` (necessário para a faixa de portas RTP — mapear porta
|
|
a porta em bridge Docker é inviável). Implicações e mitigação em
|
|
`docs/ARCHITECTURE.md` §3.1.
|
|
|
|
## Limitações conhecidas nesta fase
|
|
|
|
- `res_pjsip_outbound_registration` pode logar erro no boot se nenhum
|
|
registration estiver configurado ainda (nenhum tronco cadastrado) — não é
|
|
um erro real, some ao cadastrar o primeiro tronco com `registration`.
|
|
- `res_config_ldap` loga ERROR no boot (LDAP não configurado/não usado
|
|
neste projeto) — módulo carregado por padrão pelo build, inofensivo.
|
|
- `cdr_pgsql`/`cel_pgsql` (variante não-ODBC) não compilam nesta imagem por
|
|
falta de `libpq-dev` — não é um problema porque `cdr_adaptive_odbc`/
|
|
`cel_odbc` (a variante realmente usada) funcionam normalmente.
|
|
- Nenhuma correlação automática CDR↔`DialAttempt` para chamadas **reais**
|
|
(não simuladas) além do timeout de segurança da reconciliação — ver
|
|
`docs/PREDICTIVE_DIALER.md`.
|