Files
Investidor-V2/tests/frontend/simulator-independent-review.test.mjs
T

198 lines
7.1 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { forecastBusinessPlan } from '../../resources/js/lib/business-financial-plan.ts';
import { projectMonthlyCashflow } from '../../resources/js/lib/monthly-cashflow.ts';
import {
defaultCashflowDraftInputs,
serializeCashflowDraftSnapshot,
restoreCashflowDraftSnapshot,
} from '../../resources/js/lib/monthly-cashflow.ts';
import { serializeFinancialToolsSnapshot } from '../../resources/js/lib/financial-calculators.ts';
import { buildProjectDossierRows } from '../../resources/js/lib/project-dossier.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
void test('cashflow draft recovery preserves unanswered months and invalid editable values without using zero', () => {
const inputs = {
...defaultCashflowDraftInputs(),
openingCash: '77.01',
monthlyTaxSchedule: ['10', '', '20', ...Array(9).fill('0')],
};
const stored = serializeCashflowDraftSnapshot('schedule', inputs, 'MZN');
const restored = restoreCashflowDraftSnapshot(stored);
assert.equal(restored.taxMode, 'schedule');
assert.deepEqual(restored.inputs, inputs);
assert.equal(restored.inputs.monthlyTaxSchedule[1], '');
const rows = buildProjectDossierRows({
currency: 'MZN',
cashflow_snapshot: stored,
});
assert.equal(
rows.find(
(row) => row[0] === 'Tesouraria' && row[1] === 'Saldo final total',
)[2],
'Indeterminado',
);
assert.ok(
rows
.find((row) => row[0] === 'Tesouraria' && row[1] === 'Mês 2')[2]
.includes('Imposto assumido: Indeterminado'),
);
const report = createProjectDossierReport(
{
currency: 'MZN',
cashflow_snapshot: stored,
},
{
selectedSections: new Set(['summary', 'finance']),
version: 'SINTETICO QA',
},
);
assert.equal(
report.sections
.find((section) => section.id === 'summary')
.rows.find(
(row) => row.field === 'Saldo final de tesouraria — 12 meses',
).value,
'Indeterminado',
);
const invalid = { ...inputs, monthlyRevenue: 'corrigir' };
assert.equal(
restoreCashflowDraftSnapshot(
serializeCashflowDraftSnapshot('fixed', invalid),
).inputs.monthlyRevenue,
'corrigir',
);
assert.equal(restoreCashflowDraftSnapshot('{"version":2}'), null);
assert.equal(
restoreCashflowDraftSnapshot(
stored.replace('"version":2', '"version":3'),
),
null,
);
});
void test('credit report preserves the complete reproducible recipe alongside the result', () => {
const rows = buildProjectDossierRows({
purpose: 'simulate_only',
journey_type: 'small_business',
currency: 'MZN',
financial_tools_snapshot: serializeFinancialToolsSnapshot({
budgetPeriod: 'Mensal',
loan: {
principal: '1000',
rate: '12',
months: '12',
rateType: 'nominal',
periodMonths: '1',
gracePeriods: '0',
upfrontFee: '0',
recurringFee: '0',
},
breakEven: { fixed: '100', price: '20', variable: '10' },
funding: { total: '1000', secured: '0' },
incomes: [{ label: 'QA', amount: '100' }],
expenses: [{ label: 'QA', amount: '50' }],
}),
});
const recipe = rows.find(
(row) => row[1] === 'Método e pressupostos substituídos',
)[2];
for (const text of [
'1000 MZN',
'12% nominal anual',
'12 meses',
'periodicidade 1 meses',
'carência 0',
'comissão inicial 0 MZN',
'comissão periódica 0 MZN',
'duas casas decimais',
'metade para cima',
'saldo residual',
])
assert.ok(recipe.includes(text), text);
assert.equal(
rows.find((row) => row[1] === 'Prestação do primeiro período')[2],
'88.85 MZN',
);
});
// Synthetic QA control ledger, independently calculated before invoking the engines.
// Sales 200; variable expense 80; operations 20; payroll 10;
// depreciation 10; interest expense 5 => profit before tax 75/month.
// Cash: receipts 180; inventory 70; operations 25; payroll 10;
// interest paid 7; principal paid 12 => cash increases 56/month.
// Initial funding 1200; asset 600 + inventory 100 + receivables 50 - payables 20 = 730;
// opening cash 470 => closing 638 after three months. No tax rate is assumed.
void test('independent three-month ledger separates profit working capital and cash', () => {
const result = forecastBusinessPlan({
currency: 'MZN',
openingCash: '0',
products: [
{
label: 'SINTETICO QA',
monthlyUnits: '10',
unitPrice: '20',
unitVariableCost: '8',
},
],
monthlyCollections: '180',
monthlyVariableCostPayments: '70',
monthlyInventoryPurchases: '70',
monthlyOperatingExpenses: '20',
monthlyOperatingPayments: '25',
monthlyPayrollExpense: '10',
monthlyPayrollPayments: '10',
assetInvestment: '600',
inventory: '100',
receivables: '50',
payables: '20',
equity: '1000',
shareholderLoans: '0',
bankLoanProceeds: '200',
grants: '0',
assetDepreciation: '600',
depreciationLifeMonths: 60,
monthlyInterestExpense: '5',
monthlyInterestPayment: '7',
monthlyPrincipalRepayment: '12',
periods: 3,
});
assert.equal(result.workingCapital, '130.00');
assert.equal(result.initialUses, '730.00');
assert.equal(result.openingCashAfterInitialUses, '470.00');
assert.equal(result.totalProfitBeforeTax, '225.00');
assert.equal(result.endingCash, '638.00');
assert.equal(result.totalCashCollections, '540.00');
});
void test('independent cash ledger excludes VAT stages and preserves the unknown tax month', () => {
const input = {
currency: 'MZN',
openingCash: '100',
monthlyRevenue: '200',
monthlyOperatingCosts: '150',
monthlyDebtService: '20',
monthlyTaxSchedule: ['10', '', '20'],
vatCreditEstimated: '1000',
vatRefundRequested: '900',
vatRefundRecognized: '800',
vatRefundExpectedReceipt: '50',
vatRefundExpectedMonth: '2',
periods: 3,
};
const result = projectMonthlyCashflow(input);
// Known cash: 100 + 20 + 80 + 10 = 210; month 2 tax is unknown.
assert.equal(result.rows[0].closingCash, '120.00');
assert.equal(result.rows[1].closingCash, null);
assert.equal(result.endingCash, null);
assert.equal(result.endingCashBeforeUnassessedTax, '210.00');
assert.equal(
projectMonthlyCashflow({
...input,
monthlyTaxSchedule: ['10', '0', '20'],
}).endingCash,
'210.00',
);
assert.equal(input.monthlyTaxSchedule[1], '');
});