import { useEffect, useState } from 'react'; import { compareBusinessScenarios, exportBusinessPlanCsv, forecastBusinessPlan, MAX_BUSINESS_PLAN_SNAPSHOT_LENGTH, restoreBusinessPlanSnapshot, serializeBusinessPlanSnapshot, type BusinessFinancialPlanInput, type InventoryPurchasePeriod, type SalesReceivableRecovery, type ScenarioAdjustment, } from '@/lib/business-financial-plan'; import { isSimulationCurrency, simulationCurrencyOptions, } from '@/lib/simulation-contract'; import { buildFinancialFiscalBridge } from '@/lib/financial-fiscal-bridge'; const emptyPlan: BusinessFinancialPlanInput = { currency: '', presentationCurrency: '', fxRate: '', fxDate: '', fxSource: '', openingCash: '0', products: [ { label: 'Produto ou serviço', monthlyUnits: '100', unitPrice: '1000', unitVariableCost: '400', }, ], monthlyCollections: '100000', salesCollectionLagMonths: null, salesCollectionPercent: '100', salesRecoverySchedule: [], openingReceivablesCollectionMonth: null, openingReceivablesCollectionPercent: '100', openingReceivablesInstallments: [], openingPayablesInstallments: [], monthlyVariableCostPayments: '40000', monthlyVariableCostPaymentsByPeriod: null, monthlyInventoryPurchases: '40000', inventoryPurchaseLeadMonths: 0, inventoryPurchaseSchedule: null, useInventoryPurchaseSchedule: false, useProductInventorySchedule: false, useSupplierPaymentTerms: false, monthlyOperatingExpenses: '20000', monthlyOperatingPayments: '20000', monthlyPayrollExpense: '15000', monthlyPayrollPayments: '15000', assetInvestment: '100000', inventory: '20000', receivables: '10000', payables: '5000', equity: '80000', shareholderLoans: '0', bankLoanProceeds: '50000', grants: '0', assetDepreciation: '100000', depreciationLifeMonths: 60, monthlyInterestExpense: '0', monthlyInterestPayment: '0', monthlyPrincipalRepayment: '0', periods: 12, }; function MoneyInput({ label, value, onChange, allowNegative = false, }: { label: string; value: string; onChange: (value: string) => void; allowNegative?: boolean; }) { return ( ); } export function SimulationBusinessPlan({ currency: projectCurrency, snapshot, onCurrencyChange, onSnapshotChange, }: { currency: string; snapshot?: string; onCurrencyChange: (value: string) => void; onSnapshotChange: (snapshot: string) => void; }) { const hasOversizedStoredSnapshot = typeof snapshot === 'string' && snapshot.length > MAX_BUSINESS_PLAN_SNAPSHOT_LENGTH; const [snapshotError, setSnapshotError] = useState( hasOversizedStoredSnapshot ? 'O snapshot guardado excede 60 KB e nao pode ser carregado com seguranca. O formulario nao o vai substituir.' : '', ); const [workingPlan, setWorkingPlan] = useState(null); useEffect(() => setWorkingPlan(null), [snapshot]); if (hasOversizedStoredSnapshot) { return (
Plano financeiro empresarial

{snapshotError}

); } const savePlan = (nextPlan: BusinessFinancialPlanInput) => { if (hasOversizedStoredSnapshot) return; setWorkingPlan(nextPlan); try { onSnapshotChange(serializeBusinessPlanSnapshot(nextPlan)); setSnapshotError(''); } catch (cause) { setSnapshotError( cause instanceof Error ? cause.message : 'Nao foi possivel guardar o plano financeiro.', ); } }; const importPlanFile = async (file: File | undefined) => { if (!file) return; if (file.size > MAX_BUSINESS_PLAN_SNAPSHOT_LENGTH) { setSnapshotError( 'O ficheiro excede 60 KB. Seleccione um plano JSON exportado pelo InvestMoz dentro deste limite.', ); return; } try { const source = await file.text(); const imported = restoreBusinessPlanSnapshot(source); if (!imported) throw new Error( 'O ficheiro não contém um plano JSON reconhecido pelo InvestMoz.', ); savePlan(imported); setSnapshotError(''); } catch (cause) { setSnapshotError( cause instanceof Error ? cause.message : 'Não foi possível importar este plano.', ); } }; const exportPlanJson = () => { try { const snapshotJson = serializeBusinessPlanSnapshot({ ...plan, currency, }); const url = URL.createObjectURL( new Blob([snapshotJson], { type: 'application/json;charset=utf-8', }), ); const link = document.createElement('a'); link.href = url; link.download = 'plano-financeiro-investmoz.json'; link.rel = 'noopener'; link.click(); window.setTimeout(() => URL.revokeObjectURL(url), 1000); setSnapshotError(''); } catch (cause) { setSnapshotError( cause instanceof Error ? cause.message : 'Não foi possível exportar este plano.', ); } }; type ProductScheduleKey = | 'monthlyUnitsByPeriod' | 'unitPriceByPeriod' | 'unitVariableCostByPeriod'; type MoneyScheduleKey = | 'monthlyVariableCostPaymentsByPeriod' | 'monthlyOperatingExpensesByPeriod' | 'monthlyOperatingPaymentsByPeriod' | 'monthlyPayrollExpenseByPeriod' | 'monthlyPayrollPaymentsByPeriod'; const moneyScheduleFields: { key: MoneyScheduleKey; baseKey: | 'monthlyVariableCostPayments' | 'monthlyOperatingExpenses' | 'monthlyOperatingPayments' | 'monthlyPayrollExpense' | 'monthlyPayrollPayments'; label: string; }[] = [ { key: 'monthlyVariableCostPaymentsByPeriod', baseKey: 'monthlyVariableCostPayments', label: 'Pagamento de compras/variáveis', }, { key: 'monthlyOperatingExpensesByPeriod', baseKey: 'monthlyOperatingExpenses', label: 'Despesas operacionais reconhecidas', }, { key: 'monthlyOperatingPaymentsByPeriod', baseKey: 'monthlyOperatingPayments', label: 'Pagamento de despesas operacionais', }, { key: 'monthlyPayrollExpenseByPeriod', baseKey: 'monthlyPayrollExpense', label: 'Custo de pessoal reconhecido', }, { key: 'monthlyPayrollPaymentsByPeriod', baseKey: 'monthlyPayrollPayments', label: 'Pagamento de pessoal', }, ]; const productScheduleBaseKey: Record< ProductScheduleKey, 'monthlyUnits' | 'unitPrice' | 'unitVariableCost' > = { monthlyUnitsByPeriod: 'monthlyUnits', unitPriceByPeriod: 'unitPrice', unitVariableCostByPeriod: 'unitVariableCost', }; const plan = workingPlan ?? restoreBusinessPlanSnapshot(snapshot) ?? { ...emptyPlan, currency: projectCurrency, }; // A project edit is not a conversion or permission to relabel stored sales. const currency = plan.currency || projectCurrency; const update = ( key: keyof BusinessFinancialPlanInput, value: | string | string[] | number | null | undefined | BusinessFinancialPlanInput['openingReceivablesInstallments'] | BusinessFinancialPlanInput['salesRecoverySchedule'] | BusinessFinancialPlanInput['inventoryPurchaseSchedule'] | BusinessFinancialPlanInput['monthlyVariableCostPaymentsByPeriod'], ) => savePlan({ ...plan, currency, [key]: value, } as BusinessFinancialPlanInput); const updateSalesRecovery = ( index: number, key: 'month' | 'percent', value: string | number, ) => update( 'salesRecoverySchedule', (plan.salesRecoverySchedule ?? []).map((entry, row) => row === index ? { ...entry, [key]: key === 'month' ? Number(value) : String(value), } : entry, ) as SalesReceivableRecovery[], ); const updateReceivableInstallment = ( index: number, key: 'month' | 'percent', value: string | number, ) => savePlan({ ...plan, currency, openingReceivablesInstallments: ( plan.openingReceivablesInstallments ?? [] ).map((installment, row) => row === index ? key === 'month' ? { ...installment, month: Number(value) } : { ...installment, percent: String(value) } : installment, ), }); const addReceivableInstallment = () => update('openingReceivablesInstallments', [ ...(plan.openingReceivablesInstallments ?? []), { month: 1, percent: '0' }, ]); const removeReceivableInstallment = (index: number) => update( 'openingReceivablesInstallments', (plan.openingReceivablesInstallments ?? []).filter( (_, row) => row !== index, ), ); const updatePayableInstallment = ( index: number, key: 'month' | 'percent', value: string | number, ) => savePlan({ ...plan, currency, openingPayablesInstallments: ( plan.openingPayablesInstallments ?? [] ).map((installment, row) => row === index ? key === 'month' ? { ...installment, month: Number(value) } : { ...installment, percent: String(value) } : installment, ), }); const addPayableInstallment = () => update('openingPayablesInstallments', [ ...(plan.openingPayablesInstallments ?? []), { month: 1, percent: '0' }, ]); const removePayableInstallment = (index: number) => update( 'openingPayablesInstallments', (plan.openingPayablesInstallments ?? []).filter( (_, row) => row !== index, ), ); const validHorizon = Number.isSafeInteger(plan.periods) && (plan.periods ?? 0) >= 1 && (plan.periods ?? 0) <= 60 ? (plan.periods ?? 12) : 12; const updatePeriods = (value: number) => { const nextPeriods = Number.isSafeInteger(value) ? Math.max(1, Math.min(60, value)) : validHorizon; const resizeSchedule = ( schedule: string[] | null | undefined, baseValue: string, ) => schedule ? Array.from( { length: Math.max(nextPeriods, schedule.length), }, (_, month) => schedule[month] ?? baseValue, ) : undefined; savePlan({ ...plan, currency, periods: nextPeriods, ...Object.fromEntries( moneyScheduleFields.map(({ key, baseKey }) => [ key, resizeSchedule(plan[key], plan[baseKey]), ]), ), products: plan.products.map((product) => ({ ...product, monthlyUnitsByPeriod: resizeSchedule( product.monthlyUnitsByPeriod, product.monthlyUnits, ), unitPriceByPeriod: resizeSchedule( product.unitPriceByPeriod, product.unitPrice, ), unitVariableCostByPeriod: resizeSchedule( product.unitVariableCostByPeriod, product.unitVariableCost, ), productInventoryPurchasesByPeriod: product.productInventoryPurchasesByPeriod ? Array.from( { length: Math.max( nextPeriods, product.productInventoryPurchasesByPeriod .length, ), }, (_, month) => product.productInventoryPurchasesByPeriod?.[ month ] ?? { amount: '0', leadMonths: 0, ...(plan.useSupplierPaymentTerms ? { paymentLagMonths: 0, paymentInstallments: [ { lagMonths: 0, percent: '100', }, ], } : {}), }, ) : null, })), inventoryPurchaseSchedule: plan.inventoryPurchaseSchedule ? Array.from( { length: Math.max( nextPeriods, plan.inventoryPurchaseSchedule.length, ), }, (_, month) => plan.inventoryPurchaseSchedule?.[month] ?? { amount: plan.monthlyInventoryPurchases ?? '', leadMonths: plan.inventoryPurchaseLeadMonths ?? 0, }, ) : null, }); }; const toggleInventoryPurchaseSchedule = () => { const enabled = !(plan.useInventoryPurchaseSchedule ?? false); const schedule = plan.inventoryPurchaseSchedule ?? Array.from({ length: validHorizon }, () => ({ amount: plan.monthlyInventoryPurchases ?? '', leadMonths: plan.inventoryPurchaseLeadMonths ?? 0, })); savePlan({ ...plan, currency, inventoryPurchaseSchedule: schedule, useInventoryPurchaseSchedule: enabled, }); }; const toggleProductInventorySchedule = () => { const enabled = !(plan.useProductInventorySchedule ?? false); savePlan({ ...plan, currency, useProductInventorySchedule: enabled, useInventoryPurchaseSchedule: enabled ? false : plan.useInventoryPurchaseSchedule, useSupplierPaymentTerms: enabled ? plan.useSupplierPaymentTerms : false, products: plan.products.map((product) => ({ ...product, productInventoryOpening: product.productInventoryOpening ?? '', productInventoryPurchasesByPeriod: product.productInventoryPurchasesByPeriod ?? Array.from({ length: validHorizon }, () => ({ amount: '0', leadMonths: 0, })), })), }); }; const toggleSupplierPaymentTerms = () => { const enabled = !(plan.useSupplierPaymentTerms ?? false); savePlan({ ...plan, currency, useInventoryPurchaseSchedule: false, useProductInventorySchedule: true, useSupplierPaymentTerms: enabled, products: plan.products.map((product) => ({ ...product, productInventoryOpening: product.productInventoryOpening ?? '', productInventoryPurchasesByPeriod: Array.from( { length: Math.max( validHorizon, product.productInventoryPurchasesByPeriod?.length ?? 0, ), }, (_, month) => ({ amount: product.productInventoryPurchasesByPeriod?.[month] ?.amount ?? '0', leadMonths: product.productInventoryPurchasesByPeriod?.[month] ?.leadMonths ?? 0, paymentLagMonths: product.productInventoryPurchasesByPeriod?.[month] ?.paymentLagMonths ?? 0, paymentInstallments: product .productInventoryPurchasesByPeriod?.[month] ?.paymentInstallments ?? [ { lagMonths: product.productInventoryPurchasesByPeriod?.[ month ]?.paymentLagMonths ?? 0, percent: '100', }, ], }), ), })), }); }; const updateProductInventoryPurchase = ( index: number, month: number, key: 'amount' | 'leadMonths' | 'paymentLagMonths', value: string | number, ) => savePlan({ ...plan, currency, products: plan.products.map((product, row) => row === index && product.productInventoryPurchasesByPeriod ? { ...product, productInventoryPurchasesByPeriod: product.productInventoryPurchasesByPeriod.map( (entry, period) => period === month ? { ...entry, [key]: key === 'amount' ? String(value) : Number(value), } : entry, ), } : product, ), }); const updateProductPaymentInstallment = ( index: number, month: number, installmentIndex: number, key: 'lagMonths' | 'percent', value: string | number, ) => savePlan({ ...plan, currency, products: plan.products.map((product, row) => row === index && product.productInventoryPurchasesByPeriod ? { ...product, productInventoryPurchasesByPeriod: product.productInventoryPurchasesByPeriod.map( (entry, period) => period === month ? { ...entry, paymentInstallments: ( entry.paymentInstallments ?? [ { lagMonths: entry.paymentLagMonths ?? 0, percent: '100', }, ] ).map((part, partIndex) => partIndex === installmentIndex ? { ...part, [key]: key === 'percent' ? String( value, ) : Number( value, ), } : part, ), } : entry, ), } : product, ), }); const addProductPaymentInstallment = (index: number, month: number) => savePlan({ ...plan, currency, products: plan.products.map((product, row) => row === index && product.productInventoryPurchasesByPeriod ? { ...product, productInventoryPurchasesByPeriod: product.productInventoryPurchasesByPeriod.map( (entry, period) => period === month ? { ...entry, paymentInstallments: [ ...(entry.paymentInstallments ?? [ { lagMonths: entry.paymentLagMonths ?? 0, percent: '100', }, ]), { lagMonths: 0, percent: '0', }, ], } : entry, ), } : product, ), }); const removeProductPaymentInstallment = ( index: number, month: number, installmentIndex: number, ) => savePlan({ ...plan, currency, products: plan.products.map((product, row) => row === index && product.productInventoryPurchasesByPeriod ? { ...product, productInventoryPurchasesByPeriod: product.productInventoryPurchasesByPeriod.map( (entry, period) => period === month ? { ...entry, paymentInstallments: ( entry.paymentInstallments ?? [] ).filter( (_, partIndex) => partIndex !== installmentIndex, ), } : entry, ), } : product, ), }); const updateInventoryPurchase = ( index: number, key: keyof InventoryPurchasePeriod, value: string | number, ) => savePlan({ ...plan, currency, inventoryPurchaseSchedule: ( plan.inventoryPurchaseSchedule ?? [] ).map((entry, month) => month === index ? { ...entry, [key]: key === 'amount' ? String(value) : Number(value), } : entry, ), }); const toggleProductSchedule = ( index: number, scheduleKey: ProductScheduleKey, ) => savePlan({ ...plan, currency, products: plan.products.map((product, row) => { if (row !== index) return product; const next = { ...product }; if (next[scheduleKey]) { delete next[scheduleKey]; } else { const baseKey = productScheduleBaseKey[scheduleKey]; next[scheduleKey] = Array.from( { length: validHorizon }, () => product[baseKey], ); } return next; }), }); const updateProductSchedule = ( index: number, scheduleKey: ProductScheduleKey, month: number, value: string, ) => savePlan({ ...plan, currency, products: plan.products.map((product, row) => row === index && product[scheduleKey] ? { ...product, [scheduleKey]: product[scheduleKey]!.map( (units, currentMonth) => currentMonth === month ? value : units, ), } : product, ), }); const toggleMoneySchedule = ( key: MoneyScheduleKey, baseKey: | 'monthlyVariableCostPayments' | 'monthlyOperatingExpenses' | 'monthlyOperatingPayments' | 'monthlyPayrollExpense' | 'monthlyPayrollPayments', ) => { const schedule = plan[key]; const baseValue = plan[baseKey]; if (!schedule && typeof baseValue !== 'string') return; update( key, schedule ? undefined : Array.from( { length: validHorizon }, () => baseValue as string, ), ); }; const updateMoneySchedule = ( key: MoneyScheduleKey, month: number, value: string, ) => { const schedule = plan[key]; if (!schedule) return; update( key, schedule.map((amount, currentMonth) => currentMonth === month ? value : amount, ), ); }; const updateProduct = ( index: number, key: 'label' | 'monthlyUnits' | 'unitPrice' | 'unitVariableCost', value: string, ) => savePlan({ ...plan, currency, products: plan.products.map((row, i) => i === index ? { ...row, [key]: value } : row, ), }); let result; let error = ''; try { result = forecastBusinessPlan({ ...plan, currency }); } catch (cause) { error = cause instanceof Error ? cause.message : 'Reveja os pressupostos introduzidos.'; } const blankAdjustment: ScenarioAdjustment = { unitVolumePercent: '', unitPricePercent: '', unitCostPercent: '', operatingCostsPercent: '', cashCollectionsPercent: '', }; const scenarioAdjustments = plan.scenarioAdjustments ?? { favorable: blankAdjustment, unfavorable: blankAdjustment, }; const updateScenario = ( scenario: 'favorable' | 'unfavorable', key: keyof ScenarioAdjustment, value: string, ) => { savePlan({ ...plan, currency, scenarioAdjustments: { ...scenarioAdjustments, [scenario]: { ...scenarioAdjustments[scenario], [key]: value, }, }, }); }; const scenarioValues = [ ...Object.values(scenarioAdjustments.favorable), ...Object.values(scenarioAdjustments.unfavorable), ]; const scenarioHasChanges = scenarioValues.some((value) => value !== ''); const scenarioIncomplete = scenarioHasChanges && !scenarioValues.every((value) => value !== ''); let scenarios; let scenarioError = ''; if (scenarioValues.every((value) => value !== '')) { try { scenarios = compareBusinessScenarios({ ...plan, currency, scenarioAdjustments, }); } catch (cause) { scenarioError = cause instanceof Error ? cause.message : 'Verifique as variações dos cenários.'; } } const fiscalBridge = result ? buildFinancialFiscalBridge(result) : null; return (
Plano financeiro empresarial (pressupostos editáveis) {plan.currency && projectCurrency && plan.currency !== projectCurrency && (

A projecção guardada está em {plan.currency}; o projecto está em {projectCurrency}. Os valores e a moeda de origem foram preservados. Reveja o enquadramento monetário antes de os reutilizar; não foi feita conversão.

)} {snapshotError && (

{snapshotError}

)}

Projecção indicativa com valores de exemplo editáveis ou pressupostos introduzidos pela pessoa. Facturação, cobranças, despesas, pagamentos, juros e capital reembolsado ficam separados. Não calcula impostos, não converte moedas e não substitui um plano contabilístico validado.

O ficheiro é lido localmente. A importação substitui os pressupostos editáveis deste plano após validação do formato.
{simulationCurrencyOptions.map((code) => ( ))}

O plano financeiro aceita moedas ISO suportadas com duas casas decimais. Se um investidor indicar uma moeda com precisão diferente, mantenha o código e montante de origem no registo; não os use nesta calculadora até a precisão dessa moeda ser suportada.

A conversão só é usada neste plano quando escolher uma moeda de apresentação diferente. Introduza uma cotação documentada por si; o portal não consulta nem valida taxas cambiais. Os valores na moeda original são preservados.

{simulationCurrencyOptions.map((code) => ( ))} {plan.presentationCurrency && plan.presentationCurrency !== currency && ( <>

Indique a unidade de apresentação por 1 unidade da moeda-base, a data e a referência. Esta conversão é um pressuposto de planeamento; não é uma taxa oficial, fiscal ou aduaneira validada.

)} update('openingCash', value)} />
Produtos ou serviços facturados por mês {plan.useProductInventorySchedule && (

Declare o stock inicial e as encomendas de cada produto. A soma do stock inicial deve coincidir com Existencias iniciais no fundo de maneio. A falta nao reduz as vendas planeadas automaticamente.

)} {plan.useSupplierPaymentTerms && (

Cada encomenda e paga integralmente apos o prazo declarado, contado desde a encomenda. A agenda manual de pagamentos variaveis fica suspensa para evitar duplicacao; valores vencidos depois do horizonte sao apresentados como saldo por pagar.

)} {plan.products.map((product, index) => (
updateProduct(index, 'unitPrice', value) } /> updateProduct( index, 'unitVariableCost', value, ) } />
{plan.useProductInventorySchedule && (
savePlan({ ...plan, currency, products: plan.products.map( (row, productIndex) => productIndex === index ? { ...row, productInventoryOpening: value, } : row, ), }) } />
{( product.productInventoryPurchasesByPeriod ?? [] ) .slice(0, plan.periods ?? 12) .map((entry, month) => (
updateProductInventoryPurchase( index, month, 'amount', value, ) } /> {plan.useSupplierPaymentTerms && (
Parcelas ao fornecedor {( entry.paymentInstallments ?? [ { lagMonths: entry.paymentLagMonths ?? 0, percent: '100', }, ] ).map( ( part, partIndex, ) => (
), )}
)}
))}
)} {product.monthlyUnitsByPeriod && ( <>

Meses fora do horizonte ficam guardados. Ao ampliar o horizonte, novos meses usam as unidades base; ajuste o plano mensal.

{product.monthlyUnitsByPeriod .slice(0, plan.periods ?? 12) .map((units, month) => ( ))}
)} {product.unitPriceByPeriod && (
{product.unitPriceByPeriod .slice(0, plan.periods ?? 12) .map((price, month) => ( updateProductSchedule( index, 'unitPriceByPeriod', month, value, ) } /> ))}
)} {product.unitVariableCostByPeriod && (
{product.unitVariableCostByPeriod .slice(0, plan.periods ?? 12) .map((cost, month) => ( updateProductSchedule( index, 'unitVariableCostByPeriod', month, value, ) } /> ))}
)} {!product.monthlyUnitsByPeriod && (product.unitPriceByPeriod || product.unitVariableCostByPeriod) && (

Meses fora do horizonte ficam guardados. Ao ampliar o horizonte, novos meses usam os valores-base.

)}
))}
Operação, custos e pagamentos mensais {plan.salesCollectionLagMonths !== null && plan.salesCollectionLagMonths !== undefined && ( )} {plan.salesCollectionLagMonths !== null && plan.salesCollectionLagMonths !== undefined && (

Recuperação de vendas vencidas

{(plan.salesRecoverySchedule ?? []).map( (entry, index) => (
), )}

Cada percentagem aplica-se ao saldo vencido ainda em aberto no mês indicado, depois da cobrança inicial. Sem agenda, não se presume recuperação.

{(plan.salesRecoverySchedule ?? []).length < 60 && ( )}
)} update('monthlyCollections', value) } />

