feat: Fase 9/10 — métricas, scripts operacionais, callback/wrap-up e aceite final
Fase 9 (segurança e produção): - GET /api/metrics: endpoint Prometheus com métricas reais (chamadas, agentes, filas, CPS por campanha), protegido por permissão - scripts/backup.sh, restore.sh, healthcheck.sh, install.sh, update.sh — testados contra o ambiente real (backup.sh e healthcheck.sh rodados de verdade; install.sh/update.sh validados por inspeção, ambiente atual já provisionado) - POST /api/agent-console/dispose: aplica disposição de chamada de verdade (lacuna deixada aberta desde a Fase 6), com ações CALLBACK (agenda retorno) e DO_NOT_CALL (suprime automaticamente) - apps/dialer-worker/src/callback-sweep.ts: reativa leads com callback vencido; GET /api/callbacks para consulta - apps/dialer-worker/src/wrap-up-sweep.ts: transição automática WRAP_UP -> AVAILABLE + despausa real na fila do Asterisk. Exigiu corrigir main.ts para conectar ao AMI mesmo em DIALER_SIMULATION=true (DIALER_SIMULATION deve impedir só originação de chamada, não ações administrativas de fila) - nftables revisado (sem alterações necessárias) - Documentação completa: INSTALL, OPERATIONS, BACKUP_RESTORE, SECURITY, DATABASE, API, ASTERISK, OPENSIPS (não implementado, motivo documentado), TROUBLESHOOTING - README.md e CHANGELOG.md reescritos Fase 10 (testes e aceite): - Quality gate completo executado: build/typecheck (7 workspaces), lint, 46 testes unitários, docker compose config/ps, healthcheck — tudo verde - Aceite de segurança (seção 92): 13 itens verificados ao vivo contra o sistema real, não só por inspeção de código - Aceite Asterisk (seção 93): os 5 comandos executados e documentados, comunicação API->AMI->Asterisk validada - docs/RELATORIO_FINAL.md: relatório final no formato da seção 96 Todos os fixtures de teste desta fase foram removidos/desativados ao final. Credenciais de acesso entregues separadamente em CREDENCIAIS.txt (fora do git, nunca versionado).
This commit is contained in:
108
docs/TROUBLESHOOTING.md
Normal file
108
docs/TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# B2BCall — Troubleshooting
|
||||
|
||||
Problemas reais encontrados durante o desenvolvimento deste projeto e como
|
||||
foram diagnosticados/resolvidos — mantido como referência para o futuro.
|
||||
|
||||
## "Argument calledNumber is missing" ao originar chamada
|
||||
|
||||
**Sintoma**: `PrismaClientValidationError` ao reservar um lead, mesmo a
|
||||
query SQL parecendo correta.
|
||||
|
||||
**Causa**: `$queryRaw` do Prisma **não aplica** o mapeamento camelCase
|
||||
automático que `findMany`/`update` aplicam — colunas retornadas por SQL cru
|
||||
mantêm o nome exato da coluna (`phone_normalized`, não `phoneNormalized`).
|
||||
|
||||
**Fix**: sempre usar alias explícito no SQL: `phone_normalized AS
|
||||
"phoneNormalized"`. Ver `apps/dialer-worker/src/lead-repository.ts`.
|
||||
|
||||
## Pacing do discador oscilando/nunca parando de subir mesmo sem atividade
|
||||
|
||||
**Sintoma**: no cenário de teste da seção 66 (20 agentes/10 CPS/30% taxa de
|
||||
atendimento), o `pacingFactor` continuava subindo mesmo em períodos ociosos,
|
||||
gerando concorrência muito acima do esperado.
|
||||
|
||||
**Causas** (três, compostas):
|
||||
1. O cálculo de concorrência total não incluía chamadas já conectadas ao
|
||||
agente (`agentConnectedCalls`), subestimando quanto já estava "em uso".
|
||||
2. O ajuste de pacing subia mesmo sem nenhuma atividade de discagem no
|
||||
ciclo (nada "deu errado", então ele interpretava como "pode subir mais").
|
||||
3. Uma única iteração fechava 100% da diferença entre o alvo e o atual,
|
||||
causando overshoot.
|
||||
|
||||
**Fix**: `agentConnectedCalls` entra no total de concorrência; pacing nunca
|
||||
sobe durante ociosidade (`hasActivity` gate); fechamento de gap limitado a
|
||||
50% por ciclo (`RAMP_FRACTION`); e dampening por raiz quadrada do próprio
|
||||
`pacingFactor`. Ver `apps/dialer-worker/src/predictive-engine.ts` e
|
||||
`docs/PREDICTIVE_DIALER.md`.
|
||||
|
||||
## AMI: comando `Command` não retorna o output esperado
|
||||
|
||||
**Sintoma**: parsing manual do protocolo AMI para a action `Command`
|
||||
(usada por diagnóstico) não batia com a documentação legada
|
||||
(`Response: Follows` / `--END COMMAND--`).
|
||||
|
||||
**Causa**: o Asterisk 22 usa um formato diferente para `Command`: múltiplos
|
||||
headers `Output:` repetidos, um por linha de saída — não o bloco
|
||||
`Follows`/`END COMMAND` de versões antigas.
|
||||
|
||||
**Fix**: reverse-engineering via socket TCP cru (script de debug dedicado)
|
||||
para confirmar o formato real antes de implementar o parser em
|
||||
`packages/telephony`.
|
||||
|
||||
## Erro de sintaxe no `schema.prisma`: `@default(1_000_000)`
|
||||
|
||||
**Sintoma**: `prisma generate` falha com erro de parsing.
|
||||
|
||||
**Causa**: separador de milhar com underscore (`1_000_000`) não é suportado
|
||||
na sintaxe do `.prisma` — é um recurso de JS/TS, não do DSL do Prisma.
|
||||
|
||||
**Fix**: escrever o número sem separadores: `1000000`.
|
||||
|
||||
## `Prisma.InputJsonValue` rejeita instância de DTO
|
||||
|
||||
**Sintoma**: `Index signature for type 'string' is missing` ao passar um
|
||||
objeto DTO diretamente para um campo `Json` do Prisma (ex.: `AuditLog.after`).
|
||||
|
||||
**Causa**: uma instância de classe (mesmo com os campos certos) não
|
||||
satisfaz a assinatura de índice que `InputJsonValue` exige — só um objeto
|
||||
literal simples satisfaz.
|
||||
|
||||
**Fix**: espalhar em um objeto literal: `after: { ...dto }` em vez de
|
||||
`after: dto`.
|
||||
|
||||
## Redis: agentes travados em `IN_CALL` para sempre
|
||||
|
||||
**Sintoma**: depois de uma chamada, o agente nunca voltava a `AVAILABLE`.
|
||||
|
||||
**Causa**: o "claim" de agente disponível só marcava `IN_CALL` na conexão —
|
||||
não havia nenhum mecanismo simétrico de liberação após o fim da chamada.
|
||||
|
||||
**Fix**: `agent-call-binding.ts#releaseAgentAfterCall`, chamado
|
||||
explicitamente quando a chamada termina, com suporte a `WRAP_UP`
|
||||
intermediário antes de `AVAILABLE`.
|
||||
|
||||
## Corrida entre duas campanhas reivindicando o mesmo agente
|
||||
|
||||
**Sintoma** (encontrado em revisão de código, não em produção):
|
||||
implementação inicial de `claimAvailableAgent` usava `findFirst` + update
|
||||
separado — janela de corrida entre duas campanhas concorrentes.
|
||||
|
||||
**Fix**: reescrito com `UPDATE ... WHERE id = (SELECT ... FOR UPDATE SKIP
|
||||
LOCKED LIMIT 1) RETURNING ...`, mesmo padrão atômico já usado para reserva
|
||||
de leads.
|
||||
|
||||
## Diagnóstico geral
|
||||
|
||||
Para qualquer problema não listado acima:
|
||||
|
||||
1. `bash scripts/healthcheck.sh` — descarta problema de infraestrutura
|
||||
básica primeiro.
|
||||
2. `docker compose logs -f <serviço>` — logs estruturados JSON, procurar
|
||||
por `"level":50` (error) ou `"level":40` (warn).
|
||||
3. `docker exec b2bcall-asterisk asterisk -rx "..."` — ver `docs/ASTERISK.md`
|
||||
para comandos úteis.
|
||||
4. Consultar `AuditLog` (`GET /api/audit`) se o problema envolve uma ação
|
||||
específica de um usuário.
|
||||
5. Verificar `docker compose ps` — um container "unhealthy" quase sempre
|
||||
aponta a causa raiz de problemas em cascata nos serviços que dependem
|
||||
dele.
|
||||
Reference in New Issue
Block a user