Cuide do que você planta.
Broto é um app de controle financeiro pessoal baseado na regra 50/30/20, construído para quem quer sair da planilha sem abrir mão do controle real sobre o dinheiro. Nasceu de uma planilha com tabelas de gastos fixos, parcelamentos e gastos variados — e evoluiu para um app completo com múltiplas contas, metas financeiras, cartão de crédito e muito mais.
| Área | O que faz |
|---|---|
| 50/30/20 | Divide gastos em Essenciais / Estilo de Vida / Investimento com orçamento baseado no salário |
| Salário + Renda Extra | Salário fixo configurado em Preferências; rendas eventuais (freelance, bônus) registradas separadamente |
| Gastos | CRUD completo com categoria, grupo, forma de pagamento, conta vinculada |
| Parcelamentos | Divide compras em até 48x com datas sequenciais e rastreamento de parcelas pagas |
| Gastos Fixos | Marque um gasto como recorrente — ele aparece todo mês aguardando confirmação |
| Lançamento Rápido | Registre um gasto com apenas 3 campos (valor, categoria, conta) com grupo inferido automaticamente |
| Contas | Corrente, poupança, cartão de crédito (fatura + limite), investimento e dinheiro físico |
| Transferências | Move saldo entre contas sem impactar receitas/despesas |
| Metas | Objetivos com progresso, prazo e aportes vinculados ao orçamento de Investimento |
| Dashboard | Patrimônio total, donut 50/30/20, progresso por grupo, fixos pendentes, atividade recente |
| Histórico | Gráfico de 12 meses com evolução de receitas, despesas e saldo |
| Reset de Senha | Fluxo completo via email (Resend) com token de 1 hora |
| Exportação | Relatório completo em CSV |
- Go 1.24 — API REST, sem framework, stdlib nativa
- PostgreSQL 16 — banco de dados relacional
- JWT (HS256) — autenticação stateless
- bcrypt — hash de senhas (custo 10)
- log/slog — logs estruturados (JSON em produção, texto em dev)
- Resend — envio de emails transacionais (opcional)
- React 19 + TypeScript 5.7 — UI
- Vite 7 — build tool
- Tailwind CSS 3 — estilização
- Recharts — gráficos
- React Router 6 — navegação
- Geist — tipografia
- Render — backend (Go)
- Vercel — frontend (React)
- Docker Compose — banco local em desenvolvimento
controle-financeiro/
├── cmd/api/main.go # Entrypoint — middlewares, startup
├── internal/
│ ├── database/ # Conexão e migrations automáticas
│ ├── handlers/ # Handlers HTTP por domínio
│ ├── middleware/ # Auth JWT com invalidação pós-troca de senha
│ ├── models/ # Structs de domínio
│ ├── routes/ # Registro de rotas
│ ├── logger/ # Configuração do slog
│ └── testhelpers/ # Utilitários para testes de integração
├── migrations/ # 23 migrations SQL (executadas no startup)
├── frontend/
│ ├── src/
│ │ ├── components/ # Componentes reutilizáveis
│ │ ├── hooks/ # Custom hooks
│ │ ├── config/ # URL da API
│ │ ├── types.ts # Tipos de domínio TypeScript
│ │ └── *.tsx # Páginas
│ ├── tailwind.config.js # Design system Broto
│ └── tsconfig.json
├── docker-compose.yml # PostgreSQL local
├── render.yaml # Deploy Render
└── .env.example # Template de variáveis de ambiente
Fluxo de request:
Cliente → requestLogger → maxBytesReader (1MB) → CORS → WithAuth (JWT + DB) → Handler
- Go 1.24+
- Node.js 20+
- PostgreSQL 16+ (ou Docker)
git clone https://github.com/edgar-lins/controle-financeiro.git
cd controle-financeiro
# Backend
cp .env.example .env
# Edite .env com suas credenciais do banco e JWT_SECRET
# Frontend
cd frontend
cp .env.example .env.developmentCom Docker (recomendado):
docker compose up -dPostgreSQL local:
createdb controle_financeiroAs migrations rodam automaticamente no startup do servidor.
go mod tidy
go run cmd/api/main.go
# Servidor em http://localhost:8080cd frontend
npm install
npm run dev
# App em http://localhost:5173| Variável | Descrição | Obrigatória em prod |
|---|---|---|
ENVIRONMENT |
development ou production |
✅ |
DATABASE_URL |
URL completa do banco (Render/Railway) — substitui as vars abaixo | — |
DB_HOST |
Host do PostgreSQL | ✅ |
DB_PORT |
Porta (padrão: 5432) |
✅ |
DB_USER |
Usuário do banco | ✅ |
DB_PASSWORD |
Senha do banco | ✅ |
DB_NAME |
Nome do banco | ✅ |
PORT |
Porta do servidor (padrão: 8080) |
— |
JWT_SECRET |
Chave de assinatura JWT — obrigatória em produção | ✅ |
ALLOWED_ORIGINS |
Origens CORS permitidas (separadas por vírgula) | ✅ |
RESEND_API_KEY |
API key do Resend para envio de emails | — |
RESEND_FROM_EMAIL |
Endereço de envio (domínio verificado) | — |
APP_URL |
URL pública do frontend (usada no link de reset de senha) | — |
⚠️ O servidor não inicia emENVIRONMENT=productionsemJWT_SECRETconfigurado.
💡 Gere um secret forte:
openssl rand -hex 32
| Variável | Descrição |
|---|---|
VITE_API_URL |
URL da API backend |
VITE_ENVIRONMENT |
development ou production |
Todos os endpoints protegidos requerem o header:
Authorization: Bearer <jwt_token>
Limite de payload: 1MB por request.
| Método | Endpoint | Descrição |
|---|---|---|
POST |
/auth/signup |
Cadastro |
POST |
/auth/login |
Login — retorna JWT (24h) |
POST |
/auth/forgot-password |
Solicita reset de senha (rate limit: 5/15min por IP) |
POST |
/auth/reset-password |
Redefine senha com token |
DELETE |
/auth/delete-account |
🔒 Remove conta e todos os dados (requer senha) |
| Método | Endpoint | Query Params | Descrição |
|---|---|---|---|
GET |
/expenses |
month, year, all_installments |
Lista gastos |
POST |
/expenses |
— | Cria gasto (suporta parcelamento) |
PUT |
/expenses/update |
id |
Atualiza gasto |
DELETE |
/expenses/delete |
id, delete_group |
Remove gasto (ou todo o parcelamento) |
GET |
/expenses/recurring-pending |
— | Lista fixos sem confirmação no mês atual |
POST |
/expenses/confirm-recurring |
— | Confirma um fixo para o mês atual |
Corpo de criação de gasto:
{
"description": "Netflix",
"amount": 55.90,
"category": "streaming",
"group": "lazer",
"payment_method": "credito",
"date": "2026-05-01",
"account_id": 1,
"installments": 1,
"is_recurring": true
}| Método | Endpoint | Descrição |
|---|---|---|
GET |
/incomes |
Lista rendas extras |
POST |
/incomes |
Registra renda extra |
PUT |
/incomes/update?id=X |
Atualiza renda |
DELETE |
/incomes/delete?id=X |
Remove renda |
| Método | Endpoint | Descrição |
|---|---|---|
GET |
/accounts |
Lista contas |
POST |
/accounts |
Cria conta |
PUT |
/accounts/update?id=X |
Atualiza conta |
DELETE |
/accounts/delete?id=X |
Remove conta |
POST |
/accounts/transfer |
Transfere entre contas |
Tipos de conta: corrente, poupanca, cartao, investimento
| Método | Endpoint | Descrição |
|---|---|---|
GET |
/goals |
Lista metas |
POST |
/goals |
Cria meta |
PUT |
/goals/update?id=X |
Atualiza meta |
PUT |
/goals/add-money?id=X |
Adiciona aporte (registra como investimento no 50/30/20) |
DELETE |
/goals/delete?id=X |
Remove meta |
| Método | Endpoint | Descrição |
|---|---|---|
GET |
/summary |
Resumo do mês (salário, renda extra, gastos por grupo, patrimônio) |
GET |
/summary/history |
Histórico de 12 meses |
GET |
/summary/breakdown |
Breakdown por grupo e categoria |
Resposta do /summary:
{
"mes": "May",
"ano": 2026,
"salario": 6000.00,
"renda_extra": 800.00,
"renda_total": 6800.00,
"gasto_total": 3120.00,
"ideal_fixos": 3400.00,
"real_fixos": 1820.00,
"ideal_lazer": 2040.00,
"real_lazer": 760.00,
"ideal_invest": 1360.00,
"real_invest": 540.00,
"saldo_restante": 3680.00,
"patrimonio_total": 12450.00
}| Método | Endpoint | Descrição |
|---|---|---|
GET |
/preferences |
Retorna preferências do usuário |
PUT |
/preferences |
Atualiza salário e percentuais 50/30/20 |
Os testes rodam contra um banco de dados real. Use TEST_DATABASE_URL para configurar:
# Criar banco de teste (uma vez)
createdb controle_financeiro_test
# Rodar testes
TEST_DATABASE_URL="host=localhost port=5432 user=<usuario> dbname=controle_financeiro_test sslmode=disable" \
go test ./internal/... -v
# Sem TEST_DATABASE_URL, os testes são pulados com SKIP (não falham)
go test ./internal/...Cobertura atual (16 testes):
account_handler_test.go— Transferências, saldo insuficiente, conta de outro usuário, mesma contaauth_handler_test.go— Login, senha errada, rate limiting, reset de senha, email duplicadoexpense_handler_test.go— Gasto único, parcelamento, datas sequenciais, filtro por mêsincome_handler_test.go— Criar, deletar e atualizar renda com ajuste de saldo
| Medida | Implementação |
|---|---|
| Autenticação | JWT HS256, 24h de validade |
| Senhas | bcrypt custo 10 |
| Invalidação pós-reset | password_changed_at no banco; middleware rejeita tokens emitidos antes |
| Rate limiting | 5 tentativas/15min por IP em /auth/login e /auth/forgot-password (persiste entre restarts) |
| Deleção de conta | Requer confirmação da senha atual |
| Autorização | Todas as queries incluem AND user_id = $X — impossível acessar dados de outro usuário |
| Race condition | SELECT FOR UPDATE em transferências |
| Payload | Limite de 1MB por request via MaxBytesReader |
| Validação de input | Valores negativos rejeitados, tipos de conta validados, closing_day validado (1-31) |
| Parcelamentos | Máximo 48x no backend |
| Recorrentes | NOT EXISTS check antes de confirmar — previne duplicatas |
| Headers | X-Frame-Options, X-XSS-Protection, HSTS, CSP, X-Content-Type-Options |
| CORS | Lista explícita de origens — nega tudo se ALLOWED_ORIGINS vazio |
| JWT em produção | Servidor não inicia sem JWT_SECRET configurado (os.Exit(1)) |
| Connection pool | 25 max open, 5 idle, 5min lifetime |
| Logs | log/slog — JSON em prod, texto em dev. Nenhum dado sensível (senha, token) nos logs |
O arquivo render.yaml configura o deploy automaticamente:
# Build
go build -o bin/api cmd/api/main.go
# Start
./bin/apiAs migrations rodam automaticamente no startup. Configure as variáveis de ambiente no painel do Render.
cd frontend
npm run build
# Deploy via Vercel CLI ou GitHub integrationVariáveis necessárias no Vercel:
VITE_API_URL— URL da sua API no Render
O Broto usa um sistema de cores próprio baseado no tema verde-escuro:
| Token | Cor | Uso |
|---|---|---|
primary |
#7FE0A0 |
Verde broto — ações principais, receitas |
bg |
#0E1410 |
Fundo do app |
surface |
#161E18 |
Cards e superfícies |
terracotta |
#D8997A |
Gastos essenciais |
amber |
#E8B86A |
Estilo de vida / alertas |
blue |
#8FB7E6 |
Investimento / metas |
rose |
#E68A8A |
Erro / acima do orçamento |
Tipografia: Geist com numerais tabulares para valores monetários. Ícones: Material Symbols Rounded.
Uso pessoal. Sinta-se à vontade para adaptar para o seu próprio controle financeiro.