Files
b2bcall/TODO.md
B2BCall Bootstrap 167776ff63 feat: add campaign management
- packages/database: Campaign, LeadImport, Lead, DialAttempt,
  CallDisposition, Callback, SuppressionEntry (agente.md secao 53)
- packages/shared: normalizePhone (BR, E.164) com 7 testes unitarios
- apps/api/src/campaigns: CRUD completo (todos os campos da secao 24) com
  maquina de estados validada (DRAFT/READY/RUNNING/PAUSED/DRAINING/
  STOPPED/COMPLETED) — edicao bloqueada com campanha RUNNING
- apps/api/src/leads: import CSV via streaming multipart (deteccao
  automatica de delimitador, mapeamento de coluna por header, validacao+
  normalizacao+dedupe em lotes de 500, CSV de rejeitados com motivo, modo
  dryRun para preview)
- apps/api/src/suppression: CRUD + import CSV da lista de bloqueio,
  remocao sempre exige motivo e e auditada, isSuppressed() pronto para o
  dialer-worker checar antes de originar
- apps/api/src/dispositions: CRUD de disposicoes de chamada

Testado ponta a ponta: campanha criada com defaults corretos, import de
CSV real (3 validos/1 invalido/1 duplicado, contadores batendo), leads
persistidos com telefone normalizado, transicoes de estado da campanha
rejeitando movimentos invalidos (PAUSED->PAUSED = 400).
2026-08-27 14:48:46 -03:00

15 KiB

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

  • Levantamento do servidor (SO, recursos, rede)
  • docs/ARCHITECTURE.md
  • TODO.md
  • Git init + .gitignore + primeiro commit
  • Instalar Docker Engine + Compose plugin (29.7.2 / Compose v5.5.0)
  • Estrutura de diretórios do monorepo
  • .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

  • docker-compose: serviço postgres 17-alpine (schemas application/asterisk) — testado, healthy
  • docker-compose: serviço redis 7-alpine (auth, AOF, maxmemory 100mb) — testado, healthy
  • Health checks postgres/redis
  • packages/database: schema inicial + migrations tool

Fase 2 — Asterisk

  • Dockerfile Asterisk 22.10.1 LTS compilado do fonte (host network) — Debian trixie não tem mais o pacote asterisk nos repos oficiais
  • PJSIP (transport UDP:5060; chan_sip nem existe mais a partir do Ast 21)
  • AMI habilitado — testado login via 127.0.0.1:5038, bloqueado na LAN via nftables
  • ARI habilitado — testado GET /ari/asterisk/info via 127.0.0.1:8088, 401 sem auth
  • Realtime ODBC -> Postgres schema asterisk (sorcery.conf + extconfig.conf + res_odbc.conf) — pjsip show endpoints consulta o Postgres sem erro
  • 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)
  • CEL via cel_odbc — testado, grava em asterisk.cel (CHAN_START/ANSWER/ HANGUP/CHAN_END)
  • queue_log habilitado (padrão do app_queue, arquivo em /var/log/asterisk)
  • Dialplan inicial de teste (contexto b2bcall-healthcheck, exten 600, Answer/Playback/Echo) — testado via channel originate ... extension
  • 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

  • apps/api (NestJS 11 + Fastify) bootstrap — containerizado, healthy
  • packages/database (Prisma) + packages/shared (catálogo de permissões) — decisão de ORM documentada em docs/ARCHITECTURE.md 3.6.1
  • Migration inicial aplicada (users/sessions/roles/permissions/ user_roles/role_permissions/audit_logs/password_reset_tokens)
  • Seed (40 permissões, 4 perfis, bootstrap super_admin com senha aleatória em FIRST_LOGIN.txt chmod 600) — testado
  • 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
  • Rate limiting de login progressivo via Redis (5/60s, bloqueio 1min→2min→4min...até 1h por IP) — testado com 7 tentativas seguidas
  • 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
  • Proteção contra auto-elevação de privilégio (usuário não altera os próprios roleIds) — testado, HTTP 403
  • 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
  • Health checks /api/health, /api/health/live, /api/health/ready (Postgres + Redis reais, sem dado fake) — testado
  • Swagger/OpenAPI em /api/docs, desabilitável via SWAGGER_ENABLED=false — testado (200 com flag default true)
  • Logs estruturados JSON (nestjs-pino) com request_id de correlação e redação de senha/tokens/cookies
  • Filtro global de exceções: nunca expõe erro cru, sempre requestId
  • 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)

  • 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
  • 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
  • Schema Prisma: Trunk, Extension, ExtensionState + criptografia de segredos AES-256-GCM (packages/shared/secret-crypto.ts)
  • 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
  • CRUD Ramais (senha SIP gerada automaticamente, nunca reexibida exceto na criação/reset, reset endpoint) — testado igual aos troncos
  • 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)
  • 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)
  • 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

  • Agentes (agents, separado de users 1:1) + agent_sessions + agent_state_events + agent_pause_events — schema Prisma completo
  • 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")
  • Motivos de pausa (CRUD) — sem permissão dedicada no catálogo da seção 11, tratado sob settings.manage
  • 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
  • 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
  • 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

  • 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
  • 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)
  • Normalização de telefone (packages/shared, BR — E.164, DDD/celular validados) — 7 testes unitários passando
  • 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)
  • CPS limiter (token bucket, coordenado via Redis, multi-worker)
  • Reserva concorrente de leads (FOR UPDATE SKIP LOCKED + timeout)
  • Idempotência de originação (attempt_id/call_id/uniqueid/linkedid, state machine)
  • PredictiveDialerEngine (EWMA, pacing, previsão de liberação de agentes)
  • Controle de abandono (pacing cai / suspende originação)
  • AMD opcional por campanha
  • Wrap-up time
  • Retry engine (regras por causa, limite de tentativas)
  • Horário de campanha (timezone, dias/horários, WAITING_SCHEDULE)
  • Lock distribuído por campanha (Redis)
  • docs/PREDICTIVE_DIALER.md

Fase 7 — CDR, métricas e relatórios

  • Modelo consolidado de chamadas (CDR+CEL+AMI+queue_log)
  • Reconciliação de estados órfãos após restart
  • TME / TMA (definições documentadas, cálculo correto)
  • Relatório de Chamadas (filtros, paginação server-side, export CSV)
  • Relatório de Agentes (tempos, pausas detalhadas)
  • Dashboard geral (cards + gráficos reais)
  • Dashboard do discador (por campanha, tempo real)
  • Compliance de Chamadas (parâmetros configuráveis, alertas, contadores)

Fase 8 — Frontend completo

  • Bootstrap Next.js + Tailwind + shadcn/ui + TanStack Query + WS client
  • Logo processada (b2bcall.png) + tema light/dark
  • Menu completo (seção 52)
  • Todas as telas do checklist de aceite (seção 90)

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.