From 486c39803a4c1c099509e876ed86958aa8a31609 Mon Sep 17 00:00:00 2001 From: Matheus Date: Sun, 30 Aug 2026 17:51:31 -0300 Subject: [PATCH] =?UTF-8?q?feat(ivr):=20upload=20de=20=C3=A1udio=20pro=20p?= =?UTF-8?q?rompt=20do=20IVR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pedido do usuário: "adiciona upload de áudio pro prompt do IVR" — até aqui o prompt era só texto livre (na prática, sempre um tom padrão, nunca voz de verdade). `POST /ivr-menus/:id/prompt` (multipart via @fastify/multipart — primeiro upload de arquivo binário desta API) só aceita WAV (cabeçalho RIFF/WAVE validado antes de gravar; esta implantação do FreeSWITCH não tem mod_shout, então MP3 nunca funcionaria de qualquer forma). Gravado num bind mount NOVO (./data/ivr-prompts no host ↔ /ivr-prompts no container freeswitch) — mesma convenção já usada 3x neste projeto pra arquivo que o FreeSWITCH precisa enxergar de verdade (gateways externos, filas do callcenter, spool de gravação), mas na direção contrária: apps/api (host) escreve o que o usuário sobe, o FreeSWITCH lê ao vivo durante play_and_get_digits. Um fetch em rede (S3/HTTP) durante uma chamada ativa foi descartado de propósito — latência/confiabilidade desnecessárias pra um prompt de poucos segundos. GET /ivr-menus/:id/prompt (autenticado) serve o preview — mesmo princípio do player de gravações, nunca uma URL direta pro storage. Achado real corrigido antes de commitar: minha primeira versão do delete de menu deixava o .wav órfão no disco — agora deletar o menu ou trocar/remover o prompt sempre limpa o arquivo, confirmado com um teste real de upload+delete. Tela "Telefonia > IVR" ganhou upload/troca/remoção de áudio por menu + player de preview. Testado ponta a ponta com um WAV real de 44.1kHz/mono (não o formato "nativo" de telefonia, de propósito, pra confirmar que funciona com o que uma pessoa qualquer gravaria): softphone externo discou o DID, play_and_get_digits abriu e tocou o arquivo até o fim duas vezes (mod_sndfile resample automático, sem erro no log), colheu o dígito real e bridged corretamente com o ramal de destino. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8 --- TODO.md | 38 ++++- apps/api/package.json | 1 + apps/api/src/ivr/ivr-menus.controller.ts | 148 ++++++++++++++++++ apps/api/src/main.ts | 9 ++ .../app/api/ivr-menus/[id]/prompt/route.ts | 31 ++++ .../src/app/app/telefonia/ivr/actions.ts | 38 ++++- .../src/app/app/telefonia/ivr/ivr-view.tsx | 70 ++++++++- docker-compose.yml | 8 + docs/INBOUND_ROUTES.md | 32 +++- pnpm-lock.yaml | 24 +++ 10 files changed, 389 insertions(+), 10 deletions(-) create mode 100644 apps/frontend/src/app/api/ivr-menus/[id]/prompt/route.ts diff --git a/TODO.md b/TODO.md index 8db9619..7ed164f 100644 --- a/TODO.md +++ b/TODO.md @@ -2141,10 +2141,40 @@ tela de IVR no frontend") 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 na PHASE 56 -- [ ] Sem pipeline de upload/TTS de prompt de áudio (texto livre/tom - padrão só); sem sub-menu (IVR dentro de IVR) nem destino "fila"; - "Rotas de Entrada" ainda não tem um seletor dedicado de "IVR" como - destino (usuário copia contexto/`ivr_entry` da tela de IVR) +- [ ] Sem TTS (texto→voz); sem sub-menu (IVR dentro de IVR) nem destino + "fila"; "Rotas de Entrada" ainda não tem um seletor dedicado de + "IVR" como destino (usuário copia contexto/`ivr_entry` da tela de IVR) + +## PHASE 59 — Upload de áudio pro prompt do IVR (pedido do usuário: +"adiciona upload de áudio pro prompt do IVR") +- [x] `POST /ivr-menus/:id/prompt` (multipart, `@fastify/multipart` — + primeiro upload de arquivo binário desta API) — só aceita WAV + (cabeçalho RIFF/WAVE validado; sem `mod_shout` nesta implantação, + MP3 nunca funcionaria). Gravado num bind mount NOVO + (`./data/ivr-prompts` no host ↔ `/ivr-prompts` no container + freeswitch) — mesmo raciocínio já usado 3x neste projeto pra + arquivo que o FreeSWITCH precisa enxergar de verdade (gateways + externos, filas do callcenter, spool de gravação), mas na direção + contrária: `apps/api` (host) escreve, o FreeSWITCH lê ao vivo + durante `play_and_get_digits`. Um fetch em rede (S3/HTTP) durante + a chamada foi descartado de propósito — latência desnecessária + pra um prompt de poucos segundos +- [x] `GET /ivr-menus/:id/prompt` (autenticado, `ivr.view`) serve o + preview — mesmo princípio do player de gravações, nunca uma URL + direta pro storage/disco. Deletar o menu ou trocar/remover o + prompt sempre limpa o arquivo do disco (achado real: minha + primeira versão do delete de menu deixava o .wav órfão — corrigido + antes de commitar, confirmado com teste real de upload+delete) +- [x] Tela "Telefonia > IVR" ganhou upload/troca/remoção de áudio por + menu + player `