Calculadora de Juros em Empréstimos (CDI, SELIC, Variável)

date
Sep 11, 2025
slug
calculadora-de-juros-em-emprestimos
status
Published
tags
Tecnologia
Matemática
JavaScript
summary
Calculadora de empréstimos, SELIC, CDI, juros compostos diários, integração com API do Banco Central do Brasil, extrato auditável e exportação.
type
Post
Outra terça-feira encarei mais uma de minhas infinitas planilhas do Excel. E, dessa vez, percebi que a gambiarra tinha vencido.
Há alguns anos fiz um empréstimo de certa quantia indexada ao CDI (100% do CDI a.a.) para pagar em algumas parcelas, montei uma planilha do excel e ao menos uma vez por ano, tenho atualizado os valores.
Mas veja só, recebi o empréstimo dividido em cinco parcelas em datas diferentes, além disso tenho realizado os pagamentos também em parcelas menores e em datas nem sempre recorrentes durante o ano.
Assim, para calcular o juros diário do empréstimo sobre o montante, que agora inclui o juros acumulado da dívida, tenho que acessar o histórico de taxa SELIC do Banco Central do Brasil e conferir as datas e os correspondentes rendimentos em cada dia, copiar e colar na planilha, pra no fim calcular o rendimento diário em juros e, só ao final, recalcular o saldo devedor atual na data de hoje, por exemplo.
Entretanto, minha planilha se tornou uma hidra de células, fórmulas aninhadas e VLOOKUPs que ficaram grandes demais (cerca de 252 linhas por ano, em quatro anos).
Já que, toda vez que a SELIC muda, eu tenho que redesenhar o modelo até a data da mudança, e repetir todo o cálculo célula por célula até a data atual pra conferir o novo saldo devedor.
No primeiro ano tudo bem, mas depois de alguns anos pagando o empréstimo, fica uma uma sensação incômoda de “algo aqui pode estar errado” e não é mais tão simples de auditar.
💡
Portanto, temos o seguinte dilema: Como calcular juros diários sobre um montante que varia (entradas e saídas em datas diferentes) com uma taxa que também muda frequentemente?
A matemática é direta e transformar isso em planilha pode se tornar um pesadelo combinatório trabalhoso, a depender do tempo da dívida.
A solução também me parece óbvia, chega de gambiarras. É hora de codificar algo que funcione de verdade.

Acesso Gratuito

Quer testar? Você pode usar como quiser a calculadora, basta abrir o endereço abaixo. É gratuito, seguro, privado e o cálculo fica no seu próprio dispositivo.
Foto: screenshot do site em funcionamento e mostrando os resultados para uma simulação de empréstimo basedo em 100% do CDI. Fonte: J. Caraumã (2025).Foto: screenshot do site em funcionamento e mostrando os resultados para uma simulação de empréstimo basedo em 100% do CDI. Fonte: J. Caraumã (2025).
Foto: screenshot do site em funcionamento e mostrando os resultados para uma simulação de empréstimo basedo em 100% do CDI. Fonte: J. Caraumã (2025).

A matemática do problema

Mas se você é como eu e também gosta de ver por “dentro” como funciona. Vamos revisar um pouco da matemática usada, antes de ir pro código.
Primeiro, é fundamental deixar claro que consideramos um empréstimo baseado em CDI, por exemplo, e tem algumas características específicas:
  • Taxa variável no tempo: a SELIC (referência do CDI), por exemplo, muda com frequência.
  • Capitalização diária: os juros compostos são aplicados dia a dia.
  • Eventos assíncronos: empréstimos e pagamentos podem aparecer em datas variadas. Por exemplo, um empréstimo que foi recebido em parcelas, ou que tenha sido pago em parcelas em datas diferentes.
  • Multiplicador: contratos normalmemtne usam um pecentual multiplicador, por exemplo, um dado empréstimo pode render 105% do CDI.
Nesse contexto, a fórmula clássica de juros composto é dada por
No entanto, em nosso caso precisamos adaptar, dado que:
  • varia dia a dia, por exemplo, SELIC com rendimento diário;
  • varia conforme novos empréstimos e pagamentos são efetuados;
  • Os juros/rendimentos são aplicados diariamente ao longo do período.
Na prática, dia a dia, temos, portanto:
e a é ajustada pelo multiplicador, onde
Uma vez que foi calculada corretamente, pode-se processar o juros do empréstimo em cada dia, para isso, basta encontrar base para uma determinada data. E é o que fazemos na próxima etapa.

Integração com a API do Banco Central

