// Relatório — Diagnóstico completo (orçamento, patrimônio, proteção, sucessão) — imprimível // ===== Diagnóstico do consultor: inconsistências e insights gerados dos dados ===== function gerarDiagnostico(state, year, month) { const out = []; const add = (nivel, titulo, texto) => out.push({ nivel, titulo, texto }); const fmt = window.fmtBRLString; const hoje = new Date().toISOString().slice(0, 10); const plano = window.computePlano(state.transactions, state.aggregates, year, month); const tot = window.patrimonioTotais(state.patrimonio, state.dividas); const custo = window.custoFixoDetalhe(state.transactions, 3, (state.reserva || {}).base || "todos"); const mesLabel = window.MESES_FULL[month] + "/" + year; // --- Orçamento / plano 70·10·10·10 --- if (plano.income === 0) { add("atencao", "Sem receita lançada em " + mesLabel, "O plano 70·10·10·10 só funciona com a receita do mês registrada. Lance as entradas no Checklist para ativar os tetos e metas."); } else { const g = plano.gastos; if (g.over) { add("alerta", "Gastos " + fmt(g.overBy) + " acima do teto de 70%", "Em " + mesLabel + " os gastos comprometidos somam " + fmt(g.used) + " para um teto de " + fmt(g.target) + ". Há " + fmt(g.redutivelPendente) + " em despesas redutíveis ainda não pagas — é onde o corte não afeta o essencial."); } else { add("ok", "Gastos dentro do teto de 70%", "Folga de " + fmt(g.target - g.used) + " em " + mesLabel + ". Manter os gastos abaixo do teto é o que libera os 30% para caridade, projetos e investimentos."); } for (const b of plano.buckets) { if (b.key === "gastos") continue; if (b.used + 0.5 < b.target) { add("atencao", b.icon + " " + b.label + " abaixo da meta de 10%", "Lançado " + fmt(b.used) + " de " + fmt(b.target) + ". Faltam " + fmt(b.target - b.used) + " para cumprir o balde em " + mesLabel + "."); } } const sobra = plano.income - plano.gastos.used - plano.buckets.filter(b => b.key !== "gastos").reduce((s, b) => s + b.used, 0); if (sobra < 0) add("alerta", "Mês fechando no vermelho", "As saídas de " + mesLabel + " superam a receita em " + fmt(-sobra) + ". Priorize os cortes em redutíveis antes de comprometer a reserva."); } // --- Reserva de emergência --- const saldosReserva = (state.patrimonio.saldos || []).filter(s => s.reserva); const reservaSaldo = state.reserva && state.reserva.saldoManual != null ? state.reserva.saldoManual : saldosReserva.reduce((s, x) => s + Number(x.valor || 0), 0); const mesesAlvo = (state.reserva || {}).mesesAlvo || 6; if (custo.media > 0) { const meses = reservaSaldo / custo.media; if (reservaSaldo <= 0) add("alerta", "Sem reserva de emergência", "Com custo mensal de " + fmt(custo.media) + ", o alvo de " + mesesAlvo + " meses é " + fmt(custo.media * mesesAlvo) + ". Marque ativos líquidos como reserva ou comece um aporte mensal dedicado."); else if (meses < mesesAlvo) add("atencao", "Reserva cobre " + meses.toFixed(1) + " de " + mesesAlvo + " meses", "Faltam " + fmt(custo.media * mesesAlvo - reservaSaldo) + " para o colchão completo. Reserva vem antes de investimentos de maior risco."); else add("ok", "Reserva de emergência completa", meses.toFixed(1) + " meses de custo cobertos (" + fmt(reservaSaldo) + "). Excedentes podem migrar para objetivos e aposentadoria."); } // --- Dívidas --- const dividas = state.dividas || []; const vencidas = dividas.filter(d => d.status !== "Finalizado" && d.vencimento && d.vencimento < hoje); if (vencidas.length) add("alerta", vencidas.length + " dívida(s) com vencimento estourado", vencidas.map(d => d.descricao + " (venceu " + d.vencimento.split("-").reverse().join("/") + ")").join(" · ") + ". Renegociar evita juros e restrições."); const semNeg = dividas.filter(d => d.status === "Sem Negociação"); if (semNeg.length) add("atencao", "Dívida sem negociação", semNeg.map(d => d.descricao + " — " + fmt(d.saldoDevedor)).join(" · ") + ". Sem acordo formal, o passivo tende a crescer e a travar o planejamento."); if (tot.totalDividas > 0 && tot.total > 0) { const pctDiv = (tot.totalDividas / tot.total) * 100; add(pctDiv > 30 ? "atencao" : "insight", "Dívidas equivalem a " + pctDiv.toFixed(0) + "% do patrimônio", fmt(tot.totalDividas) + " devidos contra " + fmt(tot.total) + " de patrimônio bruto — patrimônio líquido de " + fmt(tot.liquido) + "."); } // --- Proteção --- const seguros = state.seguros || []; const vidas = seguros.filter(s => s.tipo === "Vida"); const temFilhos = (state.familia.filhos || []).length > 0; if (!vidas.length && temFilhos) add("alerta", "Família com filhos e sem seguro de vida", "Uma referência didática: capital segurado de 3 a 5 anos do custo da família" + (custo.media > 0 ? " (entre " + fmt(custo.media * 36) + " e " + fmt(custo.media * 60) + ")" : "") + " protege a transição em caso de perda do provedor."); else if (vidas.length && custo.media > 0) { const capital = vidas.reduce((s, v) => s + Number(v.capital || 0), 0); const anos = capital / (custo.media * 12); if (anos < 3) add("atencao", "Capital de vida cobre " + anos.toFixed(1) + " ano(s) de custo", "Capital segurado de " + fmt(capital) + " ante um custo anual de " + fmt(custo.media * 12) + ". Referência usual: 3 a 5 anos."); else add("ok", "Seguro de vida em patamar adequado", "Capital de " + fmt(capital) + " cobre " + anos.toFixed(1) + " anos do custo da família."); } const segVencidos = seguros.filter(s => s.vigencia && s.vigencia < hoje); if (segVencidos.length) add("alerta", "Apólice(s) com vigência vencida", segVencidos.map(s => s.nome + " (até " + s.vigencia.split("-").reverse().join("/") + ")").join(" · ") + ". Confirme a renovação para não ficar descoberto."); const naoSegurados = [...(state.patrimonio.imoveis || []), ...(state.patrimonio.veiculos || [])].filter(a => !a.segurado); if (naoSegurados.length) add("atencao", "Bens sem seguro", naoSegurados.map(a => a.nome + " (" + window.fmtCompact(a.valor) + ")").join(" · ") + ". Avalie o custo do seguro contra o impacto de uma perda total."); // --- Aposentadoria --- const apos = window.calcAposentadoria(state.aposentadoria, tot.financeiro); if ((state.aposentadoria.rendaMensalDesejada || 0) > 0) { const aporteAtual = Number(state.aposentadoria.aporteMensal || 0); if (apos.aporteNecessario > aporteAtual + 1) add("atencao", "Aporte de aposentadoria abaixo do necessário", "Para " + fmt(state.aposentadoria.rendaMensalDesejada) + "/mês aos " + state.aposentadoria.idadeAlvo + " anos, o aporte estimado é " + fmt(apos.aporteNecessario) + "/mês — hoje são " + fmt(aporteAtual) + "."); else add("ok", "Plano de aposentadoria no ritmo", "Aporte atual de " + fmt(aporteAtual) + "/mês projeta " + window.fmtCompact(apos.projecao) + " aos " + state.aposentadoria.idadeAlvo + " anos, para um alvo de " + window.fmtCompact(apos.alvo) + "."); } // --- Sucessão / liquidez --- const suc = window.calcSucessao(state); const capitalVidaSuc = seguros.filter(s => s.tipo === "Vida" || s.tipo === "Invalidez").reduce((s, v) => s + Number(v.capital || 0), 0); if (suc.custoExtrajudicial > 0 && tot.total > 0) { const cob = capitalVidaSuc / suc.custoExtrajudicial; if (cob >= 1) add("ok", "Custo do inventário coberto pelo seguro", "O capital segurado de " + fmt(capitalVidaSuc) + " cobre integralmente o custo estimado do inventário extrajudicial (" + fmt(suc.custoExtrajudicial) + ")."); else add("alerta", "Seguro cobre " + Math.round(cob * 100) + "% do custo do inventário", "O inventário extrajudicial custa ~" + fmt(suc.custoExtrajudicial) + " e o capital segurado é " + fmt(capitalVidaSuc) + ". Sugestão: aumentar a proteção em " + fmt(suc.custoExtrajudicial - capitalVidaSuc) + " para dar liquidez imediata aos herdeiros (na via judicial o custo sobe para " + fmt(suc.custoJudicial) + ")."); } if (suc.custoExtrajudicial > tot.financeiro + capitalVidaSuc && tot.total > 0) add("atencao", "Custo sucessório maior que a liquidez disponível", "Somando patrimônio financeiro (" + fmt(tot.financeiro) + ") e seguro (" + fmt(capitalVidaSuc) + "), ainda falta caixa para os ~" + fmt(suc.custoExtrajudicial) + " do inventário. Sem liquidez, herdeiros podem ter que vender bens às pressas."); if (tot.previdencia === 0 && tot.total > 0) add("insight", "Nenhum recurso livre de inventário", "Previdência (PGBL/VGBL) não passa por inventário e dá liquidez imediata aos beneficiários — instrumento útil no planejamento sucessório."); if (tot.total > 0 && tot.naoFinanceiro / tot.total > 0.8) add("insight", "Patrimônio concentrado em bens ilíquidos", ((tot.naoFinanceiro / tot.total) * 100).toFixed(0) + "% do patrimônio está em imóveis, veículos e participações. Diversificar em ativos financeiros dá flexibilidade para emergências e oportunidades."); // --- Objetivos --- for (const o of state.objetivos || []) { if (o.prazo && o.prazo < hoje && Number(o.acumulado || 0) < Number(o.alvo || 0)) { add("atencao", "Objetivo \"" + o.nome + "\" com prazo vencido", "Acumulado " + fmt(o.acumulado) + " de " + fmt(o.alvo) + " até " + o.prazo.split("-").reverse().join("/") + ". Redefina o prazo ou o aporte mensal."); } } return out; } const DIAG_NIVEIS = { alerta: { icon: "🚨", label: "Alerta" }, atencao: { icon: "⚠️", label: "Atenção" }, ok: { icon: "✅", label: "Em ordem" }, insight: { icon: "💡", label: "Insight" }, }; function RelatorioPage({ state, year, month = 0 }) { const [atualizadoEm, setAtualizadoEm] = React.useState(() => new Date()); const [flash, setFlash] = React.useState(false); function atualizar() { setAtualizadoEm(new Date()); setFlash(true); setTimeout(() => setFlash(false), 1200); } const p = state.patrimonio; const fam = state.familia; const tot = window.patrimonioTotais(p, state.dividas); const suc = window.calcSucessao(state); const saldosClasse = window.saldosPorClasse(p.saldos); const fmt = window.fmtBRLString; const mesLabel = window.MESES_FULL[month] + "/" + year; const plano = window.computePlano(state.transactions, state.aggregates, year, month); const classesMes = window.byClasse(window.txByMonth(state.transactions, year, month)); const totalClasses = classesMes.reduce((s, c) => s + c.value, 0); const custo = window.custoFixoDetalhe(state.transactions, 3, (state.reserva || {}).base || "todos"); const saldosReserva = (p.saldos || []).filter(s => s.reserva); const reservaSaldo = state.reserva && state.reserva.saldoManual != null ? state.reserva.saldoManual : saldosReserva.reduce((s, x) => s + Number(x.valor || 0), 0); const mesesAlvo = (state.reserva || {}).mesesAlvo || 6; const apos = window.calcAposentadoria(state.aposentadoria, tot.financeiro); const diag = gerarDiagnostico(state, year, month); const DIAG_ORDEM = { alerta: 0, atencao: 1, insight: 2, ok: 3 }; const diagOrdenado = [...diag].sort((a, b) => DIAG_ORDEM[a.nivel] - DIAG_ORDEM[b.nivel]); const diagCriticos = diagOrdenado.filter(d => d.nivel === "alerta"); const diagResto = diagOrdenado.filter(d => d.nivel !== "alerta"); const catItems = [ { label: "Imóveis", valor: tot.imoveis, color: "#311131" }, { label: "Veículos", valor: tot.veiculos, color: "#0663AA" }, { label: "Participações societárias", valor: tot.participacoes, color: "#7fb8e0" }, { label: "Saldos e Investimentos", valor: tot.financeiro, color: "#EA404D" }, { label: "Outros bens", valor: tot.outros, color: "#bcdcf3" }, ]; const maxCat = Math.max(...catItems.map(c => c.valor), 1); function Sec({ title, scope, explain, children }) { return (

