Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 81 additions & 21 deletions db/ESTRUTURA.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> ⚠️ **ARQUIVO GERADO. Não edite à mão.**
> `npx tsx scripts/db/gerar-estrutura.ts`

Fonte: `db/snapshot/schema.sql` (dump de **2026-08-12**, até a migration **20260812010000**)
Fonte: `db/snapshot/schema.sql` (dump de **2026-08-17**, até a migration **20260817000000**)
cruzado com o código vivo em `src/`.

A verdade do banco é **a baseline `00000000000000_baseline.sql` + as migrations
Expand All @@ -14,13 +14,13 @@ posteriores a ela**. Este documento é a leitura humana disso; a leitura de máq

| | |
|---|---|
| Tabelas | **112** |
| Tabelas | **113** |
| Views | **12** |
| Funções | **73** |
| Triggers | **93** |
| Policies RLS | **360** |
| Tabelas com RLS | **112** de 112 |
| Tabelas referenciadas no código | **88** de 112 |
| Policies RLS | **362** |
| Tabelas com RLS | **113** de 113 |
| Tabelas referenciadas no código | **89** de 113 |

## Sinais que pedem ação

Expand Down Expand Up @@ -106,7 +106,7 @@ _Identidade, acesso, projeto/cliente e infraestrutura transversal._
| [`client_project_access_deprecated`](#client_project_access_deprecated) | 5 | id | ✅ | 2 | 0 | 2 | **0** |
| [`clients`](#clients) | 42 | id | ✅ | 4 | 2 | 4 | 31 |
| [`communication_log`](#communication_log) | 14 | id | ✅ | 4 | 0 | 3 | **0** |
| [`companies`](#companies) | 25 | id | ✅ | 6 | 1 | 3 | 23 |
| [`companies`](#companies) | 25 | id | ✅ | 6 | 1 | 3 | 24 |
| [`feature_flag_overrides`](#feature_flag_overrides) | 8 | id | ✅ | 2 | 1 | 1 | 2 |
| [`file_uploads`](#file_uploads) | 18 | id | ✅ | 4 | 1 | 2 | **0** |
| [`import_logs`](#import_logs) | 10 | id | ✅ | 3 | 0 | 2 | 1 |
Expand All @@ -115,7 +115,7 @@ _Identidade, acesso, projeto/cliente e infraestrutura transversal._
| [`profiles`](#profiles) | 13 | id | ✅ | 5 | 1 | 2 | 77 |
| [`project_members`](#project_members) | 6 | id | ✅ | 2 | 0 | 5 | 33 |
| [`project_notifications`](#project_notifications) | 12 | id | ✅ | 4 | 0 | 3 | 4 |
| [`projects`](#projects) | 38 | id | ✅ | 9 | 1 | 9 | 109 |
| [`projects`](#projects) | 38 | id | ✅ | 9 | 1 | 9 | 111 |
| [`settings`](#settings) | 7 | id | ✅ | 5 | 1 | 2 | 6 |

### Financeiro · ingestão e dado bruto
Expand All @@ -124,16 +124,16 @@ _O que entra: extrato OFX, documentos, contas bancárias, saldos._

| Tabela | Cols | PK | RLS | Policies | Triggers | Índices | Usos em `src/` |
|---|--:|---|:-:|--:|--:|--:|--:|
| [`bank_account_balance_anchors`](#bank_account_balance_anchors) | 8 | id | ✅ | 3 | 2 | 2 | 6 |
| [`bank_accounts`](#bank_accounts) | 14 | id | ✅ | 6 | 2 | 0 | 21 |
| [`bank_account_balance_anchors`](#bank_account_balance_anchors) | 8 | id | ✅ | 3 | 2 | 2 | 7 |
| [`bank_accounts`](#bank_accounts) | 14 | id | ✅ | 6 | 2 | 0 | 22 |
| [`bank_balances`](#bank_balances) | 8 | id | ✅ | 3 | 1 | 1 | 3 |
| [`client_documents`](#client_documents) | 24 | id | ✅ | 4 | 1 | 5 | 6 |
| [`extrato_items`](#extrato_items) | 13 | id | ✅ | 2 | 0 | 3 | 1 |
| [`financial_documents`](#financial_documents) | 22 | id | ✅ | 5 | 1 | 5 | 15 |
| [`folha_uploads`](#folha_uploads) | 15 | id | ✅ | 4 | 0 | 1 | 6 |
| [`ingestion_documents`](#ingestion_documents) | 14 | id | ✅ | 4 | 1 | 2 | 2 |
| [`pending_ofx_distributions`](#pending_ofx_distributions) | 17 | id | ✅ | 6 | 1 | 2 | 11 |
| [`transactions`](#transactions) | 32 | id | ✅ | 9 | 5 | 15 | 76 |
| [`transactions`](#transactions) | 32 | id | ✅ | 9 | 5 | 15 | 77 |

### Financeiro · categorização

Expand Down Expand Up @@ -161,10 +161,11 @@ _O fechamento do mês: snapshots imutáveis e seus detalhamentos._
| [`cockpit_monthly_deltas`](#cockpit_monthly_deltas) | 8 | id | ✅ | 1 | 0 | 0 | 3 |
| [`dre_detalhamento`](#dre_detalhamento) | 10 | id | ✅ | 4 | 1 | 1 | 16 |
| [`dre_detalhamento_antecipacao_linha`](#dre_detalhamento_antecipacao_linha) | 11 | id | ✅ | 4 | 1 | 2 | 6 |
| [`dre_detalhamento_contrato`](#dre_detalhamento_contrato) | 14 | id | ✅ | 4 | 0 | 2 | 2 |
| [`dre_monthly_snapshots`](#dre_monthly_snapshots) | 16 | id | ✅ | 2 | 2 | 6 | 57 |
| [`dre_detalhamento_contrato`](#dre_detalhamento_contrato) | 14 | id | ✅ | 4 | 0 | 2 | 3 |
| [`dre_monthly_snapshots`](#dre_monthly_snapshots) | 16 | id | ✅ | 2 | 2 | 6 | 60 |
| [`dre_reports`](#dre_reports) | 27 | id | ✅ | 4 | 1 | 3 | **0** |
| [`financial_snapshots`](#financial_snapshots) | 13 | id | ✅ | 4 | 0 | 3 | 3 |
| [`giro_monthly_snapshots`](#giro_monthly_snapshots) | 48 | id | ✅ | 2 | 0 | 3 | 4 |

### Financeiro · análises derivadas

Expand Down Expand Up @@ -195,7 +196,7 @@ _Dívida com história, contratos de empréstimo, a pagar e a receber._
| [`endividamento_contratos`](#endividamento_contratos) | 11 | id | ✅ | 4 | 1 | 1 | **0** |
| [`endividamento_snapshots`](#endividamento_snapshots) | 33 | id | ✅ | 4 | 0 | 1 | 7 |
| [`loan_contract_pmt_overrides`](#loan_contract_pmt_overrides) | 10 | id | ✅ | 4 | 0 | 2 | 5 |
| [`loan_contracts`](#loan_contracts) | 28 | id | ✅ | 4 | 1 | 2 | 16 |
| [`loan_contracts`](#loan_contracts) | 28 | id | ✅ | 4 | 1 | 2 | 17 |
| [`recebivel_parcelas`](#recebivel_parcelas) | 16 | id | ✅ | 4 | 1 | 2 | 18 |

### Caixa e projeção
Expand Down Expand Up @@ -425,7 +426,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

> Empresas (CNPJs) pertencentes a um grupo econômico (clients). Todo cliente é grupo: 1 CNPJ → 1 company; N CNPJs → N companies.

**Domínio:** Núcleo da plataforma · **RLS:** ✅ 6 policies · **Usos em `src/`:** 23
**Domínio:** Núcleo da plataforma · **RLS:** ✅ 6 policies · **Usos em `src/`:** 24

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand Down Expand Up @@ -632,7 +633,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

### projects

**Domínio:** Núcleo da plataforma · **RLS:** ✅ 9 policies · **Usos em `src/`:** 109
**Domínio:** Núcleo da plataforma · **RLS:** ✅ 9 policies · **Usos em `src/`:** 111

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand Down Expand Up @@ -701,7 +702,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

> fin-01/A-01.6: âncora de saldo (<LEDGERBAL>) por conta bancária. Fonte de verdade auditável do modelo híbrido âncora+roll-forward. Append-only (imutável); UNIQUE(bank_account_id,as_of,balance_type) torna re-upload idempotente. Cache O(1) derivado fica em bank_accounts.saldo_atual/saldo_data + bank_balances, escritos pelo RPC registrar_ancora_saldo (FASE 1.4).

**Domínio:** Financeiro · ingestão e dado bruto · **RLS:** ✅ 3 policies · **Usos em `src/`:** 6
**Domínio:** Financeiro · ingestão e dado bruto · **RLS:** ✅ 3 policies · **Usos em `src/`:** 7

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand All @@ -720,7 +721,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

### bank_accounts

**Domínio:** Financeiro · ingestão e dado bruto · **RLS:** ✅ 6 policies · **Usos em `src/`:** 21
**Domínio:** Financeiro · ingestão e dado bruto · **RLS:** ✅ 6 policies · **Usos em `src/`:** 22

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand Down Expand Up @@ -943,7 +944,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

> Todas as transações acumuladas de todos os extratos

**Domínio:** Financeiro · ingestão e dado bruto · **RLS:** ✅ 9 policies · **Usos em `src/`:** 76
**Domínio:** Financeiro · ingestão e dado bruto · **RLS:** ✅ 9 policies · **Usos em `src/`:** 77

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand Down Expand Up @@ -1302,7 +1303,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

> Snapshot mensal da decomposição por contrato (modo por_contrato, PR 5.2). 1 linha por (project_id, mes_referencia, contract_id). Gravado por detalhamento/multi-contrato/route.ts via delete-then-insert. Re-shape em 20260616 (antes era filha de dre_detalhamento via detalhamento_id).

**Domínio:** Financeiro · apuração e DRE · **RLS:** ✅ 4 policies · **Usos em `src/`:** 2
**Domínio:** Financeiro · apuração e DRE · **RLS:** ✅ 4 policies · **Usos em `src/`:** 3

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand All @@ -1327,7 +1328,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

> Snapshots mensais imutáveis de DRE/KPIs (ATR OS v3). Cada atualização cria nova version mantendo histórico completo. Base de cross-check HTR vs. dados reais (Camada 4). NÃO confundir com financial_snapshots (módulo de metas / War Room).

**Domínio:** Financeiro · apuração e DRE · **RLS:** ✅ 2 policies · **Usos em `src/`:** 57
**Domínio:** Financeiro · apuração e DRE · **RLS:** ✅ 2 policies · **Usos em `src/`:** 60

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand Down Expand Up @@ -1412,6 +1413,65 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

**Aponta para:** `project_id` → `projects.id` (ON DELETE CASCADE)

### giro_monthly_snapshots

> Foto mensal imutável do capital de giro (spec capital-de-giro-spec.md §8). Grão projeto+mês+version. Substitui cash_cycle_analyses, que fazia upsert por project_id e destruía o histórico. Fechou o mês, congela; re-fechamento cria version+1. Escrita só por service_role (via after() do fechar-mes).

**Domínio:** Financeiro · apuração e DRE · **RLS:** ✅ 2 policies · **Usos em `src/`:** 4

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
| `id` 🔑 | uuid | não | `"gen_random_uuid"()` |
| `project_id` | uuid | não | — |
| `mes_referencia` | text | não | — |
| `version` | integer | não | `1` |
| `frozen_at` | timestamp with time zone | não | `"now"()` |
| `created_by` | uuid | sim | — |
| `nivel_efetivo` | smallint | não | `0` |
| `saldo_inicio_mes` | numeric | sim | — |
| `vale_valor` | numeric | sim | — |
| `vale_dia` | smallint | sim | — |
| `profundidade` | numeric | sim | — |
| `receita_mes` | numeric | sim | — |
| `razao_buraco` | numeric | sim | — |
| `razao_ncg` | numeric | sim | — |
| `custo_giro_desagio` | numeric | sim | — |
| `custo_giro_juros` | numeric | sim | — |
| `cobertura_antecipacao` | numeric | sim | — |
| `cobertura_aporte` | numeric | sim | — |
| `cobertura_emprestimo` | numeric | sim | — |
| `perfil_calendario` | jsonb | não | `'[]'::"jsonb"` |
| `cr_valor` | numeric | sim | — |
| `cr_fonte` | text | sim | — |
| `cp_valor` | numeric | sim | — |
| `cp_fonte` | text | sim | — |
| `estoque_valor` | numeric | sim | — |
| `estoque_fonte` | text | sim | — |
| `informado_em` | timestamp with time zone | sim | — |
| `informado_por` | uuid | sim | — |
| `ncg` | numeric | sim | — |
| `pmr` | numeric | sim | — |
| `pme` | numeric | sim | — |
| `pmp` | numeric | sim | — |
| `ciclo_financeiro` | numeric | sim | — |
| `cod_diario` | numeric | sim | — |
| `compras_mes` | numeric | sim | — |
| `delta_buraco` | numeric | sim | — |
| `buraco_efeito_volume` | numeric | sim | — |
| `buraco_efeito_eficiencia` | numeric | sim | — |
| `delta_ncg` | numeric | sim | — |
| `ncg_efeito_volume` | numeric | sim | — |
| `ncg_efeito_eficiencia` | numeric | sim | — |
| `cobertura_bancaria_pct` | numeric | sim | — |
| `pct_categorizado` | numeric | sim | — |
| `avisos` | jsonb | não | `'[]'::"jsonb"` |
| `invalidated_at` | timestamp with time zone | sim | — |
| `invalidated_reason` | text | sim | — |
| `desatualizado_em` | timestamp with time zone | sim | — |
| `desatualizado_motivo` | jsonb | sim | — |

**Aponta para:** `project_id` → `projects.id` (ON DELETE CASCADE)

### breakeven_analyses

> Output da Análise #5 (Ponto de Equilíbrio). Calculada por BreakevenService.
Expand Down Expand Up @@ -1882,7 +1942,7 @@ _Tabelas do produto Lite — namespace próprio, não se mistura com o principal

> Contratos de empréstimo/antecipação recorrente do projeto. Persistentes entre meses (não duplicam). Suportam multi-contrato (PR 5 — decomposição por taxa real de cada banco). Fundação §4.

**Domínio:** Endividamento e crédito · **RLS:** ✅ 4 policies · **Usos em `src/`:** 16
**Domínio:** Endividamento e crédito · **RLS:** ✅ 4 policies · **Usos em `src/`:** 17

| Coluna | Tipo | Nulo | Padrão |
|---|---|:-:|---|
Expand Down
86 changes: 80 additions & 6 deletions db/snapshot/SNAPSHOT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

| Campo | Valor |
|---|---|
| **Status** | 🟢 **AUTORITATIVO** — inclui as duas migrations de RLS de 2026-08-12, aplicadas no SQL Editor e conferidas neste dump. |
| Dump gerado em | **2026-08-12** (`supabase db dump --linked`, `--role-only` e `--data-only`, projeto `tdlxqqgechxhkygdmsxq`, CLI v2.84.0) |
| Commit git no dump | `fix/rls-tabelas-orfas` |
| `ultima_migration_incluida` | **`20260812010000`** |
| `ultima_migration_no_repo` | **`20260812010000`** |
| **Veredito** | 🟢 **autoritativo** — `schema.sql` **15.030 linhas**; `roles.sql` **13** (diff vazio); `seed.sql` **25.175**. |
| **Status** | 🟢 **AUTORITATIVO** — inclui `giro_monthly_snapshots` (G2 do capital de giro), aplicada no SQL Editor e conferida neste dump. |
| Dump gerado em | **2026-08-17** (`supabase db dump --linked`, `--role-only` e `--data-only`, projeto `tdlxqqgechxhkygdmsxq`, CLI v2.84.0) |
| Commit git no dump | `main` `a21c084` + working tree (a migration `20260817000000` está aplicada no remoto e **ainda não commitada**) |
| `ultima_migration_incluida` | **`20260817000000`** |
| `ultima_migration_no_repo` | **`20260817000000`** |
| **Veredito** | 🟢 **autoritativo** — `schema.sql` **15.206 linhas**; `roles.sql` **13** (diff vazio); `seed.sql` **25.095**. |

## A regra, depois da baseline de 2026-08-11

Expand All @@ -26,6 +26,80 @@ O mapa legível de tudo isso é [`db/ESTRUTURA.md`](../ESTRUTURA.md), **gerado**
`npx tsx scripts/db/gerar-estrutura.ts`. Depois de todo re-dump, rode o gerador: o
diff do `.md` mostra exatamente o que mudou no banco.

### Validação do re-dump (2026-08-17, `giro_monthly_snapshots` — G2)

Migration `20260817000000_giro_monthly_snapshots.sql`, aplicada no SQL Editor. Cria a
série mensal do capital de giro (spec `capital-de-giro-spec.md` §8) e **estende** a
função de stale-by-event para marcar também o snapshot de giro.

| Objeto | Antes | Depois | Δ |
|---|--:|--:|--:|
| **Tabelas** | 112 | **113** | +1 |
| Views | 12 | **12** | 0 |
| Funções | 73 | **73** | 0 |
| Triggers | 92 | **92** | 0 |
| **Policies** | 360 | **362** | +2 |
| **Tabelas com RLS** | 112 | **113 de 113** | +1 |
| **Índices** | 254 | **257** | +3 |

`roles.sql`: diff vazio. `seed.sql` 25.175 → **25.130**, já **com as 34 linhas do
backfill** (re-dumpado depois dele).

⚠️ **Nota de processo, porque quase virou perda de dado.** A primeira tentativa de
re-dumpar o `seed.sql` saiu **vazia** — o Docker Desktop auto-pausou no meio — e o
arquivo foi copiado por cima do bom antes de conferir o tamanho. A regra do
`db/snapshot/README.md` ("dumpe para temporário") só protege se o temporário for
**verificado antes de instalar**. `seed.sql` é gitignored, então o git não desfaz.
Recuperado do temporário anterior e, na terceira tentativa, o comando passou a instalar
só com `wc -l > 20000`. **Guarda de tamanho antes de qualquer `cp` sobre dump.**

**Os deltas batem com a intenção, um a um.** `Funções +0` é o número que interessa:
`marcar_snapshot_desatualizado()` foi **substituída**, não duplicada — era exatamente o
risco de criar uma segunda função varrendo `transactions` de novo.

**Prova de que o que está rodando é o que foi escrito.** A lei de método (anti-padrão 7)
exige provar a fidelidade de `CREATE OR REPLACE` em função `SECURITY DEFINER`. Duas
provas, ambas mecânicas:

1. `npx tsx scripts/db/provar-fidelidade-trigger.ts` → corpo idêntico à baseline em 81
linhas, única diferença = o bloco marcado de 31 linhas. *(Reprovou na primeira
execução, por uma linha em branco a mais.)*
2. Corpo da função **extraído deste dump** × corpo da migration → idênticos, 112 linhas.
É a diferença entre "a migration diz" e "o banco faz".

O diff estrutural do `schema.sql` contra o anterior mostra **apenas** objetos `giro_*`:
nenhuma tabela, view, policy ou índice alheio foi tocado.

#### Backfill + check ao vivo — e o bug que só o dado real mostrou

Backfill: **34 meses** em 2 projetos (Vertímetal 31, Di Forni 3). Check ao vivo
recalculou `vale`, `vale_dia` e `profundidade` por um caminho **independente** do
serviço, e conferiu `razao_buraco`, o fecho da decomposição e `receita_mes × DRE.L10`:
**zero divergência**.

**Mas um zero merecia conferência, e não era zero.** `custo_giro_juros` saiu `0` em
todos os 34 meses. A causa: a spec §5.4 mandava filtrar contrato por `finalidade`, e
**`finalidade` está NULL em 100% dos contratos do banco** (3 de 3) — é pergunta de
intenção que ninguém volta para responder. A Vertímetal tem dois contratos
`modalidade='capital_giro'` pagando **R$ 272.804 de juros em 12 meses**:

```
custo do giro, jul/2026 (12m) 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
```

Erro na **manchete permanente da tela**, e na direção que ninguém percebe. Corrigido:
`ehContratoDeGiro` passou a aceitar `finalidade` **OU** `modalidade`
(§5.4.1 da spec), com teste de regressão. Os 34 meses foram refeitos com
`--refazer` e o check ao vivo repassou com `juros = 272.804`.

**Este é o argumento inteiro da Fase 3 do método em um caso:** 87 testes verdes, schema
correto, dump conferido — e o número principal da tela saindo pela metade. Só o dado
real pega.

### Validação do re-dump (2026-08-12, RLS das tabelas órfãs)

Migrations `20260812000000` (RLS nas 5 tabelas + `search_path` em 39 funções) e
Expand Down
Loading
Loading