Para obter as taxas de juros base em cada dia, temos dois cenários.
Primeiro, o usuário insere diretamente o percentual da taxa e sua data de vigência através da interface.
Ou, no segundo cenário, para automatizar o processo de obtenção de taxas, tem-se o Banco Central que fornece uma API REST pública com séreis temporais (por exemplo, série 11 para SELIC) abaixo:
// Endereço da Série 11 para SELIC do Banco Central do Brasil const url = `https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=${dataInicio}&dataFinal=${dataFim}`;
A resposta vem em JSON mais ou menos assim:
[{"data":"04/01/2021","valor":"0.007469"}, {"data":"05/01/2021","valor":"0.007469"}, {"data":"06/01/2021","valor":"0.007469"}, {"data":"07/01/2021","valor":"0.007469"}, {"data":"08/01/2021","valor":"0.007469"}, {"data":"11/01/2021","valor":"0.007469"}, {"data":"12/01/2021","valor":"0.007469"},...]
Note que o valor corresponde a taxa SELIC diária (percentual). Portanto, de modo a converter para uma taxa anual equivalente ao CDI, por exemplo, usamos composta diária sobre dias úteis, por exemplo:
// função de conversão de taxa diária para anual function calcularTaxaAnualCDI(valorDiarioPercent, businessDaysPerYear = 252) { const diariaDecimal = valorDiarioPercent / 100; const anualDecimal = Math.pow(1 + diariaDecimal, businessDaysPerYear) - 1; return anualDecimal * 100; }
Por que 252 dias? É o número aproximado de dias úteis usados pelo mercado financeiro brasileiro, fim de semana e feriados ficam de fora.

Processamento Cronológico