{title}

{scope && {scope}}
{explain &&

{explain}

} {children}
); } return (

📄 Relatório

Diagnóstico financeiro, patrimonial e sucessório — pronto para imprimir/salvar em PDF.
{flash && ✓ Relatório atualizado}
{/* Capa */}

Relatório

Diagnóstico Financeiro, Patrimonial e Familiar · {mesLabel}

Gerado com os dados de {atualizadoEm.toLocaleDateString("pt-BR")} às {atualizadoEm.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" })} — o relatório reflete automaticamente tudo que foi lançado no app.

Informações do cliente

Nome completo do cliente{fam.cliente.nome}
Data de nascimento{fam.cliente.nascimento ? fam.cliente.nascimento.split("-").reverse().join("/") : "—"}
E-mail{fam.cliente.email || "—"}
Estado civil{fam.cliente.estadoCivil || "—"}
Nome do(a) cônjuge{fam.conjuge?.nome || "—"}
Nascimento do(a) cônjuge{fam.conjuge?.nascimento ? fam.conjuge.nascimento.split("-").reverse().join("/") : "—"}
Regime de bens{fam.cliente.regimeBens || "—"}
{diagCriticos.length > 0 && (
{diagCriticos.map((d, i) => (
{DIAG_NIVEIS[d.nivel].icon} {d.titulo} {d.texto}
))}
)}
{diagResto.map((d, i) => (
{DIAG_NIVEIS[d.nivel].icon}
{d.titulo}

{d.texto}

))} {diag.length === 0 &&

Sem dados suficientes para o diagnóstico. Lance receitas e gastos no Checklist.

}
Receita do mês{fmt(plano.income)}
{plano.buckets.map(b => (
{b.icon} {b.label} ({Math.round(b.pct * 100)}%)
0 ? (b.used / b.target) * 100 : 0, 100) + "%", background: b.over ? "#EA404D" : "#0663AA" }}>
{fmt(b.used)} de {fmt(b.target)}
))}
{totalClasses > 0 && ( {classesMes.map(c => ( ))}
Gastos por classeValor% do total
{c.icon} {c.label}{fmt(c.value)}{Math.round((c.value / totalClasses) * 100)}%
)}
Cliente{fam.cliente.nome}{window.idade(fam.cliente.nascimento) != null ? window.idade(fam.cliente.nascimento) + " anos" : ""}
{fam.conjuge?.nome &&
Cônjuge{fam.conjuge.nome}{window.idade(fam.conjuge.nascimento) != null ? window.idade(fam.conjuge.nascimento) + " anos" : ""}
} {(fam.filhos || []).map((f, i) => (
Filho(a){f.nome}{window.idade(f.nascimento) != null ? window.idade(f.nascimento) + " anos" : ""}
))}
Patrimônio total{fmt(tot.total)} Comum {fmt(tot.porTitularidade.Comum)} · Cliente {fmt(tot.porTitularidade.Cliente)} · Cônjuge {fmt(tot.porTitularidade["Cônjuge"])}
Patrimônio líquido{fmt(tot.liquido)}descontadas dívidas de {fmt(tot.totalDividas)}
{catItems.map(c => (
{c.label}
{fmt(c.valor)}
))}
Patrimônio não financeiro{fmt(tot.naoFinanceiro)} {p.imoveis.length} imóveis · {p.veiculos.length} veículos · {p.participacoes.length} participações · {p.outros.length} outros
Patrimônio financeiro{fmt(tot.financeiro)} {p.saldos.length} saldos e investimentos
{saldosClasse.length > 0 && ( {saldosClasse.map(s => )}
Saldos e InvestimentosValor
{s.classe}{fmt(s.valor)}
)}
Patrimônio livre de inventário{fmt(tot.previdencia)}Previdência
Bens com restrições{fmt(tot.comRestricao)}
Dívida total{fmt(tot.totalDividas)}
{(state.dividas || []).length > 0 ? ( {state.dividas.map(d => ( ))}
DescriçãoCredorSaldo devedorVencimentoStatus
{d.descricao}{d.credor || "—"}{fmt(d.saldoDevedor)}{d.vencimento ? d.vencimento.split("-").reverse().join("/") : "—"}{d.status}
) :

Nenhuma dívida cadastrada.

}
{(state.seguros || []).length > 0 ? ( {state.seguros.map(s => ( ))}
TipoApóliceSeguradoraCapitalPrêmio/mêsVigência
{s.tipo}{s.nome}{s.seguradora || "—"}{fmt(s.capital)}{fmt(s.premioMensal)}{s.vigencia ? s.vigencia.split("-").reverse().join("/") : "—"}
) :

Nenhum seguro cadastrado.

}
Reserva atual{fmt(reservaSaldo)}
Alvo ({mesesAlvo} meses){fmt(custo.media * mesesAlvo)}
Meses cobertos{custo.media > 0 ? (reservaSaldo / custo.media).toFixed(1) : "—"}
{custo.media * mesesAlvo - reservaSaldo > 0 ? "Faltam" : "Excedente"}{fmt(Math.abs(custo.media * mesesAlvo - reservaSaldo))}
{custo.media > 0 && (
{[...Array(mesesAlvo)].map((_, i) => ( ))}
{(reservaSaldo / custo.media).toFixed(1)} de {mesesAlvo} meses de custo cobertos {Math.round(Math.min(reservaSaldo / (custo.media * mesesAlvo), 1) * 100)}% do alvo
)} {custo.porClasse.length > 0 && ( {custo.porClasse.map(c => ( ))}
Custo mensal por classe (média)Valor
{c.icon} {c.label}{fmt(c.valor)}
Total{fmt(custo.media)}
)} {saldosReserva.length > 0 ? ( {saldosReserva.map(s => ( ))}
Ativos que compõem a reservaClasseInstituiçãoValor
{s.nome}{s.classe}{s.instituicao || "—"}{fmt(window.valorBRL(s))}
) :

Nenhum ativo marcado como reserva de emergência.

}
Renda desejada{fmt(state.aposentadoria.rendaMensalDesejada)}/mêsaos {state.aposentadoria.idadeAlvo} anos
Capital alvo{window.fmtCompact(apos.alvo)}
Projeção atual{window.fmtCompact(apos.projecao)}com aporte de {fmt(state.aposentadoria.aporteMensal)}/mês
Aporte necessário{fmt(apos.aporteNecessario)}/mês
{(state.objetivos || []).length > 0 ? ( {state.objetivos.map(o => ( ))}
ObjetivoAcumuladoAlvoProgressoPrazo
{o.icone} {o.nome}{fmt(o.acumulado)}{fmt(o.alvo)}{o.alvo > 0 ? Math.round((o.acumulado / o.alvo) * 100) : 0}%{o.prazo ? o.prazo.split("-").reverse().join("/") : "—"}
) :

Nenhum objetivo cadastrado.

}
{p.participacoes.length > 0 ? ( {p.participacoes.map(e => ( ))}
EntidadeCNPJ%Capital socialValor patrimonial
{e.nome}{e.cnpj || "—"}{e.pct}%{fmt(e.capitalSocial)}{fmt(e.valorPatrimonial)}
) :

Nenhuma participação societária.

}
Inventário Extrajudicial{fmt(suc.custoExtrajudicial)} Registro {fmt(suc.registro)} · Escritura {fmt(suc.escritura)} · ITCMD {fmt(suc.itcmd)} · Honorários {fmt(suc.honorariosExtra)}
Inventário Judicial{fmt(suc.custoJudicial)} Registro {fmt(suc.registro)} · Custas {fmt(suc.custasJudiciais)} · ITCMD {fmt(suc.itcmd)} · Honorários {fmt(suc.honorariosJud)}

