docs: registrar bugs encontrados na verificação real em navegador

Usuário testou em produção e confirmou que os dois bugs do commit
anterior (Content-Type em requisições sem corpo, DELETE de supressão sem
motivo) estão corrigidos. Atualiza TODO.md/RELATORIO_FINAL.md para
refletir a verificação visual como concluída, e documenta a lição em
TROUBLESHOOTING.md: teste via curl prova que o backend funciona, não que
o frontend consegue falar com ele.
This commit is contained in:
2026-08-27 20:52:48 -03:00
parent fa8339003d
commit a10a40a489
3 changed files with 55 additions and 16 deletions

30
TODO.md
View File

@@ -321,20 +321,22 @@ banco. Todos os fixtures de teste foram removidos/desativados ao final.
aceite) não exige essa tela. Criar uma tela sem a API por trás seria aceite) não exige essa tela. Criar uma tela sem a API por trás seria
construir um mockup, o que a diretriz do projeto proíbe. Fica construir um mockup, o que a diretriz do projeto proíbe. Fica
registrado como pendência explícita, não descartado silenciosamente. registrado como pendência explícita, não descartado silenciosamente.
- [ ] **Verificação visual em navegador não foi possível nesta sessão** - [x] **Verificação visual em navegador** não foi possível durante a
ambiente é um servidor Debian headless sem display/browser. A construção (ambiente headless, sem display/browser); feita depois
verificação feita foi: `tsc --noEmit` limpo, `eslint` limpo, build de pelo usuário real em `http://10.10.32.142/`, e **encontrou dois bugs
produção (`next build`) gerando as 27 rotas sem erro, e testes reais que o teste via curl nunca teria pego**: (1) o cliente HTTP do
funcionais via `curl` reproduzindo exatamente as chamadas que o frontend mandava `Content-Type: application/json` em requisições sem
navegador faria — login via `/api/auth/login`, cookie de sessão corpo, e o Fastify rejeitava com 400 "body is empty" — quebrava
validado pelo middleware (`/` redireciona para `/login` sem cookie, **todo** botão de ação sem payload (disponibilizar agente, pausar,
libera com cookie), todas as 24 páginas protegidas retornando HTTP publicar dialplan, iniciar/pausar campanha, redefinir senha de
200 através do Nginx, e os endpoints de dados (`/api/dashboard`, ramal); (2) botão "Desbloquear" da lista de bloqueio sempre falhava
`/api/reports/*`, `/api/compliance/*` etc.) retornando dados reais porque a API exige motivo no corpo do DELETE e o cliente não tinha
com o mesmo cookie de sessão. O que **não** foi verificado: renderização como enviá-lo. Ambos corrigidos na raiz (função `request()` central)
visual real, interações de clique/formulário no DOM, responsividade, e confirmados pelo usuário: "testei, funcionou". Detalhes em
tema escuro na prática. Recomenda-se ao usuário abrir `docs/TROUBLESHOOTING.md`. **Lição registrada**: testes via curl
`http://10.10.32.142/` em um navegador para essa validação final. provam que o backend funciona, não que o frontend consegue falar com
ele — teste real em navegador continua sendo necessário antes de dar
uma tela por concluída.
## Fase 9 — Segurança e produção ## Fase 9 — Segurança e produção
- [x] Criptografia de segredos de trunk (AES-256-GCM) — já implementado na - [x] Criptografia de segredos de trunk (AES-256-GCM) — já implementado na

View File

@@ -130,8 +130,12 @@ Itens genuinamente pendentes, nunca escondidos:
3. **`scripts/install.sh`/`update.sh`** não foram testados de ponta a ponta 3. **`scripts/install.sh`/`update.sh`** não foram testados de ponta a ponta
num servidor limpo (o servidor atual já estava provisionado) — validados num servidor limpo (o servidor atual já estava provisionado) — validados
por inspeção linha a linha contra os comandos manuais já executados. por inspeção linha a linha contra os comandos manuais já executados.
4. **Verificação visual em navegador real** não foi feita (ambiente 4. ~~Verificação visual em navegador real~~ — feita posteriormente pelo
headless) — validado via `curl` reproduzindo as chamadas do navegador. usuário em produção. Confirmou que o `curl` sozinho não é suficiente:
achou dois bugs reais de integração frontend↔API invisíveis a testes
via curl (Content-Type em requisições sem corpo quebrando todo botão de
ação; DELETE de supressão sem forma de enviar o motivo exigido). Ambos
corrigidos, usuário confirmou funcionamento. Ver `docs/TROUBLESHOOTING.md`.
5. **Tela de Callbacks** no frontend não existe — o agendamento e a 5. **Tela de Callbacks** no frontend não existe — o agendamento e a
reativação automática funcionam via API/worker (`POST /agent-console/ reativação automática funcionam via API/worker (`POST /agent-console/
dispose`, `GET /api/callbacks`, `callback-sweep.ts`), mas não há uma dispose`, `GET /api/callbacks`, `callback-sweep.ts`), mas não há uma

View File

@@ -3,6 +3,39 @@
Problemas reais encontrados durante o desenvolvimento deste projeto e como Problemas reais encontrados durante o desenvolvimento deste projeto e como
foram diagnosticados/resolvidos — mantido como referência para o futuro. foram diagnosticados/resolvidos — mantido como referência para o futuro.
## "Body cannot be empty when content-type is set to 'application/json'" em quase todo botão de ação
**Sintoma**: usuário real testando no navegador (não `curl`) relatou erro
"body is empty" ao clicar em redefinir senha de um ramal. Investigação
mostrou que o mesmo bug afetava **todo** botão de ação sem payload:
disponibilizar agente, pausar/despausar, publicar dialplan, iniciar/
pausar/parar campanha, etc.
**Causa**: o cliente HTTP central do frontend
(`apps/frontend/src/lib/api-client.ts`) sempre enviava
`Content-Type: application/json` mesmo em requisições sem corpo. O
Fastify rejeita isso com 400 antes de a requisição chegar ao controller
— o `@Body()` do NestJS nem entra em cena, o parser de body do Fastify já
barra antes.
**Por que passou despercebido por tanto tempo**: todo teste de API deste
projeto, em toda fase anterior, foi feito via `curl` — e `curl -X POST`
sem `-H 'Content-Type: ...'` e sem `-d` não define esse header. O
`fetch()` do navegador (usado pelo cliente HTTP real do frontend) sempre
define o header por padrão. Ou seja: **testar só via curl não teria
pegado nunca esse bug** — só apareceu quando um usuário de verdade clicou
em botões reais no navegador. Lição: `curl` prova que o *backend*
funciona: não prova que o *frontend* consegue falar com ele.
**Fix**: só incluir o header `Content-Type` quando `body !== undefined`,
na única função `request()` usada por `get`/`post`/`patch`/`delete`.
**Bug relacionado, achado na mesma revisão**: `DELETE /api/suppression/:id`
exige `removalReason` no corpo, mas `api.delete()` nem aceitava um
argumento de corpo — o botão "Desbloquear" da lista de bloqueio sempre
falhava com 403. Mesma causa raiz (função de ação sem forma de levar
payload), mesmo tipo de bug que só um clique real revela.
## "Argument calledNumber is missing" ao originar chamada ## "Argument calledNumber is missing" ao originar chamada
**Sintoma**: `PrismaClientValidationError` ao reservar um lead, mesmo a **Sintoma**: `PrismaClientValidationError` ao reservar um lead, mesmo a