{plan.salesCollectionLagMonths === null || plan.salesCollectionLagMonths === undefined ? 'Modo manual: este valor representa todas as cobranças mensais e não é reconciliado automaticamente com a facturação.' : `Modo por prazo: ${plan.salesCollectionPercent ?? '100'}% da facturação vence após o prazo indicado; o restante fica em aberto, excepto recuperações declaradas acima sobre saldos vencidos. O campo acima soma outras cobranças.`}

{plan.useProductInventorySchedule ? (

As compras agregadas ficam inactivas enquanto o stock estiver planeado por produto.

) : ( <>
{plan.useInventoryPurchaseSchedule ? (
{(plan.inventoryPurchaseSchedule ?? []) .slice(0, plan.periods ?? 12) .map((entry, index) => (
updateInventoryPurchase( index, 'amount', value, ) } />
))}
) : ( <> update( 'monthlyInventoryPurchases', value, ) } /> )}

As compras são encomendas e cada prazo declarado afecta o mês da recepção; pagamentos de compras continuam a ser um fluxo de caixa separado. Encomendas ainda não recebidas aparecem como mercadoria em trânsito. Se faltar stock, reveja compras ou vendas: a facturação introduzida não é reduzida automaticamente.

)} update('monthlyOperatingExpenses', value) } /> update('monthlyOperatingPayments', value) } /> update('monthlyPayrollExpense', value) } /> update('monthlyPayrollPayments', value) } /> {plan.useSupplierPaymentTerms && (

Os pagamentos de compras sao calculados pelas encomendas e prazos acima; o valor mensal manual fica suspenso para evitar contar os mesmos pagamentos duas vezes.

)} {moneyScheduleFields .filter( ({ key }) => !plan.useSupplierPaymentTerms || key !== 'monthlyVariableCostPaymentsByPeriod', ) .map(({ key, baseKey, label }) => (
{key === 'monthlyVariableCostPaymentsByPeriod' && !plan[key] && ( update(baseKey, value) } /> )}{' '} {plan[key] && (
{plan[key]!.slice( 0, plan.periods ?? 12, ).map((amount, month) => ( updateMoneySchedule( key, month, value, ) } /> ))}
)}
))}
Investimento, fundo de maneio e financiamento inicial update('assetInvestment', value)} /> update('inventory', value)} /> update('receivables', value)} /> {plan.openingReceivablesCollectionMonth !== null && plan.openingReceivablesCollectionMonth !== undefined && ( )}

