:root{
–bg:#f8fafc; –card:#ffffff; –ink:#0f172a; –muted:#475569;
–primary:#16a34a; –line:#e2e8f0; –warn:#dc2626;
}
*{box-sizing:border-box}
html,body{margin:0;background:var(–bg);color:var(–ink);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,”Helvetica Neue”,Arial,”Noto Sans”,sans-serif}
h1{font-size:clamp(1.3rem,2.5vw,1.8rem);margin:0 0 12px}
p.lead{color:var(–muted);margin:0 0 24px}
.grid{gap:16px;grid-template-columns:1fr}
@media (min-width: 880px){.grid{grid-template-columns: 1fr 1.2fr}}
.card{background:var(–card);border:1px solid var(–line);border-radius:14px;box-shadow:0 1px 0 rgba(0,0,0,.03);padding:18px}
label{display:block;font-weight:600;margin:8px 0 6px}
.row{display:grid;grid-template-columns:1fr;gap:12px}
@media(min-width:580px){.row.two{grid-template-columns:1fr 1fr}}
.field{position:relative}
.suffix{
position:absolute; right:12px; top:50%; translate:0 -50%;
color:var(–muted); pointer-events:none; font-weight:600;
}
input{
width:100%;padding:12px 14px;border:1px solid var(–line);border-radius:10px;background:#fff;color:var(–ink);
font:inherit;outline:none;
}
input:focus{border-color:#94a3b8;box-shadow:0 0 0 3px rgba(148,163,184,.25)}
.actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px}
.btn{
display:inline-block !important;text-decoration:none !important;border:1px solid transparent !important;border-radius:999px !important;padding:12px 18px !important;font-weight:800 !important;
transition:transform .05s ease, box-shadow .2s ease, background .2s ease !important;
}
.btn[role=”button”]{cursor:pointer !important}
.btn-primary{background:var(–primary);color:#fff !important;box-shadow:0 2px 0 rgba(0,0,0,.05) !important}
.btn-primary:hover,.btn-primary:focus-visible{background:#22c55e !important}
.btn-primary:active{transform:translateY(1px) !important}
.btn-secondary{background:#fff !important;color:var(–ink);border-color:var(–line) !important}
.btn-secondary:hover,.btn-secondary:focus-visible{background:#f1f5f9 !important}
.btn-ghost{background:transparent !important;color:var(–muted) !important}
.hint{font-size:.9rem;color:var(–muted);margin-top:6px}
.error{color:var(–warn);font-weight:600;margin:8px 0}
.summary{display:grid;grid-template-columns:1fr;gap:12px}
@media(min-width:700px){.summary{grid-template-columns:repeat(3,1fr)}}
.kpi{background:#fff;border:1px dashed var(–line);border-radius:12px;padding:12px}
.kpi .k{font-size:.85rem;color:var(–muted)}
.kpi .v{font-size:1.15rem;font-weight:800;margin-top:6px}
table{width:100%;border-collapse:collapse}
thead th{position:sticky;top:0;background:#fff;border-bottom:2px solid var(–line);text-align:left;font-size:.9rem;padding:10px}
tbody td{border-bottom:1px solid var(–line);padding:10px;font-size:.95rem}
.table-wrap{max-height:420px;overflow:auto;border:1px solid var(–line);border-radius:12px;background:#fff}
.muted{color:var(–muted)}
.badge{display:inline-block;background:#ecfeff;border:1px solid #a5f3fc;color:#0e7490;padding:2px 8px;border-radius:999px;font-size:.75rem;font-weight:700}
.sr-only{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}
.footnote{margin-top:12px;color:var(–muted);font-size:.9rem}
document.getElementById(id);
const inputs = {
credito: $(‘credito’),
prazo: $(‘prazo’),
taxaAdm: $(‘taxaAdm’),
fundoReserva: $(‘fundoReserva’),
reajuste: $(‘reajuste’),
seguro: $(‘seguro’),
lance: $(‘lance’)
};
const out = {
erro: $(‘erro’),
kParcelaBem: $(‘kParcelaBem’),
kBase: $(‘kBase’),
kTotal: $(‘kTotal’),
tb: $(‘tb’),
tabelaWrap: $(‘tabelaWrap’)
};
// ==========
// Máscaras
// ==========
function onlyDigits(str){ return (str||”).replace(/\D+/g,”); }
// Moeda – máscara simples enquanto digita
function formatCurrencyBR(v){
const digits = onlyDigits(v);
const n = (Number(digits)||0)/100;
return n.toLocaleString(‘pt-BR’,{style:’currency’,currency:’BRL’});
}
function parseCurrencyBR(str){
if (!str) return NaN;
const cleaned = String(str).replace(/\s/g,”).replace(/[R$\u00A0]/g,”).replace(/\./g,”).replace(‘,’, ‘.’);
return Number(cleaned);
}
document.querySelectorAll(‘input[data-mask=”currency”]’).forEach(inp=>{
inp.addEventListener(‘input’, ()=>{
const selEnd = inp.selectionEnd;
inp.value = formatCurrencyBR(inp.value);
inp.setSelectionRange(inp.value.length, inp.value.length);
});
});
// Percentual “easy”: digita só número/virgula/ponto; sem % durante a edição
function parsePercentToNumber(str){
if (!str) return NaN;
// aceita “6”, “6,5”, “6.5”, “16,25”
const s = String(str).trim().replace(‘%’,”).replace(/\s/g,”).replace(‘,’, ‘.’);
return Number(s);
}
function formatPercentBR(num){
if (!isFinite(num)) return ”;
return num.toLocaleString(‘pt-BR’,{minimumFractionDigits:2, maximumFractionDigits:4}) + ‘%’;
}
function attachPercentEasy(inp){
// ao focar: tira % e deixa só número (com vírgula opcional)
inp.addEventListener(‘focus’, ()=>{
const raw = String(inp.value||”).replace(‘%’,”).trim();
// Converte “6,00%” -> “6,00”
const s = raw.replace(/\s/g,”);
inp.value = s;
// posiciona no fim
requestAnimationFrame(()=> inp.setSelectionRange(inp.value.length, inp.value.length));
});
// ao sair: formata e aplica %
inp.addEventListener(‘blur’, ()=>{
const n = parsePercentToNumber(inp.value);
if (isFinite(n)) inp.value = formatPercentBR(n);
else inp.value = ”;
});
// durante a digitação: bloqueia caracteres não numéricos (exceto , .)
inp.addEventListener(‘input’, ()=>{
let v = inp.value;
v = v.replace(/[^\d,\.]/g,”);
// impede mais de um separador decimal
const parts = v.split(/[,\.]/);
if (parts.length > 2){
v = parts[0] + ‘,’ + parts.slice(1).join(”);
}
inp.value = v;
});
}
document.querySelectorAll(‘input[data-mask=”percent-easy”]’).forEach(attachPercentEasy);
// ==========
// Utilidades
// ==========
const fmtBRL = n => isFinite(n) ? n.toLocaleString(‘pt-BR’,{style:’currency’,currency:’BRL’}) : ‘—’;
function safeNumber(v, def=0){ return isFinite(v) ? v : def; }
function validate(){
out.erro.textContent = ”;
const credito = parseCurrencyBR(inputs.credito.value);
const prazo = Math.floor(Number(inputs.prazo.value.replace(/\D+/g,”)));
const taxaAdm = parsePercentToNumber(inputs.taxaAdm.value);
if (!isFinite(credito) || credito <= 0) return 'Informe um valor de crédito válido.';
if (!isFinite(prazo) || prazo <= 0) return 'Informe um prazo (meses) válido.';
if (!isFinite(taxaAdm) || taxaAdm = 100) return ‘O seguro não pode ser igual ou superior a 100%.’;
return null;
}
function simular(){
const err = validate();
if (err){ out.erro.textContent = err; out.tabelaWrap.scrollTop = 0; return; }
const credito = parseCurrencyBR(inputs.credito.value);
const prazo = Math.floor(Number(inputs.prazo.value.replace(/\D+/g,”)));
const taxaAdmTotalPct = safeNumber(parsePercentToNumber(inputs.taxaAdm.value),0);
const fundoReservaPct = safeNumber(parsePercentToNumber(inputs.fundoReserva.value),0);
const reajusteAnualPct = safeNumber(parsePercentToNumber(inputs.reajuste.value),0);
const seguroPct = safeNumber(parsePercentToNumber(inputs.seguro.value),0);
const lance = safeNumber(parseCurrencyBR(inputs.lance.value),0);
const creditoBaseInicial = Math.max(credito – Math.max(0, Math.min(lance, credito)), 0);
// Componentes fixos mensais (adm/FR) sobre crédito original
const admMensal = (taxaAdmTotalPct/100 * credito) / prazo;
const frMensal = (fundoReservaPct/100 * credito) / prazo;
let saldo = creditoBaseInicial;
let parcelasRestantes = prazo;
let cotaBemAtual = parcelasRestantes > 0 ? (saldo / parcelasRestantes) : 0;
out.tb.innerHTML = ”;
let primeiraLinha = null;
for (let mes = 1; mes 1 && ((mes – 1) % 12 === 0) && reajusteAnualPct > 0){
const fator = 1 + (reajusteAnualPct/100);
saldo = saldo * fator;
cotaBemAtual = saldo / parcelasRestantes;
houveReajuste = true;
}
const cotaBem = cotaBemAtual;
const base = cotaBem + admMensal + frMensal;
const p = (seguroPct/100);
const parcelaTotal = (p >= 1) ? Infinity : base / (1 – p);
const seguro = parcelaTotal – base;
if (mes === 1){
primeiraLinha = { cota: cotaBem, base, total: parcelaTotal };
}
saldo = Math.max(0, saldo – cotaBem);
parcelasRestantes = Math.max(0, parcelasRestantes – 1);
cotaBemAtual = parcelasRestantes > 0 ? (saldo / parcelasRestantes) : 0;
const tr = document.createElement(‘tr’);
const cols = [
mes,
fmtBRL(cotaBem),
fmtBRL(admMensal),
fmtBRL(frMensal),
fmtBRL(seguro),
fmtBRL(parcelaTotal),
fmtBRL(saldo),
houveReajuste ? ‘Sim’ : ‘—’
];
cols.forEach(txt=>{
const td = document.createElement(‘td’);
td.textContent = txt;
tr.appendChild(td);
});
out.tb.appendChild(tr);
}
if (primeiraLinha){
out.kParcelaBem.textContent = fmtBRL(primeiraLinha.cota);
out.kBase.textContent = fmtBRL(primeiraLinha.base);
out.kTotal.textContent = fmtBRL(primeiraLinha.total);
}
document.getElementById(‘resultado’).scrollIntoView({behavior:’smooth’, block:’start’});
out.tabelaWrap.focus();
}
function limpar(){
inputs.credito.value = ”;
inputs.prazo.value = ”;
inputs.taxaAdm.value = ”;
inputs.fundoReserva.value = ”;
inputs.reajuste.value = ”;
inputs.seguro.value = ”;
inputs.lance.value = ”;
out.erro.textContent = ”;
out.kParcelaBem.textContent = ‘—’;
out.kBase.textContent = ‘—’;
out.kTotal.textContent = ‘—’;
out.tb.innerHTML = ”;
document.body.scrollIntoView({behavior:’smooth’, block:’start’});
}
function baixarCSV(){
if (!out.tb.children.length){
simular();
if (!out.tb.children.length) return;
}
let csv = ‘Mes,Cota do Bem,Taxa Adm.,Fundo Res.,Seguro,Parcela Total,Saldo Credito,Reajuste\n’;
[…out.tb.children].forEach(tr=>{
const cols = […tr.children].map(td=> td.textContent
.replace(/\./g,”).replace(‘R$’,”).replace(/\s/g,”).replace(‘,’, ‘.’));
csv += cols.join(‘,’) + ‘\n’;
});
const blob = new Blob([csv],{type:’text/csv;charset=utf-8;’});
const url = URL.createObjectURL(blob);
const a = document.createElement(‘a’);
a.href = url; a.download = ‘simulacao-consorcio.csv’;
document.body.appendChild(a);
a.click();
setTimeout(()=>{ URL.revokeObjectURL(url); a.remove(); }, 100);
}
// Ações
$(‘btnCalcular’).addEventListener(‘click’, e=>{ e.preventDefault(); simular(); });
$(‘btnLimpar’).addEventListener(‘click’, e=>{ e.preventDefault(); limpar(); });
$(‘btnCSV’).addEventListener(‘click’, e=>{ e.preventDefault(); baixarCSV(); });
// Enter dispara calcular
Object.values(inputs).forEach(inp=>{
inp.addEventListener(‘keydown’, ev=>{
if (ev.key === ‘Enter’){ ev.preventDefault(); simular(); }
});
});
// Exemplos (edite à vontade)
inputs.credito.value = ‘R$ 120.000,00’;
inputs.prazo.value = ‘120’;
inputs.taxaAdm.value = ‘16,00’;
inputs.fundoReserva.value = ‘3,00’;
inputs.reajuste.value = ‘6,00’;
inputs.seguro.value = ‘0,50’;
inputs.lance.value = ”;
})();
//–>
Descubra o valor das parcelas no consórcio imobiliário ou consórcio de veículos e planeje sua conquista com segurança.
Parcelas = Cota do bem (sem juros) + Taxa de adm. + Fundo de reserva + Seguro (sobre a parcela total). Reajuste anual do crédito aplicado no mês 13, 25, 37…
Formulário de Simulação
%
Percentual sobre o crédito total, diluído nas parcelas.
%
%
Aplicado ao saldo do crédito no mês 13, 25, 37…
%
Total = Base ÷ (1 − %Seguro); Seguro = Total − Base.
Abatido do crédito para recalcular a cota do bem.
Resultado
Resumo da primeira parcela
Cota do Bem
—
Base sem Seguro
—
Parcela Total
—
Reajuste anual aplica-se apenas à cota do bem. Taxa de administração e fundo de reserva permanecem constantes.
| Mês | Cota do Bem | Taxa Adm. | Fundo Res. | Seguro | Parcela Total | Saldo Crédito | Reajuste? |
|---|
Seguro calculado sobre a parcela total: Total = Base ÷ (1 − p), p em %/100.