// Relatório — Budget da família (orçamento mensal) — imprimível function RelatorioBudgetPage({ 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 fmt = window.fmtBRLString; const fam = state.familia; const mesLabel = window.MESES_FULL[month] + "/" + year; const mt = window.monthTotals(state.transactions, state.aggregates, year, month); const receita = mt.income, despesa = mt.expense, saldo = receita - despesa; const txPoupanca = receita > 0 ? (saldo / receita) * 100 : 0; const prevM = month === 0 ? { m: 11, y: year - 1 } : { m: month - 1, y: year }; const prev = window.monthTotals(state.transactions, state.aggregates, prevM.y, prevM.m); const prevLabel = window.MESES_FULL[prevM.m] + "/" + prevM.y; const plano = window.computePlano(state.transactions, state.aggregates, year, month); const monthTxs = window.txByMonth(state.transactions, year, month); const classes = window.byClasse(monthTxs); const totalClasses = classes.reduce((s, c) => s + c.value, 0); // Despesas por categoria: lançamentos detalhados primeiro, senão agregados let cats = window.byCategory(monthTxs); if (!cats.length) cats = window.aggCategoryBreakdown(state.aggregates, year, month); const totalCats = cats.reduce((s, c) => s + c.value, 0); // Receitas por fonte const recMap = {}; for (const t of monthTxs) { if (t.type !== "Receita") continue; const k = t.category || t.description || "Receita"; recMap[k] = (recMap[k] || 0) + Number(t.amount || 0); } for (const [nome, arr] of Object.entries(state.aggregates?.[year]?.receitas || {})) { const v = Number((arr || [])[month] || 0); if (v > 0) recMap[nome] = (recMap[nome] || 0) + v; } const receitas = Object.entries(recMap).map(([nome, valor]) => ({ nome, valor })).sort((a, b) => b.valor - a.valor); const custo = window.custoFixoDetalhe(state.transactions, 3, (state.reserva || {}).base || "todos"); const membros = [fam.cliente.nome, fam.conjuge?.nome].filter(Boolean).concat((fam.filhos || []).map(f => f.nome)).filter(Boolean); function Sec({ title, explain, children }) { return (

{title}

{explain &&

{explain}

} {children}
); } return (

💰 Budget da família

Orçamento mensal da família — pronto para imprimir/salvar em PDF.
{flash && ✓ Relatório atualizado}

Budget da família

Orçamento mensal · {mesLabel}

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

Família

Cliente{fam.cliente.nome || "—"}
Cônjuge{fam.conjuge?.nome || "—"}
Filhos{(fam.filhos || []).length > 0 ? fam.filhos.map(f => f.nome).filter(Boolean).join(", ") || fam.filhos.length : "—"}
Membros{membros.length || "—"}
Receita{fmt(receita)}
Despesas{fmt(despesa)}
Saldo do mês{fmt(saldo)}taxa de poupança {receita > 0 ? txPoupanca.toFixed(0) + "%" : "—"}
Receita em {prevLabel}{fmt(prev.income)}
Despesas em {prevLabel}{fmt(prev.expense)}
Δ despesas vs {prevLabel} prev.expense ? "rel-neg" : ""}>{prev.expense > 0 ? (despesa >= prev.expense ? "+" : "") + fmt(despesa - prev.expense) : "—"}
Custo médio mensal (3m){fmt(custo.media)}
{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)}
))}
{receitas.length > 0 ? ( {receitas.map(r => ( ))}
FonteValor% da receita
{r.nome}{fmt(r.valor)}{receita > 0 ? Math.round((r.valor / receita) * 100) : 0}%
Total{fmt(receita)}100%
) :

Nenhuma receita lançada em {mesLabel}.

}
{totalClasses > 0 ? ( {classes.map(c => ( ))}
ClasseValor% do total
{c.icon} {c.label}{fmt(c.value)}{Math.round((c.value / totalClasses) * 100)}%
Total{fmt(totalClasses)}100%
) :

Nenhum lançamento classificado em {mesLabel}.

}
{cats.length > 0 ? ( {cats.map(c => ( ))}
CategoriaValor% das despesas
{c.category}{fmt(c.value)}{totalCats > 0 ? Math.round((c.value / totalCats) * 100) : 0}%
Total{fmt(totalCats)}100%
) :

Nenhuma despesa lançada em {mesLabel}.

}

Este estudo é dirigido exclusivamente ao destinatário indicado na capa e pode conter informações confidenciais. Tem natureza informativa e orientadora, e não deve ser interpretado como aconselhamento jurídico ou financeiro.

); } Object.assign(window, { RelatorioBudgetPage });