Parcelas adicionais de recebíveis de abertura

Declare cada mês e a percentagem do saldo inicial a cobrar. Inclua a cobrança única acima na soma: o total não pode exceder 100%. Sem parcela declarada, não se presume recebimento.

{(plan.openingReceivablesInstallments ?? []).map( (installment, index) => (
), )}
update('payables', value)} />

Liquidação de valores a pagar de abertura

Opcional. Declare o mês e a percentagem do saldo inicial a pagar. A soma não pode exceder 100%; sem parcelas, não se presume pagamento e o saldo continua visível como pendente no fim. Estes pagamentos reduzem caixa, não o resultado do período.

{(plan.openingPayablesInstallments ?? []).map( (installment, index) => (
), )}
update('equity', value)} /> update('shareholderLoans', value)} /> update('bankLoanProceeds', value)} /> update('grants', value)} />
Depreciação contabilística e dívida mensal update('assetDepreciation', value)} /> update('monthlyInterestExpense', value) } /> update('monthlyInterestPayment', value) } /> update('monthlyPrincipalRepayment', value) } />
Comparar cenários financeiros

“Favorável” e “desfavorável” são rótulos de cenários, não previsões. Defina como variam volume, preço, custo variável, despesas/pessoal e cobranças declaradas face ao cenário base. Com cobrança de vendas por prazo, a facturação ajustada determina esses recebimentos; esta variação aplica-se apenas às outras cobranças. Sem variações introduzidas, não há comparação.

