Skip to content

feat(giro): Capital de Giro G1-G3 — núcleo, série mensal e a tela do Nível 0 - #57

Merged
BarryBits merged 2 commits into
mainfrom
feat/capital-de-giro-g1-g3
Aug 17, 2026
Merged

feat(giro): Capital de Giro G1-G3 — núcleo, série mensal e a tela do Nível 0#57
BarryBits merged 2 commits into
mainfrom
feat/capital-de-giro-g1-g3

Conversation

@BarryBits

Copy link
Copy Markdown
Owner

Fecha as fases G1, G2 e G3 da trilha de Capital de Giro.
Spec canônica: docs/atros-v3/capital-de-giro-spec.md ·
Handoff: handoff-2026-08-13-capital-de-giro.md

O problema

/financeiro/giro era uma tela morta — três cards vazios e "Ainda não calculado".

A causa não era preguiça: a tela calculava um índice de balanço (CR + estoque − fornecedores) a partir de uma fonte de fluxo (o extrato). Extrato é o que passou pelo banco; essas três coisas são exatamente o que ainda não passou. Sem resposta na fonte, o sistema fabricou: PMR/PMP saíam de regex na descrição do OFX mapeando para prazos constantes (STONE→1d, PAGTO BOLETO→30d), com selo de "confiança alta" que media quantas linhas casaram o regex — não acerto.

E cash_cycle_analyses fazia upsert por project_id: uma linha por projeto, sobrescrita para sempre. O sistema nunca teve histórico de capital de giro.

O que entra

Fase Entrega
G1 Núcleo puro em src/lib/giro/ — zero I/O, zero Supabase, 121 testes
G2 giro_monthly_snapshots + snapshotGiro no after() do fechar-mes + backfill
G3 A tela do Nível 0, consumindo um DTO só (§24.1)

A pergunta mudou. De "qual é o meu PMR?" para "quanto preciso ter parado pra atravessar o mês, e quanto me custa não ter?". A régua é dias de faturamento parados — o dono negocia prazo a vida inteira, não índice. "Razão de giro" e centavos por real saíram do vocabulário do produto.

Dois bugs que só o dado real pegou

Os testes passavam. O check contra produção não.

1. O custo do giro saía pela metade. A spec mandava filtrar contrato por finalidade — e ela está NULL em 100% dos contratos do banco. Dois contratos modalidade='capital_giro' pagando R$ 272.804/ano estavam invisíveis:

                    medido          real
deságio (L65)    R$ 201.390    R$ 201.390
juros de giro    R$       0    R$ 272.804
─────────────────────────────────────────
total            R$ 201.390    R$ 474.194   ← 58% pra MENOS

Na manchete permanente da tela, e na direção que ninguém percebe — número menor não provoca conferência. Agora é finalidade OU modalidade, com teste de regressão.

2. O texto do descasamento assumia saída-antes-de-entrada. As duas empresas reais recebem antes de pagar (sai dia 19, entra dia 17). A tela teria dito "é esse descasamento que precisa ser financiado todo mês" sobre um calendário que trabalha a favor delas. Virou centro de massa com sinal, que funciona nos dois sentidos.

Banco

Migration já aplicada no remoto e re-dump feito. Deltas batendo um a um com a intenção:

antes depois Δ
Tabelas 112 113 +1
Policies 360 362 +2
Índices 254 257 +3
Funções 73 73 0

Funções +0 é o número que importa: marcar_snapshot_desatualizado() foi substituída, não duplicada — o risco era criar uma segunda função varrendo transactions (tabela quente) de novo.

Fidelidade da função SECURITY DEFINER provada duas vezes (anti-padrão 7 da lei de método): scripts/db/provar-fidelidade-trigger.ts compara migration × baseline, e o corpo extraído do dump foi comparado com o da migration — idênticos, 112 linhas. É a diferença entre "a migration diz" e "o banco faz". (O provador reprovou na primeira execução, por uma linha em branco a mais.)

Backfill de 34 meses rodado, com check ao vivo recalculando vale, vale_dia e profundidade por caminho independente: zero divergência.

Nada foi deletado

Os 6 leitores de cash_cycle_analyses (§3.1) são repontados um a um. PainelAnaliseSections e EstoqueSection ficaram órfãs e foram preservadas com comentário honesto — o DROP é o G7, em sessão própria com grep-de-prova e re-dump. Misturar deleção com feature é o anti-padrão 6.

Verificação

  • tsc --noEmit limpo
  • eslint src/lib/giro src/components/giroexit 0
  • PainelCasaTab.tsx → 2 erros de token, os mesmos 2 do HEAD (débito pré-existente, não tocado)
  • 1863/1863 testes da casa
  • gerar-estrutura.ts --check em dia
  • Paleta dos gráficos validada nos 6 checks em dark: CVD deutan ΔE 13,1 (alvo ≥ 8), visão normal 31,4 (piso 15)

next build não foi rodado localmente de propósito — o dev server do Lucas estava no ar e o build derruba as rotas dele. O CI cobre.

Pendente

