OpenChart
Free Desktop charting with Claude or Codex. Describe alerts, research chart moves, and choose your data providers.
Central financeira compartilhada: faturas, categorias, parcelas, previsões, orçamentos e metas do casal.
Translation pending
FinanceTypeScript · PLpgSQLGitHub @TSH364OnlineOwner 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
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Central financeira compartilhada: importa faturas, categoriza lançamentos, projeta o que já está comprometido e registra quem fez cada alteração.
As sete etapas estão implementadas. O que não está pronto está listado abaixo, em "Limitações conhecidas" — nada aparece na interface como dado inventado.
| Etapa | Escopo | Situação |
|---|---|---|
| 1 | Estrutura, autenticação, banco, migrations, seed, layout base | Pronta |
| 2 | Cartões, categorias, lançamentos, extratos, membros, resumo do mês | Pronta |
| 3 | Importação de fatura, normalização, duplicidades, revisão | Pronta para CSV — XLSX e PDF não leem |
| 4 | Extratos, edição, exclusão, histórico, auditoria na tela | Pronta |
| 5 | Parcelas, recorrências, previsão, conciliação | Pronta |
| 6 | Orçamentos, metas, insights, divisão de despesas | Pronta |
| 7 | Testes E2E, acessibilidade, deploy | Parcial — E2E escritos, não executados aqui |
159 testes unitários cobrem dinheiro, datas, resumo do mês, leitura de fatura, parcelas, recorrências, previsão, metas, insights e divisão.
Estas são reais e nenhuma está escondida atrás de uma tela que finge funcionar.
.next a cada compilação (EBUSY: resource busy or locked), derrubando o servidor de desenvolvimento com erro 500 no meio da
suíte. Os testes estão escritos e configurados; para executá-los, mova o
projeto para fora do OneDrive ou pause a sincronização antes de rodar./historico) com filtro por pessoa, ação e tipo de
registro. Frases prontas, nunca JSON cru:Vinicius alterou o orçamento de Alimentação: o limite de R$ 2.000,00 para R$ 2.300,00
Vinicius alterou Starlink: a categoria de Serviços para Assinaturas
Vinicius excluiu Starlink de R$ 98,00
O JSON antes/depois continua guardado no banco para rastreabilidade, mas a tela não o lê.
Fluxo de importação em /importar, com o arquivo lido no navegador — nada
sai da máquina antes de você revisar e confirmar.
date,title,amount), do Itaú (separado por ;, com
Valor (em US$), Cotação (em R$) e Valor (em R$)) e variantes em
português.2026-08, 08-2026, agosto-2026, 202608,
20260105), com as datas como segunda tentativa e confirmação manual sempre
disponível. Num arquivo nomeado pelo vencimento, o mês é o do vencimento — as
compras são do mês anterior.Parcela = 2/12, Única) ou, quando não
existe, extraída da descrição (NETFLIX 3/12), sem confundir 12/2026.Desconto nunca vira pagamento — exigência da secao 6.Arquivo de exemplo para testar: docs/exemplo-nubank.csv.
XLSX e PDF ainda não leem. A tela mostra mensagem clara pedindo o CSV, em vez de importar dado parcial em silêncio.
Suspense com chave derivada deles, então trocar de mês derruba os
cards em skeleton em vez de mostrar o total anterior sob o título novo
(secao 20).Ainda não feito na Etapa 2: convite de membro por e-mail e CRUD de categorias e subcategorias.
O projeto Supabase já existe e as migrations já estão aplicadas:
| Projeto | Fluxo (admauxgjmhskhvrogaaf) |
| Região | sa-east-1 — São Paulo |
| URL | https://admauxgjmhskhvrogaaf.supabase.co |
| Migrations aplicadas | 0001 → 0005 |
Contra o banco real, em transações revertidas ao final (nenhum dado de teste ficou gravado):
owner.PG *99 RIDE 3/12 normaliza para 99 RIDE (prefixo e sufixo de parcela removidos).duplicate_key é gerada no insert.accept_house_invite é SECURITY DEFINER chamável por usuário logado, o que é
necessário porque um convidado ainda não é membro; a função só casa o convite
com o e-mail autenticado de quem chama.Também passam: npm run typecheck, npm test (159 testes) e npm run build
(14 rotas). As telas de login e cadastro foram carregadas contra o banco real,
em 375px e em desktop, sem erro de console.
Pela interface, já com a conta real logada: Início, Extratos, Cartões e Casa carregam contra o banco; o modal de lançamento abre com cabeçalho e rodapé fixos e só o corpo rolando; nenhum erro de console.
Ainda não exercitado: gravar de fato um lançamento, editar e excluir pela interface. As actions estão escritas e tipadas, mas nenhum dado foi criado na casa real — esse teste é seu.
No painel do Supabase, em Authentication → URL Configuration, adicione às Redirect URLs:
http://localhost:3000/auth/confirmar
E depois, quando publicar na Vercel, também a URL de produção. Sem isso o link de confirmação de e-mail não volta para o app.
| Peça | Escolha | Por quê |
|---|---|---|
| Framework | Next.js 15, App Router | Server Actions evitam uma camada de API só para formulários |
| Linguagem | TypeScript, strict + noUncheckedIndexedAccess | Acesso a índice devolve T | undefined, o que pega acesso fora de faixa em tempo de compilação |
| Estilo | Tailwind CSS v4 | Tokens de tema em CSS puro, sem tailwind.config.js |
| Componentes | Padrão shadcn/ui, escritos à mão | Mesma API, sem depender de rodar a CLI para compilar |
| Banco e auth | Supabase (Postgres + GoTrue) | RLS resolve o isolamento por casa dentro do banco |
| Gráficos | Recharts | Pedido na especificação |
| Validação | Zod | Mesmo schema valida formulário e Server Action |
| Testes | Vitest, Playwright | Vitest compartilha a resolução de @/ com o Vite |
profiles em vez de User. No Supabase a identidade canônica vive em
auth.users, gerenciada pelo GoTrue e não alterável por nós. public.profiles
espelha os dados de exibição e é o que a aplicação lê. Os nomes vêm sempre
daí — não há nome fixo em código.
goals.current_amount não é coluna. É a soma de goal_deposits,
exposta pela view goals_with_progress. Guardar o saldo em duplicidade
abriria espaço para ele divergir do histórico de depósitos.
duplicate_key tem índice não-único. A especificação exige que
duplicidades sejam exibidas para revisão, não bloqueadas — a mesma compra
pode aparecer legitimamente em faturas de meses diferentes.
Sinal do CSV Nubank é detectado, não fixado. No arquivo real do Nubank a
despesa vem positiva e o pagamento negativo; o exemplo da especificação usa a
convenção oposta (PG *99 RIDE,"-32,90"). Como as duas aparecem, a Etapa 3
vai inferir a convenção por arquivo e pedir confirmação, em vez de assumir.
xlsx, pdfjs-dist e papaparse completos entram na Etapa 3. Instalar
agora bibliotecas pesadas de parsing sem código que as use só engordaria o
bundle.
A rosca de categorias é SVG puro, não Recharts. A biblioteca está no
projeto e vai ganhar o mapa de fluxo e as séries da Etapa 5, onde eixos,
escala e tooltip valem o peso. Para uma rosca estática ela custava 98 kB no
bundle de /inicio — mais que todo o resto da página somado. Em SVG o
componente roda no servidor e envia zero JavaScript.
PIX e dinheiro não são tipos de lançamento. São formas de pagamento: uma
despesa paga em PIX é expense sem cartão. Criar um tipo por meio de
pagamento faria os totais somarem categorias que são a mesma coisa.
Pré-requisitos: Node 20+ e Docker Desktop (para o Supabase local).
npm install
npm run db:start
O comando imprime API URL e anon key. Copie .env.example para .env.local
e preencha com esses valores. Depois:
npm run db:reset
Isso aplica as três migrations e roda o seed. Contas de teste:
[email protected] e [email protected], senha fluxo1234.
O .env.local já aponta para o projeto Fluxo em São Paulo, com as migrations
aplicadas. Não há nada a fazer além de npm run dev.
Para vincular a CLI e aplicar migrations futuras:
npx supabase link --project-ref admauxgjmhskhvrogaaf
npm run db:push
Não rode o seed na nuvem: ele cria usuários fictícios.
npm run dev
O app precisa de um servidor Node rodando: ele monta cada tela no servidor, com a sessão de quem pediu. Não funciona como site estático — exportar para HTML derrubaria middleware de autenticação, Server Actions e importação.
Nos dois caminhos abaixo, o banco continua sendo o Supabase. A escolha de hospedagem não move seus dados.
As duas variáveis públicas ficam versionadas em .env.production, com a
explicação de por que isso é seguro: a URL do Supabase é pública e a chave
anon foi feita para ir dentro do navegador — quem isola uma casa da outra é
o Row Level Security, não o sigilo dessa chave.
Isso significa que o deploy funciona sem configurar nada no painel. Variáveis definidas na hospedagem continuam tendo precedência sobre o arquivo, então apontar o app para outro projeto Supabase não exige editar código.
SUPABASE_SERVICE_ROLE_KEY nunca entra nesse arquivo: ela ignora o RLS,
nenhuma rota a usa, e o lugar dela é o .env.local da sua máquina.
| Variável | Valor | Obrigatória? |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | URL do projeto Supabase | sim |
NEXT_PUBLIC_SUPABASE_ANON_KEY | chave publicável (anon) | sim |
NEXT_PUBLIC_SITE_URL | endereço final do app | só fora da Vercel |
Na Vercel, o endereço de produção é descoberto sozinho por
VERCEL_PROJECT_PRODUCTION_URL, então bastam as duas primeiras. Defina a
terceira quando usar domínio próprio ou outra hospedagem — sem ela, e sem a
variável da Vercel, o app se recusa a subir em produção em vez de mandar o
link de confirmação de e-mail para localhost.
SUPABASE_SERVICE_ROLE_KEY não vai para o servidor de produção: ela ignora
o RLS e nenhuma rota do app a usa. Ela existe só para scripts administrativos
rodados na sua máquina.
Depois de publicar, nos dois casos:
https://SEU-DOMINIO/auth/confirmar às Redirect URLs. Sem isso o link de
confirmação de e-mail volta para localhost.supabase/config.toml desliga isso
apenas no ambiente local.git push na main republica sozinho.É a implementação de referência do Next.js: middleware, Server Actions e revalidação são de primeira mão. Para um app de duas pessoas o plano gratuito sobra — e ele é permanente nesse volume, não período de teste. O plano gratuito proíbe uso comercial; finanças pessoais de um casal está dentro.
Só o produto de aplicações web (Node.js). A hospedagem de site comum (Premium/Business) é PHP e não roda este app.
npm run build, de início npm run start.Dois pontos antes de fechar o plano:
next build costuma pedir 1–2 GB;
com menos que isso o build falha. O plano com 4 GB não tem esse problema.Faz sentido se você já paga Hostinger e quer um fornecedor só, ou se quer os dados fora dos EUA.
| Comando | O que faz |
|---|---|
npm run dev | Servidor de desenvolvimento |
npm run build | Build de produção |
npm run typecheck | tsc --noEmit |
npm test | Testes unitários (Vitest) |
npm run test:e2e | Testes end-to-end (Playwright) — Etapa 7 |
npm run db:start | Sobe o Supabase local |
npm run db:reset | Recria o banco local e roda o seed |
npm run db:push | Aplica as migrations no projeto vinculado |
src/
app/
(app)/ área autenticada; o layout exige sessão e casa ativa
entrar/ login, cadastro e Server Actions de auth
nova-casa/ fora de (app) para não criar laço de redirecionamento
auth/confirmar/ troca o token do e-mail por sessão
components/
ui/ botão, campos, card — padrão shadcn/ui
states.tsx skeleton, estado vazio, estado de erro
domain/ regras financeiras puras, sem I/O
lib/
money.ts leitura e formatação de valores; aritmética em centavos
supabase/ clients de browser e de servidor
houses.ts casa ativa e membros
middleware.ts renova a sessão e protege as rotas
supabase/
migrations/ 0001 estrutura · 0002 RLS · 0003 auditoria
seed.sql dados fictícios — nunca dados reais
importers/ leitura de fatura: colunas, sinal, mês, duplicidade
tests/unit/ 159 testes
Dinheiro em centavos inteiros. 0.1 + 0.2 em ponto flutuante dá
0.30000000000000004. Numa fatura de centenas de linhas isso vira divergência
contra o total impresso pelo banco — justamente o número que a especificação
manda comparar. Todo cálculo em src/domain opera em centavos.
parseAmount devolve null, não 0. Zero é um valor legítimo; usá-lo
como sinal de erro faria a importação engolir linhas quebradas em silêncio.
Despesa e receita têm funções separadas. Somar receita com sinal invertido dentro do total de despesa faz os cards "total gasto" e "receitas" saírem do mesmo número e nunca fecharem.
Meses são strings YYYY-MM, nunca Date. new Date("2026-08-01") é
interpretado como UTC e, no fuso de Brasília, getMonth() devolve julho.
Auditoria por trigger, não pela aplicação. Assim ela registra também alterações feitas por script, RPC ou pelo painel do Supabase.
audit_log é append-only. Não existe policy de UPDATE nem de DELETE; com
RLS ligada, a ausência de policy nega a operação.
A normalização de estabelecimento existe em SQL e em TypeScript. O banco
precisa dela para preencher merchant_normalized em qualquer gravação; o
importador precisa dela antes de gravar, para agrupar e comparar. As duas
implementações são cobertas pelos mesmos exemplos em
tests/unit/importers.test.ts — foi assim que apareceu o bug em que "PAG"
comia o começo de "PAGUE MENOS" (corrigido na migration 0006).
viewer lê e não escreve. A função app.can_write() exclui esse papel, e
todas as policies de escrita passam por ela.
SECURITY DEFINER para não recursionar nas
próprias policies, e vivem no schema app, fora do PostgREST.?proximo= é validado como caminho interno, contra open redirect.SUPABASE_SERVICE_ROLE_KEY só é legível em código de servidor e não é usada
por nenhuma rota — existe para scripts administrativos locais.category_hint, card_last_four). Lançamentos
gravados antes dessas colunas não têm esses dados: a categoria ainda sai do
nome da loja, mas o cartão não tem de onde sair — só reimportando.