Orçamento mensal
Localização: Dashboard (section#dashboard) → formulário .dashboard-setup
Módulo responsável: js/lancamentos.js
O orçamento define o valor total disponível para o mês corrente. Funciona como âncora de todos os cálculos financeiros do período.
Fluxo de criação/atualização
- Usuário digita o valor no
#orcamentoInput(com máscara BRL em tempo real) - Clica em "Salvar Orçamento" → chama
salvarOrcamentoHandler() desformatarMoeda()converte a string para number- Busca orçamentos existentes para
mesAtual()(formato"YYYY-MM") - Upsert: se existe registro para o mês, atualiza
valor_total; se não, cria novo - Persiste e chama
renderComplete()
Regras
- Valor deve ser
> 0 - Apenas um orçamento por mês (identificado por
mes_referencia) - Atualizar o orçamento não recalcula nem remove lançamentos existentes
Lançamentos (Despesas)
Localização: Tela section#lancamentos
Módulo responsável: js/lancamentos.js
Fluxo de adição
- Preenche descrição (
#desc), valor (#valorInput), categoria (#categoriaSelect) e data (#dataInput, padrão = hoje) - O hint de orçamento (
#catHint) atualiza em tempo real ao mudar categoria ou valor, exibindo o saldo disponível na categoria selecionada - Clica em "Adicionar Despesa" →
adicionarLancamento() - Validações em sequência (aborta na primeira falha):
- Orçamento do mês deve existir
- Descrição não pode estar vazia
- Valor deve ser
> 0 total_gasto_na_categoria + novo_valor <= limite_da_categoria
- Cria o objeto com campo
data, adiciona ao array, limpa os inputs, persiste e chamarenderComplete()
Cálculo do limite por categoria
const limite = orcamento.valor_total * categoria.percentual / 100;
const gastoAtual = lancamentos
.filter(l => l.id_categoria === cat.id && l.id_orcamento === orcamento.id)
.reduce((s, l) => s + l.valor, 0);
Se gastoAtual + novoValor > limite, o lançamento é rejeitado com notificar("Limite excedido nesta categoria", 'error').
Edição de lançamento
Cada item da lista possui botão de edição (data-action="editar-lancamento") que abre o modal #editarLancamentoModal.
editarLancamento(id)emcore.js— Promise-based, pré-popula o modal com os dados atuaiseditarLancamentoHandler(id)emlancamentos.js— recebe o resultado, revalida o limite da (nova) categoria excluindo o próprio registro, persiste e re-renderiza- Na validação do limite de edição, o lançamento original é excluído do cálculo de gasto atual para evitar dupla contagem
Ordenação e exibição
- Lançamentos são ordenados por
datadecrescente (mais recentes no topo), com desempate porid - Cada item exibe: ponto colorido da categoria, descrição, nome da categoria, data formatada (
DD/MM/YYYY), valor e ações (editar/deletar)
Exclusão e limpeza
deletarLancamento(id)abre modal de confirmação antes de remover.resetarMes()remove todos os lançamentos do orçamento atual e zeravalor_total. Ação irreversível (com modal).
Categorias
Localização: Tela section#categorias
Módulo responsável: js/categorias.js
Seed inicial
Na primeira execução (criarCategorias()), 6 categorias são criadas automaticamente. A função é idempotente — não executa se já existirem categorias.
Adição de categoria
- Nome: obrigatório, único (case-insensitive), máx. 30 caracteres
- Cor: hex via
<input type="color">, padrão#f59e0b percentualinicial: 0 — o usuário deve ajustar nos sliders
Exclusão em cascata
id_categoria. O modal de confirmação exibe aviso explícito.Gerenciamento de percentuais
Os sliders e inputs numéricos são sincronizados a cada evento input. O totalizador muda de cor dinamicamente:
- Verde (
--ok): total === 100% - Laranja (
--warn): total < 100% - Vermelho (
--danger): total > 100%
Dois containers distintos renderizam o editor:
#listaCategorias— telasection#configuracoes(sem ações de editar/deletar)#categoriasListaEditor— telasection#categorias(com ações completas)
Dashboard
Localização: Tela section#dashboard (tela inicial)
Módulo responsável: js/dashboard.js
O dashboard é completamente reconstruído a cada chamada de renderDashboard() — não há atualizações parciais.
Comportamento sem orçamento
Se orcamentoAtual() retornar undefined ou valor_total <= 0, exibe empty state no lugar dos cards.
Cards de resumo
Os valores dos cards são animados com animarValor(el, valorFinal) — interpolação ease-out cúbica de R$ 0,00 até o valor real em ~650 ms usando requestAnimationFrame.
| Card | Valor | Variação visual |
|---|---|---|
| Total Disponível | valor_total | Animação de entrada |
| Total Gasto | Soma dos lançamentos + count de lançamentos | Barra de progresso + animação |
| Disponível | valor_total - totalGasto | Verde se ≥ 0, vermelho se < 0 + animação |
| Atenção (opcional) | Count de categorias acima do limite | Aparece somente se count > 0 |
Barras de status por categoria
Cada barra exibe: ponto colorido, nome da categoria, badge com percentual utilizado, barra preenchida e linha de valores (gasto / limite). O badge e a barra usam cores semânticas:
- Verde (
--ok): abaixo de 80% - Laranja (
--warn): entre 80% e 100% - Vermelho (
--danger): acima de 100%
Gráfico de distribuição
- Tipo: doughnut (Chart.js)
- Dados: percentual de cada categoria sobre o total gasto
- Se
totalGasto === 0: distribui igualmente para exibir gráfico colorido vazio - Instância anterior é destruída antes de criar a nova (
window.graficoChart.destroy()) - Legenda customizada em
#grafico-legenda— nativa do Chart.js desabilitada
Gastos Fixos
Localização: Tela section#gastos-fixos
Módulo responsável: js/gastos-fixos.js
Despesas recorrentes mensais cadastradas uma única vez e aplicadas automaticamente como lançamentos no primeiro dia de cada mês.
Fluxo de aplicação automática
aplicarGastosFixos()é chamada eminit()- Verifica se já existe algum lançamento com
id_gasto_fixo === fixo.idno mês atual - Se não, cria o lançamento com o valor e categoria do gasto fixo
- Exibe notificação informativa se algum gasto foi aplicado
Regras
- Requer orçamento definido para o mês para ser aplicado
- O lançamento gerado é idêntico a um lançamento manual — sujeito ao limite da categoria
- Não são removidos por
resetarMes()— persistem entre meses
Receitas
Localização: Tela section#receitas
Módulo responsável: js/receitas.js
Entradas financeiras do mês que controlam automaticamente o orçamento disponível.
Sincronização com orçamento
Ao adicionar ou remover uma receita, sincronizarOrcamentoComReceitas() soma todas as receitas do mês e atualiza o valor_total do orçamento correspondente. Enquanto houver receitas, o input manual de orçamento fica desabilitado.
Regras
- Cada receita pertence a um mês via
mes_referencia - Tipos disponíveis: Salário, Freelance, Rendimento, Outro
- Ao deletar todas as receitas do mês, o campo de orçamento manual é reabilitado
Carteira de Investimentos
Localização: Tela section#carteira
Módulo responsável: js/carteira.js
Abas disponíveis
| Aba | Descrição |
|---|---|
| Estrutura | Seleção de ativos com % de alocação, perfil do investidor, alertas e gráficos |
| Calcular Aporte | Distribui um valor de aporte pelos ativos da carteira usando aritmética em centavos |
| Simulador | Projeta evolução patrimonial com aporte mensal × prazo (máx 50 anos) |
| Histórico | Últimos 50 cálculos de aporte realizados |
Aba Estrutura
- 30 ativos fixos organizados em 5 classes — Renda Fixa, Renda Variável, Fundos, Previdência, Criptomoedas
- Checkboxes + inputs de percentual por ativo. Totalizador em tempo real: verde se soma = 100%, vermelho caso contrário
- Botão "Salvar" habilitado somente quando soma === 100%
- Perfil do investidor calculado pelo percentual de Renda Fixa (Conservador ≥ 70%, Moderado 40–69%, Arrojado < 40%)
- Alerta de concentração exibido se qualquer ativo ultrapassar 40% do total
- Gráfico doughnut (Chart.js) + gráfico sunburst "Distribuição Hierárquica" (ECharts)
Aba Calcular Aporte
- Distribui o valor digitado entre os ativos proporcionalmente à alocação
- Aritmética em centavos inteiros (
Math.floor) para evitar erros de ponto flutuante - O resíduo de centavos é somado ao último ativo da lista
- Alerta visual se algum ativo ficou abaixo do mínimo permitido (varia por tipo de ativo)
- Exportação do resultado como CSV com BOM UTF-8
- Resultado salvo automaticamente no histórico a cada cálculo
Aba Simulador
- Projeta evolução do patrimônio mês a mês com juros compostos
- Taxa anual calculada pela média ponderada da alocação da carteira por classe
- Prazo limitado a 50 anos para evitar travamento do navegador
- Exibe cards com Patrimônio Estimado, Total Aportado e Rendimento Estimado
- Gráfico de área (Chart.js) com evolução anual
Exportação de PDF
Módulo responsável: js/pdf.js
Disparo: botão #btnExportPdf
Pré-condições
- Orçamento do mês deve existir com
valor_total > 0 window.jspdfdeve estar disponível (carregado via CDN)
Estrutura do documento (A4 portrait, 210×297mm)
- Cabeçalho — logo (via
fetch), nome, tagline, mês/ano - Cards de resumo — orçamento, gasto, disponível + barra
- Título de seção "Categorias"
- Tabela de categorias — zebra striping, cor semântica nos valores
- Título de seção "Lançamentos" (se houver)
- Tabela de lançamentos — por categoria, paginação automática
- Rodapé — em todas as páginas, com data/hora
fetch do logo — isso falha em file:// por CORS. Use um servidor HTTP local para testar o PDF com logo.Backup e Restauração
Módulo responsável: js/backup.js
Exportação
Serializa o estado completo com metadados. O arquivo é criado como Blob e baixado via <a> temporário + URL.createObjectURL (revogado imediatamente).
Nome: SaldoMais-backup-YYYY-MM-DD.json
Importação
- Usuário seleciona arquivo
.json FileReader.readAsText()lê o conteúdoJSON.parse()— exceção capturada se inválido- Valida presença das chaves
categorias,orcamentos,lancamentos(receitas incluída na versão 2) - Sobrescreve o storage completamente (sem merge)
- Chama
renderComplete() e.target.value = ""— reseta para permitir reimportar
Atalhos de teclado
| Atalho | Comportamento |
|---|---|
| Alt+1 | Navega para Dashboard |
| Alt+2 | Navega para Lançamentos |
| Alt+3 | Navega para Categorias |
| Alt+4 | Navega para Calculadoras |
| Alt+5 | Navega para Gastos Fixos |
| Alt+6 | Navega para Receitas |
| Alt+7 | Navega para Carteira de Investimentos |
| Alt+B | Colapsa/expande a sidebar (apenas desktop) |
Enter (em input de .calc-card) | Dispara o cálculo do card correspondente |
A navegação por Alt+número simula um clique no .nav-btn[data-screen], acionando o mesmo fluxo da navegação por clique.
Máscara de moeda
Aplicada em dois contextos:
setupCurrencyMask(el)— aplicada explicitamente em#orcamentoInpute#valorInput- Listener global no
document— aplicada em qualquer.input-moeda(campos das calculadoras)
Algoritmo
- Remove tudo que não é dígito
- Limita a 11 dígitos (máximo: R$ 999.999.999,99)
- Divide por 100 e formata com
Intl.NumberFormat pt-BR
Simula o comportamento de digitação da direita para a esquerda (como terminais de pagamento).