- apps/frontend: Next.js 15 (App Router) + Tailwind v4 + componentes estilo shadcn/ui sobre Radix UI + TanStack Query. Tema light/dark, logo processada. Menu completo (secao 52) com gating por permissao real. Todas as telas do checklist de aceite (secao 90) conectadas a endpoints reais (nao mockup): login, usuarios, perfis/permissoes, ramais/troncos, dialplan, filas/agentes, console do agente, campanhas (CPS/CSV/ iniciar/pausar), monitoramento ao vivo (polling, nao WebSocket real), TME/TMA, busca/export de chamadas, administracao do Asterisk, auditoria - infrastructure/nginx: reverse proxy colocando frontend+API na mesma origem (porta 80), antecipado da Fase 9 pois a API nao publica porta propria - apps/api: GET /api/monitoring/agents (estado corrente real via agent_state_events em aberto) e filtro queueId em GET /api/reports/calls Pendencia registrada: tela de Callbacks nao implementada (schema existe desde a Fase 6, mas nunca houve controller/service — construir a tela sem API real seria mockup). Verificacao visual em navegador nao foi possivel neste ambiente headless; validado via tsc/eslint/next build limpos + curl reproduzindo as chamadas do navegador (middleware de auth, 24 paginas protegidas via Nginx, endpoints de dados com cookie de sessao).
355 lines
23 KiB
Markdown
355 lines
23 KiB
Markdown
# B2BCall — TODO Operacional
|
||
|
||
Checklist vivo. Marcar `[x]` somente após testar. Não deixar item concluído sem
|
||
validação. Ver critérios de aceite completos em `docs/ARCHITECTURE.md` e no prompt
|
||
mestre original (`agente.md`, seções 90-93).
|
||
|
||
## Fase 0 — Infraestrutura base
|
||
- [x] Levantamento do servidor (SO, recursos, rede)
|
||
- [x] docs/ARCHITECTURE.md
|
||
- [x] TODO.md
|
||
- [x] Git init + .gitignore + primeiro commit
|
||
- [x] Instalar Docker Engine + Compose plugin (29.7.2 / Compose v5.5.0)
|
||
- [x] Estrutura de diretórios do monorepo
|
||
- [x] .env.example (+ scripts/generate-secrets.sh, .env real gerado localmente)
|
||
- [ ] nftables (firewall base, default-deny administrativo, sem bloquear SSH) —
|
||
adiado para a Fase 9, quando houver serviços/portas reais a proteger
|
||
|
||
## Fase 1 — Dados e cache
|
||
- [x] docker-compose: serviço postgres 17-alpine (schemas application/asterisk) — testado, healthy
|
||
- [x] docker-compose: serviço redis 7-alpine (auth, AOF, maxmemory 100mb) — testado, healthy
|
||
- [x] Health checks postgres/redis
|
||
- [ ] packages/database: schema inicial + migrations tool
|
||
|
||
## Fase 2 — Asterisk
|
||
- [x] Dockerfile Asterisk 22.10.1 LTS compilado do fonte (host network) —
|
||
Debian trixie não tem mais o pacote `asterisk` nos repos oficiais
|
||
- [x] PJSIP (transport UDP:5060; chan_sip nem existe mais a partir do Ast 21)
|
||
- [x] AMI habilitado — testado login via 127.0.0.1:5038, bloqueado na LAN via nftables
|
||
- [x] ARI habilitado — testado GET /ari/asterisk/info via 127.0.0.1:8088, 401 sem auth
|
||
- [x] Realtime ODBC -> Postgres schema `asterisk` (sorcery.conf + extconfig.conf
|
||
+ res_odbc.conf) — `pjsip show endpoints` consulta o Postgres sem erro
|
||
- [x] CDR via cdr_adaptive_odbc — testado, grava em asterisk.cdr (ANSWERED,
|
||
duration/billsec corretos). Nota: coluna `end` renomeada para
|
||
`end_time` (palavra reservada no Postgres, cdr_adaptive_odbc não
|
||
quota identificadores)
|
||
- [x] CEL via cel_odbc — testado, grava em asterisk.cel (CHAN_START/ANSWER/
|
||
HANGUP/CHAN_END)
|
||
- [x] queue_log habilitado (padrão do app_queue, arquivo em /var/log/asterisk)
|
||
- [x] Dialplan inicial de teste (contexto `b2bcall-healthcheck`, exten 600,
|
||
Answer/Playback/Echo) — testado via `channel originate ... extension`
|
||
- [x] nftables protegendo AMI/ARI/SIP na interface da LAN (ens18), trazido da
|
||
Fase 9 para já — não testado a partir de outra máquina na LAN (só
|
||
localmente, onde tráfego para o próprio IP vai por `lo` e não
|
||
exercita a regra; revisar quando OpenSIPS/outro host estiver disponível)
|
||
|
||
### Pendências conhecidas da Fase 2 (não bloqueiam, revisitar depois)
|
||
- [ ] `res_pjsip_outbound_registration` recusa carregar no boot (mensagem
|
||
"no existing documentation" para o tipo 'registration' do sorcery) —
|
||
carregamento manual via `module load` funciona mas é desfeito por uma
|
||
flag interna de "declined at boot". Só afeta troncos com registration
|
||
outbound; endpoints/aors funcionam normalmente. Investigar na Fase 4
|
||
ao implementar cadastro de troncos com registration.
|
||
- [ ] `res_config_ldap` gera ERROR de log no boot (sem LDAP configurado,
|
||
não usamos). Cosmético — considerar `noload` em modules.conf.
|
||
- [ ] `cdr_pgsql`/`cel_pgsql` não compilaram (falta libpq-dev na imagem);
|
||
usamos `cdr_adaptive_odbc`/`cel_odbc` como alternativa funcional via
|
||
ODBC — decisão adequada, não precisa dos módulos pgsql nativos.
|
||
|
||
## Fase 3 — Backend base
|
||
- [x] apps/api (NestJS 11 + Fastify) bootstrap — containerizado, healthy
|
||
- [x] packages/database (Prisma) + packages/shared (catálogo de permissões)
|
||
— decisão de ORM documentada em docs/ARCHITECTURE.md 3.6.1
|
||
- [x] Migration inicial aplicada (users/sessions/roles/permissions/
|
||
user_roles/role_permissions/audit_logs/password_reset_tokens)
|
||
- [x] Seed (40 permissões, 4 perfis, bootstrap super_admin com senha
|
||
aleatória em FIRST_LOGIN.txt chmod 600) — testado
|
||
- [x] Autenticação (Argon2id, access JWT + refresh opaco com rotação,
|
||
cookies HttpOnly/SameSite=Lax, refresh_token restrito a /api/auth) —
|
||
testado: login, /me, refresh, change-password, revogação de sessão
|
||
- [x] Rate limiting de login progressivo via Redis (5/60s, bloqueio
|
||
1min→2min→4min...até 1h por IP) — testado com 7 tentativas seguidas
|
||
- [x] RBAC completo (users/roles/permissions/user_roles/role_permissions)
|
||
reforçado no backend via PermissionsGuard — testado (nega sem
|
||
permissão, permite com todas as permissões) + unit tests
|
||
- [x] Proteção contra auto-elevação de privilégio (usuário não altera os
|
||
próprios roleIds) — testado, HTTP 403
|
||
- [x] Auditoria (audit_logs) chamada explicitamente em cada ação sensível
|
||
(login/login_failed/logout/password_changed/user_created/
|
||
user_updated/role_*) com redação de campos sensíveis — testado via
|
||
GET /api/audit (roles.manage/audit.view), paginação server-side
|
||
- [x] Health checks /api/health, /api/health/live, /api/health/ready
|
||
(Postgres + Redis reais, sem dado fake) — testado
|
||
- [x] Swagger/OpenAPI em /api/docs, desabilitável via SWAGGER_ENABLED=false
|
||
— testado (200 com flag default true)
|
||
- [x] Logs estruturados JSON (nestjs-pino) com request_id de correlação e
|
||
redação de senha/tokens/cookies
|
||
- [x] Filtro global de exceções: nunca expõe erro cru, sempre requestId
|
||
- [x] Lint (eslint --fix) e testes unitários (PermissionsGuard) passando
|
||
- [ ] Recuperação de senha por e-mail: lógica completa (token com
|
||
expiração, hash, uso único), mas envio real via SMTP é um stub que
|
||
só loga — falta credencial SMTP real (.env SMTP_*), meramente
|
||
externo — trocar MailerService por nodemailer quando houver
|
||
|
||
## Fase 4 — Telefonia (camada de aplicação)
|
||
- [x] packages/telephony: cliente AMI próprio (TCP raw, sem dependência de
|
||
terceiros) + TelephonyProvider + AsteriskTelephonyProvider — testado
|
||
contra o Asterisk real: login, PJSIPShowEndpoints/QueueStatus (lista
|
||
vazia tratada corretamente), Command (parsing do formato real do
|
||
Asterisk 22: múltiplos headers "Output:", não mais "Follows"/"--END
|
||
COMMAND--"), Originate, stream de eventos
|
||
- [x] apps/asterisk-events (worker dedicado, containerizado) — conecta via
|
||
host.docker.internal (rede bridge -> Asterisk em host network),
|
||
normaliza eventos relevantes, persiste ExtensionState (Postgres),
|
||
publica em Redis pub/sub (b2bcall:events:asterisk e :extensions),
|
||
heartbeat para health check — testado ponta a ponta com chamada real
|
||
- [x] Schema Prisma: Trunk, Extension, ExtensionState + criptografia de
|
||
segredos AES-256-GCM (packages/shared/secret-crypto.ts)
|
||
- [x] CRUD Troncos (IP/AUTH/REGISTRATION, CPS máximo, ACL, secret cifrado
|
||
AES-256-GCM, teste de status) — testado: criado via API, verificado
|
||
que o Asterisk enxerga o endpoint via realtime SEM reload, status
|
||
consultado via PJSIPShowContacts, exclusão em cascata confirmada
|
||
- [x] CRUD Ramais (senha SIP gerada automaticamente, nunca reexibida exceto
|
||
na criação/reset, reset endpoint) — testado igual aos troncos
|
||
- [x] GET /api/monitoring/extensions — status com prioridade de cor
|
||
(offline/available/busy; pausa e agente logado chegam na Fase 5)
|
||
- [ ] Painel visual de Ramais (WebSocket) — backend (gateway WS) ainda
|
||
pendente; ExtensionState/pub-sub e o endpoint de monitoramento já
|
||
prontos como base (frontend consumirá via WS na Fase 8)
|
||
- [x] Dialplan estruturado e versionado (dialplan_entries + dialplan_versions)
|
||
— testado ponta a ponta: criar entradas -> publicar -> gera
|
||
b2bcall-dialplan.conf (volume compartilhado api<->asterisk) -> AMI
|
||
"dialplan reload" -> "dialplan show" confirma ativo, SEM restart.
|
||
Validação estrita de caracteres em cada campo (nunca aceita entrada
|
||
capaz de injetar linhas de config). Rollback automático se a
|
||
verificação pós-reload detectar erro; rollback manual para qualquer
|
||
versão anterior via endpoint dedicado. Limitação conhecida: detecta
|
||
falhas estruturais/sintáticas, não erros semânticos como nome de
|
||
aplicação Asterisk inexistente (só se manifesta em tempo de chamada)
|
||
- [x] Administração do Asterisk: status (AMI + heartbeat asterisk-events),
|
||
module show, e diagnóstico com ALLOWLIST ESTRITA de comandos exatos
|
||
(core show uptime/channels/version, pjsip show *, queue show, module
|
||
show, dialplan show) — testado: comando permitido executa, comando
|
||
arbitrário rejeitado com 400. Abas de configuração fina (pjsip/rtp/
|
||
cdr/cel/logs como formulário editável) ficam para quando o fluxo de
|
||
"editor avançado com backup/validação" for generalizado além do
|
||
dialplan — no momento config geral do Asterisk é só leitura via
|
||
status/diagnóstico, não tem tela de edição de asterisk.conf/rtp.conf
|
||
|
||
## Fase 5 — Call Center
|
||
- [x] Agentes (agents, separado de users 1:1) + agent_sessions +
|
||
agent_state_events + agent_pause_events — schema Prisma completo
|
||
- [x] Máquina de estados do agente (LOGGED_IN→AVAILABLE→PAUSED→AVAILABLE,
|
||
logout) com histórico início/fim por estado — testado ponta a ponta
|
||
contra o Asterisk real: login cria sessão, "disponível" faz AMI
|
||
QueueAdd (confirmado via `queue show`: membro dinâmico aparece),
|
||
"pausar" faz QueuePause com motivo (confirmado: "paused:Almoco"
|
||
aparece ao vivo), "despausar" reverte, "logout" faz QueueRemove
|
||
(confirmado: fila volta a "No Members")
|
||
- [x] Motivos de pausa (CRUD) — sem permissão dedicada no catálogo da
|
||
seção 11, tratado sob settings.manage
|
||
- [x] Filas (CRUD + 8 estratégias do enum QueueStrategy) — gera
|
||
queues.conf via o mesmo padrão do dialplan (arquivo compartilhado +
|
||
reload), mas SEM membros estáticos: membership é 100% dinâmica via
|
||
AMI (login/logout do agente), evitando dessincronia entre duas
|
||
fontes de verdade
|
||
- [x] Tela do agente (backend completo: login/available/pause/unpause/
|
||
logout via /api/agent-console/*) — disposição de chamada fica para
|
||
quando existir uma chamada de verdade para dispor (Fase 6)
|
||
- [ ] Disposições de chamada (CRUD + ações: callback, DNC) — adiado para
|
||
Fase 6 junto com Campanhas/Leads, já que disposição só faz sentido
|
||
quando há chamadas de campanha reais para classificar
|
||
- [ ] Callback (agendamento + scheduler) — idem, depende de leads/campanhas
|
||
- [x] Monitoramento de Filas (tempo real): GET /api/monitoring/queues
|
||
(chamadas aguardando, maior espera, agentes logados/pausados/
|
||
disponíveis) via AMI QueueStatus ao vivo. TME/TMA/SLA/taxa de
|
||
abandono HISTÓRICOS ficam para a Fase 7 (dependem de consolidação
|
||
de CDR/CEL/queue_log) — não inventamos número aqui
|
||
|
||
## Fase 6 — Campanhas e discador preditivo
|
||
- [x] CRUD Campanhas (todos os campos da seção 24) — testado: criação com
|
||
defaults corretos, máquina de estados (DRAFT/READY/RUNNING/PAUSED/
|
||
DRAINING/STOPPED/COMPLETED) rejeitando transições inválidas (ex.:
|
||
PAUSED->PAUSED = 400), edição bloqueada com campanha RUNNING
|
||
- [x] Import CSV streaming (detecção automática de delimitador — vírgula/
|
||
ponto-e-vírgula/tab —, mapeamento de coluna por nome de header,
|
||
validação+normalização, dedupe dentro do arquivo E contra leads já
|
||
existentes na campanha, CSV de rejeitados com motivo, modo dryRun
|
||
para preview sem persistir) — testado com CSV real (5 linhas: 3
|
||
válidas, 1 inválida, 1 duplicada — todos os contadores bateram)
|
||
- [x] Normalização de telefone (packages/shared, BR — E.164, DDD/celular
|
||
validados) — 7 testes unitários passando
|
||
- [x] Lista de supressão (DNC): CRUD + import CSV + remoção sempre exige
|
||
motivo e é auditada — checagem obrigatória pré-originação
|
||
(isSuppressed) implementada e usada pelo dialer-worker (ver abaixo)
|
||
- [x] CPS limiter (token bucket via Lua atômico no Redis, dois buckets
|
||
independentes campanha+tronco, min() dos dois — multi-worker seguro)
|
||
- [x] Reserva concorrente de leads (SQL único com FOR UPDATE SKIP LOCKED —
|
||
atômico; timeout de 90s libera reservas órfãs após queda de worker)
|
||
- [x] Idempotência de originação (DialAttempt.id é o attempt_id de negócio,
|
||
nunca reoriginado; UNIQUEID nunca usado como PK — seção 97)
|
||
- [x] PredictiveDialerEngine (EWMA de answerProbability/avgTalkTimeSeconds/
|
||
abandonRate, previsão de liberação de agentes via TMA histórico +
|
||
tempo decorrido, ajuste de pacing) — apps/dialer-worker/src/
|
||
predictive-engine.ts, 100% lógica pura testável, 14 testes unitários
|
||
- [x] Controle de abandono (pacing cai imediatamente acima da meta; nunca
|
||
sobe "porque nada de ruim aconteceu" durante período ocioso — bug
|
||
real encontrado e corrigido durante o teste do cenário da seção 66)
|
||
- [ ] AMD opcional por campanha — campo existe no schema/DTO, detecção real
|
||
(app AMD do Asterisk) ainda não integrada à originação (ver
|
||
docs/PREDICTIVE_DIALER.md seção 7)
|
||
- [ ] Wrap-up time — campo existe, mas transição automática do agente para
|
||
WRAP_UP via eventos reais (AgentComplete) não implementada; hoje só
|
||
funciona no modo simulação e nas transições manuais da Fase 5 (ver
|
||
docs/PREDICTIVE_DIALER.md seção 7)
|
||
- [x] Retry engine (regras por causa configuráveis por campanha via JSON,
|
||
default seção 79, nunca rediscagem infinita via maxAttempts) —
|
||
testado ponta a ponta (NO_ANSWER agendou retry corretamente)
|
||
- [x] Horário de campanha (timezone via Intl.DateTimeFormat, dias da
|
||
semana, HH:MM, WAITING_SCHEDULE computado) — 6 testes unitários
|
||
cobrindo timezone, dias, startDate/endDate
|
||
- [x] Lock distribuído por campanha (Redis SET NX PX + token de posse +
|
||
renovação/liberação seguras via Lua — nunca libera lock de outro dono)
|
||
- [x] docs/PREDICTIVE_DIALER.md — algoritmo, decisões, bugs encontrados e
|
||
corrigidos durante o teste, limitações conhecidas documentadas
|
||
|
||
**Testado ponta a ponta contra containers reais** (Postgres/Redis/Asterisk):
|
||
campanha completa criada → agente logado/disponível via API real → leads
|
||
importados → campanha iniciada → reserva atômica → CPS respeitado →
|
||
simulação de NO_ANSWER (retry agendado) e ANSWERED (AGENT_CONNECTED, EWMA
|
||
atualizada ao vivo no Redis) → parada sem derrubar chamadas em andamento.
|
||
|
||
## Fase 7 — CDR, métricas e relatórios
|
||
- [x] Modelo consolidado de chamadas — `DialAttempt` já concentra os timestamps
|
||
reais (started/ringing/answered/queued/agentConnected/ended) e
|
||
hangupCause; CDR/CEL do Asterisk seguem gravados via `cdr_adaptive_odbc`/
|
||
`cel_odbc` na base `asterisk` (não duplicados no domínio da app). Não há
|
||
correlação automática CDR↔DialAttempt para chamadas reais (não
|
||
simuladas) além do timeout de segurança — ver nota em
|
||
docs/PREDICTIVE_DIALER.md.
|
||
- [x] Reconciliação de estados órfãos após restart — `reconciliation.ts`
|
||
(`reconcileOrphanedAttempts`), rodando a cada 60s via `main.ts`; força
|
||
`DialAttempt`s presos há >10min para `FAILED`/`RECONCILED_ORPHAN` e
|
||
devolve o lead para `READY`. Testado por unit tests; não exercitado
|
||
neste ciclo via kill real do worker (fica para Fase 10).
|
||
- [x] TME / TMA — cálculo real em `reports.service.ts#calculateMetrics` e
|
||
`dashboard.service.ts#overview/campaignDashboard`, a partir de
|
||
`queuedAt`/`agentConnectedAt`/`endedAt`. Validado end-to-end: campanha
|
||
de teste "Campanha F7" retornou `tmeSeconds: 0.023`, `tmaSeconds: 136.6`
|
||
batendo com os timestamps reais da chamada simulada conectada.
|
||
- [x] Relatório de Chamadas — `GET /api/reports/calls` (filtros
|
||
campaignId/agentId/dispositionId/phone/state/from/to, paginação
|
||
server-side) e `GET /api/reports/calls/export` (CSV via
|
||
`csv-stringify`, teto de 100k linhas). Testado contra as 10 tentativas
|
||
reais da campanha de teste — paginação e filtros funcionando.
|
||
- [x] Relatório de Agentes — `GET /api/reports/agents/:agentId`, tempo real
|
||
por estado a partir de `agent_state_events`/`agent_pause_events`.
|
||
Validado: agente de teste retornou `timeByStateSeconds` com
|
||
LOGGED_IN/AVAILABLE/IN_CALL/WRAP_UP batendo com o ciclo real observado.
|
||
- [x] Dashboard geral — `GET /api/dashboard` e `/api/dashboard/calls-by-hour`,
|
||
cards/gráficos 100% derivados de `dial_attempts`/`agent_state_events`
|
||
reais (sem números inventados, seção 72). Validado com dados reais.
|
||
- [x] Dashboard do discador — `GET /api/dashboard/campaigns/:id`, combina
|
||
contagens Postgres com snapshot EWMA do Redis
|
||
(`dialer:stats:{campaignId}`). Validado: `abandonRate` e
|
||
`pacingFactor` refletiram corretamente o abandono real ocorrido no
|
||
teste.
|
||
- [x] Compliance de Chamadas — `ComplianceSettings` (singleton configurável:
|
||
limiar de chamada curta, limites de tentativas por número/dia/mês,
|
||
threshold de alto volume) + `GET/PATCH /api/compliance/settings` e
|
||
`GET /api/compliance/indicators` (contadores reais de chamadas
|
||
hoje/mês, chamadas curtas, abandonadas, números acima do limite
|
||
diário, alertas). Validado com dados reais da campanha de teste.
|
||
|
||
**Teste E2E executado (2026-08-27):** criados trunk/fila/ramal/agente/usuário/
|
||
campanha de teste ("Campanha F7"), 10 leads importados via CSV, campanha
|
||
rodada em `DIALER_SIMULATION=true`. Confirmado em produção real (não só no
|
||
harness isolado): `DialAttempt.agentId` populado corretamente ao conectar
|
||
(bug da Fase 7 corrigido em `agent-call-binding.ts`); ciclo completo de
|
||
`AgentStateEvent` `AVAILABLE → IN_CALL → WRAP_UP → AVAILABLE`; `abandonRate`
|
||
no Redis atualizado de `0` para `0.05` após abandono real (bug do EWMA não
|
||
conectado ao `campaign-worker.ts` corrigido); todos os endpoints de
|
||
relatórios/dashboard/compliance retornando dados reais e coerentes com o
|
||
banco. Todos os fixtures de teste foram removidos/desativados ao final.
|
||
|
||
## Fase 8 — Frontend completo
|
||
- [x] Bootstrap Next.js 15 (App Router) + Tailwind v4 + componentes estilo
|
||
shadcn/ui (escritos à mão sobre Radix UI, sem CLI interativo) +
|
||
TanStack Query. WS client não implementado (ver nota abaixo) —
|
||
telas ao vivo usam polling via `refetchInterval` (5–15s conforme a
|
||
tela), suficiente para o volume atual mas não é WebSocket real.
|
||
- [x] Logo processada (`b2bcall.png` copiada, original nunca modificada) +
|
||
tema light/dark com toggle manual + `prefers-color-scheme`.
|
||
- [x] Menu completo (seção 52) com gating por permissão real
|
||
(`RequirePermission` + itens de menu ocultos por `useAuth().can()`),
|
||
Dashboard/Discador/Call Center/Telefonia/Monitoramento/
|
||
Relatórios/Sistema + Console do Agente.
|
||
- [x] Reverse proxy Nginx (`infrastructure/nginx/`) colocando frontend e
|
||
API na mesma origem (porta 80, único serviço publicado à LAN) —
|
||
antecipado da Fase 9 (seção 87) porque sem isso não havia como um
|
||
navegador de verdade alcançar a API (que não publica porta própria).
|
||
- [x] Todas as telas do checklist de aceite (seção 90) implementadas e
|
||
conectadas a endpoints reais: login, usuários, perfis/permissões,
|
||
ramais, status de ramal, troncos, dialplan, filas, agentes,
|
||
associação agente↔fila, motivos de pausa, console do agente
|
||
(login/pausa/retirar pausa), campanhas (CPS, importação CSV,
|
||
iniciar/pausar), monitoramento (discagem/agentes/fila) ao vivo,
|
||
TME/TMA, busca de chamadas com filtro por fila/agente/estado/
|
||
telefone/data, exportação CSV, administração do Asterisk
|
||
(status + diagnóstico allowlist + reload), auditoria.
|
||
Adicionado `queueId` a `GET /api/reports/calls` (não existia antes)
|
||
para permitir o filtro por fila explicitamente pedido na seção 90.
|
||
- [ ] Callbacks (menu da seção 52) — **não implementado nesta fase**: o
|
||
modelo `Callback` existe no schema desde a Fase 6, mas nunca houve
|
||
controller/service de API para ele, e a seção 90 (critério literal de
|
||
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
|
||
registrado como pendência explícita, não descartado silenciosamente.
|
||
- [ ] **Verificação visual em navegador não foi possível nesta sessão** —
|
||
ambiente é um servidor Debian headless sem display/browser. A
|
||
verificação feita foi: `tsc --noEmit` limpo, `eslint` limpo, build de
|
||
produção (`next build`) gerando as 27 rotas sem erro, e testes
|
||
funcionais via `curl` reproduzindo exatamente as chamadas que o
|
||
navegador faria — login via `/api/auth/login`, cookie de sessão
|
||
validado pelo middleware (`/` redireciona para `/login` sem cookie,
|
||
libera com cookie), todas as 24 páginas protegidas retornando HTTP
|
||
200 através do Nginx, e os endpoints de dados (`/api/dashboard`,
|
||
`/api/reports/*`, `/api/compliance/*` etc.) retornando dados reais
|
||
com o mesmo cookie de sessão. O que **não** foi verificado: renderização
|
||
visual real, interações de clique/formulário no DOM, responsividade,
|
||
tema escuro na prática. Recomenda-se ao usuário abrir
|
||
`http://10.10.32.142/` em um navegador para essa validação final.
|
||
|
||
## Fase 9 — Segurança e produção
|
||
- [ ] Criptografia de segredos de trunk (AES-256-GCM)
|
||
- [ ] HTTP security headers, CORS, CSRF, Helmet
|
||
- [ ] nftables final revisado
|
||
- [ ] Logs estruturados JSON (sem segredos)
|
||
- [ ] Correlation IDs (request_id/attempt_id/call_id)
|
||
- [ ] Métricas Prometheus (/metrics)
|
||
- [ ] Bootstrap super_admin (senha aleatória, FIRST_LOGIN.txt, forçar troca)
|
||
- [ ] Seed (permissões, perfis, pausas, disposições) sem dados fake em produção
|
||
- [ ] Backup/restore (postgres, asterisk config, .env seguro)
|
||
- [ ] scripts/install.sh, update.sh, backup.sh, restore.sh, healthcheck.sh
|
||
- [ ] Nginx reverse proxy (80/443, WS, HTTPS documentado)
|
||
|
||
## Fase 10 — Testes e aceite
|
||
- [ ] Unit tests (predictive engine, CPS limiter, permissions, phone norm, retry,
|
||
state machines, TME/TMA, scheduling)
|
||
- [ ] Integration tests (postgres, redis, repositories, API, AMI mock)
|
||
- [ ] E2E (login → ... → RBAC, conforme seção 64)
|
||
- [ ] Modo simulação (DIALER_SIMULATION=true) + testes do predictive engine
|
||
- [ ] Prova de CPS respeitado / concorrência máxima / sem discagem dupla /
|
||
pausa efetiva / recuperação após restart / pacing reage a abandono /
|
||
supressão respeitada / horário respeitado
|
||
- [ ] Quality gate (lint, typecheck, unit, integration, e2e, compose config,
|
||
compose ps, health checks) — tudo verde
|
||
- [ ] Aceite de segurança (seção 92)
|
||
- [ ] Aceite Asterisk (seção 93, comandos documentados)
|
||
- [ ] README final completo
|
||
- [ ] Relatório final da implementação
|
||
|
||
---
|
||
**Nota de ambiente:** VM atual com 1.9 GiB RAM / 2 vCPU — adequada para dev e
|
||
simulação, não para carga real de produção. Ver docs/ARCHITECTURE.md seção 1.
|