feat: add outbound routes (Issabel-style dial pattern masking)

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.
This commit is contained in:
2026-08-27 20:13:21 -03:00
parent 80e72881b2
commit 9857904c6d
16 changed files with 885 additions and 4 deletions

16
TODO.md
View File

@@ -126,6 +126,22 @@ mestre original (`agente.md`, seções 90-93).
versão anterior via endpoint dedicado. Limitação conhecida: detecta
falhas estruturais/sintáticas, não erros semânticos como nome de
aplicação Asterisk inexistente (só se manifesta em tempo de chamada)
- [x] Rotas de saída (`OutboundRoute`, aba "Dialplan → Rotas de Saída") —
**adicionado após a Fase 10**, a pedido do usuário: abstração amigável
estilo Issabel/FreePBX (prepend + prefix + match pattern + seleção de
tronco) sobre o dialplan bruto, sem exigir que o usuário escreva
`exten =>`/`Dial()` à mão. Prefixo sempre removido do número antes de
discar, substituído pelo prepend. Gera o contexto
`b2bcall-outbound-routes`, incluído em `b2bcall-agents` (contexto
padrão de `Extension.context`), publicado/versionado pelo mesmo
`DialplanService.publish()` das entradas brutas (mesma validação
`dialplan show`/rollback automático — um único `verify()` cobre os
dois). 6 testes unitários (`outbound-route-generator.spec.ts`) +
testado ponta a ponta contra o Asterisk real: rota `(55)+0|NXXXXXXXXX`
criada, publicada, `dialplan show b2bcall-outbound-routes` confirmou
`exten => _0NXXXXXXXXX` ativo com o `Dial()` mascarando o número
corretamente. Não usado pelo discador preditivo (campanhas já sabem o
tronco via `Campaign.trunkId` diretamente).
- [x] Administração do Asterisk: status (AMI + heartbeat asterisk-events),
module show, e diagnóstico com ALLOWLIST ESTRITA de comandos exatos
(core show uptime/channels/version, pjsip show *, queue show, module