feat(ivr): tela de autoria de menu de IVR no frontend

Pedido do usuário: "constrói a tela de IVR no frontend". Até aqui um
menu de IVR só existia se alguém escrevesse as regras à mão no editor
genérico de dialplan (o que eu fiz manualmente pra testar na PHASE 56) —
sem UI nenhuma pra isso.

`IvrMenu`/`IvrMenuOption` (RLS real): nome + contexto (derivado do nome)
+ opções (dígito → ramal + rótulo opcional). Nenhuma tabela nova pro
dialplan em si — `IvrMenusController` compila o menu inteiro em
`DialplanExtension`/`DialplanVersion` do contexto do menu
(`buildIvrDialplanExtensions`, packages/telephony) usando as MESMAS 2
formas de `<extension>` já testadas com DTMF real na PHASE 56 (entrada
com `play_and_get_digits` + `transfer` usando o dígito coletado como
novo destination_number, uma extension por dígito) — e já gera + ativa
a versão nova automaticamente, o mesmo generate+activate manual que o
editor de dialplan faz, só que embutido no create/update do menu.

`greeting` (o prompt do menu) é texto livre do tenant, então passa pela
MESMA proteção anti-RCE já aplicada em `data` de dialplan
(`IsSafeDialplanData`) — nunca pode virar `${system(...)}`.

Tela "Telefonia > IVR": lista de menus com as opções de cada um, criação
com nome/contexto (auto-gerado do nome, editável) + linhas dinâmicas de
opção (dígito + select de ramal já cadastrado + rótulo), remoção com
confirmação de 2 cliques. Mostra o contexto/destino fixo (`ivr_entry`)
que uma Rota de Entrada precisa usar pra apontar pro menu.

Testado ponta a ponta criando um menu DE VERDADE pela tela/API (não só
lendo o XML manualmente escrito antes): softphone externo discou um DID
apontado pro menu recém-criado, atendeu, tocou o prompt, colheu o dígito
com DTMF real (`uuid_recv_dtmf`) e bridged com o ramal certo — confirma
que o compilador produz XML funcionalmente idêntico ao testado
manualmente. Falta pipeline de upload/TTS de áudio, sub-menus e destino
"fila" — ver docs/INBOUND_ROUTES.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-30 17:24:07 -03:00
parent 83eb2c5aea
commit 36e85c1abf
16 changed files with 1000 additions and 6 deletions

View File

@@ -25,6 +25,8 @@ const PERMISSIONS: Array<{ key: string; description: string }> = [
{ key: "trunks.manage", description: "Criar/editar troncos" },
{ key: "inbound_routes.view", description: "Ver rotas de entrada" },
{ key: "inbound_routes.manage", description: "Criar/editar rotas de entrada" },
{ key: "ivr.view", description: "Ver menus de IVR" },
{ key: "ivr.manage", description: "Criar/editar menus de IVR" },
{ key: "agents.view", description: "Ver agentes" },
{ key: "agents.manage", description: "Criar/editar agentes" },
{ key: "queues.view", description: "Ver filas" },
@@ -62,6 +64,7 @@ const ROLE_PERMISSIONS: Record<string, string[]> = {
"extensions.view",
"trunks.view",
"inbound_routes.view",
"ivr.view",
"agents.view",
"agents.manage",
"queues.view",