OpenChart
Claude·Codex와 함께하는 무료 데스크톱 차트 도구. 알림 설정·차트 움직임 분석·데이터 제공자 선택
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
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.