*O valor do honorário é sugerido conforme a recomendação da tabela OAB de cada estado.

Patrimônio transmissível{fmt(suc.transmissivel)}
Meação{fmt(suc.meacao)}
Previdência isenta{fmt(suc.previdenciaIsenta)}
Valor tributável{fmt(suc.valorTributavel)}
ITCMD estimado ({suc.estado}){fmt(suc.itcmd)}
Alíquota média{suc.aliquota}%
Total{fmt(suc.transmissivel)}
Meação{suc.meacao > 0 ? fmt(suc.meacao) : "— (sem meação)"}
Herança{fmt(suc.heranca)}
Legítima{fmt(suc.legitima)}
{suc.quinhaoConjuge > 0 &&
Quinhão do cônjuge (herança){fmt(suc.quinhaoConjuge)}
}
0 ? (suc.meacao / suc.transmissivel) * 100 + "%" : "50%", background: "#311131" }}>
0 ? (suc.heranca / suc.transmissivel) * 100 + "%" : "50%", background: "#EA404D" }}>
Meação {suc.transmissivel > 0 ? Math.round((suc.meacao / suc.transmissivel) * 100) : 0}% Herança {suc.transmissivel > 0 ? Math.round((suc.heranca / suc.transmissivel) * 100) : 0}%
{suc.herdeiros.map((h, i) => (
{h.papel} {h.nome} {h.composicao}
{window.fmtCompact(h.valor)}
{suc.transmissivel > 0 ? ((h.valor / suc.transmissivel) * 100).toFixed(0) : 0}% do transmissível
))}

*Partilha simulada para o falecimento do cliente, conforme o regime de bens ({suc.regime}) — CC arts. 1.829 e 1.832. Estimativa didática; não substitui aconselhamento jurídico.

Este estudo é dirigido exclusivamente ao destinatário indicado na capa e pode conter informações confidenciais. Seu propósito é fornecer informações para auxiliar o destinatário no planejamento financeiro, patrimonial e sucessório. As informações pessoais são fornecidas pelo destinatário levando em consideração seus dados cadastrais, familiares e patrimoniais, bem como suas necessidades financeiras e objetivos pessoais. O relatório tem natureza informativa e orientadora, e não deve ser interpretado como aconselhamento jurídico ou financeiro. Não nos responsabilizamos por decisões de investimento ou quaisquer decisões pessoais tomadas com base nas informações apresentadas neste relatório.

); } Object.assign(window, { RelatorioPage, gerarDiagnostico });