Commit Graph

8 Commits

Author SHA1 Message Date
97ef8a6ba8 feat: edição de rotas de entrada, fix de 2 bugs reais no ESL, diagnóstico de NAT/áudio e softphone WebRTC (PHASE 65/66)
Três achados reportados pelo usuário numa mensagem só: (1) Rotas de Entrada
não tinha edição depois de criada — implementada no mesmo padrão de Filas;
(2) telas de Platform > Infraestrutura sempre davam "Timeout no ESL" nesta
VM — não era limitação permanente como o comentário antigo dizia, e sim
ESL_HOST=freeswitch (nome DNS que só existe dentro da rede do Docker) mais
um segundo bug independente (`show gateways as json` não é comando válido
nesta versão do FreeSWITCH); (3) ramal externo registrava mas sem áudio —
diagnosticado com contadores de pacote do iptables: a VM está atrás de um
roteador sem port-forward pra faixa de RTP, achado de infraestrutura de
rede, não bug de código.

Também integra o softphone WebRTC (handphone.js/OpenSIPS, já em produção):
código-fonte encontrado em git.falehandix.com.br/Handix/handphone-2.0,
patch mínimo pra aceitar o endereço do proxy em runtime (era build-time),
nova config global (Platform > Infraestrutura > Softphone WebRTC) e widget
na topbar do tenant que pega usuário/domínio/senha do ramal vinculado ao
agente logado.

Adiciona docs/QA_SETUP.md — runbook completo pra subir o ambiente do zero
numa máquina nova (Docker, migrations, seed, systemd), e completa o
.env.example que estava faltando a maioria das variáveis reais.

Testado ponta a ponta com Playwright: edição de rota (criar/editar/F5),
as 3 telas de Infraestrutura com dado real, e um tenant/ramal/agente de
teste criados na hora confirmando que o script do softphone recebe as
credenciais certas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 22:14:39 -03:00
fcf334c7c0 feat(frontend): Billing > Consumo (platform) + Sistema > Configurações — zera "em breve"
GET /billing/consumo agrega o mesmo uso bruto de /reports/consumo (tenant), só que
em loop por todos os tenants — nunca dinheiro, só quantidade (dinheiro é Billing >
Relatórios, já existia).

GET /platform/system-config: "Sistema > Configurações" nunca teve escopo definido na
especificação. Decisão desta implementação: painel somente leitura das flags de
segurança/infra que já existem como variável de ambiente (DIALER_SIMULATION/
ALLOW_REAL_OUTBOUND_CALLS, ESL configurado, storage provider, NODE_ENV) — nunca
editável por aqui, mudar exige editar o .env e reiniciar o serviço. Nunca expõe
segredo nenhum.

Com isto, todo item dos menus Platform e Tenant tem uma tela real por trás — zero
"em breve" restando em nav-data.ts nos dois lados.

Testado ponta a ponta: /billing/consumo batendo com os mesmos números já vistos em
Relatórios > Consumo/Quotas, /platform/system-config confirmado mostrando o valor
real do .env desta VM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 09:01:10 -03:00
cc80310cb5 feat(frontend): Platform > IA > Providers/Modelos/Uso/Custos + achado real de autorização
Providers/Modelos reaproveitam os endpoints /ai/providers e /ai/models já
existentes (só filtram scope GLOBAL na tela); Modelos expõe custo unitário
(inputCost/outputCost/audioCost) que a tela de tenant nunca mostra, porque só
platform precisa cadastrar preço. GET /platform/ai-usage (novo) agrega uso bruto de
AIUsageRecord de todos os tenants no mês corrente (Uso) e estima custo casando cada
registro com o AIModel correspondente (Custos) — quando não dá pra casar, o registro
fica de fora da soma e o tenant é marcado costIncomplete, nunca um número inventado.

Achado real de autorização ao revisar AIModelsController antes de construir a tela
de Modelos: POST/DELETE /ai/models não checava isPlatformUser quando o alvo era
scope=GLOBAL — como a RLS híbrida (OR tenant_id IS NULL) deixa qualquer tenant
ENXERGAR um provider/modelo GLOBAL, qualquer Tenant Admin com `ai.manage` (permission
de escopo TENANT) conseguia injetar um modelo no catálogo global ou desabilitar um
modelo GLOBAL só sabendo o id. O endpoint irmão (AIProvidersController) já tinha o
check certo; corrigido com o mesmo padrão. Confirmado com um teste de ataque real:
403 depois do fix (era 201/sucesso antes), com regressão confirmando que BYOK do
próprio tenant continua funcionando normalmente.

Testado ponta a ponta: provider+modelo GLOBAL com custo real, uso de teste inserido
direto no Postgres, /platform/ai-usage devolvendo o valor exato esperado (bate com a
conta manual), tudo removido no final.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 08:53:35 -03:00
12af12f276 feat(frontend): Platform > Infraestrutura > FreeSWITCH/SIP Profiles/Nodes
GET /platform/freeswitch/channels|profiles|nodes fazem introspecção ESL real
(show channels/show calls/sofia status/show registrations/status/show gateways),
reaproveitando os métodos do FreeSwitchTelephonyProvider já verificados manualmente
contra o FreeSWITCH real. Mesmo padrão de conexão avulsa do PlatformHealthController
(connect → comando → disconnect).