O coração da aplicação processa os eventos, isto é, empréstimos, pagamentos, variações de taxa em ordem cronológica. A estrutura de dados é simples.
Por exemplo, os dados são arranjados da seguinte forma.
// Arrays de objetos emprestimos = [{data: "2023-01-15", valor: 10000}, ...]; pagamentos = [{data: "2023-02-15", valor: 2000}, ...]; taxas = [{data: "2023-01-01", taxa: 13.65}, ...];
Em seguida, seguem o fluxo abaixo.
function calcularDivida() { // 1. Consolida todos os eventos const eventos = []; emprestimos.forEach(e => eventos.push({ tipo: 'emprestimo', data: e.data, valor: e.valor })); pagamentos.forEach(p => eventos.push({ tipo: 'pagamento', data: p.data, valor: -p.valor })); // 2. Ordena cronologicamente os eventos eventos.sort((a, b) => compareDates(a.data, b.data)); // 3. Processa dia a dia (balanço de cada dia) let saldo = 0; let totalJuros = 0; for (let i = 0; i < taxasOrdenadas.length; i++) { const taxaDia = taxasOrdenadas[i]; // Processa eventos do dia selecionado while (eventoPtr < eventos.length && compareDates(eventos[eventoPtr].data, taxaDia.data) <= 0) { saldo += eventos[eventoPtr].valor; eventoPtr++; } // Aplicar juros se há saldo devedor if (saldo > 0) { const taxaEfetiva = taxaDia.taxa * (multiplicadorCDI / 100); const jurosDia = saldo * (taxaEfetiva / 100 / 365); saldo += jurosDia; totalJuros += jurosDia; } } }
Com estes dados em mãos, fica fácil calcular o saldo devedor em qualquer dada. Por outro lado, é importante esclarecer alguns pontos que merecem atenção.
O primeiro, diz respeito a ordenação de eventos simultâneos, ou seja, quando pagamento e juros caem no mesmo dia, a ordem muda o resultado. Como resolvemos isso? Basta processar todos os movimentos financeiros do dia antes de calcular os juros, em outras palavras, primeiro entradas/saídas, depois aplicação de juros sobre o saldo resultando. Fica mais ou menos assim,
// Primeiro: movimentos do dia while (evento.data === dataAtual && evento.tipo !== 'juros') { processarEvento(evento); } // Depois: calcular juros sobre o saldo resultante if (saldo > 0) { aplicarJuros(); }
Em seguida, temos outro desafio para resolver, pois a API do BCB fornece taxas apenas para dias úteis (datas bancárias). Se houver sábados, domingos ou feriados, deve-se utilizar a última taxa disponível anterior. Ou seja,
function obterTaxaDia(data) { // Buscar taxa exata, conforme datas advindas da API let taxa = taxas.find(t => t.data === data); // Se não encontrar, usar última disponível if (!taxa) { taxa = taxas .filter(t => new Date(t.data) <= new Date(data)) .sort((a,b) => new Date(b.data) - new Date(a.data))[0]; } return taxa; }
Isto é, se o dia não tem taxa, copia-se a taxa do último dia útil.

Armazenamento dos dados e Privacidade

Como se trata de uma ferramenta pessoal com foco em privacidade, preferei por amazenar os dados localmente no próprio dispositivo através do localStorage em vez de backend.
Para alguns anos de dados funciona muito bem; para volumes maiores, seria preciso migrar. No código, a persistência automática fica assim:
// Persistência automática function salvarDados() { localStorage.setItem('emprestimos', JSON.stringify(emprestimos)); localStorage.setItem('pagamentos', JSON.stringify(pagamentos)); localStorage.setItem('taxas', JSON.stringify(taxas)); localStorage.setItem('multiplicadorCDI', multiplicadorCDI); } // Carregamento na inicialização function carregarDados() { emprestimos = JSON.parse(localStorage.getItem('emprestimos')) || []; pagamentos = JSON.parse(localStorage.getItem('pagamentos')) || []; taxas = JSON.parse(localStorage.getItem('taxas')) || []; multiplicadorCDI = parseFloat(localStorage.getItem('multiplicadorCDI')) || 100; }

Interface e experiência do usuário

A escolha por ter utilizado uma planilha era fundamentada em simplicidade, não foi diferente agora. Preferi optar por HTML/CSS/JavaScript vanilla, sem frameworks, com arquitetura em tabs e dashboard em tempo real. Simples, leve e controlável.
// Sistema de tabs tabs.forEach(tab => { tab.addEventListener('click', () => { const tabId = tab.getAttribute('data-tab'); // Esconder todos os conteúdos tabContents.forEach(tc => tc.classList.remove('active')); // Mostrar o selecionado document.getElementById(tabId).classList.add('active'); // Atualizar dados se necessário if (tabId === 'dashboard') calcularDivida(); }); });
E, na dashboard, tem-se os resultados calculados do empréstimo.
function atualizarDashboard() { document.getElementById('saldo-atual').textContent = formatarMoeda(saldo); document.getElementById('saldo-sm-juros').textContent = formatarMoeda(saldo - totalJuros); document.getElementById('total-juros').textContent = formatarMoeda(totalJuros); document.getElementById('total-pago').textContent = formatarMoeda(totalPago); }
Leve, direto e com as informações que interessam de cara.

Importante: Extrato detalhado e autitabilidade

Um ponto não negociável: o extrato dia a dia. Ele permite auditar cada centavo e reproduzir o cálculo na íntegra:
extrato.push({ data: taxaData, descricao: `Juros CDI (${taxaDia.taxa.toFixed(2)}% a.a. x ${multiplicadorCDI}%)`, valor: jurosDia, saldo: saldo, juros: jurosDia });
Para legibilidade implementei agrupamento por mês e alternância de cores:
// Agrupar por mês const grupos = {}; extrato.forEach(item => { const [y,m] = item.data.split('-'); const key = `${y}-${m}`; if (!grupos[key]) grupos[key] = []; grupos[key].push(item); }); // Renderizar com alternância de cores let monthToggle = 0; for (const key of Object.keys(grupos).sort()) { // Header do mês const headerTr = document.createElement('tr'); headerTr.classList.add('month-header'); // Itens do mês com cor alternada grupos[key].forEach(item => { const tr = document.createElement('tr'); tr.classList.add(monthToggle % 2 === 0 ? 'month-alt-0' : 'month-alt-1'); // ... renderizar linha }); monthToggle++; }
Visual limpo e auditável: você vê a evolução dia a dia e pode explicar cada número.

Exportação e Backup

Portabilidade é essencial: CSV para análise rápida e JSON para backup completo.
// Exportar tudo para CSV function exportarCSV() { let csv = 'Data,Descrição,Valor (R$),Saldo (R$),Juros (R$)\n'; extrato.forEach(item => { csv += `"${formatarData(item.data)}","${item.descricao}",${item.valor.toFixed(2)},${item.saldo.toFixed(2)},${item.juros.toFixed(2)}\n`; }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'extrato-divida.csv'; link.click(); } // ou exportar tudo em JSON function exportarJSON() { const dados = { emprestimos, pagamentos, taxas, extrato, multiplicadorCDI, metadata: { exportadoEm: new Date().toISOString(), versao: '1.1' } }; const blob = new Blob([JSON.stringify(dados, null, 2)], { type: 'application/json' }); // ... download }

Resultados

Bons testes básicos comprovaram que a lógica principal está sólida, a minha planilha antiga possui os mesmos resultados que os calculados em nossa aplicação, inclusive, com casos de borda (vários eventos no mesmo dia, feriados longos) que recebem atenção específica.
Além disso, para uso p142857essoal (alguns anos de histórico) o desempenho é ótimo. O algoritmo tem complexidade aproximada O(n × m) — n dias × m eventos por dia — e atende bem ao escopo pessoal. Claro, escalar para uso corporativo exigiria estruturas mais eficientes.
E em termos de precisão, o JavaScript usa IEEE 754 e pode ter pequenos erros de ponto flutuante em finanças. Se precisar de precisão absoluta, recomendo acrescentar decimal.js:
// Ao invés de: const juros = saldo * (taxa / 100 / 365); // Usar: const juros = new Decimal(saldo).mul(new Decimal(taxa).div(100).div(365)).toNumber();
Para meu empréstimo de quatro anos, o SPA vanilla foi a escolha certa: simples e direto. Se a ferramenta crescer (multiusuário, sincronização), certamente migrar para React/Vue e um backend será um caminho natural.
E, de modo geral, a ferramenta resolveu o problema original e virou uma base extensível para outros cálculos financeiros. E o melhor: funciona exatamente como eu preciso, ainda que possa ter bugs que não tenha percebido.

Repositório Gratuito + Código-Fonte

calculadora-de-emprestimos
jancaraumaUpdated Sep 13, 2025
If you have any questions, please contact me.
© 2016 - 2025 | J. Caraumã

Made in Roraima, Brazil