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

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