Nunca deixa a indisponibilidade do ESL virar 500/503 — devolve { ok: false, error }
com 200, mesma filosofia do health check. Necessário: nesta VM apps/api roda fora do
Docker e a porta 8021 é deliberadamente não publicada no host, então as 3 telas
sempre mostram essa explicação aqui (mesmo texto já usado em Infraestrutura >
Saúde), mesmo com o endpoint 100% funcional — confirmado indiretamente pelo
b2bcall-fs-events, que fala ESL de dentro da rede Docker e está com heartbeat ativo.

"Nodes" mostra explicitamente 1 node (container único, sem clustering) em vez de
fingir uma lista.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 08:41:49 -03:00
b1a409da09 feat(frontend): Platform > Clientes > Assinaturas/Quotas + achado real no dashboard
GET /billing/subscriptions e POST /billing/plan-versions já existiam desde a fase
Billing (PHASE 22) sem tela nenhuma — Assinaturas agora deixa escolher um tenant, ver
o histórico de versões de preço assinadas, e criar uma nova assinatura reaproveitando
uma versão existente ou versionando um preço novo na mesma ação (preço nunca é
sobrescrito, sempre uma linha nova).

GET /platform/quotas (novo) agrega uso vs. limite do plano em todos os tenants de
uma vez (ramais/agentes/troncos/filas/campanhas/chamadas-mês/armazenamento), com a
tela destacando quem está em 80%+ (amarelo) ou 100%+ (vermelho) do limite.

Achado real ao revisar o dashboard "Visão Geral" antes de escrever a agregação
cross-tenant de Quotas: aiUsageThisMonth/recordingStorageBytes sempre devolviam
zero/vazio, porque a query rodava direto no Prisma sem nenhum app.current_tenant_id
setado — ai_usage_records/recordings têm FORCE RLS, então a policy nega a leitura
silenciosamente (0 linhas, sem erro), não importa quanto uso real existisse. Mesma
classe de bug já corrigida 2x antes nesta sessão; corrigido com o mesmo padrão (loop
withTenantContext por tenant). Confirmado inserindo um AIUsageRecord de teste no
Postgres, vendo o número aparecer, e removendo o teste depois.

Testado ponta a ponta: fluxo completo de criar assinatura via UI pro tenant Beta
Corp (nova versão de preço + assinatura, confirmado na tela e no banco), Quotas
mostrando os números reais dos dois tenants de teste.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 08:32:53 -03:00
03ec0d556b feat(platform): Sistema > Permissões (catálogo RBAC, só leitura)
GET /platform/roles novo — lista as 4 roles do sistema com as permissions
de cada uma, mais o catálogo completo de permissions. roles/permissions
não têm RLS (catálogo global do seed); só leitura, RBAC é system-defined,
sem UI de criar role customizada ainda.

Frontend: /platform/sistema/permissoes, um card por role + tabela do
catálogo completo. Testado ponta a ponta: as 4 roles reais (platform_super_admin
33 permissions, tenant_admin 30, supervisor 17, agent 2) corretas. Smoke
test nas 19 telas do tenant + 9 telas platform, todas 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-29 20:41:19 -03:00
a23e68b011 feat(platform): Sistema > Usuários/Auditoria, Infraestrutura > Saúde
Três endpoints novos, todos platform-only: GET /platform/users (cross-
tenant, users não tem RLS) + PATCH .../status (desabilitar tem efeito
real — login() já checava status ACTIVE desde a PHASE 04); GET
/platform/audit-log (últimos 200 eventos, audit_logs também sem RLS,
linha imutável); GET /platform/health (Postgres/Redis + FreeSWITCH via
conexão ESL avulsa, sem manter estado).

Achado de arquitetura documentado explicitamente na própria tela: o check
de FreeSWITCH sempre falha neste ambiente porque apps/api roda no host e
a porta 8021 é deliberadamente não publicada (decisão da PHASE 01/05) —
não é um bug, é a rede isolada do jeito certo.

Frontend: /platform/sistema/usuarios, /auditoria, /platform/
infraestrutura/saude. Testado ponta a ponta contra dados reais (3
usuários da plataforma, audit log com eventos reais desta sessão,
inclusive uma referência órfã tratada corretamente). Smoke test nas 19
telas anteriores, todas 200.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-29 20:04:05 -03:00
b27cfaab02 feat(billing): rating engine, fechamento de periodo, dashboard platform (fase 22)
Fecha a orquestracao de Billing (agente.md secao 120-139) sobre o schema/
RatingEngine puro ja existentes: escritores do ledger UsageEvent
(CALL_SECONDS no CDR, ACTIVE_DAY via sweep diario), closeBillingPeriod/
reopenBillingPeriod (fechamento imutavel com audit trail), e os
controllers de price books/rate decks/plan versions/subscriptions/
periods/statements. Corrige 2 bugs reais de RLS achados no teste ponta a
ponta (reopen sem tenant context, subscriptions sem withTenantContext) e
adiciona teste unitario do RatingEngine (17 casos).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
2026-08-29 00:28:35 -03:00