Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
Importe faturas e extratos em PDF e enxergue seus gastos por categoria, dia, mês e ano.
Translation pending
FinanceTypeScript · PythonGitHub @cielioqueirozOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
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.
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
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:Read the full 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 |