import { useEffect, useState } from 'react'; import { calculateFinancialTools, exportFinancialToolsCsv, restoreFinancialToolsSnapshot, serializeFinancialToolsSnapshot, } from '@/lib/financial-calculators'; import { isSimulationCurrency, simulationCurrencyOptions, } from '@/lib/simulation-contract'; function Field({ label, value, onChange, step = '0.01', }: { label: string; value: string; onChange: (value: string) => void; step?: string; }) { return ( ); } export function SimulationFinancialTools({ currency, snapshot, onSnapshotChange, onCurrencyChange, }: { currency: string; snapshot?: string; onSnapshotChange: (snapshot: string) => void; onCurrencyChange: (currency: string) => void; }) { const initial = restoreFinancialToolsSnapshot(snapshot); const [loan, setLoan] = useState( initial?.loan ?? { principal: '100000', rate: '18', months: '24', rateType: 'nominal' as 'nominal' | 'effective', periodMonths: '1', gracePeriods: '0', upfrontFee: '0', recurringFee: '0', }, ); const [breakEven, setBreakEven] = useState( initial?.breakEven ?? { fixed: '50000', price: '1000', variable: '600', }, ); const [funding, setFunding] = useState( initial?.funding ?? { total: '1000000', secured: '250000' }, ); const [incomeRows, setIncomeRows] = useState([ { label: 'Vendas e serviços', amount: '200000' }, ]); const [expenseRows, setExpenseRows] = useState([ { label: 'Despesas operacionais', amount: '150000' }, ]); const [budgetPeriod, setBudgetPeriod] = useState('Mensal'); const [snapshotHydrated, setSnapshotHydrated] = useState(false); useEffect(() => { const stored = restoreFinancialToolsSnapshot(snapshot); if (stored) { setBudgetPeriod(stored.budgetPeriod); setLoan({ principal: stored.loan.principal, rate: stored.loan.rate, months: stored.loan.months, rateType: stored.loan.rateType, periodMonths: stored.loan.periodMonths, gracePeriods: stored.loan.gracePeriods, upfrontFee: stored.loan.upfrontFee, recurringFee: stored.loan.recurringFee, }); setBreakEven(stored.breakEven); setFunding(stored.funding); setIncomeRows(stored.incomes); setExpenseRows(stored.expenses); } setSnapshotHydrated(true); }, []); useEffect(() => { if (!snapshotHydrated) return; const serialized = serializeFinancialToolsSnapshot({ budgetPeriod, loan, breakEven, funding, incomes: incomeRows, expenses: expenseRows, }); if (serialized !== snapshot) onSnapshotChange(serialized); }, [ snapshotHydrated, snapshot, onSnapshotChange, budgetPeriod, loan, breakEven, funding, incomeRows, expenseRows, ]); const calculations = calculateFinancialTools({ loan: { principal: loan.principal, annualRatePercent: loan.rate, rateType: loan.rateType, termMonths: Number(loan.months), periodMonths: Number(loan.periodMonths), gracePeriods: Number(loan.gracePeriods), upfrontFee: loan.upfrontFee, recurringFee: loan.recurringFee, }, breakEven: { fixedCosts: breakEven.fixed, unitPrice: breakEven.price, variableCostPerUnit: breakEven.variable, }, funding: { totalInvestment: funding.total, securedFunding: funding.secured, }, incomes: incomeRows.map((row) => row.amount), expenses: expenseRows.map((row) => row.amount), }); const schedule = calculations.loan.value; const point = calculations.breakEven.value; const gap = calculations.funding.value; const budget = calculations.budget.value; const calculationErrors = [ { label: 'Empréstimo', message: calculations.loan.error }, { label: 'Ponto de equilíbrio', message: calculations.breakEven.error }, { label: 'Financiamento', message: calculations.funding.error }, { label: 'Orçamento', message: calculations.budget.error }, ].filter( (entry): entry is { label: string; message: string } => entry.message !== null, ); const update = >( setter: (value: T) => void, current: T, key: keyof T, value: string, ) => setter({ ...current, [key]: value }); return (

Ferramentas de planeamento financeiro

Estimativas educativas com os valores que introduzir. Não incluem impostos, comissões ou seguros, salvo indicação. Use a mesma moeda em cada cálculo.

Os valores que aparecem ao abrir são exemplos demonstrativos. Altere ou confirme cada pressuposto antes de interpretar os resultados ou exportar o relatório.

{simulationCurrencyOptions.map((code) => ( ))} {calculationErrors.length > 0 && (

{calculationErrors.map((entry) => ( {entry.label}: {entry.message} ))}

)}
Prestação indicativa —{' '} {currency || 'moeda por seleccionar'}

Simulação educativa, não constitui oferta nem concessão de crédito. A investeMoz não capta depósitos nem se apresenta como operador de microfinanças autorizado; o registo de operadores é um processo separado desta calculadora.

update(setLoan, loan, 'principal', value) } /> update(setLoan, loan, 'rate', value) } /> update(setLoan, loan, 'months', value) } /> update(setLoan, loan, 'periodMonths', value) } /> update(setLoan, loan, 'gracePeriods', value) } /> update(setLoan, loan, 'upfrontFee', value) } /> update(setLoan, loan, 'recurringFee', value) } /> {schedule && isSimulationCurrency(currency) && ( <>

Montantes a duas casas decimais; juros e prestação arredondados à unidade monetária mínima, metade para cima. A última prestação ajusta o saldo residual.

Taxa por período: {schedule.periodicRatePercent} %; desembolso líquido: { schedule.netDisbursed }{' '} {currency}; juros: {schedule.totalInterest}; comissões: {schedule.totalFees}; total pago:{' '} {schedule.totalPaid}.

Método: saldo decrescente com prestação nivelada; a última prestação ajusta cêntimos residuais. A carência paga apenas juros. Taxa nominal divide a taxa anual pelos períodos; taxa efectiva converte a taxa anual para a periodicidade indicada.

{[ 'Nº', 'Juros', 'Capital', 'Comissão', 'Prestação', 'Saldo', ].map((title) => ( ))} {schedule.schedule.map((row) => ( ))}
Plano de reembolso indicativo
{title}
{row.period} {row.type === 'interest_only' ? '*' : ''} {row.interest} {row.principal} {row.fee} {row.totalPayment} {row.closingBalance}

* Período de carência: pagamento apenas de juros e comissão indicada.

)}
Ponto de equilíbrio —{' '} {currency || 'moeda por seleccionar'} update(setBreakEven, breakEven, 'fixed', value) } /> update(setBreakEven, breakEven, 'price', value) } /> update(setBreakEven, breakEven, 'variable', value) } /> {isSimulationCurrency(currency) && (

Unidades: {point?.breakEvenUnits ?? '—'}; vendas:{' '} {point?.breakEvenRevenue ?? '—'} {currency}

)}
Necessidade de financiamento —{' '} {currency || 'moeda por seleccionar'} update(setFunding, funding, 'total', value) } /> update(setFunding, funding, 'secured', value) } /> {isSimulationCurrency(currency) && (

Diferença por financiar: {gap?.gap ?? '—'}{' '} {currency}

)}
Orçamento do período —{' '} {currency || 'moeda por seleccionar'}

Introduza os totais por fonte de receita e categoria de despesa.

{incomeRows.map((row, index) => (
setIncomeRows((rows) => rows.map((item, i) => i === index ? { ...item, amount: value } : item, ), ) } />
))} {expenseRows.map((row, index) => (
setExpenseRows((rows) => rows.map((item, i) => i === index ? { ...item, amount: value } : item, ), ) } />
))} {budget && isSimulationCurrency(currency) && (

Receitas: {budget.incomeTotal} {currency}; despesas:{' '} {budget.expenseTotal}; saldo operacional:{' '} {budget.operatingBalance}.

)}
); }