{( [ ['favorable', 'Favorável'], ['unfavorable', 'Desfavorável'], ] as const ).map(([scenario, title]) => (
{title} (%) {( [ [ 'unitVolumePercent', 'Variação de unidades', ], ['unitPricePercent', 'Variação do preço'], [ 'unitCostPercent', 'Variação do custo variável', ], [ 'operatingCostsPercent', 'Variação de despesas e pessoal', ], [ 'cashCollectionsPercent', 'Variação das cobranças', ], ] as const ).map(([key, label]) => ( ))}
))}
{scenarioError && (

{scenarioError}

)} {scenarioIncomplete && ( Complete todos os pressupostos dos dois cenários ou limpe as variações para exportar o relatório. )} {scenarios && isSimulationCurrency(currency) && (
{[ 'Cenário', 'Variações declaradas', 'Facturação', 'Resultado antes impostos', 'Caixa final', 'Existências finais', 'Falta de stock', ].map((label) => ( ))} {( [ ['Favorável', scenarios.favorable], ['Desfavorável', scenarios.unfavorable], ] as const ).map(([label, scenario]) => ( ))}
Comparação de resultados por cenário
{label}
Base Sem variação {scenarios.base.totalRevenue} {scenarios.base.totalProfitBeforeTax} {scenarios.base.endingCash} {scenarios.base.inventoryProjection .endingInventory ?? 'indeterminadas'} {scenarios.base.inventoryProjection .totalShortfall ?? 'indeterminada'}
{label} Unidades{' '} { scenario.adjustments .unitVolumePercent } %; preço{' '} { scenario.adjustments .unitPricePercent } %; custo variável{' '} { scenario.adjustments .unitCostPercent } %; compras de stock ajustadas por volume/custo; despesas{' '} { scenario.adjustments .operatingCostsPercent } %; cobranças{' '} { scenario.adjustments .cashCollectionsPercent } % {scenario.result.totalRevenue} { scenario.result .totalProfitBeforeTax } {scenario.result.endingCash} {scenario.result.inventoryProjection .endingInventory ?? 'indeterminadas'} {scenario.result.inventoryProjection .totalShortfall ?? 'indeterminada'}

Valores em {currency}, antes de impostos. As alterações são pressupostos do utilizador; as componentes fiscais mantêm-se indeterminadas.

)}
{error && (

{error}

)} {result && isSimulationCurrency(currency) && (

Resumo ({currency})

{result.fundingStatus === 'balanced' ? 'As fontes de financiamento reconciliam com os usos iniciais.' : result.fundingStatus === 'shortfall' ? `Falta financiar ${result.fundingGap} ${currency} dos usos iniciais.` : `As fontes excedem os usos iniciais em ${result.fundingGap.replace('-', '')} ${currency}.`}

{[ ['Fundo de maneio líquido', result.workingCapital], ['Usos iniciais', result.initialUses], ['Fontes de financiamento', result.financing], [ 'Diferenca entre usos e fontes', result.fundingGap, ], ['Facturação no horizonte', result.totalRevenue], [ 'Cobranças no horizonte', result.totalCashCollections, ], [ 'Pagamentos de valores a pagar de abertura', result.totalOpeningPayablesPayments, ], [ 'Valores a pagar de abertura pendentes no fim', result.openingPayablesAtEnd, ], [ 'Cobrança prevista de valores a receber de abertura', result.totalOpeningReceivablesCollections, ], [ 'Valores de abertura ainda por receber', result.openingReceivablesAtEnd, ], ...(result.totalSalesCollections === null ? [] : [ [ 'Cobranças da facturação modelada', result.totalSalesCollections, ], [ 'Cobranças iniciais das vendas', result.totalScheduledSalesCollections, ], [ 'Recuperações de vendas vencidas', result.totalSalesRecoveryCollections, ], [ 'Facturação por cobrar no fim', result.salesReceivablesAtEnd, ], ]), [ 'Resultado antes de impostos', result.totalProfitBeforeTax, ], ['Caixa final projectada', result.endingCash], ].map(([label, value]) => (
{label}
{value}
))}
{result.openingReceivablesInstallments.length > 0 && (
    {result.openingReceivablesInstallments.map( (installment, index) => (
  • Mês {installment.month}:{' '} {installment.percent}% do saldo inicial ({installment.amount} {currency})
  • ), )}
)} {result.openingPayablesInstallments.length > 0 && (
    {result.openingPayablesInstallments.map( (installment, index) => (
  • Mês {installment.month}:{' '} {installment.percent}% do saldo inicial ({installment.amount} {currency})
  • ), )}
)}

Fontes iniciais = capital próprio + suprimentos + empréstimos recebidos + subvenções. Usos iniciais = activos + fundo de maneio líquido. Juros são despesa; reembolso de capital é fluxo de caixa, não custo operacional.

{fiscalBridge && (
Ponte contabilístico-fiscal — incompleta

Resultado contabilístico antes de impostos:{' '} {fiscalBridge.accountingProfitBeforeTax}{' '} {currency}. Matéria colectável: indeterminada; não se presume que as despesas sejam fiscalmente dedutíveis.

{[ 'Origem', 'Efeito contabilístico', 'Total no período', 'Tratamento fiscal', ].map((label) => ( ))} {fiscalBridge.accountingLines.map( (line) => ( ), )}
Linhas contabilísticas propostas para classificação fiscal
{label}
{line.origin} {line.accountingEffect === 'income' ? 'Rendimento' : 'Despesa'} ; por mês:{' '} {line.monthlyAmounts .map( (item) => `${item.period}: ${item.amount}`, ) .join(' · ')} {line.total} {currency} Por classificar

Cobranças e pagamentos são apresentados separadamente no calendário de caixa. Reembolsos de capital não entram como despesa. Não são acrescentados valores fiscais zero nem pagamentos estimados.

)} {result.inventoryProjection.status === 'indeterminate' ? ( Existências finais indeterminadas: indique compras mensais para calcular saldo e falta de stock. Pagamentos de compras são um fluxo de caixa separado e não substituem este pressuposto. ) : ( Existências finais estimadas:{' '} {result.inventoryProjection.endingInventory}{' '} {currency}; prazo de entrega:{' '} {result.inventoryProjection.leadMonths === null ? 'variável por mês' : `${result.inventoryProjection.leadMonths} mês(es)`} . Mercadoria em trânsito no fim do horizonte:{' '} {result.inventoryProjection.inTransitAtEnd}{' '} {currency}. Falta total de stock estimada:{' '} {result.inventoryProjection.totalShortfall}{' '} {currency}. As vendas planeadas permanecem inalteradas e devem ser revistas se houver falta. )}
{[ 'Mês', 'Saldo inicial', 'Encomendas', 'Prazo (meses)', 'Recepções', 'Em trânsito', 'Consumo', 'Falta de stock', 'Saldo final', ].map((label) => ( ))} {result.rows.map((row) => ( ))}
Encomendas, recepções, mercadoria em trânsito, consumo, falta e saldo de existências por mês
{label}
{row.month} {row.inventoryOpening ?? 'indeterminado'} {row.inventoryPurchases ?? 'indeterminadas'} {row.inventoryLeadMonths ?? 'indeterminado'} {row.inventoryReceipts ?? 'indeterminadas'} {row.inventoryInTransit ?? 'indeterminadas'} {row.inventoryConsumption} {row.inventoryShortfall ?? 'indeterminada'} {row.inventoryClosing ?? 'indeterminadas'}
{result.inventoryProjection.productSchedules && (

Existencias por produto

{result.inventoryProjection.productSchedules.map( (product) => (
{product.label}: stock final{' '} {product.endingInventory} {currency} ; falta {product.totalShortfall}{' '} {currency}
{[ 'Mes', 'Inicial', 'Encomendas', 'Prazo', 'Recepcoes', 'Em transito', 'Consumo', 'Falta', 'Final', ].map((label) => ( ))} {product.monthlyRows.map( (row) => ( ), )}
{label}
{row.month} { row.opening } { row.purchases } { row.leadMonths } { row.receipts } { row.inTransit } { row.consumption } { row.shortfall } { row.closing }
{product.paymentSchedule && (

Pagamentos declarados ao fornecedor

{product.paymentSchedule.map( (payment) => ( ), )}
Encomenda Pagamento Parcela Montante Estado
Mes{' '} { payment.orderMonth } {payment.paymentMonth === null ? 'Sem prazo declarado' : `Mes ${payment.paymentMonth}`} {payment.percent === 'remaining' ? 'Saldo nao calendarizado' : `${payment.percent}%`} { payment.amount }{' '} { currency } {payment.paidWithinHorizon ? 'Incluido no fluxo de caixa' : payment.paymentMonth === null ? 'Saldo sem parcela calendarizada' : 'Por pagar no fim do horizonte'}
)}
), )}
)}{' '} {result.supplierPaymentsOutstandingAtEnd !== null && ( Compras a fornecedores ainda por pagar no fim do horizonte: {result.supplierPaymentsOutstandingAtEnd}{' '} {currency}. Os pagamentos sao calculados pelas encomendas e prazos declarados; nao representam credito confirmado pelo fornecedor. )} {result.conversion && ( <>

Conversão de apresentação: 1 {currency} ={' '} {result.conversion.rate}{' '} {result.conversion.currency}, em{' '} {result.conversion.date}; fonte declarada:{' '} {result.conversion.source}. Cotação introduzida pelo utilizador e não verificada pelo portal; resultado apenas indicativo para planeamento. Os valores originais permanecem em {currency}.

{[ [ 'Fundo de maneio', result.conversion.workingCapital, ], [ 'Usos iniciais', result.conversion.initialUses, ], [ 'Fontes de financiamento', result.conversion.financing, ], [ 'Estado da reconciliacao do financiamento', result.conversion.fundingStatus, ], [ 'Diferenca entre usos e fontes', result.conversion.fundingGap, ], [ 'Facturação', result.conversion.totalRevenue, ], [ 'Cobranças no horizonte', result.conversion.totalCashCollections, ], [ 'Cobrança de valores de abertura', result.conversion .totalOpeningReceivablesCollections, ], [ 'Valores de abertura por receber', result.conversion .openingReceivablesAtEnd, ], [ 'Resultado antes impostos', result.conversion.totalProfitBeforeTax, ], [ 'Caixa final', result.conversion.endingCash, ], ].map(([label, value]) => (
{label} ( {result.conversion.currency})
{value}
))}
)}
{[ 'Mês', 'Facturação', 'Cobranças', 'Cobranças das vendas', 'Cobranças iniciais', 'Recuperações vencidas', 'Cobrança de valores de abertura', 'Valores de abertura por receber', 'Outras cobranças', 'Custos variáveis', 'Despesas/pessoal', 'Depreciação', 'Juros', 'Resultado antes de impostos', 'Pagamento de capital', 'Caixa final', ].map((label) => ( ))} {result.rows.map((row) => ( ))}
Resultados financeiros mensais antes de impostos
{label}
{row.month} {row.revenue} {row.cashCollections} {row.salesCollections ?? 'manual'} {row.salesScheduledCollections ?? '—'} {row.salesRecoveryCollections ?? '—'} {row.openingReceivablesCollections} {row.openingReceivablesBalance} {row.otherCollections ?? 'incluídas'} {row.variableCost} {row.operatingExpenses} /{' '} {row.payrollExpense} {row.depreciation} {row.interestExpense} {row.profitBeforeTax} {row.principalRepayment} {row.closingCash}
)}
); }