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
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 - 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 |
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).