O visual. Hierarquia, fonte, cores e disposição ficam como estão: o Lucas desenha e eu aplico depois. Três fatos conhecidos: a casa usa font-mono só até text-xl (acima disso vira fonte de terminal), o mockup usava Rajdhani (que o app não carrega), e a composição do mockup foi desenhada para max-width: 1000px.

Próximo: G4 — os 3 saldos no fechamento, que destravam o Nível 1.

🤖 Generated with Claude Code

…Nível 0

A tela `/financeiro/giro` era morta: três cards vazios alimentados por um motor que
inferia PMR/PMP de regex na descrição do OFX mapeando para prazos CONSTANTES
(`STONE`→1d, `PAGTO BOLETO`→30d), com selo de "confiança alta" que media quantas
linhas casaram o regex — não acerto. E `cash_cycle_analyses` fazia upsert por
`project_id`: uma linha por projeto, sobrescrita para sempre. O sistema nunca teve
histórico de capital de giro.

Spec canônica: docs/atros-v3/capital-de-giro-spec.md

G1 — núcleo puro (src/lib/giro/), zero I/O, 121 testes
  · série operacional: mede o buraco ANTES do socorro (exclui L90 e L65).
    Medir depois da antecipação é medir o buraco já tapado — erra PRA MENOS.
  · régua em DIAS de faturamento. "Razão de giro" e centavos saíram do produto:
    o dono negocia prazo, não índice. As pernas somam com o total exibido.
  · decomposição volume × eficiência, fecha exata, termo cruzado na eficiência
  · custo do giro = deságio (L65) + juros de contrato de giro
  · cadeia do crescimento: pede quanto → tenho → falta → de onde tiro

G2 — série mensal (giro_monthly_snapshots)
  · grão projeto+mês+version, imutável; re-fechamento cria version+1
  · snapshotGiro no 3o after() do fechar-mes, depois da reconciliação
  · estende marcar_snapshot_desatualizado() para marcar o giro na MESMA
    varredura (transactions é tabela quente; STATEMENT-level custa O(meses))
  · backfill de 34 meses + provador de fidelidade da função SECURITY DEFINER

G3 — a tela do Nível 0
  · um DTO só, montado por um serviço só (GET /api/projetos/[id]/giro)
  · herói (custo + variação) · mecanismo · crescer · custo · captura
  · paleta validada nos 6 checks: CVD deutan dE 13,1 · normal 31,4

Dois bugs que só o dado real pegou, não os testes:

1. custo do giro saía pela METADE. A spec mandava filtrar contrato por
   `finalidade`, e ela está NULL em 100% dos contratos do banco. Dois contratos
   modalidade='capital_giro' pagando R$ 272.804/ano estavam invisíveis:
   R$ 201.390 medido contra R$ 474.194 real — 58% pra menos, na manchete
   permanente da tela. Agora é finalidade OU modalidade.

2. o texto do descasamento assumia saída-antes-de-entrada. As duas empresas reais
   RECEBEM antes de pagar — a tela teria fabricado um aperto inexistente. Virou
   centro de massa com sinal, que funciona nos dois sentidos.

Nada foi deletado: os 6 leitores de cash_cycle_analyses (§3.1) são repontados um a
um, e o DROP é o G7, em sessão própria com re-dump.

Migration aplicada no remoto e re-dump feito (schema.sql 15.030 → 15.206; +1 tabela,
+2 policies, +3 índices, +0 funções — a função foi REPLACE, não duplicada).

Pendente: o visual (hierarquia, fonte, disposição) — o Lucas desenha e eu aplico.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
atr-os Ready Ready Preview Aug 17, 2026 10:15pm

@supabase

supabase Bot commented Aug 17, 2026

Copy link
Copy Markdown

Updates to Preview Branch (feat/capital-de-giro-g1-g3) ↗︎

Deployments Status Updated
Database Mon, 17 Aug 2026 22:12:41 UTC
Services Mon, 17 Aug 2026 22:12:41 UTC
APIs Mon, 17 Aug 2026 22:12:41 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Mon, 17 Aug 2026 22:12:43 UTC
Migrations Mon, 17 Aug 2026 22:12:44 UTC
Seeding Mon, 17 Aug 2026 22:12:44 UTC
Edge Functions Mon, 17 Aug 2026 22:12:44 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

O gate `gerar-estrutura.ts --check` reprovou no CI. Causa: rodei o gerador logo
depois do G2, antes de `giro-view-service.ts` existir, e não repeti depois. O
serviço do DTO acrescentou três leituras que o mapa não contava:

  projects                110 → 111
  dre_monthly_snapshots    59 → 60
  giro_monthly_snapshots    3 → 4

O mapa é gerado do dump cruzado com `src/`, então ele envelhece a cada arquivo
novo que lê tabela — não só a cada re-dump. É exatamente o apodrecimento
silencioso que o gate existe para pegar, e pegou.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BarryBits
BarryBits merged commit ca56603 into main Aug 17, 2026
4 checks passed
@BarryBits
BarryBits deleted the feat/capital-de-giro-g1-g3 branch August 17, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant