feat(frontend): Relatórios > Chamadas + Gravações (proxy de áudio autenticado)

Relatórios > Chamadas: lista das últimas 500 chamadas dos últimos 30 dias,
nomes de fila/agente/campanha/disposição resolvidos client-side, busca por
telefone. Só o filtro de telefone nesta primeira versão.

Gravações: lista + player + download. Achado de arquitetura resolvido
antes de codar: <audio src>/<a download> não mandam Authorization Bearer
(só cookie), e a API nunca expõe o storage por URL direta — criado um
proxy autenticado (Route Handler /api/recordings/[id]/audio) que lê o
cookie de sessão, chama a API real com o access token do lado do servidor,
e reencaminha o stream com Content-Disposition: inline (a API manda
attachment). Mesmo princípio de apiFetch: token nunca chega em JS legível.

Testado ponta a ponta contra a API real (estados vazios honestos, proxy
confirmado 401 sem sessão). Smoke test de regressão nas 15 telas
anteriores do tenant + platform, todas 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-29 16:20:01 -03:00
parent f1cadddc91
commit 4d112fcea0
12 changed files with 366 additions and 2 deletions

39
TODO.md
View File

@@ -1015,6 +1015,45 @@ Troncos, Discador > Lista de Bloqueio (agente.md secao 50-51, 41-42, 71,
decisões deliberadas de escopo, mesmas simplificações já existentes
no backend desde a PHASE 10
## PHASE 30 — Frontend: Relatórios > Chamadas, Gravações
(agente.md secao 90-94, 121, 157)
- [x] Relatórios > Chamadas (`/app/relatorios/chamadas`): lista das
últimas 500 chamadas dos últimos 30 dias, nomes de fila/agente/
campanha/disposição resolvidos client-side (mesmo padrão de
Campanhas/Bloqueio), busca por telefone. Só o filtro de telefone
desta primeira versão — o backend já aceita ramal/agente/fila/
campanha/tronco/hangup cause/disposição, sem UI pra eles ainda.
- [x] Gravações (`/app/gravacoes`): lista + player de áudio + download.
**Achado de arquitetura, resolvido antes de codar**: `<audio
src>`/`<a download>` do browser não mandam `Authorization: Bearer
...` (só cookie), e a API deliberadamente nunca expõe o storage por
URL direta (secao 121) — criado um proxy autenticado
(`/api/recordings/[id]/audio`, Route Handler) que lê o cookie de
sessão, refaz a chamada real pra `apps/api` com o access token do
lado do servidor, e reencaminha o stream; troca
`Content-Disposition: attachment` (da API) por `inline` (pro player
tocar em vez de forçar download) — quem quiser baixar usa o
atributo `download` do `<a>`, mesma origem, ignora o header do
servidor. Mesmo princípio de `apiFetch`: token nunca chega em JS
legível no browser.
- [x] Testado ponta a ponta contra a API real (tenant Acme): as duas
telas renderizam estado vazio honesto (nenhuma chamada/gravação
real neste tenant ainda — precisa de uma campanha rodando de
verdade); proxy de áudio confirmado devolvendo 401 sem cookie de
sessão. Smoke test de regressão nas 15 telas anteriores do tenant +
platform, todas 200.
- [ ] **Nunca exercitado**: o proxy de áudio contra uma gravação real
(nenhuma foi gerada nesta sessão — precisa de uma campanha com
`recordingEnabled=true` rodando via `PredictiveDialerEngine` de
verdade, como na PHASE 18, não reproduzido aqui por tempo).
Comportamento de `<audio>`/download nunca visto num browser de
verdade com um WAV real tocando.
- [ ] Relatórios > Chamadas: sem os outros filtros (ramal/agente/fila/
campanha/tronco/hangup cause/disposição) nem seletor de período —
só telefone
- [ ] Relatórios > Consumo (dashboard de quotas, secao 139) — ainda "em
breve"
---
## Riscos conhecidos