Aplicativo de controle de orçamento pessoal com rendas, despesas mensais, despesas futuras, rendas futuras e investimentos. Inclui gráficos, indicadores de saúde financeira, projeção de despesas futuras e histórico mensal.
Desenvolvido por Pedro Henrique de Almeida.
A aplicação está rodando em:
Stack: Python 3.11 + Flask + SQLite, deploy em PythonAnywhere. Back e front servidos pelo mesmo processo (mesma origem, sem CORS aberto).
- Autenticação: cadastro e login com usuário/e-mail e senha (hash PBKDF2 + JWT).
- Rendas ativas e passivas com categoria (Salário, Freelance, Aluguel…).
- Rendas futuras (13º, PLR, restituição de IR…) com prazo em meses.
- Despesas mensais com dia de vencimento e categoria.
- Despesas futuras com prazo em meses (ex.: IPTU daqui a 6 meses).
- Investimentos com rendimento anual (% a.a.) e cálculo de rendimento mensal.
- Histórico mensal: salve um snapshot dos totais a cada mês e acompanhe a evolução da renda, despesas e saldo em um gráfico de linha.
- Gráficos: pizza de distribuição de despesas + barra renda × despesas + linha de evolução temporal.
- Saúde financeira: taxa de poupança, reserva de emergência (em meses), comprometimento da renda e classificação geral (Excelente → Crítico).
- Projeção de despesas futuras com sugestão de reserva mensal.
- Dados isolados por usuário — ninguém vê os dados de outra pessoa.
- UI responsiva com Tailwind via CDN (sem build step).
| Camada | Tecnologia |
|---|---|
| Front-end | HTML + CSS + JS (vanilla, Tailwind/Chart.js CDN) |
| Back-end | Python 3.9+ / Flask 3 + flask-cors |
| Banco | SQLite (arquivo local) |
| Auth | JWT (PyJWT) + PBKDF2-SHA256 para hash de senha |
Nenhuma etapa de build. pip install + python app.py e está rodando.
Fin_Control/
├── index.html # App principal (protegido)
├── login.html # Tela de login
├── register.html # Tela de cadastro
├── style.css # Estilos (inclui tema das telas de auth)
├── script.js # Lógica do app (consome a API)
├── api.js # Cliente HTTP + storage do token JWT
├── auth.js # Comportamento das telas de login/registro
├── render.yaml # Deploy 1-clique no Render.com
├── security_audit.py # Auditoria automatizada (roda contra a URL em produção)
├── backend/
│ ├── app.py # Entry point Flask + hardening (CORS, headers)
│ ├── rate_limit.py # Flask-Limiter compartilhado
│ ├── database.py # Schema SQLite + helpers de query
│ ├── auth.py # Hash PBKDF2 + JWT + decorator @auth_required
│ ├── auth_routes.py # /api/auth/register, /login, /me, /logout
│ ├── data_routes.py # /api/data + CRUD por recurso + /api/history
│ ├── test_api.py # Smoke test do backend
│ ├── test_e2e.py # Fluxo end-to-end
│ ├── test_new_features.py # future_incomes + monthly_history
│ ├── requirements.txt
│ ├── Dockerfile # Imagem de produção (gunicorn)
│ ├── Procfile # Heroku-style start command
│ ├── runtime.txt # Versão do Python
│ └── .gitignore
└── README.md
cd backend
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtpython app.pyO servidor escuta em
http://127.0.0.1:5000por padrão. Para mudar:PORT=8080 python app.py. Para expor o backend em outra origem e permitir o front em outro host, definawindow.API_BASEantes deapi.js(ou ajusteBASEemapi.js).
- Vá em http://127.0.0.1:5000/login.html ou
- Crie uma conta em
register.html - O servidor serve o front-end e a API pelo mesmo host (sem CORS no dev).
| Variável | Padrão | Descrição |
|---|---|---|
PORT |
5000 |
Porta HTTP |
FLASK_DEBUG |
0 |
1 para modo debug do Flask |
JWT_SECRET |
dev-secret-change-me-in-production |
Chave HMAC dos tokens (troque em prod!) |
FINCONTROL_DB |
backend/fincontrol.db |
Caminho do arquivo SQLite |
Todas as rotas de dados exigem o header Authorization: Bearer <token>.
Respostas em JSON. Erros vêm como {"error": "mensagem"}.
| Método | Rota | Body | Resposta |
|---|---|---|---|
| POST | /api/auth/register |
{username, email, password} |
201 {token, user} |
| POST | /api/auth/login |
{username, password} ou {email, password} |
200 {token, user} |
| GET | /api/auth/me |
— | 200 {user} |
| POST | /api/auth/logout |
— | 200 {ok: true} |
| Método | Rota | Descrição |
|---|---|---|
| GET | /api/data |
Snapshot completo |
| GET | /api/<resource> |
Lista itens |
| POST | /api/<resource> |
Cria item |
| PUT | /api/<resource>/<id> |
Atualiza item |
| DELETE | /api/<resource>/<id> |
Remove item |
Recursos: incomes, passive_incomes, monthly_expenses,
future_expenses, future_incomes, investments.
| Método | Rota | Descrição |
|---|---|---|
| GET | /api/history |
Lista snapshots salvos (mais recente primeiro) |
| POST | /api/history/snapshot |
Salva/atualiza um snapshot (recalcula totais se omitidos) |
| PUT | /api/history/<id> |
Atualiza apenas as notas de um snapshot |
| DELETE | /api/history/<id> |
Remove um snapshot |
Exemplos:
// POST /api/monthly_expenses
{
"name": "Aluguel",
"value": 2200,
"category": "Moradia",
"due_day": 5
}
// POST /api/history/snapshot
{
"month_year": "2026-07", // opcional: padrão = mês atual
"notes": "Mês de férias", // opcional
"totals": { // opcional: se omitido, o servidor recalcula
"income": 8000,
"passive": 500,
"expenses": 2000,
"investments": 10000
}
}| Variável | Obrigatória? | Descrição |
|---|---|---|
PORT |
automática | A plataforma define. Default 5000. |
JWT_SECRET |
sim | Chave HMAC dos tokens. Gere um valor forte! |
FINCONTROL_DB |
opcional | Caminho do SQLite. Default: backend/fincontrol.db. |
FLASK_DEBUG |
opcional | 0 em produção. |
Dois scripts prontos, ambos usam apenas a stdlib (urllib):
cd backend
python3 test_api.py # ~20 cenários: auth, validações, isolamento
python3 test_e2e.py # fluxo UI-equivalente completo
python3 test_new_features.py # future_incomes + monthly_historyO app foi endurecido para uso público. Medidas implementadas:
- Senhas com PBKDF2-HMAC-SHA256, 200.000 iterações, salt aleatório de 16 bytes.
- JWT com
HS256e expiração de 7 dias. - Senha: 6–128 caracteres; usuário: 3-32 chars (
a-z A-Z 0-9 _ . -).
X-Content-Type-Options: nosniffX-Frame-Options: DENY(anti-clickjacking)Strict-Transport-Security: max-age=31536000; includeSubDomains(HSTS)Content-Security-Policy(CSP) restritivo, liberando só os CDNs usados pelo frontReferrer-Policy: strict-origin-when-cross-originPermissions-Policydesabilitando geolocation, câmera, microfone, etc.Serverheader reescrito paraFinControl(esconde stack)
- Origens explícitas e restritas (configurável via
ALLOWED_ORIGINS). - Default:
https://fincontrol.pythonanywhere.com. - Em dev, defina
ALLOWED_ORIGINS=http://localhost:5000,http://127.0.0.1:5000.
/api/auth/logine/api/auth/register: 10 req/min e 30 req/hora por IP via Flask-Limiter.- Resposta
429 Too Many Requestsquando excede.
- Cada query SQL filtra por
user_iddo JWT — um usuário nunca vê dados de outro. - Todos os endpoints de dados exigem
Authorization: Bearer <token>; sem ele →401.
- Username regex, email regex, senha com limites.
- Payloads grandes / strings longas são rejeitados com
400. - Queries 100% parametrizadas (sem concatenação) — imune a SQL Injection clássico.
- Tokens
alg=nonee inválidos são rejeitados com401.
security_audit.py(na raiz do repo) faz 80+ verificações automatizadas contra uma URL alvo.
JWT_SECRET: gere um valor forte compython -c "import secrets; print(secrets.token_hex(32))". O defaultdev-secret-change-me-in-productioné apenas para dev local.ALLOWED_ORIGINS: defina o(s) domínio(s) do app, separados por vírgula.