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:
@@ -123,15 +123,43 @@ bidirecional confirmado); dígito `2` → resposta alternativa (tom
|
||||
diferente + desliga) — confirma que a ramificação distingue de verdade,
|
||||
não só "sempre cai na primeira opção".
|
||||
|
||||
## Tela de autoria de IVR (PHASE 58)
|
||||
|
||||
`Telefonia > IVR` (`apps/frontend/.../telefonia/ivr`) — cria um `IvrMenu`
|
||||
(nome + contexto derivado do nome) com opções (dígito → ramal + rótulo
|
||||
opcional), sem precisar tocar no editor genérico de dialplan. Por baixo,
|
||||
`IvrMenusController` (`POST/PATCH/DELETE /ivr-menus`) compila o menu +
|
||||
opções em `buildIvrDialplanExtensions` (`packages/telephony/src/ivr-xml.ts`)
|
||||
e já gera + ativa uma nova versão do dialplan do contexto — o mesmo fluxo
|
||||
generate+activate manual, automático. `IVR_ENTRY_DESTINATION`
|
||||
("ivr_entry") é o valor fixo que toda `InboundRoute` precisa usar como
|
||||
`destinationNumber` pra entrar nesse menu (`destinationContext =
|
||||
IvrMenu.context`).
|
||||
|
||||
`greeting` (o prompt tocado ao entrar) passa pela MESMA proteção
|
||||
anti-RCE de `data` de dialplan (`IsSafeDialplanData`) — é texto livre do
|
||||
tenant, nunca pode virar `${system(...)}`. Sem pipeline de upload/TTS de
|
||||
áudio ainda: null usa um tom padrão; texto livre vira o argumento `file`
|
||||
de `play_and_get_digits` (aceita qualquer caminho/URL que o FreeSWITCH
|
||||
já resolva, incluindo `tone_stream://`).
|
||||
|
||||
Testado ponta a ponta criando um menu de verdade via API (não só
|
||||
lendo XML manualmente escrito): softphone externo discou o DID, o menu
|
||||
recém-criado atendeu, tocou o prompt, colheu o dígito com DTMF real
|
||||
(`uuid_recv_dtmf`), e bridged com o ramal certo — confirmando que o
|
||||
compilador produz XML funcionalmente idêntico ao testado manualmente na
|
||||
PHASE 56.
|
||||
|
||||
## O que falta
|
||||
|
||||
- Tela de frontend pra autoria de IVR — hoje o menu é construído à mão
|
||||
no editor genérico de dialplan (`Telefonia > Dialplan`, escolhendo um
|
||||
`context` novo), não tem uma UI dedicada de "menu com opções" ainda.
|
||||
Backend já suporta tudo que uma UI assim precisaria gerar.
|
||||
- Sem pipeline de upload/TTS de prompt de áudio — hoje é texto livre
|
||||
(tom padrão ou um caminho/URL que o FreeSWITCH já sabe tocar).
|
||||
- Menu de IVR não suporta sub-menus (uma opção levando a OUTRO IVR) nem
|
||||
destino "fila" — só ramal, dentro do contexto `default`.
|
||||
- Tela de frontend "Rotas de Entrada" cobre só CRUD simples (DID →
|
||||
ramal); não tem seletor de "fila" ou "IVR" como destino ainda — hoje é
|
||||
texto livre pro `destinationContext`/`destinationNumber`.
|
||||
ramal); não tem seletor dedicado de "IVR" como destino ainda (o
|
||||
operador digita o contexto/`ivr_entry` manualmente, mostrados na
|
||||
própria tela de IVR pra copiar).
|
||||
- Perda das proteções de toll-fraud do `public.xml` vanilla (unroll de
|
||||
loop de chamada, etc.) — não replicadas no contexto `inbound` novo.
|
||||
Aceitável pra esta fase (sem trunks reais ainda), mas revisar antes de
|
||||
|
||||
Reference in New Issue
Block a user