Setup do ambiente
Não há dependências de build, npm ou bundler. O projeto roda diretamente no browser.
git clone https://github.com/mat7heus/SaldoMais.git
cd SaldoMais
Abra index.html diretamente no browser (duplo clique) ou sirva via HTTP local:
# Python 3
python3 -m http.server 8080
# Node (npx)
npx serve .
file:// vs HTTP: O protocolo
file:// funciona para a maioria das funcionalidades, mas o cabeçalho do PDF faz fetch para buscar o logo — isso falha em file:// por restrições de CORS. Use um servidor HTTP local para testar o PDF com logo.Estrutura de arquivos
SaldoMais/
├── index.html # Único arquivo HTML — estrutura do app
├── css/
│ └── index.css # Todos os estilos da aplicação
├── js/
│ ├── core.js # Fundação: constantes, DOM, storage, utils
│ ├── lancamentos.js # Orçamentos e transações
│ ├── categorias.js # CRUD de categorias e percentuais
│ ├── dashboard.js # Dashboard e gráfico
│ ├── calculadoras.js # 8 calculadoras financeiras
│ ├── backup.js # Exportar/importar JSON
│ ├── pdf.js # Geração de relatório PDF
│ └── app.js # Orquestração e inicialização
└── docs/ # Documentação técnica
Convenções: JavaScript
- Sem módulos ES — o projeto usa escopo global. Não use
import/export. - Ordem de carregamento importa —
core.jsdeve ser o primeiro,app.jso último. Ao adicionar um novo arquivo, insira a tag<script defer>na posição correta. - Nomenclatura em camelCase e português — ex:
adicionarLancamento,renderCategoriasLista. - Separadores de seção — use o padrão
// ─── NOME DA SEÇÃO ───para demarcar grupos de funções. - Render total — ao modificar dados, chame
renderComplete()no final. Não faça atualizações parciais de DOM fora das funções de render.
Convenções: CSS
- Use variáveis CSS — nunca hardcode cores. Consulte os tokens em design-system.html.
- border-radius padrão: 14px para cards/containers, 10px para inputs/botões, 8px para elementos menores.
- Transições: 0.2s–0.25s ease para a maioria dos elementos interativos.
- backdrop-filter: blur(20px) nos panels elevados (cards, sidebar, modais).
Convenções: HTML
- Novos elementos interativos dinâmicos devem usar
data-action+data-idpara event delegation em vez deonclickinline (exceto nas calculadoras, que já usamonclickdiretamente por simplicidade). - Ícones Lucide: use
<i data-lucide="nome-do-icone" size="N"></i>. Lembre de chamarlucide.createIcons()após injetar HTML com ícones.
Adicionando uma nova tela
- Crie um
<section id="nova-tela" class="screen">emindex.html - Adicione um botão de navegação na
.sidebar-nav:<button data-screen="nova-tela" class="nav-btn" title="Descrição (Alt+N)"> <span class="nav-icon"><i data-lucide="icone"></i></span> <span class="nav-label">Nome</span> <kbd class="nav-shortcut">Alt+N</kbd> </button> - Registre o atalho em
app.js:const screenMap = { '1': 'dashboard', '2': 'lancamentos', '3': 'categorias', '4': 'calculadoras', 'N': 'nova-tela' }; - Se precisar de re-render na navegação, adicione hook em
navegar()emcore.js:if (btn.dataset.screen === "nova-tela") renderNovaTela();
Adicionando uma nova calculadora
- Crie a função matemática pura em
calculadoras.js:function calcMinhaCalc(param1, param2) { // lógica sem acesso ao DOM return { resultado1, resultado2 }; } - Crie a função de UI em
calculadoras.js:function calcularMinhaCalc() { const param1 = parseBRCalc(document.getElementById('mc-param1').value); if (param1 <= 0) { notificar('Mensagem de erro'); return; } const r = calcMinhaCalc(param1, param2); document.getElementById('mc-cards').innerHTML = `...`; mostrarResultado('mc-resultado'); if (window.lucide) lucide.createIcons(); } - Adicione o HTML do card em
index.htmldentro do.calc-grid:<div class="calc-card"> <div class="calc-card-header">...</div> <div class="input-group">...</div> <button onclick="calcularMinhaCalc()" class="btn-primary">Calcular</button> <div id="mc-resultado" class="calc-result" style="display:none;"> <div class="calc-result-cards" id="mc-cards"></div> </div> </div> - Para que Enter dispare o cálculo, adicione ao mapeamento em
calculadoras.js:const calcMap = [ // ... existentes ... ['mc-param1', calcularMinhaCalc], ];
Modificando o schema de dados
Antes de qualquer alteração no schema: Backups existentes de usuários ficarão incompatíveis se você renomear ou remover campos. A importação de backup não valida estrutura interna — dados antigos serão carregados silenciosamente com campos faltando.
Se precisar evoluir o schema:
- Mantenha compatibilidade com os campos existentes
- Adicione novos campos com valor default (os registros antigos não os terão)
- Atualize
_versaono formato de backup - Documente a mudança em dados.html
Depuração
Todos os dados estão acessíveis no DevTools do browser:
// Ver categorias
JSON.parse(localStorage.getItem('saldomain_categorias'))
// Ver orçamentos
JSON.parse(localStorage.getItem('saldomain_orcamentos'))
// Ver lançamentos
JSON.parse(localStorage.getItem('saldomain_lancamentos'))
// Limpar tudo (apaga todos os dados)
localStorage.clear()
O gráfico Chart.js é acessível via window.graficoChart para inspeção ou destruição manual.
Erros de render são capturados pelo
try/catch em renderAll() e init() — verifique o console para mensagens de erro silenciosas.Testes
O projeto não tem suite de testes automatizados. As funções matemáticas puras são candidatas naturais a testes unitários — elas não têm efeitos colaterais e retornam objetos determinísticos.
Para testar funções puras manualmente no console:
calcJurosCompostos(1000, 200, 0.12, 12)
// → { saldo: ..., totalInvestido: ..., totalJuros: ..., linhas: [...] }
calcCDBCDI(10000, 10.5, 110, 365)
// → { rendBruto: ..., ir: ..., valorLiq: ..., aliquota: 0.175 }