feat(frontend): Discador > Campanhas — wizard de 7 passos, ciclo de vida, leads

Tela completa de campanhas do discador preditivo: listagem com busca/
filtro/status, wizard de criacao em 7 passos exatamente como a
especificacao nomeia (Geral -> Telefonia -> Discagem -> Horarios ->
Gravacao e IA -> Leads -> Revisao, agente.md secao 170) — nada e criado
ate confirmar na revisao, upload de CSV de leads na mesma acao de
criacao — e detalhe com acoes de ciclo de vida (iniciar/pausar/drenar/
parar, cada botao so aparece quando a transicao e valida pro status
atual), pacing ao vivo, previa de leads com import adicional, e remover.

Corrige de quebra um erro real de build: Server Actions exportadas como
arrow function que so repassam argumentos pra outra funcao quebram
("Server Actions must be async functions") — precisam ser declaradas
como async function de verdade.

Testado ponta a ponta com fila+tronco reais: os 7 passos preenchidos e
revisados, campanha criada com CSV de 3 leads importado, ciclo de vida
completo start->pause->drain->stop->delete via API, e confirmado que uma
campanha iniciada de verdade e pega pelo PredictiveDialerEngine real
rodando em Docker (stats deixam de ser null depois de alguns segundos).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
This commit is contained in:
2026-08-29 10:55:08 -03:00
parent 34ef408f00
commit 927a2623c2
22 changed files with 1375 additions and 10 deletions

56
TODO.md
View File

@@ -812,7 +812,61 @@ app do Tenant + Dashboard (agente.md secao 162, 169)
a PHASE 14, mas a UI não mostra consumo/limite antes de tentar
criar — o erro 403 apareceria só depois do submit
## PHASE 26+ver `agente.md` seções 140 em diante (resto do Frontend,
## PHASE 26 — Frontend: Discador > Campanhas (agente.md secao 63-86, 170)
- [x] Tela `/app/discador/campanhas`: listagem com busca + filtro de
status + ordenação, `StatusBadge` (já existia desde o design
system da Tarifas, criado exatamente pra status de campanha —
só faltava `WAITING_SCHEDULE`, adicionado). Nomes de fila/tronco
resolvidos client-side (a lista busca `/queues`+`/trunks` junto,
sem precisar mudar o backend). Aviso explícito quando o tenant
ainda não tem fila+tronco (pré-requisito real pra criar
qualquer campanha).
- [x] Wizard de criação em 7 passos, exatamente como a especificação
nomeia (secao 170): Geral → Telefonia → Discagem → Horários →
Gravação e IA → Leads → Revisão. Nada é criado até confirmar na
Revisão (`POST /campaigns` e, se um CSV foi anexado no passo
Leads, `POST /campaigns/:id/leads/import` na mesma ação — a
campanha nunca fica "meio criada" se o import falhar, só reporta
o problema). Upload de CSV lido no browser via `FileReader`
(endpoint espera o CSV cru no body, não multipart).
- [x] Detalhe da campanha: `StatusBadge` + ações de ciclo de vida
(Iniciar/Pausar/Drenar/Parar, cada botão só aparece quando a
transição é válida pro status atual — mesma tabela
`ALLOWED_TRANSITIONS` do backend, duplicada no client só pra
UI, o backend segue sendo a autoridade), pacing ao vivo (`GET
/campaigns/:id/stats`), configuração completa, prévia de leads
(até 20, com import adicional) e remover (zona de risco, só
quando parada).
- [x] **Bug real, achado nesta fase**: `startCampaign`/`pauseCampaign`/
`drainCampaign`/`stopCampaign` eram `const x = (id) =>
transition(...)` — Next.js exige que toda Server Action exportada
seja uma `async function` de verdade, não uma arrow function que
apenas retorna uma Promise (erro de build: "Server Actions must
be async functions"). Corrigido; útil lembrar em qualquer ação
futura que só repassa argumentos pra outra função.
- [x] **Achado sistêmico, não é bug novo**: `@@unique([tenantId, name])`
em `Campaign` não exclui `deletedAt` — não dá pra reusar o nome
de uma campanha apagada, mesma classe de problema já documentada
pra Agent/Extension/Trunk/Queue/PauseReason na PHASE 12.
- [x] Testado ponta a ponta contra a API real com fila+tronco reais
(semeados via API pro tenant Acme): os 7 passos do wizard
preenchidos e revisados (screenshot da Revisão confere cada
valor), campanha criada com CSV de 3 leads importado (`imported:
3, total: 3` batendo), ciclo de vida completo start→pause→
drain→stop→delete confirmado via API, e confirmado que uma
campanha iniciada de verdade é pega pelo `PredictiveDialerEngine`
real rodando em Docker (stats deixam de ser null depois de
alguns segundos — `answerProbability`/`pacingFactor` reais
aparecem na tela, não só o placeholder "sem tentativas ainda").
- [ ] `Discador > Leads` (tela dedicada, fora do wizard/detalhe) e
`Importações` continuam "em breve" — a prévia de leads no
detalhe da campanha cobre o básico, mas não pagina nem edita
leads individualmente
- [ ] O detalhe da campanha não escuta o WebSocket de monitoramento
(secao 161) — `stats`/`callsInFlight` só atualizam ao recarregar
a página, não em tempo real
## PHASE 27+ — ver `agente.md` seções 140 em diante (resto do Frontend,
Security, Tests)
---