尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Importe a fatura ou o extrato do banco em PDF e veja para onde o dinheiro foi: leitura no navegador, categorizacao automatica, competencia por fatura e graficos. React + TypeScript + Neon.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Importe a fatura ou o extrato do banco em PDF e veja para onde o seu dinheiro foi. O PDF é lido dentro do navegador — ele nunca sai da sua máquina. O app extrai os lançamentos, confere o total contra o gabarito impresso no próprio PDF, impede a reimportação do mesmo Documento mesmo quando o PDF é renomeado ou reexportado, e guarda só as transações. 🔗 Abrir o app · Estado do projeto · Minha situação financeira · Relatórios · Arquitetura · As regras que fazem a conta fechar A tela de acesso, nos dois temas — papel frio e tinta de livro-razão:查看完整 README ↗
Importe a fatura ou o extrato do banco em PDF e veja para onde o seu dinheiro foi. O PDF é lido dentro do navegador — ele nunca sai da sua máquina. O app extrai os lançamentos, confere o total contra o gabarito impresso no próprio PDF, impede a reimportação do mesmo Documento mesmo quando o PDF é renomeado ou reexportado, e guarda só as transações.
A tela de acesso, nos dois temas — papel frio e tinta de livro-razão:
| Tema claro | Tema escuro |
|---|---|
O painel: em que o dinheiro foi, quando saiu, e como o mês se compara.
Repare na barra branca e serrilhada do dia 16: um pagamento de empréstimo de R$ 41.653 num mês de compras de dezenas. A escala vai até o maior dia não discrepante (R$ 816) e declara o corte em vez de achatar os outros 25 dias contra o chão — que é o que uma escala pelo máximo faz.
A fileira de saldos — o que cada banco declara, sob um rótulo só:
O que já está comprado e ainda vem, por mês e por cartão (roxo Nubank, vermelho Bradesco — as cores institucionais, as mesmas em toda a tela):
Dois rankings que respondem perguntas diferentes — e é a diferença que importa:
Repare no ATACADAO: à esquerda ele aparece duas vezes, em 3º e 5º, como duas compras de R$ 456 e R$ 321. À direita, somado, ele sobe para 2º com R$ 777. A lista da esquerda acha a compra única e grande; a da direita acha o ralo que só existe somado — e é justamente esse que passa despercebido.
Uma visão histórica inspirada na seção “Minha Vida Financeira” do projeto de referência. Ela permite alternar entre 3, 6 e 12 meses e mostra saldo atual, receitas contra despesas, melhor mês, mês mais caro, categorias que puxaram o mês mais caro, fluxo acumulado ou resultado por competência e distribuição por categoria. Tudo é calculado a partir dos documentos importados; patrimônio, investimentos e metas digitadas não são inventados pelo app.
O cabeçalho das seções internas também oferece Voltar, preservando o recorte da URL e evitando depender do botão do navegador.
Uma leitura aprofundada do histórico importado, com receitas, despesas, resultado,
taxa de economia, evolução por competência, categorias, maiores saídas e os
estabelecimentos que mais concentraram gastos. A janela pode ser alternada entre
3, 6 e 12 meses, e a navegação rápida abre com Ctrl+K/⌘K.
A janela usa meses de calendário e fica salva na URL. A comparação só aparece quando os dois intervalos têm transações em todos os meses; meses ausentes são marcados no gráfico, sem virarem zero. Ver meses e documentos detalha o gasto real e a quantidade de documentos por competência. Essa presença não garante que todos os documentos de todas as contas foram importados.
Os gráficos usam cores distintas para entradas, saídas e resultado, com destaque por mouse, teclado ou toque. As categorias mantêm a mesma cor ao mudar de posição no ranking. Nos relatórios, clicar numa categoria, estabelecimento ou maior saída abre as transações que compõem o valor, agrupadas por documento, sem perder o intervalo analisado. Voltar ao gráfico retorna ao controle acionado.
As convenções visuais estão no design system, e as regras dessa comparação na spec dos relatórios.
⚠️ Nenhum número aqui é real
Todas as imagens saem de
frontend/demo.html, uma folha de provas com dados fictícios — saldos, compras, estabelecimentos e e-mail, todos inventados. Nenhum extrato, valor ou dado pessoal real aparece neste repositório, e é por desenho: PDFs reais são ignorados pelo git e os prints nunca são tirados do app com a conta de verdade. Regere compython scripts/gerar-prints.py.
| 📄 Lê o PDF no navegador | pdf.js em worker; o arquivo não é enviado a servidor nenhum |
| ✅ Confere ao centavo | cada parser valida o total lido contra o total declarado no PDF |
| 🏦 7 bancos | Nubank, Bradesco, Banco do Brasil, Sicredi, Sicoob, Mercado Pago e Caixa (GovConta) — conforme o layout do documento |
| 🏷️ Categoriza e aprende | 30 categorias embutidas; corrigir uma compra ensina o app para as próximas |
| 🔗 Não conta o mesmo dinheiro duas vezes | cruza fatura × extrato e marca quitação e transferência entre contas próprias |
| 📅 Dia / Semana / Mês / Ano | Mês e Ano agrupam por competência da fatura; Dia e Semana, pela data da compra |
| 📊 Gráficos próprios em SVG | donut por categoria, saídas por dia, entradas × saídas, parcelas por mês |
| 📈 Minha situação financeira | histórico de 3/6/12 meses, melhor e pior mês, fluxo acumulado e categorias do mês mais caro |
| 📊 Relatórios financeiros | receitas, despesas, resultado, economia, categorias, maiores saídas e comparações por janela |
| 💡 Leituras do período | maior categoria, maior estabelecimento, pico diário e taxa de economia, com links para o detalhe |
| 🏪 Onde mais saiu dinheiro | ranking por estabelecimento, somando as compras repetidas — o gasto que nenhuma lista de "maior compra" mostra |
| 📈 Compara com o período anterior | "12% acima do mês passado" nos tiles; some quando não há base de comparação, em vez de inventar um "+100%" |
| 🔮 Compromissos futuros | resume os próximos meses no Painel e projeta todas as parcelas por mês e cartão em Recorrências, sem duplicar quando a fatura chegar |
| 🔁 Recorrências e alertas | detecta assinaturas pelo histórico e avisa quando o valor muda ou a cobrança some |
| 💵 Saldo por conta e próximas faturas | lidos do documento mais recente de cada banco |
| 📤 Relatório em PDF | gera o período em jsPDF e compartilha (celular) ou baixa (desktop) |
flowchart LR
PDF["📄 PDF da fatura<br/>ou do extrato"]
subgraph NAV ["🖥️ Navegador — o PDF não sai daqui"]
W["pdf.js worker<br/>→ linhas com coordenadas"]
P["detecta o banco<br/>→ parser do documento"]
V{{"confere contra o<br/>gabarito do PDF"}}
C["categoriza · vincula<br/>· dedupe por conteúdo"]
AV["avisa, não esconde"]
end
DB[("🐘 Neon Postgres<br/>RLS por usuário")]
UI["📊 Painel · Lançamentos · Relatórios<br/>Faturas · Recorrências · Situação financeira"]
PDF --> W --> P --> V
V -->|"bate ao centavo ✅"| C
V -->|"não bate ⚠️"| AV
C -->|"só as transações"| DB --> UI
style PDF fill:#1b5e8f,color:#fff
style DB fill:#1b5e8f,color:#fff
style UI fill:#2e7d5b,color:#fff
style AV fill:#945f14,color:#fff
O gabarito é o coração. Todo banco imprime, na própria fatura, um total que ele declara. O parser soma o que leu e compara. Se não bater, o app diz que não bateu — nunca mostra um número que não fecha como se fechasse.
Monorepo com npm workspaces. O domínio é puro — sem React, sem I/O — e é onde vivem as regras que fazem o dinheiro fechar. É isso que permite testá-las sem navegador e sem banco.
graph TD
PG["<b>paginas/</b> · as 8 telas<br/>Painel · Lançamentos · Faturas · Importação · Relatórios<br/>Categorias · Recorrências · Situação financeira"]
CP["<b>ui/</b> · componentes<br/><i>gráficos em SVG próprio, sem biblioteca</i>"]
RC["<b>dados/</b> · o recorte da tela<br/><i>useRecorte + filtros na URL</i>"]
DOM["<b>domain/</b> · puro, sem React e sem I/O<br/>parsers · validate · categorize · link<br/>dedupe · recorrencias · normalize · agrupar"]
APL["<b>aplicacao/</b> · fronteira das telas<br/>consultas de leitura · comandos de escrita"]
PER["<b>persist/</b> · adaptador do Neon<br/><i>salvar · puxar · editar</i>"]
LIB["<b>lib/</b> · cliente Neon<br/>e auth por HTTP"]
NEON[("Neon Data API<br/>PostgREST + RLS")]
AUTH["Neon Auth<br/>Better Auth"]
PG --> RC
CP --> RC
RC --> APL --> PER
PG --> APL
PG --> DOM
PER --> DOM
PER --> NEON
PG --> LIB --> AUTH
style DOM fill:#eef4f8,stroke:#1b5e8f,stroke-width:2px,color:#12303f
style NEON fill:#1b5e8f,color:#fff
style AUTH fill:#1b5e8f,color:#fff
capital-financeiro/
├── frontend/ # o app React (workspace @capital/frontend)
│ ├── index.html # entrada de produção (CSP, tema pré-pintura, OG)
│ ├── demo.html + src/demo.tsx # folha de provas: componentes com dados fictícios
│ ├── public/ # fontes próprias (16 woff2), og.png, favicon
│ ├── tests/fixtures/ # JSON extraído e anonimizado
│ └── src/
│ ├── domain/ # ⭐ regras puras (sem React, sem I/O)
│ │ ├── normalize/ # dinheiro em centavos, datas, parcelas, estabelecimento
│ │ ├── pdf/ # texto com coordenadas, linhas, detecção do banco
│ │ ├── parsers/ # Nubank, Bradesco, BB, Sicredi, Sicoob, Mercado Pago, Caixa GovConta
│ │ ├── categorize/ # catálogo de 30 categorias, regras e aprendizado
│ │ ├── validate/ # conferência contra o gabarito do banco
│ │ ├── link/ # vínculos entre documentos (dupla contagem)
│ │ ├── dedupe/ # conteúdo do Documento e hash de transação
│ │ └── recorrencias.ts # séries que se repetem + alertas
│ ├── persist/ # adaptador da Data API
│ ├── aplicacao/ # consultas e comandos usados pelas telas
│ ├── dados/ # filtros na URL, recorte, provider do histórico
│ ├── ui/ # primitivos, marca e o que é usado por 2+ grupos
│ │ ├── acesso/ # as telas de anônimo (entrar, criar, recuperar)
│ │ ├── graficos/ # gráficos em SVG próprio + comparativos + escala robusta
│ │ └── listas/ # rankings e listas de lançamentos
│ ├── paginas/ # as 8 telas roteadas e autenticadas
│ ├── i18n/ # pt / en / es
│ └── lib/ # cliente Neon, auth por HTTP, perfil local
├── backend/
│ └── db/migrations/ # SQL versionado (schema + RLS)
├── scripts/ # medidores e ferramentas (ver abaixo)
└── docs/ # ESTADO-ATUAL.md, specs, planos, imagens
Stack: React 19 · TypeScript (strict) · Vite 8 · Tailwind v4 · Motion · sonner · pdf.js · jsPDF · Vitest · oxlint · Neon (Postgres + Data API + Auth) · Vercel.
Seis tabelas, incluindo client_errors, todas com RLS. O cliente nunca envia user_id: ele
vem do default (auth.user_id()) no servidor, então não há como um cliente adulterado
gravar linha no nome de outra pessoa.
erDiagram
ACCOUNTS ||--o{ DOCUMENTS : "origem"
ACCOUNTS ||--o{ TRANSACTIONS : "conta"
DOCUMENTS ||--o{ TRANSACTIONS : "cascata ao apagar"
TRANSACTIONS ||--o| TRANSACTIONS : "vínculo (quitação)"
ACCOUNTS {
uuid id PK
uuid user_id "auth.user_id()"
text bank "nubank, bradesco, bb…"
text type "checking, credit_card"
text agency_number "agência e conta"
}
DOCUMENTS {
uuid id PK
text file_hash "identidade do PDF bruto"
text content_hash "identidade do conteúdo financeiro"
text doc_type "fatura, extrato"
date period_end "⭐ define a COMPETÊNCIA"
bigint declared_total "o gabarito do banco"
bigint parsed_total "o que o app leu"
bigint total_open_balance "ciclo em aberto"
bigint future_installments_total "parcelas a vencer"
}
TRANSACTIONS {
uuid id PK
date date "data da compra"
bigint amount_cents "inteiro, nunca float"
text kind "expense, income, card_payment, internal_transfer"
text category_slug
text hash "dedupe da linha"
uuid linked_transaction_id FK
}
CATEGORIES {
text slug
text nome_icone_cor "personalização do usuário"
}
MERCHANT_RULES {
text padrao "o que o app aprendeu"
text categoria
int prioridade
}
💵 Dinheiro é
bigintem centavos, nunca float.0.1 + 0.2 !== 0.3em ponto flutuante, e um app cuja promessa é fechar ao centavo não pode começar perdendo centavos na soma.
Quem for mexer no código precisa destas. São a diferença entre um número certo e um número que mente com confiança:
Fatura e extrato contam o mesmo dinheiro duas vezes. Somar cru infla o gasto em
~2×. A fatura manda no detalhe; o pagamento dela, que aparece no extrato, é quitação
(card_payment) e não despesa nova. → domain/link/vinculos.ts
Mês = competência da fatura, não a data da compra. Uma fatura cobre um ciclo que
atravessa dois meses; agrupar pela data real partia a fatura ao meio (o supermercado
de junho aparecia como R$ 287 em vez dos R$ 918 reais). A competência é o mês do
documents.period_end. Dia e Semana seguem a data real, porque ali a pergunta é
outra. → domain/agrupar.ts
O PDF declara totais que servem de gabarito. O parser se autoconfere contra eles.
Nunca exiba um número que não bateu sem avisar. → domain/validate/checksum.ts
Compromissos futuros nunca vão para o banco. São calculados na hora, a partir da
parcela mais recente de cada série — por isso não duplicam quando a próxima fatura é
importada. → domain/agrupar.ts::projecaoFutura
Um Documento é identificado pelo conteúdo financeiro, não pelo nome do PDF.
Reexportar o mesmo extrato pode mudar metadados e IDs internos do PDF; a impressão
canônica usa banco, período, gabarito, conta e transações normalizadas. →
domain/dedupe/hash.ts
flowchart TB
subgraph B ["🖥️ Navegador — o PDF entra e não sai"]
PDF["PDF com CPF, agência,<br/>conta e nomes de terceiros"]
APP["App React<br/><i>CSP completa</i>"]
PDF --> APP
end
APP -->|"HTTPS + JWT<br/>só as transações"| API["Neon Data API<br/>PostgREST"]
API --> RLS{{"RLS: user_id = auth.user_id()<br/><i>nas 6 tabelas</i>"}}
RLS --> T1[("linhas do usuário A")]
RLS -.->|"invisível"| T2[("linhas do usuário B")]
style PDF fill:#945f14,color:#fff
style RLS fill:#1b5e8f,color:#fff
style T2 stroke-dasharray: 5 5
*.pdf): contêm CPF, agência, conta e nomes de
terceiros, e o histórico do git é permanente. Os fixtures são anonimizados
(scripts/gerar-fixtures.ts).auth.user_id(). O cliente nunca manda user_id.vercel.json), incluindo script-src 'self' + hash, base-uri 'none' e connect-src com as duas origens exatas da Neon — um curinga
*.neon.tech seria canal de exfiltração pronto, porque qualquer pessoa cria um
projeto Neon e ganha um endpoint sob ele.scripts/medir-csp.py sobe o build com os
headers lidos do próprio vercel.json e dispara 16 sondas, metade delas esperando
bloqueio (script inline injetado, eval, imagem para fora, <base> externa…).
Sem esses controles negativos, um header que nem chegou ao navegador daria zero
violações e nota máxima.VITE_* são públicas por design (vão para o bundle). A proteção real
é o RLS + o JWT. Nunca ponha a string de conexão do Postgres num VITE_.npm audit faz parte da revisão. Em 13/08/2026 ele apontou uma falha alta no
pdfjs-dist — execução de JavaScript arbitrário ao abrir um PDF malicioso —, que é
a superfície mais exposta do app inteiro: abrir PDF é o que ele faz. Corrigida
naquela rodada (5.7.284 → 6.2.108); o manifesto atual já usa ^6.3.289.
O SDK do Neon já está em ^0.7.0-beta. Esses registros descrevem correções
realizadas; a avaliação de segurança de uma instalação exige auditar o lockfile
atual, sem reaproveitar um resultado antigo como prova.Sobre o upgrade do pdf.js: nenhum teste do projeto exercita o pdf.js de verdade (os fixtures são JSON já extraído, e
domain/pdf/load.tsé mockado em jsdom, que não temDOMMatrix). A suíte inteira passaria verde com o parser quebrado. Por isso o salto de major foi verificado gerando um PDF com texto conhecido e extraindo de volta pela mesma chamada que o app usa: texto e coordenadas idênticos nas duas versões.
npm test && npm run build && npm run lint # os três, sempre
python scripts/medir-csp.py # DEPOIS do build
python scripts/medir-contraste.py # WCAG de todos os pares em uso
python scripts/medir-overflow.py # rolagem lateral, 1280×800 e 390×844
⚠️
npm testnão checa tipos. Verde no Vitest não é verde — quem pega erro de tipo é otsc -bdonpm run build. Esta armadilha já mordeu três vezes neste projeto.
| Ferramenta | O que ela reprova |
|---|---|
| 1.095 testes (129 arquivos) | regra de negócio, componente e contrato de HTTP |
medir-csp.py | política que quebra o app ou que não bloqueia o que promete |
medir-contraste.py | par de cores abaixo de AA — já achou 6, corrigidos por busca preservando matiz |
medir-overflow.py | rolagem lateral no desktop e no celular |
gerar-prints.py | (não reprova) regera as imagens deste README |
diagnostico.ts | roda o pipeline real nos seus PDFs e imprime o total por categoria |
Método: TDD. E a lição mais cara registrada aqui é teste que passa dos dois jeitos é pior que nenhum — mais de um defeito sobreviveu a uma suíte verde porque a asserção era fraca demais para notar a diferença. Correções importantes são verificadas por mutação: quebra-se a implementação de propósito e confere-se que o teste cai.
npm install
npm run dev # http://localhost:5173
Para visualizar o app já autenticado, com dados fictícios e sem usar a conta real do Neon:
npm run build:login
python scripts/medir-login.py
Abra http://127.0.0.1:4599. A demonstração usa a conta fictícia Ana; as
credenciais de teste ficam no próprio script do servidor local e não dão acesso
a nenhum ambiente de produção.
Sem variáveis de ambiente o app roda em modo "importa e vê": lê o PDF e mostra os
gráficos, sem login e sem salvar nada. Para persistir, crie um .env.local na raiz
(não em frontend/ — o Vite está com envDir: '..'):
VITE_NEON_DATA_API_URL=https://<endpoint>.apirest.<região>.aws.neon.tech/neondb/rest/v1
VITE_NEON_AUTH_URL=https://<endpoint>.neonauth.<região>.aws.neon.tech/neondb/auth
Depois aplique as migrações de backend/db/migrations/ no SQL Editor do Neon, na ordem.
Outros comandos:
npm run build # tsc -b + build de produção
npm test # Vitest
npm run lint # oxlint
npm run fixtures -- <pasta-com-os-pdfs> # regenera fixtures anonimizados
npx tsx scripts/diagnostico.ts <pasta> # diagnóstico nos seus PDFs
python scripts/gerar-prints.py # prints deste README (com o dev no ar)
O visual anterior — creme, serifada de alto contraste, âmbar, cantos de 16px, brilho e grão — era o cluster nº 1 do que hoje se reconhece de longe como página gerada por IA. A direção atual sai do que o app faz: ele confere, ao centavo, contra o gabarito do banco. Isso é conciliação contábil, e o desenho vem desse mundo.
#f6f7f9), tinta azul-ferro, marca azul de
carimbo (#1b5e8f)font-src 'self'Um pagamento de empréstimo de R$ 41.653 num mês de compras de dezenas achatava as outras
25 barras contra o chão. Escala logarítmica resolveria o aperto e cobraria caro: as
alturas deixariam de ser comparáveis em silêncio, e ninguém desconfia de um gráfico
bonito. A saída foi escala linear até um teto robusto (cerca de Tukey, q3 + 1,5·IQR), com quem passa dele desenhado cortado — serrilha à vista, valor cheio no
nome acessível e o aviso "escala até R$ 816 · 1 dia acima" embaixo. A distorção existe,
é local, e está declarada. → ui/escala-barras.ts
Implementado: importação, conferência ao centavo, categorização com aprendizado, vínculos, persistência, login, recuperação de senha, confirmação de e-mail por código, as oito páginas, filtros na URL, gráficos, recorrências, compromissos futuros, tutorial e relatório em PDF.
O backend próprio foi descartado, conforme a
ADR-0011. O adaptador conversa
com a Data API sob RLS; backend/ guarda as migrações SQL. O upgrade do SDK
para ^0.7.0-beta já consta no manifesto.
Em execução na rodada de 18/10/2026: vínculos com documentos do histórico, explicação das duas pontas na tela e regras de categoria com operadores. A spec da rodada e os planos registram escopo e critérios; implementação local não significa publicação.
Ainda dependem de prova ou recurso externo:
0011-caixa.sql precisa ser
aplicada antes de publicar o suporte Caixa. O
inventário registra as provas
e os limites de cobertura.medir:login usa SDK real com Auth sintético e não prova a
entrega de e-mail nem o OAuth real.A confirmação de e-mail funciona por código de seis dígitos; confirmação por link não é uma pendência do fluxo atual.
O histórico completo, com o porquê de cada decisão, está em
docs/ESTADO-ATUAL.md — é o arquivo para ler antes de retomar
o trabalho.
Cielio Queiroz — portfólio
Projeto de uso livre para fins pessoais e de estudo.
| 🔐 Conta com RLS | login e recuperação de senha via Neon Auth; cada usuário só enxerga as próprias linhas |
| 🌗 Tema claro/escuro | responsivo, do desktop largo ao celular |