SaldoMais
Carregando…
Documento 06 de 07

Funcionalidades

Comportamento técnico de cada funcionalidade — regras de negócio, validações, fluxos de dados e side effects relevantes para quem vai modificar, depurar ou estender uma feature.

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

  1. Usuário digita o valor no #orcamentoInput (com máscara BRL em tempo real)
  2. Clica em "Salvar Orçamento" → chama salvarOrcamentoHandler()
  3. desformatarMoeda() converte a string para number
  4. Busca orçamentos existentes para mesAtual() (formato "YYYY-MM")
  5. Upsert: se existe registro para o mês, atualiza valor_total; se não, cria novo
  6. 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

  1. Preenche descrição (#desc), valor (#valorInput), categoria (#categoriaSelect) e data (#dataInput, padrão = hoje)
  2. O hint de orçamento (#catHint) atualiza em tempo real ao mudar categoria ou valor, exibindo o saldo disponível na categoria selecionada
  3. Clica em "Adicionar Despesa" → adicionarLancamento()
  4. 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
  5. Cria o objeto com campo data, adiciona ao array, limpa os inputs, persiste e chama renderComplete()

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) em core.js — Promise-based, pré-popula o modal com os dados atuais
  • editarLancamentoHandler(id) em lancamentos.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 data decrescente (mais recentes no topo), com desempate por id
  • 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 zera valor_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
  • percentual inicial: 0 — o usuário deve ajustar nos sliders

Exclusão em cascata

Atenção: A exclusão remove a categoria e todos os lançamentos com aquele 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 — tela section#configuracoes (sem ações de editar/deletar)
  • #categoriasListaEditor — tela section#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.

CardValorVariação visual
Total Disponívelvalor_totalAnimação de entrada
Total GastoSoma dos lançamentos + count de lançamentosBarra de progresso + animação
Disponívelvalor_total - totalGastoVerde se ≥ 0, vermelho se < 0 + animação
Atenção (opcional)Count de categorias acima do limiteAparece 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

  1. aplicarGastosFixos() é chamada em init()
  2. Verifica se já existe algum lançamento com id_gasto_fixo === fixo.id no mês atual
  3. Se não, cria o lançamento com o valor e categoria do gasto fixo
  4. 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

AbaDescrição
EstruturaSeleção de ativos com % de alocação, perfil do investidor, alertas e gráficos
Calcular AporteDistribui um valor de aporte pelos ativos da carteira usando aritmética em centavos
SimuladorProjeta 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.jspdf deve estar disponível (carregado via CDN)

Estrutura do documento (A4 portrait, 210×297mm)

  1. Cabeçalho — logo (via fetch), nome, tagline, mês/ano
  2. Cards de resumo — orçamento, gasto, disponível + barra
  3. Título de seção "Categorias"
  4. Tabela de categorias — zebra striping, cor semântica nos valores
  5. Título de seção "Lançamentos" (se houver)
  6. Tabela de lançamentos — por categoria, paginação automática
  7. Rodapé — em todas as páginas, com data/hora
file://: O cabeçalho faz 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

  1. Usuário seleciona arquivo .json
  2. FileReader.readAsText() lê o conteúdo
  3. JSON.parse() — exceção capturada se inválido
  4. Valida presença das chaves categorias, orcamentos, lancamentos (receitas incluída na versão 2)
  5. Sobrescreve o storage completamente (sem merge)
  6. Chama renderComplete()
  7. e.target.value = "" — reseta para permitir reimportar
Atenção: A importação não valida a estrutura interna dos objetos — um backup corrompido pode introduzir dados inconsistentes sem erro visível.

Atalhos de teclado

AtalhoComportamento
Alt+1Navega para Dashboard
Alt+2Navega para Lançamentos
Alt+3Navega para Categorias
Alt+4Navega para Calculadoras
Alt+5Navega para Gastos Fixos
Alt+6Navega para Receitas
Alt+7Navega para Carteira de Investimentos
Alt+BColapsa/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:

  1. setupCurrencyMask(el) — aplicada explicitamente em #orcamentoInput e #valorInput
  2. Listener global no document — aplicada em qualquer .input-moeda (campos das calculadoras)

Algoritmo

  1. Remove tudo que não é dígito
  2. Limita a 11 dígitos (máximo: R$ 999.999.999,99)
  3. 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).