Skip to content

Admin › Financeiro — UI

Status: produção (Fase D do roadmap de billing). Rota interna: admin-financeiro (sidebar do admin). Backend: edge function admin-financeiro (13 actions agrupadas por tab). Permissão: admin_financeiro (papel administrador tem bypass total). Feature flag: admin_financeiro (gate backend simétrico).

Estrutura

src/components/admin-financeiro/
├── index.tsx                       # Tabs orchestrator + toggle "incluir teste"
├── kpi-cards.tsx                   # 6 cards (Visão Geral)
├── vencidas-table.tsx              # Tabela de vencidas + bulk send (Visão Geral)
├── cobrancas-tab.tsx               # Visão por escola + drawer (Cobranças)
├── templates-tab.tsx               # CRUD de templates (Templates)
├── automacao-tab.tsx               # Status read-only dos cron jobs (Automação)
├── configuracoes-tab.tsx           # Toggle excluir_financeiro por escola (Configurações)
├── escola-drawer.tsx               # Drawer compartilhado: gestor + faturas + histórico
├── enviar-cobranca-dialog.tsx      # Envio individual (templates fixos + manuais)
├── historico-cobrancas-modal.tsx   # Histórico por fatura (sem PII)
└── use-template-options.ts         # Helper que mescla fixos + manuais ativos

Hook único: src/hooks/useAdminFinanceiro.ts — wrappers React Query para todas as actions.

Tabs

1. Visão Geral

  • 6 KPIs (dashboard_kpis) + tabela paginada de faturas vencidas (list_vencidas).
  • Toggle global "Incluir escolas de teste" (default OFF) propaga incluir_teste para queryKey.
  • Ações por linha: Histórico, Enviar cobrança individual, seleção em lote → bulk.

2. Cobranças

  • Agregação por escola (list_cobrancas_overview) com filtro por status (vencida / a vencer / todas) e busca por nome/CNPJ.
  • Click na linha → abre EscolaDrawer (lateral) com:
    • Header: nome, CNPJ, status_assinatura, badge "excluída do financeiro" se aplicável.
    • Bloco Gestor: nome / WhatsApp / e-mail (papel escola).
    • Bloco Faturas: últimas 20 com ações (histórico + enviar).
    • Bloco Cobranças: últimas 50 (canal, slug, origem, status).

3. Faturas

  • Tab dedicada renderizando <AdminFaturas /> (consolidação 2026-05: a antiga seção de menu admin-faturas foi unificada aqui).
  • Listagem paginada com filtros (status, escola, plano), criação manual, edição, cancelamento.
  • Backend: edge function admin-faturas (mantida; feature flag admin_faturas segue gateando server-side).
  • Deep-links antigos activeSection === 'admin-faturas' redirecionam para admin-financeiro na aba faturas (via sessionStorage.admin_financeiro_return).

4. Templates

  • Lista os 4 slugs fixos (fixo_regua) e os manuais (manual_*).
  • Editor permite alterar nome + corpo (e ativo apenas para manuais).
  • Trigger fn_cobranca_templates_protect_fixos impede archive/rename/inativação dos fixos.
  • Variáveis suportadas: {{escola_nome}}, {{numero_fatura}}, {{valor}}, {{vencimento}}, {{dias_atraso}}. Ver cobrancas-templates.md.

5. Emails

  • Aba dedicada para gestão de templates de e-mail transacional (Resend).

6. Configurações

  • Lista de escolas (list_escolas_financeiro) com toggle excluir_financeiro.
  • Escolas com status_assinatura='trial' aparecem com toggle desabilitado (auto-excluídas).
  • Toggle manual logado em faturamento.escola_excluir_financeiro.

KPIs (Visão Geral)

CardFonteFórmula
A receber (próx. 30d)a_receber_30dΣ(valor − desconto + taxas) onde status=pendente e vencimento ∈ [hoje, hoje+30d]
Recebido (últ. 30d)recebido_ultimos_30dΣ(...) onde status=paga e pago_em ≥ hoje−30d
Vencidasvencidas_total + vencidas_valorcount e Σ onde status=vencido
Ticket médioticket_mediorecebido_30d / nº faturas pagas no período
Taxa inadimplênciataxa_inadimplenciavencidas / (vencidas + recebidas_30d)
Saúde da carteiraderivada<5% Boa · <15% Atenção · ≥15% Crítica

Todas as queries respeitam incluir_teste: quando false, escolas com status_assinatura='trial' OU excluir_financeiro=true são removidas da agregação.

