import CurrencySelect from '@/components/currency-select'; import { fundingDifferences, canLinkFundingSource, } from '@/lib/planning-finance-context'; 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, sourceFunding, onFundingChange, focus = 'all', loanRelevant = true, }: { currency: string; snapshot?: string; onSnapshotChange: (snapshot: string) => void; onCurrencyChange: (currency: string) => void; sourceFunding?: { total: string; secured: string }; onFundingChange?: (field: 'total' | 'secured', value: string) => void; focus?: 'funding' | 'all'; loanRelevant?: boolean; }) { const initial = restoreFinancialToolsSnapshot(snapshot); const [loan, setLoan] = useState( initial?.loan ?? { principal: '', rate: '', months: '', rateType: 'nominal' as 'nominal' | 'effective', periodMonths: '1', gracePeriods: '', upfrontFee: '', recurringFee: '', }, ); const [breakEven, setBreakEven] = useState( initial?.breakEven ?? { fixed: '', price: '', variable: '', }, ); const [storedFunding, setFunding] = useState( initial?.funding ?? sourceFunding ?? { total: '', secured: '' }, ); const [fundingLinked, setFundingLinked] = useState(() => canLinkFundingSource(sourceFunding, initial?.funding), ); const funding = sourceFunding && fundingLinked ? sourceFunding : storedFunding; const [incomeRows, setIncomeRows] = useState([ { label: 'Vendas e serviços', amount: '' }, ]); const [expenseRows, setExpenseRows] = useState([ { label: 'Despesas operacionais', amount: '' }, ]); 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 differences = sourceFunding ? fundingDifferences(sourceFunding, funding) : []; const gap = differences.length ? null : calculations.funding.value; const budget = calculations.budget.value; const calculationErrors = [ { label: 'Empréstimo', message: focus === 'all' && loanRelevant && [loan.principal, loan.rate, loan.months].every((value) => value.trim(), ) ? calculations.loan.error : null, }, { label: 'Ponto de equilíbrio', message: focus === 'all' && Object.values(breakEven).every((value) => value.trim()) ? calculations.breakEven.error : null, }, { label: 'Financiamento', message: Object.values(funding).every((value) => value.trim()) ? calculations.funding.error : null, }, { label: 'Orçamento', message: focus === 'all' && [...incomeRows, ...expenseRows].every((row) => row.amount.trim(), ) ? calculations.budget.error : null, }, ].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.

Introduza os seus pressupostos ou confira os valores recuperados do rascunho antes de interpretar os resultados ou exportar o relatório.

{focus === 'all' && loanRelevant && ( )} {calculationErrors.length > 0 && (

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

)}
{focus === 'all' && loanRelevant && (
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.

)}
)} {focus === 'all' && (
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'} {!!differences.length && sourceFunding && ( )} { update(setFunding, funding, 'total', value); onFundingChange?.('total', value); }} /> { update(setFunding, funding, 'secured', value); onFundingChange?.('secured', value); }} /> {isSimulationCurrency(currency) && (

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

)}
{focus === 'all' && (
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}.

)}
)}
); }