Envio de cobrança

  • Individual (enviar_cobranca_manual): skipDelay=true, resposta síncrona com cobranca_id.
  • Bulk (enviar_cobranca_bulk, ≥1 selecionada): retorna 202 + jobId, processa em EdgeRuntime.waitUntil com 6s entre mensagens (Wasender). Resultado consolidado em faturamento.cobranca_envio_bulk.
  • Slugs disponíveis: 4 fixos da régua + manuais ativos (useTemplateOptions).

Régua automática (referência cruzada)

Cron regua-cobranca-diario (09:00 BRT) e marcar-vencidas (03:00 BRT). Ver docs/operations/CRON_JOBS.md.

Logs (LOG_ACTION_OPTIONS — SSOT)

  • faturamento.cobranca_envio_manual
  • faturamento.cobranca_envio_bulk
  • faturamento.cobranca_falha
  • faturamento.template_criado / template_atualizado / template_arquivado
  • faturamento.escola_excluir_financeiro

Out of scope (próximas fases)

  • Fase E: gate de bloqueio por inadimplência (exige ADR).
  • Botão "Forçar execução agora" na tab Automação.
  • Deep-link ?tab= para tabs.
  • Polling de progresso do bulk (hoje: usar Logs).
  • Agendamento de envio futuro / segmentos.

Integração com o AdminDashboard

A tela inicial do admin (AdminDashboard) renderiza a seção Saúde Financeira (src/components/admin-dashboard/financial-health-section.tsx) reaproveitando os mesmos hooks (useDashboardKpis, useListVencidas):

  • 4 mini-cards: A receber (30d), Vencidas (qtd + valor), Ticket médio, Inadimplência (com badge tonal: <5% saudável, <15% atenção, ≥15% crítica).
  • Mini-tabela "Top 5 vencidas" com badge de tier (D+1 / D+7 / D+15).
  • Cards e linhas são clicáveis e levam para o módulo admin-financeiro.

Sem chamadas extras: o cache de 60s no dashboard_kpis é compartilhado entre dashboard e tela detalhada.

Contrato taxa_inadimplencia (CRÍTICO)

dashboard_kpis.taxa_inadimplencia é retornado como fração 0–1 (ex.: 1.0 = 100%, 0.05 = 5%).

Todo consumidor DEVE multiplicar por 100 antes de:

  • Exibir como percentual na UI ((v * 100).toFixed(1) + '%').
  • Comparar contra os limites de tier (<5 saudável, <15 atenção, ≥15 crítica — em pontos percentuais).

Bug histórico: financial-health-section.tsx exibia 1.0% Saudável enquanto kpi-cards.tsx exibia 100% Crítica sobre o mesmo dado, porque o primeiro consumidor esquecia o *100. Não introduzir formatação de inadimplência sem aplicar essa regra.

UI/UX de produção (entrega 2026-05)

Drawer da escola (escola-drawer.tsx)

  • Header em card (bg-muted/30 rounded-lg p-4) com nome / CNPJ / status.
  • Grid 3-col com contatos do gestor (nome, WhatsApp, e-mail).
  • Botão "Histórico de cobranças da escola" no canto superior direito → abre Sheet (não modal).
  • Faturas ordenadas por vencimento_em ASC (backend).
  • Sem rodapé de "últimas cobranças": substituído pelo Sheet.

Histórico (historico-cobrancas-modal.tsx)

  • Sheet (não modal). Lista de cards (1 por envio) com badge de status, canal, slug, origem e mensagem de erro quando aplicável.
  • Datas exibidas via src/lib/format-datetime-brt.ts (Intl + America/Sao_Paulo).

Aba Cobranças (cobrancas-tab.tsx)

  • Substitui lista de escolas por grid 2×2 de KPIs: Faturas, Vencidas, Atraso máximo (dias), Valor em aberto. Cores por severidade.
  • Busca debounced (350ms), paginação server-side via q/page/pageSize.

Aba Templates (templates-tab.tsx)

  • Ordem fixa: Manuais primeiro, Régua depois (regras fixas abaixo dos manuais).

Paginação e busca server-side

  • Edge admin-financeiro aceita q (ilike) + page + pageSize em list_vencidas, list_cobrancas_overview, list_escolas_financeiro.
  • Frontend usa src/hooks/use-debounced-value.ts (350ms) e propaga em queryKeys.

Helper de data BRT (frontend)

  • src/lib/format-datetime-brt.ts usa Intl.DateTimeFormat('pt-BR', { timeZone: 'America/Sao_Paulo' }) para exibir timestamptz UTC sem o bug "21h vira amanhã".
  • Não duplica _shared/date-brt.ts (que permanece edge-only para comparações de dia).