feat(irpc): map short periods and foreign currency plans with explicit simulation evidence
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-26 09:55:25 +02:00
parent 0effdc9fcb
commit 56475acb2f
17 changed files with 1105 additions and 25 deletions
@@ -668,3 +668,32 @@ Instalado `e9fe102259c183a9d9bf7e39f246a8c3860839c6` sobre `043dd35d`, preservad
Revisão/manifest público,5marcadores compilados,4assets e simuladorHTTP200 conferidos. Pipeline normal concluído; provas QA18frontend/API1-84/UI/tipos/build reutilizadas,sem nova ronda manual. **Exemplo disponível: lucro6780 → matéria colectável4480 → colecta1433,60MZN. Saldo final a pagar/recuperar indeterminado emL8.** Históricos1–6 preservados; versão7 apenas por activação do novo mapa,substituindo o efeito anterior sem duplicação. Recibo:`docs/investmoz/simulations/irpc-l6-depreciation-delivery-20261026.json`.
O estado anterior «QA sem instalação» de e9fe1022 é histórico. Mantêm-se explícitos os outros códigos de tabela;usados/abertura de escrita/reparações/reavaliação/transição;uso intensivo/concessões/peças/intangíveis específicos/eventos;mudanças/excepções de método e opções fora do recorte. Retoma por correspondência da regra específica,base/vida/quotas e prova do delta,sem autorização genérica adicional. Períodos curtos e planos noutra moeda são o próximo incrementoQA,reutilizando o mecanismo cambial. Restantes custos/períodos/representação/camposM20-M22 conservam critérios próprios;divergência201/Q8 versus200/Q10 restringe apenas o transporte afectado. **L6 parcial;L7/L8 próprios;dez dúvidasL5 não reabertas.**
## 26/10/2026 — L6: períodos curtos e planos noutra moeda em QA
`e9fe1022` está instalado, com o âmbito de amortizações já demonstrado e os restantes casos de tabela/método explícitos acima. Este novo conjunto optativo `IRPC-normal-adjustments/8` está **emQA,sem instalação**. Reutiliza os meses e montantes do plano,os mapas fiscais emMZN e o mecanismo cambial entregue. Os históricos1–7 e o plano original não são reescritos.
Períodos de meses completos com termo em2026: início7(4)a,cessação7(4)b,início/fim de sujeição7(4)c e transição7(4)d,mediante identificação do evento/datas/suporte; plano com6meses ou selecção explícita de6dos12meses,sem anualizar receita,resultado,base ou taxa. Primeiro mês civil do plano é declarado,não inferido do mês orçamental. Anual diferente do ano civil conserva a correspondência do período autorizado/comunicado existente. Duodécimos ordinários do art9(3) do Decreto72/2013 aplicam o número de meses de utilização;art17 integral em período curto e restantes excepções não se tornam suportados por aproximação. Elemento conhecido como não amortizável mantém acréscimo integral,sem exigir método irrelevante.
Planos em outra moeda: cada mês seleccionado reutiliza ou confirma selecção cambial pelo procedimento existente,coluna/data/boletim/sentido e vínculo à origem. Moeda e valores originais permanecem no relatório. Não se adopta taxa actual,única ou de outro mês por defeito. **A conversão mensal para simulação exige pressuposto documentado;não confirma automaticamente uma regra fiscal de conversão anual.** Os mapas/activos/quotas/perdas existentes continuam emMZN e são reconciliados expressamente;nenhuma segunda conversão automática. Saldos patrimoniais de créditos em moeda estrangeira não são apresentados comoMZN pela conversão dos fluxos do plano;correspondência de saldo/data/taxa continua separada.
| Caso novo | Efeito demonstrado emMZN |
|---|---|
| Início1Julho–31Dezembro,meses7–12: resultado3120;quota fiscal50 e ajustamento10 | Lucro3130 → matéria colectável3130 → colecta1001,60,à taxa32%;ausência de perdas/benefícios declarada |
| Mesmo recorte emUSD,pressuposto sintético64MZN/USD identificado em cada mês;mapasMZN reconciliados | Resultado199680;ajustamento640;lucro/base200320 → colecta64102,40. Taxa64 é pressupostoQA,não boletimBM real ou parâmetro fiscal confirmado |
| Anual emUSD;plano de6meses iniciado emJulho;cessação e sujeição no mesmo ano | Novos testes próprios;respectivamente colecta128204,80 e1001,60nos casos correspondidos,sem anualização do semestre |
| Falta de selecção de um mês,moeda incompatível ou taxa editada sem confirmação | Valores originais e conversões independentes visíveis;resultado/lucro/base/colecta dependentes indeterminados |
| Perdas reportadas propostas num período curto | Lucro3130conhecido;dedução/base/colecta pendentes da sequência de períodos. Art41(5) exclui da contagem o período de transição7(4)d inferior a6meses;não autoriza agregar todos os períodos curtos |
Cálculo → gravação → recuperaçãoAPI → relatório/dossier demonstrado em4casos. **13testes novos frontend,API1teste/58asserções**, rollback na baseQA127.0.0.1:55441;routes/actions pelo procedimento existente,TypeScript/build e interface real passaram. Interface demonstrou alteração de datas e taxa,confirmação cambial,recuperação e exportação. Mudança na origem cambial invalida a actualidade do novo resultado,conservando o histórico. Provas aceites reutilizadas,sem repetição manual. Delta:**9ficheiros de runtime,43protegidos e31originais**;sem API/schema/configuração/permissões/dependências ou escrita no activo.
| Pendência concreta | Condição de retoma e bloqueio |
|---|---|
| Datas parciais,períodos superiores a12meses/liquidação e curtos fora do recorte2026 | Corresponder datas/evento,parcelas de dias e regra aplicável;apenas esse período depende da implementação |
| Dedução de perdas em sequência com períodos curtos/transição | Declarar sequência efectiva de períodos posteriores e aplicar41(5) ao caso7(4)d inferior a6meses;sem prazo presumido só por ano civil. Lucro independente continua calculável |
| Limites anuais de publicidade e provisão de créditos em período curto | Corresponder expressamente a base/limite do período e regra de proporcionalização,se aplicável;sem anualização ou rateio presumidos. Outros custos e períodos anuais não bloqueados |
| Quota integral art17 em período curto,opções/métodos/tabelas e casos de amortização ainda não suportados | Implementar combinação específica e provar só o novo efeito;condições restantes do conjuntoe9fe1022 continuam válidas |
| Conversão fiscal anual confirmada,saldos em moeda estrangeira e fracções de cêntimo da conversão | Identificar regra fiscal/data/base,taxa e âmbito;para saldos,conversão própria em vez de somar fluxos;para fracções,regra de representação. Até lá,pressupostos de simulação explícitos e total indeterminado quando não representável neste recorte |
| Restantes custos/periodizações e correspondênciasM20/M22 | Mantêm os critérios já registados. Divergência201/Q8 versus200/Q10 limita só o transporte afectado |
Fontes primárias reutilizadas: CIRPC Lei34/2007 PDF4/p768(58),art7(4)/(7),ePDF12/p768(66),art41(5);Decreto72/2013 PDF3/p1074(7),art9(3). Exemplar e originais/hash preservados;sem importação/publicação documental. **L6 continua parcial. Colecta não é saldo final a pagar;L7 eL8 mantêm âmbitos próprios.** As dez dúvidasL5 não foram reabertas. Recibo:`docs/investmoz/simulations/irpc-l6-periods-fx-qa-20261026.json`.
@@ -0,0 +1,869 @@
{
"status": "short_period_and_foreign_currency_plan_QA_prepared_not_installed",
"installed_baseline": "e9fe102259c183a9d9bf7e39f246a8c3860839c6",
"branch": "codex/irpc-l6-periods-fx-qa-20261026",
"version": "IRPC-normal-adjustments/8",
"cases_recovered": {
"short": {
"accountingProfit": {
"numerator": "3120",
"denominator": "1"
},
"taxableProfit": {
"numerator": "3130",
"denominator": "1"
},
"taxableBase": {
"numerator": "3130",
"denominator": "1"
},
"colecta": {
"numerator": "5008",
"denominator": "5"
},
"period": {
"version": 1,
"kind": "start",
"from": "2026-07-01",
"to": "2026-12-31",
"planStart": "2026-01-01",
"reference": "QA início documentado1Julho;correspondência completa dos meses7–12 ao período do sujeito",
"conversionReference": "",
"mznMapsReference": "QA activo/quotas/juros e demais mapas reconciliados explicitamente emMZN no período",
"months": []
},
"pending": []
},
"fx": {
"accountingProfit": {
"numerator": "199680",
"denominator": "1"
},
"taxableProfit": {
"numerator": "200320",
"denominator": "1"
},
"taxableBase": {
"numerator": "200320",
"denominator": "1"
},
"colecta": {
"numerator": "320512",
"denominator": "5"
},
"period": {
"version": 1,
"kind": "start",
"from": "2026-07-01",
"to": "2026-12-31",
"planStart": "2026-01-01",
"reference": "QA início documentado1Julho;correspondência completa dos meses7–12 ao período do sujeito",
"conversionReference": "QA pressuposto de simulação64MZN/USD aplicado aos meses seleccionados;sem regra fiscal anual confirmada",
"mznMapsReference": "QA activo/quotas/juros e demais mapas reconciliados explicitamente emMZN no período",
"months": [
{
"period": 7,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.920Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 8,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.920Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 9,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.920Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 10,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.920Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 11,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.920Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 12,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.920Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
}
]
},
"pending": []
},
"missing": {
"accountingProfit": null,
"taxableProfit": null,
"taxableBase": null,
"colecta": null,
"period": {
"version": 1,
"kind": "start",
"from": "2026-07-01",
"to": "2026-12-31",
"planStart": "2026-01-01",
"reference": "QA início documentado1Julho;correspondência completa dos meses7–12 ao período do sujeito",
"conversionReference": "QA pressuposto de simulação64MZN/USD aplicado aos meses seleccionados;sem regra fiscal anual confirmada",
"mznMapsReference": "QA activo/quotas/juros e demais mapas reconciliados explicitamente emMZN no período",
"months": [
{
"period": 7,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.924Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 8,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.924Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 9,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.924Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 10,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.924Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
},
{
"period": 11,
"currency": "USD",
"fxSelection": {
"nature": "commercial",
"submissionDate": "2026-10-26",
"bulletinDate": "2026-10-26",
"bulletinRef": "QA pressuposto sintético64; não boletim real",
"source": "https://www.bancomoc.mz/",
"sourceCurrency": "USD",
"direction": "mzn_per_unit",
"bulletinPerspective": "",
"relation": "",
"buy": "",
"sell": "",
"mean": "64",
"mode": "general",
"exceptionRef": "",
"exceptionScope": "",
"exceptionDate": "",
"exceptionRate": "",
"applied": {
"column": "mean",
"rate": "64",
"date": "2026-10-26",
"source": "QA pressuposto sintético64; não boletim real;https://www.bancomoc.mz/;perspectiva:média de referênciaBM;natureza:commercial;relação:não aplicável à média;Orientação operacional expressa da Tempo,25/10/2026:compra de moeda→taxa de compra;venda→taxa de venda;operações comerciais→taxa média de referência do Banco de Moçambique na data de submissão indicada. Interpretar compra/venda pela perspectiva do boletim. Excepções fiscais específicas conservam disposição,data,taxa e âmbito;sem recálculo automático dos históricos.",
"selectedAt": "2026-10-26T07:51:24.924Z",
"selectionKey": "{\"nature\":\"commercial\",\"submissionDate\":\"2026-10-26\",\"bulletinDate\":\"2026-10-26\",\"bulletinRef\":\"QA pressuposto sintético64; não boletim real\",\"source\":\"https://www.bancomoc.mz/\",\"sourceCurrency\":\"USD\",\"direction\":\"mzn_per_unit\",\"bulletinPerspective\":\"\",\"relation\":\"\",\"buy\":\"\",\"sell\":\"\",\"mean\":\"64\",\"mode\":\"general\",\"exceptionRef\":\"\",\"exceptionScope\":\"\",\"exceptionDate\":\"\",\"exceptionRate\":\"\"}"
}
}
}
]
},
"pending": [
"revenue/mês12: Error: Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito.",
"cost_of_sales/mês12: Error: Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito.",
"operating_expenses/mês12: Error: Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito.",
"personnel/mês12: Error: Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito.",
"depreciation/mês12: Error: Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito.",
"interest_expense/mês12: Error: Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito."
]
},
"loss": {
"accountingProfit": {
"numerator": "3120",
"denominator": "1"
},
"taxableProfit": {
"numerator": "3130",
"denominator": "1"
},
"taxableBase": null,
"colecta": null,
"period": {
"version": 1,
"kind": "start",
"from": "2026-07-01",
"to": "2026-12-31",
"planStart": "2026-01-01",
"reference": "QA início documentado1Julho;correspondência completa dos meses7–12 ao período do sujeito",
"conversionReference": "",
"mznMapsReference": "QA activo/quotas/juros e demais mapas reconciliados explicitamente emMZN no período",
"months": []
},
"pending": []
}
},
"verification": {
"frontend_tests": 13,
"API_tests": 1,
"API_assertions": 58,
"database": "127.0.0.1:55441/simulator_fiscal_delta_20261021_test;DatabaseTransactions rollback",
"routes_actions": "generated by guarded existing procedure",
"typescript": "passed",
"build": "passed",
"UI": "actual component;dates/FX edit+confirmation;originals/assumptions;save/recovery/export;missingdata",
"accepted_proofs": "reused"
},
"runtime_delta": [
{
"path": "resources/js/components/simulation-irpc-normal.tsx",
"sha256_LF": "557714bb3af75080e443ae67b11a66d9feb49d9a4b269065769b128a6d712837"
},
{
"path": "resources/js/components/simulation-irpc-period-fx.tsx",
"sha256_LF": "4b5f718b5ef415ecf5f96dabb0e3806254558c20905de6478e10dacf67569c49"
},
{
"path": "resources/js/components/simulation-irpc-schedules.tsx",
"sha256_LF": "3768c316d5887d337eb3647671863c9464f961d1df4703403e7d57e5641273ed"
},
{
"path": "resources/js/lib/irpc-normal-assessment.ts",
"sha256_LF": "a9b3ced852e37e3e087ce5dab1add4ec0737145ce19f61857a54d5d3d24a6f8c"
},
{
"path": "resources/js/lib/irpc-normal-base.ts",
"sha256_LF": "22019d678f6eb16fead2207dc3d3fa9fd63413c6254633c1ffd4f1c0320344ac"
},
{
"path": "resources/js/lib/irpc-normal-costs.ts",
"sha256_LF": "471457692a2a2f70a1bd1c7abb38d3ce42f07b62a63e5a4ab985ad1bbd068b46"
},
{
"path": "resources/js/lib/irpc-normal-depreciation.ts",
"sha256_LF": "1dad3053157aeac44f6d3bd58885cfc47a73c19d2808284c16931679dfe4291b"
},
{
"path": "resources/js/lib/irpc-normal-period-fx.ts",
"sha256_LF": "a2e7b066623eae17f0d8185d065d0adf5d3a485f780f8b09118a664dc4ba0531"
},
{
"path": "resources/js/lib/irpc-normal.ts",
"sha256_LF": "e4de2fe0421e489bf555885b9cbb459c00ee77803ea9b6e5af45179bd6bb4f21"
}
],
"protected_runtime": [
{
"path": "resources/js/components/simulation-irpc-depreciation.tsx",
"sha256_LF": "1f9918bfb435bb5b7c0ddd2fafd7bace2a02514c6ece04b93f15975e494a5f53"
},
{
"path": "resources/js/lib/irpc-normal-profit.ts",
"sha256_LF": "909e129a0245e6bf098317485b886a2d19e02c64bbfd9c6f718e98ec6b935b8d"
},
{
"path": "resources/js/lib/irpc-normal-schedules.ts",
"sha256_LF": "edfb6fd338411fa8f87a6bdf9a9d4fd5c6dc2fad9d2eb3d32f2640e2951cbbe9"
},
{
"path": "resources/js/components/simulation-irpc-costs.tsx",
"sha256_LF": "c4156916835daf0f33addf89dfedd40c31b8dc3297112cd98beac33422af274d"
},
{
"path": "resources/js/lib/irpc-normal-forms.ts",
"sha256_LF": "4e986addb9f2d74e7a80345f8f1e4d8e0b2f562c867fa6eee84880d9f72a3649"
},
{
"path": "resources/js/components/simulation-irpc-taxable-base.tsx",
"sha256_LF": "e4fe9f66d35d24aa68fd87de6a5a6a0e901706b270c18962fd0c7ab00f9abd93"
},
{
"path": "resources/js/components/simulation-irpc-assessment.tsx",
"sha256_LF": "7174cc0160005a0c332ea40f02ba8a6d57985081b5ab45177ccd6e721bceea54"
},
{
"path": "resources/js/components/acquisition-beverage-restitution.tsx",
"sha256_LF": "6f44897b8b12569314551df0022c26da57a165634087942a724293e66837558d"
},
{
"path": "resources/js/components/fiscal-calculation-gaps.tsx",
"sha256_LF": "1dab6ceb73756d4e023096b3b2ba767aaa843ff6cc69046ea630ab449a00b0dd"
},
{
"path": "resources/js/components/fiscal-l3-preparation.tsx",
"sha256_LF": "e84a7b1ccadaa1b5bd78d778a3287f96ccaf530855eef404786cd7aa3d321b73"
},
{
"path": "resources/js/components/simulation-acquisitions.tsx",
"sha256_LF": "c206d4cfe575d6ba007aa2b425b5f898efa97e4162b8710e358b6ecfd9458542"
},
{
"path": "resources/js/components/simulation-operations.tsx",
"sha256_LF": "a7e55972fa790063dcf11945055de2af46a77db9e509900af22ede7ec47bb4cf"
},
{
"path": "resources/js/lib/iva-acquisition-bc.ts",
"sha256_LF": "b0ef55e0917bdb79f14b53ba9e95d73c08153befeb7d4ce73f81e6fb13992225"
},
{
"path": "resources/js/lib/iva-beverage-allocation.ts",
"sha256_LF": "32968d198c7fe0c53ca89641c22d5749be6636be2c79127b867e4e3afd0dd40d"
},
{
"path": "resources/js/lib/iva-beverage-restitution.ts",
"sha256_LF": "5c650cbd9469d5f99001f56fc4ff99ac973eb368036d4e96c6d171f1b220bd9e"
},
{
"path": "resources/js/lib/iva-calculation-gaps.ts",
"sha256_LF": "2e7d84a0c196bd5b56fd67cc1a389d43d5041894184e675680a4de3cd10b31a2"
},
{
"path": "resources/js/lib/iva-operations.ts",
"sha256_LF": "ec37b949f1233c5f5c14a87450c421ed29fabf1ed5b73fb32564564a2bc31f54"
},
{
"path": "resources/js/lib/l3-form-preparation.ts",
"sha256_LF": "fc47f5fa5ec1420fbd847d0d081f00d99ebbb524164eef785b20877e0562f704"
},
{
"path": "resources/js/lib/ispc-residuals.ts",
"sha256_LF": "40d5751c06c7c155ab4b7a17ee2d1653da2fbbf075807fbb8561cdbe19820955"
},
{
"path": "resources/js/components/simulation-ispc-residuals.tsx",
"sha256_LF": "a9219d6f41ccbd0ce45659d4f6aa0fa1c169a685840f5cb1e18daeca377d5e18"
},
{
"path": "resources/js/lib/ispc-eligibility.ts",
"sha256_LF": "44675c5efacdc1ae19505667611ba3b522d3043cebacc8754597c3a68ca89d25"
},
{
"path": "resources/js/components/simulation-ispc-quarter.tsx",
"sha256_LF": "2c2555309b32d4261f14e7164f39314c9188129c2990357fc54090a2468dd2b4"
},
{
"path": "resources/js/components/simulation-ispc-withholding.tsx",
"sha256_LF": "e0445d015b72859e48726959aa52686f79b1cf5c2ade29751efa83257068b878"
},
{
"path": "resources/js/lib/ispc-quarter.ts",
"sha256_LF": "f66bd52e9cdfe1ddaa6b00a931465862b3d890b9be216b7a056740f027f24129"
},
{
"path": "resources/js/lib/ispc-withholding.ts",
"sha256_LF": "6c31b240299edd3262efa28f7f3d5aee21123f94e30ef4ab90e26c35777cb6e4"
},
{
"path": "resources/js/components/simulation-ispc-eligibility.tsx",
"sha256_LF": "f48dd4a8f87a994eda0447f81a463589905e5873b2e1044debac9d10825a880c"
},
{
"path": "resources/js/lib/iva-acquisitions.ts",
"sha256_LF": "cce283b1d0a07fb383fcb527225f4dd5bec829010a662cbaae1d60e595221977"
},
{
"path": "resources/js/lib/iva-modelo-a.ts",
"sha256_LF": "c641619a05613c1620385baad0e9ffbf810dd970aafb30e12b3a7f9a3bf11846"
},
{
"path": "resources/js/lib/iva-fx-selection.ts",
"sha256_LF": "9aa07ad443bc777b80510a3b5bd6aff9ddb9814d2450f3c690e531e74d71d998"
},
{
"path": "resources/js/components/simulation-fx-selection.tsx",
"sha256_LF": "39bb6d87d404745b0b66349bc11626545a3672ae27321ce9b4658416592059bf"
},
{
"path": "resources/js/lib/business-financial-plan.ts",
"sha256_LF": "c20274722d1ffcd0360f041a6cacb5d868fe0621cb27a1aca6e06826612c3ebb"
},
{
"path": "resources/js/lib/iva-currency-events.ts",
"sha256_LF": "cc9f2045d302d7987310da4006cc250fc5c99a27397918d166d649bfc32fab08"
},
{
"path": "resources/js/lib/irpc-fx.ts",
"sha256_LF": "5c4a7a477429f8d775acf6db3e117008468f5554e5731dec81f60fbb75e948fb"
},
{
"path": "resources/js/lib/irpc-withholdings.ts",
"sha256_LF": "8e5f3c035ce5e4d6c3fc82f933439ab580fa5519492f9c4d32cfb71b77b54803"
},
{
"path": "resources/js/components/simulation-irpc-withholdings.tsx",
"sha256_LF": "751af4fab346659bc5ccb08b8a2169769af7521ea0035050af32b41ea7f5aa76"
},
{
"path": "resources/js/lib/irpc-interest.ts",
"sha256_LF": "f8e6390b3b2c58c296f7e3fd14576e06ae248bc0cabce1a666b7094611f2a7dd"
},
{
"path": "resources/js/lib/irpc-mrfta.ts",
"sha256_LF": "76dcce8954f68b081d500be61de9c5b146a452a3eb578a0b78776ba9e1f8051c"
},
{
"path": "resources/js/lib/irpc-mrfta-cases.ts",
"sha256_LF": "4266746076ab3f70312fd042351e24dd2f136c4f3e8a277b57dd97849a9f1862"
},
{
"path": "resources/js/components/simulation-irpc-mrfta.tsx",
"sha256_LF": "913d1bb4ac0fc59763cf5d27bc637f6a713b2050c4463748db554af4be451538"
},
{
"path": "resources/js/lib/financial-fiscal-bridge.ts",
"sha256_LF": "5a30c501653b16b5fedcb12631ce81fcce4fe473aa542e94374986b3c50f2d33"
},
{
"path": "resources/js/components/simulation-form.tsx",
"sha256_LF": "85b024a7f45ceb52efd6ff1f21d0eab1fd8fcf1ab59e4b5302c08685235c472a"
},
{
"path": "resources/js/lib/project-dossier.ts",
"sha256_LF": "a0909bbfcdf7e8542b010faa104fcb918e86ae38ddf096082d4d5be98d31b2b9"
},
{
"path": "resources/js/lib/project-dossier-report.ts",
"sha256_LF": "5207a2ffeb208cd5ace6713038cee85208020ee618194dca7114e94fc3200b6c"
}
],
"originals_preserved": [
{
"path": "docs/investmoz/simulations/source-archive/AT-Comunicado-9-2026-ISPC.pdf",
"sha256": "106f8a9ce9255c03549c70bf07fe999d32e6e55302d009b509862aa74ccb4973"
},
{
"path": "docs/investmoz/simulations/source-archive/AT-Modelo-30-ISPC.pdf",
"sha256": "6802edd06b6ff638a0b851a3ec7ecf69af8f07840faa47446becad67d35d445c"
},
{
"path": "docs/investmoz/simulations/source-archive/AT-Modelo-B-Geral.pdf",
"sha256": "bd977c7c59d4d98befd92e2c566b1bf168cb1343bb519f51b41a3807f431fc57"
},
{
"path": "docs/investmoz/simulations/source-archive/AT-MRFTA-19-39.pdf",
"sha256": "8ea62c53066b482f87078a7a7b1ecf15ca98a12a122e8d7abca63acfc993186b"
},
{
"path": "docs/investmoz/simulations/source-archive/BM-Mercado-Monetario-MAIBOR-20261025.html",
"sha256": "00e8a7e0d3a1db4ca3beb56d5361b2084a0f1684f3d40fc1c203702a2190e063"
},
{
"path": "docs/investmoz/simulations/source-archive/BR-16-I-Serie-2008-04-16.pdf",
"sha256": "fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee"
},
{
"path": "docs/investmoz/simulations/source-archive/BR-248-I-Suplemento-1-2025.pdf",
"sha256": "ccf2a8deeca820f2dd076b4b2141b1efe3f6a806568c3a587ca59a8e92f4c251"
},
{
"path": "docs/investmoz/simulations/source-archive/BR-89-I-Serie-2017-06-08.pdf",
"sha256": "11f0cab15eb6e51010952203ffa24fdc533a24fd642e19525fe4f46b17a1f5e5"
},
{
"path": "docs/investmoz/simulations/source-archive/BR-95-I-Suplemento-2025-05-21.pdf",
"sha256": "e0e682e2c027c3cd9c1d46a73a3f48bc5f6bdbf05667fd97314d7a3f6a456358"
},
{
"path": "docs/investmoz/simulations/source-archive/BR252-I-Serie-2022-Lei20-PESOE.pdf",
"sha256": "1159fd3ce1ba5581671796d083534da48bd3c5578298819e896f1c2a8026318d"
},
{
"path": "docs/investmoz/simulations/source-archive/Decreto-22-2021-BR70.pdf",
"sha256": "a8fac38dcfe08f9c7a8e46254cff10e1ec82fc4f7a8d88ad6e4fe7d91ffc21a5"
},
{
"path": "docs/investmoz/simulations/source-archive/Decreto-27-2026-BR123.pdf",
"sha256": "26249c3fa58f149c81de36e99d1aac2819a4771379d9e8833159660dabf539bb"
},
{
"path": "docs/investmoz/simulations/source-archive/Decreto-29-1998-BR22-5-suplemento.pdf",
"sha256": "6242cbe675faadf085f43f95ec8c7e8868aafe67c3b2ab2bb010992288f954f8"
},
{
"path": "docs/investmoz/simulations/source-archive/Decreto-3-2011-BR-11-I-mineiros.pdf",
"sha256": "5bca1133666ba9c69870b89823420175b7b5b8d0eccb667ecdf5c29716f3b6be"
},
{
"path": "docs/investmoz/simulations/source-archive/Decreto-37-2023-BR124.pdf",
"sha256": "7e680b191481faa0e2e9057fa5053587eb2a29d1d8c819e0eb9f02814255a04c"
},
{
"path": "docs/investmoz/simulations/source-archive/Decreto-9-2008-BR16.pdf",
"sha256": "96fccdecbfdc11c9eafb90fbc6c7bc259f88bbd4c7507d1ac3af1fa03f960697"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-13-2016-CIVA-BR-156-10-suplemento.pdf",
"sha256": "b90dc7c14ecea2e378c4a62e8e5a47d54a5644c457baeb8b8053b92fd2a5710b"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-14-1992-BR42.pdf",
"sha256": "089fff7423e99a70a230a73ca379e4ea3a91d124703f91016e57ff82794b9e56"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-17-2022-BR251-exemplar-MCNet5.pdf",
"sha256": "14eb34f0af91a8f5cb1770c7b52d6ff839c6818302d1ed96d79afbfb9fe999d4"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-17-2022-IPP-BR250-Parlamento.pdf",
"sha256": "978cd22403ffd336a631704ecd7a5bbd814a427c815b9acdaca5515591dd9e2a"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-19-2013-BR76.pdf",
"sha256": "bba96d645cc935ffc3d1a79b9f0f73ac1c693d6459c2ad41f56cd2841accae76"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-19-2017-IRPS-BR-OCAM.pdf",
"sha256": "c3a0186cb9ad090d7a9533006295dc9bb6a62ad0d6150173cbd49dba4ee4b52d"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-2-2011-BR-1-4-suplemento-MCNet.pdf",
"sha256": "a7543a68cc3287a46a83c39a04821b40f649dd06c4e9b5dc51ff0b47fba08899"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-20-2009-BR36-AT-exemplar19.pdf",
"sha256": "fc1c8144db42960cbf73e717e095896f511ece0f907f58f78819b27d6917a7ee"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-22-2022-IVA-republicada-BR-250-2-suplemento.pdf",
"sha256": "ea1b0dc6f5cf6e595f6d9a19e7042f398999e0087caef1785f6e8fac571b5f53"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-34-2007-BR52.pdf",
"sha256": "cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-4-1994-BR37-3-suplemento.pdf",
"sha256": "210df9db8980d0a5690cd187a6eeec7e37a900d743e39062c16f4e0d12215c7e"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-4-2012-BR3.pdf",
"sha256": "b8845cef41dcbd9ba50c43dbfa743a56030dd3d348242a107e1ae30279eec121"
},
{
"path": "docs/investmoz/simulations/source-archive/Lei-7-1991-Partidos-IFES.pdf",
"sha256": "6b783a0bb3330688ef10660a58060d9724aa2da6a873d234fe902f0eaaeda42d"
},
{
"path": "docs/investmoz/simulations/source-archive/PT-MZ-CDT-1992.pdf",
"sha256": "d504e1024b28aee5c6acf4592acb96ec9c915a8414f67a61ab6984d6acf9df9d"
},
{
"path": "docs/investmoz/simulations/source-archive/PT-MZ-Protocolo-2009.pdf",
"sha256": "f353773dcf41f09f4daa6cf5ba5a5f7994f3e02615867c2dcffc61c3d901bf9b"
}
],
"source_evidence": {
"CIRPC": "Lei34/2007 PDF4 art7(4)/(7),PDF12 art41(5);original preserved",
"decree72_reused_sha256": "922ef67cb0c1111405c3c0a092d8c302a74fba8a34d627b6e9e13223612e0659",
"visual_pages": [
"cirpc-page4.png",
"cirpc-page12.png",
"decreto72-page3.png"
]
},
"installed7_fixture_sha256": "d58beac3cf7db665a632f516a736f754a5874f82585291dfbc5fa6bcda654d0d",
"history_versions1_to7": "unchanged;optional8;source plan not rewritten",
"FX_engine": "Existing selectFx/convertIrpcAmount/convertCents/direction/evidence/link unchanged;monthly assumptions explicit;no fiscal annual rule asserted",
"FX_rate64": "synthetic QA assumption,not real BM bulletin",
"schema_API_config_permissions_dependencies_changed": false,
"active_data_changed": false,
"L6_complete": false,
"L7_L8_own_scopes": true,
"remaining_reference": "Same matrix section26/10 períodos curtos e planos noutra moeda;concrete conditions per case",
"proof_sha256": {
"api.txt": "e2c50de5a45a76cdd596d4093974b88c449d23ba8011edf5f8a6325d2cf01d07",
"build-exit.txt": "13bf7b3039c63bf5a50491fa3cfd8eb4e699d1ba1436315aef9cbe5711530354",
"build.txt": "9b167ca7bcd724a65f6a4030a46ce9e81accaad5a2e80160b623ca3807400bc7",
"cirpc-page12.png": "12bb97ef4964b69816c30894bca201cedf2dcbadb29b3be9e867f2325e575abd",
"cirpc-page4.png": "3f6fdfd75d4fef4d07f461969675118f5f000c33ab593fcb6e0ee0fc93f56737",
"cirpc.txt": "c3ba3b845fcb12c25bc28b08485f3e5d70034895ece4ff60cf640fd0be796f9a",
"decreto-72-2013.pdf": "922ef67cb0c1111405c3c0a092d8c302a74fba8a34d627b6e9e13223612e0659",
"decreto72-page3.png": "4a150dd00dc7c7d4602c231bb42ebea5d7505917f1e21781c2d967cf5da8e327",
"dossier-fx.json": "5d1e429a48e8fd6c53a9116b3176641146e2aa6b68c888558ac6c4ff0950138c",
"dossier-loss.json": "862ee41f5b38546255335ab569039e52388d64f7bf30f38c3033f6f583e04d7c",
"dossier-missing.json": "514c2386e8e1cd687545c880fd5e66a0099c851433c0c8f337439a69e56db5b1",
"dossier-short.json": "cdab2ed8019f46f132f600c6b301d99ad44e8027926117abf3e57eb6c6000071",
"dossier.mjs": "cd967cfdba196bf0b7c691fe418f38dd39232030c6e601c22f8e25bd27e93d46",
"download.html": "95f59ff469af21bc2a41225358769bf99f25cf056479ff9a9c3d1dad4cb5f521",
"frontend.txt": "d9c7e51b63e020bf35d2a4a3aa22f29ca71d003e4bcf22b111e90173e442745b",
"fx.html": "5bae85164ca7cec78d76fc4c72c3c33cdaca70c63c42929c8e037ab7e91ba93f",
"fx.json": "e92ed4b718eef390aef573728a459679fdd3130bdf923db4cfd8f7a560958fbf",
"index.html": "09941968500d2910121bdcb0029e326fc6d44bb025b01766654289a28caa62fa",
"loss.html": "b672fd43361efcc81c4da4f83b7eba12dd9bd7799de0b463f248928065a4de97",
"loss.json": "07e56fb88034d377ca9bc2b098b9a4a266058a46aeb7b127a99b3c539bceec1b",
"missing.html": "ed41bf1b5ffab532a59b8e07d8a3700bd62576bc15c9256750c84a371a2cbc8a",
"missing.json": "5022f8eddd7f09b9a10a837f0e407e7db3619e27302e19f367923a956ebe47a6",
"qa.tsx": "c7c06f793e4a5aa3df70f36ec6681a6399b6016f27168d18917be2f22dffdbaf",
"recovered-fx.html": "5bae85164ca7cec78d76fc4c72c3c33cdaca70c63c42929c8e037ab7e91ba93f",
"recovered-fx.json": "2c7d1e8858af254930bff8a420f3369475ebd335ca4251b4ddc691558ed1c9aa",
"recovered-loss.html": "b672fd43361efcc81c4da4f83b7eba12dd9bd7799de0b463f248928065a4de97",
"recovered-loss.json": "2618a36d92005ebf791c938687662789086d351606f95f62f4eb1133f4b029e2",
"recovered-missing.html": "ed41bf1b5ffab532a59b8e07d8a3700bd62576bc15c9256750c84a371a2cbc8a",
"recovered-missing.json": "094b1ee4822bdcee6ac8ce5dc9ea5cfc178bf555b15f31a6cd2a1c6becd759ae",
"recovered-short.html": "955f182b29c160774bacc8997477c2da44cd1fcc401d1ed1955fa62f9f94d743",
"recovered-short.json": "e4399308561d5f412c9bb803c83fd6e045d89d24986a4d6a8279c2977044bb96",
"short.html": "955f182b29c160774bacc8997477c2da44cd1fcc401d1ed1955fa62f9f94d743",
"short.json": "a6b0e4ea322353f84c0ba6ff09d6cf89ee270dbefb850f225c4f8fe4485f68fb",
"types.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"ui-fx.png": "deaede13c0c4eb6cc74ce0a55fc9b7bc2948f52fe7863ffc53ab83ca42456587",
"ui-proof.json": "56b6005ea84dfd370ac55167a5f47d19bffcee8408982c2046cb1ad27ecc9f20",
"ui-short.png": "b716d583fc3ca286c2e19cbeefca018ec08e8b75e0e95499ff7d27ab06a843d6",
"ui.cjs": "1f5506f7865cdbfcfa8aea853be241bab849807e6d641f3c5596534727cb5938",
"ui.txt": "85e77d9f361edf72d68990272040cc9b6edab3cc0873f921b1d5e561dc0553af",
"vite.config.ts": "0351ec239d5a6ba429ee17b9aa68757e31f59902f8f88a522afdd56bd3b44879"
}
}
@@ -6185,3 +6185,32 @@ Instalado `e9fe102259c183a9d9bf7e39f246a8c3860839c6` sobre `043dd35d`, preservad
Revisão/manifest público,5marcadores compilados,4assets e simuladorHTTP200 conferidos. Pipeline normal concluído; provas QA18frontend/API1-84/UI/tipos/build reutilizadas,sem nova ronda manual. **Exemplo disponível: lucro6780 → matéria colectável4480 → colecta1433,60MZN. Saldo final a pagar/recuperar indeterminado emL8.** Históricos1–6 preservados; versão7 apenas por activação do novo mapa,substituindo o efeito anterior sem duplicação. Recibo:`docs/investmoz/simulations/irpc-l6-depreciation-delivery-20261026.json`.
O estado anterior «QA sem instalação» de e9fe1022 é histórico. Mantêm-se explícitos os outros códigos de tabela;usados/abertura de escrita/reparações/reavaliação/transição;uso intensivo/concessões/peças/intangíveis específicos/eventos;mudanças/excepções de método e opções fora do recorte. Retoma por correspondência da regra específica,base/vida/quotas e prova do delta,sem autorização genérica adicional. Períodos curtos e planos noutra moeda são o próximo incrementoQA,reutilizando o mecanismo cambial. Restantes custos/períodos/representação/camposM20-M22 conservam critérios próprios;divergência201/Q8 versus200/Q10 restringe apenas o transporte afectado. **L6 parcial;L7/L8 próprios;dez dúvidasL5 não reabertas.**
## 26/10/2026 — L6: períodos curtos e planos noutra moeda em QA
`e9fe1022` está instalado, com o âmbito de amortizações já demonstrado e os restantes casos de tabela/método explícitos acima. Este novo conjunto optativo `IRPC-normal-adjustments/8` está **emQA,sem instalação**. Reutiliza os meses e montantes do plano,os mapas fiscais emMZN e o mecanismo cambial entregue. Os históricos1–7 e o plano original não são reescritos.
Períodos de meses completos com termo em2026: início7(4)a,cessação7(4)b,início/fim de sujeição7(4)c e transição7(4)d,mediante identificação do evento/datas/suporte; plano com6meses ou selecção explícita de6dos12meses,sem anualizar receita,resultado,base ou taxa. Primeiro mês civil do plano é declarado,não inferido do mês orçamental. Anual diferente do ano civil conserva a correspondência do período autorizado/comunicado existente. Duodécimos ordinários do art9(3) do Decreto72/2013 aplicam o número de meses de utilização;art17 integral em período curto e restantes excepções não se tornam suportados por aproximação. Elemento conhecido como não amortizável mantém acréscimo integral,sem exigir método irrelevante.
Planos em outra moeda: cada mês seleccionado reutiliza ou confirma selecção cambial pelo procedimento existente,coluna/data/boletim/sentido e vínculo à origem. Moeda e valores originais permanecem no relatório. Não se adopta taxa actual,única ou de outro mês por defeito. **A conversão mensal para simulação exige pressuposto documentado;não confirma automaticamente uma regra fiscal de conversão anual.** Os mapas/activos/quotas/perdas existentes continuam emMZN e são reconciliados expressamente;nenhuma segunda conversão automática. Saldos patrimoniais de créditos em moeda estrangeira não são apresentados comoMZN pela conversão dos fluxos do plano;correspondência de saldo/data/taxa continua separada.
| Caso novo | Efeito demonstrado emMZN |
|---|---|
| Início1Julho–31Dezembro,meses7–12: resultado3120;quota fiscal50 e ajustamento10 | Lucro3130 → matéria colectável3130 → colecta1001,60,à taxa32%;ausência de perdas/benefícios declarada |
| Mesmo recorte emUSD,pressuposto sintético64MZN/USD identificado em cada mês;mapasMZN reconciliados | Resultado199680;ajustamento640;lucro/base200320 → colecta64102,40. Taxa64 é pressupostoQA,não boletimBM real ou parâmetro fiscal confirmado |
| Anual emUSD;plano de6meses iniciado emJulho;cessação e sujeição no mesmo ano | Novos testes próprios;respectivamente colecta128204,80 e1001,60nos casos correspondidos,sem anualização do semestre |
| Falta de selecção de um mês,moeda incompatível ou taxa editada sem confirmação | Valores originais e conversões independentes visíveis;resultado/lucro/base/colecta dependentes indeterminados |
| Perdas reportadas propostas num período curto | Lucro3130conhecido;dedução/base/colecta pendentes da sequência de períodos. Art41(5) exclui da contagem o período de transição7(4)d inferior a6meses;não autoriza agregar todos os períodos curtos |
Cálculo → gravação → recuperaçãoAPI → relatório/dossier demonstrado em4casos. **13testes novos frontend,API1teste/58asserções**, rollback na baseQA127.0.0.1:55441;routes/actions pelo procedimento existente,TypeScript/build e interface real passaram. Interface demonstrou alteração de datas e taxa,confirmação cambial,recuperação e exportação. Mudança na origem cambial invalida a actualidade do novo resultado,conservando o histórico. Provas aceites reutilizadas,sem repetição manual. Delta:**9ficheiros de runtime,43protegidos e31originais**;sem API/schema/configuração/permissões/dependências ou escrita no activo.
| Pendência concreta | Condição de retoma e bloqueio |
|---|---|
| Datas parciais,períodos superiores a12meses/liquidação e curtos fora do recorte2026 | Corresponder datas/evento,parcelas de dias e regra aplicável;apenas esse período depende da implementação |
| Dedução de perdas em sequência com períodos curtos/transição | Declarar sequência efectiva de períodos posteriores e aplicar41(5) ao caso7(4)d inferior a6meses;sem prazo presumido só por ano civil. Lucro independente continua calculável |
| Limites anuais de publicidade e provisão de créditos em período curto | Corresponder expressamente a base/limite do período e regra de proporcionalização,se aplicável;sem anualização ou rateio presumidos. Outros custos e períodos anuais não bloqueados |
| Quota integral art17 em período curto,opções/métodos/tabelas e casos de amortização ainda não suportados | Implementar combinação específica e provar só o novo efeito;condições restantes do conjuntoe9fe1022 continuam válidas |
| Conversão fiscal anual confirmada,saldos em moeda estrangeira e fracções de cêntimo da conversão | Identificar regra fiscal/data/base,taxa e âmbito;para saldos,conversão própria em vez de somar fluxos;para fracções,regra de representação. Até lá,pressupostos de simulação explícitos e total indeterminado quando não representável neste recorte |
| Restantes custos/periodizações e correspondênciasM20/M22 | Mantêm os critérios já registados. Divergência201/Q8 versus200/Q10 limita só o transporte afectado |
Fontes primárias reutilizadas: CIRPC Lei34/2007 PDF4/p768(58),art7(4)/(7),ePDF12/p768(66),art41(5);Decreto72/2013 PDF3/p1074(7),art9(3). Exemplar e originais/hash preservados;sem importação/publicação documental. **L6 continua parcial. Colecta não é saldo final a pagar;L7 eL8 mantêm âmbitos próprios.** As dez dúvidasL5 não foram reabertas. Recibo:`docs/investmoz/simulations/irpc-l6-periods-fx-qa-20261026.json`.
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
import {SimulationIrpcPeriodFx} from '@/components/simulation-irpc-period-fx';
import {SimulationIrpcDepreciation} from '@/components/simulation-irpc-depreciation';
import {SimulationIrpcCosts} from '@/components/simulation-irpc-costs';
import {SimulationIrpcSchedules} from '@/components/simulation-irpc-schedules';
@@ -16,9 +17,9 @@ export function SimulationIrpcNormal({draft,onChange}:{draft:Record<string,unkno
const profit=(patch:Partial<NormalProfitInput>)=>save({...b,profit:{...(b.profit??newNormalProfitInput()),...patch}});
const field=(r:NormalAdjustment,k:'document'|'line'|'amount'|'reference',label:string)=><label className="block">{label}<input value={r[k]} onChange={e=>patch(r.id,{[k]:e.target.value})}/></label>;
return<section aria-label="IRPC normal — ajustamentos" className="space-y-3 rounded border p-4"><h4>L6 — IRPC normal: ajustamentos ao resultado financeiro</h4><p>Acréscimos de multas sem origem contratual e juros compensatórios incluídos nos custos do plano, artigo 36(1)c. A conferência anual pode determinar o lucro tributável simulado no recorte ordinário documentado. A matéria colectável exige conferência específica de deduções e perdas; a colecta tem cálculo próprio e o saldo final a pagar ou recuperar pertence aL8. Não reutiliza automaticamente juros simulados de L5.</p>{error&&<p role="alert">{error}</p>}
{source?<p>Origem: plano financeiro desta oportunidade; {source.periods} meses; {source.currency}; resultado antes de imposto: {source.accountingProfitBeforeTax}. Cobranças, pagamentos e reembolso de capital ficam fora do resultado.</p>:<p>Prepare o plano financeiro para reutilizar o resultado e os custos reconciliados.</p>}
{source?<p>Origem: plano financeiro desta oportunidade; {source.periods} meses; {source.currency}; resultado antes de imposto: {source.accountingProfitBeforeTax}. Cobranças, pagamentos e reembolso de capital ficam fora do resultado.</p>:<p>{b.periodsFx?'Complete a correspondência do período e moeda abaixo para reutilizar o plano existente emMZN.':'Prepare o plano financeiro para reutilizar o resultado e os custos reconciliados.'}</p>}
<label className="block">Exercício fiscal<input value={b.year} onChange={e=>save({...b,year:e.target.value})}/></label>
<label className="block">{b.schedules?'Correspondência dos12 meses do plano ao período anual documentado':'Correspondência dos 12 meses do plano a Janeiro–Dezembro de 2026'}<input value={b.mappingReference} onChange={e=>save({...b,mappingReference:e.target.value})}/></label>
<label className="block">{b.periodsFx?'Correspondência do plano ao período documentado':b.schedules?'Correspondência dos12 meses do plano ao período anual documentado':'Correspondência dos 12 meses do plano a Janeiro–Dezembro de 2026'}<input value={b.mappingReference} onChange={e=>save({...b,mappingReference:e.target.value})}/></label>
<label className="block">Âmbito<select value={b.scope} onChange={e=>save({...b,scope:e.target.value as NormalBook['scope']})}><option value="">Por identificar</option><option value="resident_general">Sujeito residente — regime geral, fora de transparência e regimes especiais</option><option value="ep_general">Não residente com estabelecimento estável — regime geral, só resultado imputável ao EP (mapas complementares)</option><option value="other">Outro âmbito — por corresponder</option></select></label>
<label className="block">Suporte do âmbito e da imputação integral do resultado ao sujeito/EP<input value={b.scopeReference} onChange={e=>save({...b,scopeReference:e.target.value})}/></label>
<button type="button" onClick={()=>save({...b,rows:[...b.rows,newNormalAdjustment(crypto.randomUUID())]})}>Acrescentar ajustamento ao resultado</button>
@@ -31,6 +32,7 @@ export function SimulationIrpcNormal({draft,onChange}:{draft:Record<string,unkno
<p>Depreciação positiva e juros financeiros não integralmente acrescidos exigem correspondência própria;uma declaração de ausência não apaga os valores do plano.</p>
{profitTopics.map(([k,label])=>{const t=b.profit!.topics[k];const change=(p:Partial<ProfitEvidence>)=>profit({topics:{...b.profit!.topics,[k]:{...t,...p}}});return<fieldset key={k} className="rounded border p-2"><legend>{label}</legend><label className="block">Estado — {label}<select aria-label={'Estado — '+label} value={t.state} onChange={e=>change({state:e.target.value as ProfitEvidence['state']})}><option value="">Informação em falta</option><option value="known">Valor conhecido</option><option value="absent">Ausência declarada com suporte</option></select></label>{(t.state==='known'||t.amount!=='')&&<label className="block">Montante conhecido — {label}<input value={t.amount} onChange={e=>change({amount:e.target.value})}/></label>}<label className="block">Suporte — {label}<input value={t.reference} onChange={e=>change({reference:e.target.value})}/></label></fieldset>;})}
</fieldset>}
<SimulationIrpcPeriodFx draft={draft} book={b} save={save}/>
<SimulationIrpcSchedules book={b} save={save}/>
<SimulationIrpcDepreciation draft={draft} book={b} save={save}/>
<SimulationIrpcCosts draft={draft} book={b} save={save}/>
@@ -0,0 +1,21 @@
import {SimulationFxSelection} from '@/components/simulation-fx-selection';
import {rawNormalFinancialSource,type NormalBook} from '@/lib/irpc-normal';
import {newNormalPeriodFx,mapNormalPeriodFx,type NormalPeriodFxInput} from '@/lib/irpc-normal-period-fx';
import {irpcFxSources,irpcFxRow,updateIrpcFx} from '@/lib/irpc-fx';
export function SimulationIrpcPeriodFx({draft,book,save}:{draft:Record<string,unknown>;book:NormalBook;save:(b:NormalBook)=>void}){
const raw=rawNormalFinancialSource(draft),i=book.periodsFx;
if(!i)return<button type="button" onClick={()=>save({...book,periodsFx:newNormalPeriodFx()})}>Preparar período curto ou plano noutra moeda</button>;
const patch=(p:Partial<NormalPeriodFxInput>)=>save({...book,periodsFx:{...i,...p}}),model=mapNormalPeriodFx(draft,i,raw),sources=irpcFxSources(draft);
const text=(k:'from'|'to'|'planStart'|'reference'|'conversionReference'|'mznMapsReference',label:string)=><label className="block">{label}<input value={i[k]} onChange={e=>patch({[k]:e.target.value})}/></label>;
return<fieldset aria-label="Período e moeda L6" className="space-y-3 rounded border p-3"><legend>Período fiscal e conversão do plano — simulação L6</legend><p>Reutiliza os meses e valores originais do plano. Seleccione apenas os meses do período documentado;sem anualizar o resultado. Os mapas,activos e saldos fiscais permanecem emMZN e exigem reconciliação explícita. A conversão cambial para simulação usa o mecanismo existente; não confirma automaticamente a regra fiscal de conversão anual.</p>
<label className="block">Tipo de período<select value={i.kind} onChange={e=>patch({kind:e.target.value as NormalPeriodFxInput['kind']})}><option value="">Por identificar</option><option value="annual">Anual documentado — 12meses</option><option value="start">Início de actividade — 7(4)a</option><option value="cessation">Cessação de actividade — 7(4)b</option><option value="same_year">Início e fim de sujeição no mesmo exercício — 7(4)c</option><option value="transition">Transição para período autorizado/comunicado — 7(4)d</option></select></label>
{text('from','Início do período fiscal (AAAA-MM-DD)')}{text('to','Fim do período fiscal (AAAA-MM-DD)')}{text('planStart','Primeiro mês civil do plano (AAAA-MM-01)')}{text('reference','Suporte do evento,período e correspondência dos meses')}
{book.schedules&&<button type="button" onClick={()=>patch({from:i.from||book.schedules!.period.from,to:i.to||book.schedules!.period.to,reference:i.reference||book.schedules!.period.reference})}>Reutilizar datas e suporte já declarados nos campos vazios</button>}
{text('mznMapsReference','Reconciliação dos mapas,parcelas,activos e saldos existentes em MZN para este período')}
{raw?.currency!=='MZN'&&<>{text('conversionReference','Pressuposto documentado da conversão mensal para simulação')}
{[...new Set(model.rows.map(r=>r.period))].map(period=>{const date=model.rows.find(r=>r.period===period)?.date,entry=i.months.find(m=>m.period===period)??{period,currency:raw?.currency??''};const change=(p:Partial<typeof entry>)=>patch({months:[...i.months.filter(m=>m.period!==period),{...entry,...p}]});return<fieldset key={period} aria-label={'Câmbio do mês '+period} className="rounded border p-2"><legend>Mês{period} — {date} — {raw?.currency}</legend><label>Reutilizar selecção cambial existente<select value={entry.fxLink?.id??''} onChange={e=>{const s=sources.find(s=>s.id===e.target.value);if(s)change({currency:raw?.currency??'',fxSelection:s.selection,fxLink:{id:s.id,fingerprint:s.fingerprint}});}}><option value="">Seleccione uma origem documentada</option>{sources.map(s=><option value={s.id} key={s.id}>{s.label}</option>)}</select></label><SimulationFxSelection row={irpcFxRow('normal-'+period,entry)} onChange={p=>{if(p.fxSelection)change({...updateIrpcFx(entry,p.fxSelection),currency:raw?.currency??''});}}/></fieldset>;})}
</>}
{i.months.filter(m=>!model.rows.some(r=>r.period===m.period)).map(m=><p key={m.period}>Selecção conservada do mês{m.period},fora do período actual. <button type="button" onClick={()=>patch({months:i.months.filter(x=>x.period!==m.period)})}>Retirar selecção do mês{m.period} deste mapa</button></p>)}
<p>{model.months}meses;resultado convertido:{model.source?.accountingProfitBeforeTax??'Indeterminado'} MZN. {model.pending.join(' | ')}</p><p>Meses parciais,limites anuais por corresponder e contagem de perdas41(5) afectam apenas os respectivos casos. Colecta não equivale ao saldo final a pagar.</p>
</fieldset>;
}
@@ -7,7 +7,7 @@ export function SimulationIrpcSchedules({book,save}:{book:NormalBook;save:(b:Nor
const asset=(id:string,p:Partial<NormalAsset>)=>patch({assets:s.assets.map(a=>a.id===id?{...a,...p}:a)});
return<fieldset aria-label="Mapas complementares L6" className="space-y-3 rounded border p-3"><legend>Mapas complementares — regras ordinárias documentadas</legend>
<p>Reutiliza o resultado e as linhas do plano. As somas brutas de cada tema devem corresponder à conferência do lucro tributável. Os efeitos com sinal ficam discriminados; uma dúvida não apaga as parcelas independentes. L7 conserva benefícios e casos especiais; L8 conserva o fecho anual.</p>
<fieldset><legend>Período anual e correspondência dos meses</legend><label>Tipo de período<select aria-label="Tipo de período L6" value={s.period.kind} onChange={e=>patch({period:{...s.period,kind:e.target.value as NormalSchedules['period']['kind']}})}><option value="">Por identificar</option><option value="calendar">Ano civil — artigo7(1)</option><option value="authorised_annual">Anual diferente — autorização artigo7(2)</option><option value="ep_annual">Anual diferente de EP — comunicação artigo7(3)</option></select></label>{(['from','to','reference']as const).map(k=><label key={k} className="block">{({from:'Início do período AAAA-MM-DD',to:'Termo do período AAAA-MM-DD',reference:'Suporte do período e da correspondência mensal'})[k]}<input aria-label={'Período L6 '+k} value={s.period[k]} onChange={e=>patch({period:{...s.period,[k]:e.target.value}})}/></label>)}<p>Período anual diferente: documente autorização do artigo7(2) ou comunicação do EP do artigo7(3), início de aplicação e manutenção por cinco exercícios. O plano representa o período completo com termo em2026, emMZN. Períodos curtos e conversão fiscal anual de outra moeda ficam por corresponder.</p></fieldset>
{book.periodsFx&&book.periodsFx.kind!=='annual'?<p>Período actual no mapa «Período fiscal e conversão do plano». O período anual anterior permanece conservado no histórico.</p>:<fieldset><legend>Período anual e correspondência dos meses</legend><label>Tipo de período<select aria-label="Tipo de período L6" value={s.period.kind} onChange={e=>patch({period:{...s.period,kind:e.target.value as NormalSchedules['period']['kind']}})}><option value="">Por identificar</option><option value="calendar">Ano civil — artigo7(1)</option><option value="authorised_annual">Anual diferente — autorização artigo7(2)</option><option value="ep_annual">Anual diferente de EP — comunicação artigo7(3)</option></select></label>{(['from','to','reference']as const).map(k=><label key={k} className="block">{({from:'Início do período AAAA-MM-DD',to:'Termo do período AAAA-MM-DD',reference:'Suporte do período e da correspondência mensal'})[k]}<input aria-label={'Período L6 '+k} value={s.period[k]} onChange={e=>patch({period:{...s.period,[k]:e.target.value}})}/></label>)}<p>Período anual diferente: documente autorização do artigo7(2) ou comunicação do EP do artigo7(3), início de aplicação e manutenção por cinco exercícios. O plano representa o período completo com termo em2026, emMZN. Períodos curtos e conversão fiscal anual de outra moeda ficam por corresponder.</p></fieldset>}
<button type="button" onClick={()=>patch({rows:[...s.rows,newScheduleRow(crypto.randomUUID())]})}>Acrescentar regra complementar</button>
{s.rows.map((r,n)=><fieldset key={r.id} className="rounded border p-2"><legend>Regra complementar {n+1}</legend>
<label>Tratamento<select aria-label={'Tratamento complementar '+(n+1)} value={r.rule} onChange={e=>row(r.id,{rule:e.target.value as ScheduleRow['rule']})}><option value="">Por identificar</option>{Object.entries(scheduleRules).map(([k,v])=><option key={k} value={k}>{v[0]} — {v[2]}</option>)}</select></label>
+3 -3
View File
@@ -5,7 +5,7 @@ export type NormalAssessmentInput={version:1;treatment:''|'general_61_1'|'other'
export const newNormalAssessmentInput=():NormalAssessmentInput=>({version:1,treatment:'',reference:''});
export function validateNormalAssessment(i:NormalAssessmentInput){if(!i||i.version!==1||!['','general_61_1','other'].includes(i.treatment)||typeof i.reference!=='string')throw Error('Enquadramento da colecta não recuperável;original conservado.');}
export type NormalAssessmentModel={input:NormalAssessmentInput;period:{year:string;from:string;to:string};classification:string;rate:FiscalFraction|null;taxableBase:FiscalFraction|null;colecta:FiscalFraction|null;centExact:boolean|null;pending:string[];rule:string;source:string;finalBalance:{scope:'L8';status:'not_calculated';payable:null;recoverable:null;reason:string}};
export function calculateNormalAssessment(input:NormalAssessmentInput,base:FiscalFraction|null,context:{year:string;scope:string;currency:string;extended?:boolean;period?:{from:string;to:string}}):NormalAssessmentModel{
export function calculateNormalAssessment(input:NormalAssessmentInput,base:FiscalFraction|null,context:{year:string;scope:string;currency:string;extended?:boolean;short?:boolean;period?:{from:string;to:string}}):NormalAssessmentModel{
validateNormalAssessment(input);const pending:string[]=[],period={year:context.year,from:/^20\d{2}$/.test(context.year)?context.year+'-01-01':'',to:/^20\d{2}$/.test(context.year)?context.year+'-12-31':''};
if(context.year!=='2026')pending.push('Taxa geral correspondida neste incremento apenas ao exercício2026;sem taxa transportada para outro período.');
if(!(context.scope==='resident_general'||context.extended&&context.scope==='ep_general')||context.currency!=='MZN')pending.push('Colecta delimitada ao sujeito residente no regime geral emMZN;outros âmbitos por corresponder.');
@@ -13,8 +13,8 @@ export function calculateNormalAssessment(input:NormalAssessmentInput,base:Fisca
if(!input.reference.trim())pending.push('Identifique suporte ou pressuposto da aplicação da taxa geral ao caso;uma actividade semelhante não prova o regime.');
if(base===null)pending.push('Matéria colectável actual indeterminada;lucro tributável não é substituto da base.');
else if(!/^-?\d{1,80}$/.test(base.numerator)||!/^\d{1,80}$/.test(base.denominator)||BigInt(base.denominator)===0n||BigInt(base.numerator)<0n)pending.push('Matéria colectável deve ser um valor conhecido não negativo e reconciliado.');
if(context.period){period.from=context.period.from;period.to=context.period.to;const start=new Date(period.from+'T00:00:00Z'),end=new Date(period.to+'T00:00:00Z');if(!Number.isFinite(+start)||!Number.isFinite(+end)||start.toISOString().slice(0,10)!==period.from||end.toISOString().slice(0,10)!==period.to||!period.from.endsWith('-01')||!period.to.startsWith('2026-'))pending.push('Período fiscal anual não reconciliado.');else{start.setUTCFullYear(start.getUTCFullYear()+1);start.setUTCDate(start.getUTCDate()-1);if(start.toISOString().slice(0,10)!==period.to)pending.push('Colecta anual: período de12 meses por reconciliar.');}}
if(context.period){period.from=context.period.from;period.to=context.period.to;const start=new Date(period.from+'T00:00:00Z'),end=new Date(period.to+'T00:00:00Z');if(!Number.isFinite(+start)||!Number.isFinite(+end)||start.toISOString().slice(0,10)!==period.from||end.toISOString().slice(0,10)!==period.to||!period.from.endsWith('-01')||!period.to.startsWith('2026-'))pending.push('Período fiscal anual não reconciliado.');else if(context.short){if(period.from>period.to||!period.from.startsWith('2026-')||period.from===context.year+'-01-01'&&period.to===context.year+'-12-31')pending.push('Período curto por reconciliar.');}else{start.setUTCFullYear(start.getUTCFullYear()+1);start.setUTCDate(start.getUTCDate()-1);if(start.toISOString().slice(0,10)!==period.to)pending.push('Colecta anual: período de12 meses por reconciliar.');}}
const rate=input.treatment==='general_61_1'&&context.year==='2026'?fraction(32n):null;
const colecta=pending.length===0&&base!==null&&rate?fraction(BigInt(base.numerator)*32n,BigInt(base.denominator)*100n):null;
return{input:structuredClone(input),period,classification:input.treatment==='general_61_1'?'Regime normal — taxa geral do artigo61(1)':input.treatment==='other'?'Caso especial fora deste recorte':'Enquadramento por identificar',rate,taxableBase:base,colecta,centExact:colecta?(BigInt(colecta.numerator)*100n)%BigInt(colecta.denominator)===0n:null,pending,rule:'CIRPC61(1)-general-2026/1',source:context.extended?'CIRPC61(1), taxa geral32% mantida pelos diplomas inventariados até Lei12/2025; período anual documentado com termo em2026. CIRPC15(1)a/c,7(1–3): sujeito residente ou resultado imputável aEP no regime geral; ano civil, autorização ou comunicação expressamente identificadas. Sem arredondamento fiscal ou saldoL8 presumidos.':'CIRPC Lei34/2007 art61(1),PDF17/p768(71):taxa geral32%. Lei4/2012 altera61(2);Lei20/2022 PDF1/p2731 mantém61(1),altera61(2) com termo31/12/2025;Lei12/2025 PDF18/20 não altera61(1),adita61-A para mais-valias,fora deste recorte. Exercício anual2026/MZN/residente geral,matéria colectável actual×32/100 em aritmética racional exacta. Sem arredondamento fiscal ou transcriçãoM20/M22 presumidos.',finalBalance:{scope:'L8',status:'not_calculated',payable:null,recoverable:null,reason:'Saldo final a pagar ou recuperar por determinar emL8:retenções,pagamentos e créditos do fecho anual não apurados nem abatidos à colecta neste incremento. Colecta não equivale ao saldo final.'}};
return{input:structuredClone(input),period,classification:input.treatment==='general_61_1'?'Regime normal — taxa geral do artigo61(1)':input.treatment==='other'?'Caso especial fora deste recorte':'Enquadramento por identificar',rate,taxableBase:base,colecta,centExact:colecta?(BigInt(colecta.numerator)*100n)%BigInt(colecta.denominator)===0n:null,pending,rule:'CIRPC61(1)-general-2026/1',source:context.short?'CIRPC7(4)/61(1):período curto documentado em2026,matéria colectável efectiva ×32%;sem anualizar lucro/base ou proporcionalizar a taxa;saldo finalL8 indeterminado.':context.extended?'CIRPC61(1), taxa geral32% mantida pelos diplomas inventariados até Lei12/2025; período anual documentado com termo em2026. CIRPC15(1)a/c,7(1–3): sujeito residente ou resultado imputável aEP no regime geral; ano civil, autorização ou comunicação expressamente identificadas. Sem arredondamento fiscal ou saldoL8 presumidos.':'CIRPC Lei34/2007 art61(1),PDF17/p768(71):taxa geral32%. Lei4/2012 altera61(2);Lei20/2022 PDF1/p2731 mantém61(1),altera61(2) com termo31/12/2025;Lei12/2025 PDF18/20 não altera61(1),adita61-A para mais-valias,fora deste recorte. Exercício anual2026/MZN/residente geral,matéria colectável actual×32/100 em aritmética racional exacta. Sem arredondamento fiscal ou transcriçãoM20/M22 presumidos.',finalBalance:{scope:'L8',status:'not_calculated',payable:null,recoverable:null,reason:'Saldo final a pagar ou recuperar por determinar emL8:retenções,pagamentos e créditos do fecho anual não apurados nem abatidos à colecta neste incremento. Colecta não equivale ao saldo final.'}};
}
+2 -1
View File
@@ -15,7 +15,7 @@ export type NormalBaseModel={input:NormalBaseInput;taxableProfit:FiscalFraction|
const zero=()=>fraction(0n);
function amount(s:string){if(!/^\d{1,18}(?:\.\d{1,2})?$/.test(s))throw Error();return decimalFraction(s);}
const units=(f:FiscalFraction)=>BigInt(f.numerator)*100n/BigInt(f.denominator);
export function calculateNormalBase(input:NormalBaseInput,profit:FiscalFraction|null,year:string,currentLossReference?:string):NormalBaseModel{
export function calculateNormalBase(input:NormalBaseInput,profit:FiscalFraction|null,year:string,currentLossReference?:string,short=false):NormalBaseModel{
validateNormalBase(input);const i=structuredClone(input),pending:string[]=[],losses:NormalBaseModel['losses']=[];
if(year!=='2026')pending.push('Matéria colectável delimitada ao exercício2026.');
if(!/^\d{9}$/.test(i.subjectNuit)||!i.identityReference.trim())pending.push('Identifique NUIT e suporte do sujeito passivo do lucro e das perdas.');
@@ -45,6 +45,7 @@ export function calculateNormalBase(input:NormalBaseInput,profit:FiscalFraction|
if(issues.length){r.status='indeterminate';r.reason=issues.join(' ');pending.push(l.year+': '+r.reason);}
losses.push(r);
}
if(short&&losses.some(l=>l.requestedAmount&&BigInt(l.requestedAmount.numerator)>0n))pending.push('Período curto:sequência dos períodos posteriores por reconciliar;41(5) exclui da contagem o período de transição7(4)d inferior a6meses,sem agregar todos os períodos curtos ou presumir prazo por ano civil.');
const requestedLosses=addFractions(losses.filter(l=>l.status==='eligible').map(l=>l.requestedAmount!));
if(profit&&BigInt(requestedLosses.numerator)*BigInt(profit.denominator)>(BigInt(profit.numerator)>0n?BigInt(profit.numerator)*BigInt(requestedLosses.denominator):0n))pending.push('Utilizações propostas excedem o lucro tributável disponível;reparta explicitamente sem criar base negativa ou escolher uma ordem de perdas.');
let benefitDeduction:FiscalFraction|null=null;const benefit=i.benefits;
+2 -1
View File
@@ -36,7 +36,7 @@ const min=(...v:FiscalFraction[])=>v.reduce((a,b)=>cmp(a,b)<0n?a:b),max0=(v:Fisc
const costMoney=(v:FiscalFraction|null)=>{if(!v)return'Indeterminado';const p=fractionPresentation(v);return(p.approximate?'≈':'')+p.decimal+' MZN'+(p.approximate?' (exacto '+v.numerator+'/'+v.denominator+')':'');};
const identity=(r:{document:string;line:string})=>r.document.trim()+'\u0000'+r.line.trim();
export function reusableCreditBalance(source:Source|null){const l=source?.cashSchedule?.at(-1);if(!l||l.salesReceivablesBalance===null)return null;try{return addFractions([fiscalMoney(l.salesReceivablesBalance),fiscalMoney(l.openingReceivablesBalance)]);}catch{return null;}}
export function calculateNormalCosts(input:NormalCostsInput,source:Source|null,existing:CostClaim[],period:{from:string;to:string}){
export function calculateNormalCosts(input:NormalCostsInput,source:Source|null,existing:CostClaim[],period:{from:string;to:string},short=false){
validateNormalCosts(input);const rows=input.rows,claims=rows.flatMap(r=>[{...r},...(r.rule==='work'?[{id:r.id,document:r.document,line:r.line+'/custos',sourceCode:r.facts.costSource??'',amount:r.facts.bookCost??''}]:[])]),pending:string[]=[];
const sourceAmount=(code:string,effect:string)=>{const l=source?.accountingLines.find(l=>l.code===code);if(!l||l.accountingEffect!==effect)throw Error('Seleccione uma linha contabilística compatível.');return fiscalMoney(l.total);};
const pool=(rule:CostRule)=>addFractions(rows.filter(r=>r.rule===rule).map(r=>fiscalMoney(r.amount)));
@@ -50,6 +50,7 @@ export function calculateNormalCosts(input:NormalCostsInput,source:Source|null,e
if(!rule||!r.document.trim()||!r.line.trim()||!r.reference.trim()||r.conditions!=='yes')issues.push('Regra, documento/linha, condições e suporte em falta.');
if([...existing,...claims.filter(c=>c!==r&&c.id!==r.id)].some(c=>identity(c)===identity(r)))issues.push('Documento/linha repetidos entre mapas.');
if(claims.filter(c=>identity(c)===identity(r)).length>1)issues.push('Documento/linha repetidos no mapa.');
if(short&&['credit','advertising'].includes(r.rule))issues.push('Limite anual neste período curto por corresponder;sem proporcionalização ou anualização presumida.');
if(source?.currency!=='MZN'||!period.to.startsWith('2026-'))issues.push('Recorte anual emMZN com termo em2026 por reconciliar.');
try{amount=fiscalMoney(r.amount);if(cmp(amount,z())<=0n&&r.rule!=='work')throw Error('Identifique parcela positiva conhecida.');
const isIncome=['provision_reversal','taxed_reversal','prior_income','work'].includes(r.rule);const omitted=r.rule==='provision_reversal'&&f.inResult==='no';if(!omitted)checkOrigin(r.sourceCode,amount,isIncome?'income':'expense');else if(r.sourceCode)throw Error('Reversão não incluída no resultado não deve repetir uma linha de proveitos.');
+3 -2
View File
@@ -30,11 +30,12 @@ const ratio=(a:FiscalFraction,b:FiscalFraction)=>fraction(BigInt(a.numerator)*Bi
const date=(v:string)=>/^20\d{2}-\d{2}-\d{2}$/.test(v)&&Number.isFinite(Date.parse(v))&&new Date(v).toISOString().slice(0,10)===v;
const month=(v:string)=>Number(v.slice(0,4))*12+Number(v.slice(5,7))-1;
const show=(v:FiscalFraction|null)=>{if(!v)return'Indeterminado';const p=fractionPresentation(v);return(p.approximate?'≈':'')+p.decimal+' MZN'+(p.approximate?' (exacto '+v.numerator+'/'+v.denominator+')':'');};
export function calculateNormalDepreciation(input:NormalDepreciationInput,source:{currency:string;accountingLines:Array<{code:string;total:string}>}|null,period:{from:string;to:string},claims:Array<{sourceCode:string;document:string;line:string;amount:string}>){
export function calculateNormalDepreciation(input:NormalDepreciationInput,source:{currency:string;accountingLines:Array<{code:string;total:string}>}|null,period:{from:string;to:string},claims:Array<{sourceCode:string;document:string;line:string;amount:string}>,short=false){
validateNormalDepreciation(input);const pending:string[]=[];
if(!input.completenessReference.trim())pending.push('Reconciliação integral dos activos e quotas anteriores com a depreciação da origem em falta.');
if(source?.currency!=='MZN'||!date(period.from)||!date(period.to)||!period.to.startsWith('2026-')||month(period.to)-month(period.from)!==11)pending.push('Período anual emMZN com termo em2026 por reconciliar; períodos curtos permanecem separados.');
if(source?.currency!=='MZN'||!date(period.from)||!date(period.to)||!period.to.startsWith('2026-')||(!short&&month(period.to)-month(period.from)!==11||short&&(month(period.to)<month(period.from)||month(period.to)-month(period.from)>=11)))pending.push('Período anual emMZN com termo em2026 por reconciliar; períodos curtos permanecem separados.');
const assets=input.assets.map(a=>{const issues:string[]=[],notes:string[]=[];let accounting:FiscalFraction|null=null,basis:FiscalFraction|null=null,taxBasis:FiscalFraction|null=null,quota:FiscalFraction|null=null,deduction:FiscalFraction|null=null,adjustment:FiscalFraction|null=null,priorUsed:FiscalFraction|null=null,priorRemaining:FiscalFraction|null=null,remainingFiscal:FiscalFraction|null=null,eligibleCurrent:FiscalFraction|null=null,months:number|null=null;const entry=Object.hasOwn(depreciationCatalog,a.category)?depreciationCatalog[a.category as DepreciationCategory]:null;
if(short&&a.category!=='nondepreciable'&&(a.timing!=='months'||a.method==='low_value'))issues.push('Período curto exige duodécimos;opção integral art17 neste período por corresponder.');
const money=(s:string,label:string)=>{if(!s.trim())throw Error(label+' em falta; não presumir zero.');return fiscalMoney(s);};
const yesno=(v:string,label:string)=>{if(!['yes','no'].includes(v))throw Error(label+' por documentar.');return v==='yes';};
if(!a.assetKey.trim()||!a.description.trim()||!a.document.trim()||!a.line.trim()||!a.reference.trim()||a.conditions!=='yes')issues.push('Identificação do activo, documento/linha, condições ordinárias e suporte em falta.');
+39
View File
@@ -0,0 +1,39 @@
import {addFractions,fraction,type FiscalFraction} from './fiscal-rational.ts';
import {convertIrpcAmount,irpcFxDependency,type IrpcFxAmount,type IrpcFxEvidence,irpcFxEvidenceLabel} from './irpc-fx.ts';
import type {rawNormalFinancialSource} from './irpc-normal.ts';
export const normalPeriodFxVersion='IRPC-normal-adjustments/8';
export type NormalPeriodFxInput={version:1;kind:''|'annual'|'start'|'cessation'|'same_year'|'transition';from:string;to:string;planStart:string;reference:string;conversionReference:string;mznMapsReference:string;months:Array<IrpcFxAmount&{period:number}>};
export const newNormalPeriodFx=():NormalPeriodFxInput=>({version:1,kind:'',from:'',to:'',planStart:'',reference:'',conversionReference:'',mznMapsReference:'',months:[]});
export function validateNormalPeriodFx(i:NormalPeriodFxInput){
if(!i||i.version!==1||!['','annual','start','cessation','same_year','transition'].includes(i.kind)||['from','to','planStart','reference','conversionReference','mznMapsReference'].some(k=>typeof i[k as 'from']!=='string')||!Array.isArray(i.months)||new Set(i.months.map(m=>m.period)).size!==i.months.length||i.months.some(m=>!Number.isSafeInteger(m.period)||m.period<1||typeof m.currency!=='string'||m.fxSelection!==undefined&&(!m.fxSelection||typeof m.fxSelection!=='object')||m.fxLink!==undefined&&(!m.fxLink||typeof m.fxLink.id!=='string'||typeof m.fxLink.fingerprint!=='string')))throw Error('Correspondência do período/moeda não recuperável;originais preservados.');
}
const iso=(s:string)=>/^20\d{2}-\d{2}-\d{2}$/.test(s)&&Number.isFinite(Date.parse(s))&&new Date(s).toISOString().slice(0,10)===s;
const month=(s:string)=>Number(s.slice(0,4))*12+Number(s.slice(5,7))-1;
const monthEnd=(s:string)=>{const d=new Date(s+'T00:00:00Z');d.setUTCMonth(d.getUTCMonth()+1,0);return d.toISOString().slice(0,10);};
const amount=(v:FiscalFraction)=>{const n=BigInt(v.numerator)*100n,d=BigInt(v.denominator);if(n%d!==0n)throw Error('Conversão com fracção de cêntimo: representação fiscal por corresponder,sem arredondamento novo.');const c=n/d,a=c<0n?-c:c;return(c<0n?'-':'')+a/100n+'.'+String(a%100n).padStart(2,'0');};
type Source=NonNullable<ReturnType<typeof rawNormalFinancialSource>>;
export function mapNormalPeriodFx(d:Record<string,unknown>,input:NormalPeriodFxInput,raw:Source|null){
validateNormalPeriodFx(input);const pending:string[]=[],short=input.kind!==''&&input.kind!=='annual';let count=0;
if(!input.kind||!input.reference.trim())pending.push('Tipo,evento e suporte da correspondência do período por identificar.');
if(!iso(input.from)||!iso(input.to)||input.from>input.to||!input.from.endsWith('-01')||monthEnd(input.to.slice(0,7)+'-01')!==input.to||!input.to.startsWith('2026-'))pending.push('Recorte:meses completos,termo em2026;datas parciais ou inválidas permanecem por corresponder.');
else{count=month(input.to)-month(input.from)+1;if(count<1||count>12||short&&count>=12||!short&&count!==12)pending.push('Número de meses incompatível com o tipo de período.');}
if(short&&(!input.from.startsWith('2026-')||input.kind==='start'&&input.to!=='2026-12-31'||['cessation','transition'].includes(input.kind)&&input.from!=='2026-01-01'))pending.push('Datas incompatíveis com início,cessação ou transição no ano civil2026.');
if(!iso(input.planStart)||!input.planStart.endsWith('-01'))pending.push('Identifique o primeiro mês civil do plano;meses orçamentais não têm data fiscal presumida.');
if(!input.mznMapsReference.trim())pending.push('Confirme que parcelas,activos,quotas,saldos e mapas fiscais existentes estão reconciliados emMZN com este período;sem converter novamente esses dados.');
if(!raw)pending.push('Plano financeiro reconciliado em falta.');
const selected=raw?.accountingLines[0]?.monthlyAmounts.filter(m=>iso(input.planStart)&&month(input.planStart)+m.period-1>=month(input.from)&&month(input.planStart)+m.period-1<=month(input.to))??[];
if(selected.length!==count||!count)pending.push('Meses do plano não cobrem integralmente o período indicado;sem anualização ou preenchimento de meses em falta.');
if(raw?.currency!=='MZN'&&!input.conversionReference.trim())pending.push('Identifique pressuposto documentado da conversão mensal para a simulação;selecção cambial não confirma,por si,a regra fiscal de conversão anual.');
for(const m of input.months)if(!selected.some(x=>x.period===m.period))pending.push('Selecção cambial sem mês seleccionado no período: '+m.period);
const rows:Array<{code:string;period:number;date:string;originalAmount:string;originalCurrency:string;value:FiscalFraction|null;evidence:IrpcFxEvidence|null;reason:string}>=[];
const lines=raw?.accountingLines.map(l=>{const monthlyAmounts=l.monthlyAmounts.filter(m=>selected.some(x=>x.period===m.period)).map(m=>{let value:FiscalFraction|null=null,evidence:IrpcFxEvidence|null=null,reason='';const day=iso(input.planStart)?new Date(Date.UTC(Number(input.planStart.slice(0,4)),Number(input.planStart.slice(5,7))-1+m.period-1,1)).toISOString().slice(0,10):'';
try{const sign=m.amount.startsWith('-')?-1n:1n,original=sign<0n?m.amount.slice(1):m.amount;const selection=input.months.find(x=>x.period===m.period);if(raw.currency!=='MZN'&&(!selection||selection.currency!==raw.currency))throw Error('Selecção cambial deste mês/moeda em falta;sem usar taxa de outro mês por defeito.');const converted=convertIrpcAmount(d,original,selection??{currency:'MZN'});value=fraction(BigInt(converted.value.numerator)*sign,BigInt(converted.value.denominator));evidence=converted.evidence;amount(value);}catch(e){value=null;reason=String(e);pending.push(l.code+'/mês'+m.period+': '+reason);}
rows.push({code:l.code,period:m.period,date:day,originalAmount:m.amount,originalCurrency:raw.currency,value,evidence,reason});return{period:m.period,amount:value?amount(value):'0.00'};});
const total=addFractions(rows.filter(r=>r.code===l.code&&r.value).map(r=>r.value!));return{...l,monthlyAmounts,total:amount(total)};
})??[];
const ready=pending.length===0;const profit=addFractions(lines.map(l=>{const f=rows.filter(r=>r.code===l.code&&r.value).map(r=>r.value!);const sum=addFractions(f);return l.accountingEffect==='income'?sum:fraction(-BigInt(sum.numerator),BigInt(sum.denominator));}));
return{input:structuredClone(input),short,months:count,originalCurrency:raw?.currency??'',rows,pending,source:ready&&raw?{...raw,currency:'MZN',cashSchedule:raw.currency==='MZN'?raw.cashSchedule.filter(r=>selected.some(m=>m.period===r.period)):[],periods:count,accountingLines:lines,accountingProfitBeforeTax:amount(profit)}:null,rule:'CIRPC7(4)a–d;Decreto72/2013 art9(3)a–c para os casos correspondidos. Meses completos do plano,sem anualização. Conversão para simulação reutiliza mecanismo cambial entregue e pressupostos explícitos;sem confirmação fiscal anual presumida.'};
}
export type NormalPeriodFxModel=ReturnType<typeof mapNormalPeriodFx>;
export function normalPeriodFxDependency(d:Record<string,unknown>,i:NormalPeriodFxInput){return i.months.map(m=>irpcFxDependency(d,m)).filter(Boolean).join(' | ');}
export function normalPeriodFxReport(m:NormalPeriodFxModel):Array<[string,string,string,string]>{return[['IRPC normal — L6','Período e moeda do plano',`${m.input.from}–${m.input.to};${m.months}meses;origem:${m.originalCurrency};resultado emMZN:${m.source?.accountingProfitBeforeTax??'Indeterminado'};${m.originalCurrency!=='MZN'?'Conversão para simulação com pressupostos documentados;regra fiscal anual não confirmada automaticamente.':''}${m.pending.join(' | ')}`,`${m.input.reference};${m.input.conversionReference};${m.input.mznMapsReference};${m.rule}`],...m.rows.map(r=>['IRPC normal — L6',`${({revenue:'Receitas',cost_of_sales:'Custo das vendas',operating_expenses:'Despesas operacionais',personnel:'Pessoal',depreciation:'Depreciação contabilística',interest_expense:'Juros reconhecidos'}as Record<string,string>)[r.code]??r.code}/mês${r.period} (${r.date})`,`Original:${r.originalAmount} ${r.originalCurrency};MZN:${r.value?amount(r.value):'Indeterminado'};${r.reason}`,r.evidence?irpcFxEvidenceLabel(r.evidence):'OrigemMZN;sem conversão']as[string,string,string,string])];}
+22 -14
View File
@@ -1,3 +1,4 @@
import {normalPeriodFxVersion,validateNormalPeriodFx,mapNormalPeriodFx,normalPeriodFxDependency,normalPeriodFxReport,type NormalPeriodFxInput,type NormalPeriodFxModel} from './irpc-normal-period-fx.ts';
import {normalDepreciationVersion,validateNormalDepreciation,calculateNormalDepreciation,normalDepreciationReport,type NormalDepreciationInput,type NormalDepreciationModel} from './irpc-normal-depreciation.ts';
import {normalCostsVersion,validateNormalCosts,calculateNormalCosts,normalCostReport,type NormalCostsInput,type NormalCostsModel} from './irpc-normal-costs.ts';
import {normalFormFields} from './irpc-normal-forms.ts';
@@ -13,18 +14,20 @@ import {addFractions,decimalFraction,fraction,fractionPresentation,type FiscalFr
export const irpcNormalVersion='IRPC-normal-adjustments/1';
export const irpcNormalSource='CIRPC Lei34/2007 art17(1),PDF6/p768(60):resultado e correcções;art36(1)c,PDF10/p768(64):multas e encargos por infracções sem origem contratual,incluindo juros compensatórios. Lei4/2012 PDF2 altera17(6)/acrescenta36(1)o;Lei19/2013 PDF2 mantém36(1)a–o e acrescenta p/q. Lei12/2025 altera3/5/20/62/67/75,adita61-A e revoga39/47/75(2)/76;sem alteração deste recorte36(1)c. Não é apuramento integral nem Modelo20/22.';
export type NormalAdjustment={id:string;sourceCode:string;document:string;line:string;amount:string;rule:''|'noncontractual_fine'|'compensatory_interest'|'other';included:''|'yes'|'no';reference:string};
export type NormalBook={version:1;year:string;mappingReference:string;scope:''|'resident_general'|'ep_general'|'other';scopeReference:string;rows:NormalAdjustment[];profit?:NormalProfitInput;base?:NormalBaseInput;assessment?:NormalAssessmentInput;schedules?:NormalSchedules;costs?:NormalCostsInput;depreciation?:NormalDepreciationInput;result?:NormalRecord;history?:NormalRecord[]};
type NormalModel={depreciation?:NormalDepreciationModel;costs?:NormalCostsModel;fiscalPeriod?:ReturnType<typeof normalSchedulePeriod>;schedules?:SchedulesModel;currentLoss?:FiscalFraction|null;assessment?:NormalAssessmentModel;colecta?:FiscalFraction|null;base?:NormalBaseModel;profit?:NormalProfitModel;version:string;year:string;currency:string;origin:'projected';accountingProfit:FiscalFraction|null;additions:FiscalFraction;partialAdjustedResult:FiscalFraction|null;taxableProfit:FiscalFraction|null;taxableBase:FiscalFraction|null;tax:null;rows:Array<NormalAdjustment&{status:'calculated'|'indeterminate'|'outside_scope';adjustment:FiscalFraction|null;reason:string}>;pending:string[];source:string};
export type NormalBook={version:1;year:string;mappingReference:string;scope:''|'resident_general'|'ep_general'|'other';scopeReference:string;rows:NormalAdjustment[];profit?:NormalProfitInput;base?:NormalBaseInput;assessment?:NormalAssessmentInput;schedules?:NormalSchedules;costs?:NormalCostsInput;depreciation?:NormalDepreciationInput;periodsFx?:NormalPeriodFxInput;result?:NormalRecord;history?:NormalRecord[]};
type NormalModel={periodsFx?:NormalPeriodFxModel;depreciation?:NormalDepreciationModel;costs?:NormalCostsModel;fiscalPeriod?:{kind:string;from:string;to:string;reference:string;pending:string[]};schedules?:SchedulesModel;currentLoss?:FiscalFraction|null;assessment?:NormalAssessmentModel;colecta?:FiscalFraction|null;base?:NormalBaseModel;profit?:NormalProfitModel;version:string;year:string;currency:string;origin:'projected';accountingProfit:FiscalFraction|null;additions:FiscalFraction;partialAdjustedResult:FiscalFraction|null;taxableProfit:FiscalFraction|null;taxableBase:FiscalFraction|null;tax:null;rows:Array<NormalAdjustment&{status:'calculated'|'indeterminate'|'outside_scope';adjustment:FiscalFraction|null;reason:string}>;pending:string[];source:string};
type NormalRecord={key:string;createdAt:string;model:NormalModel};
export const newNormalBook=():NormalBook=>({version:1,year:'',mappingReference:'',scope:'',scopeReference:'',rows:[]});
export const newNormalAdjustment=(id:string):NormalAdjustment=>({id,sourceCode:'',document:'',line:'',amount:'',rule:'',included:'',reference:''});
export function normalFinancialSource(d:Record<string,unknown>){
export function rawNormalFinancialSource(d:Record<string,unknown>){
const p=restoreBusinessPlanSnapshot(String(d.business_plan_snapshot??''));if(!p)return null;
const forecast=forecastBusinessPlan(p),bridge=buildFinancialFiscalBridge(forecast);
return{...bridge,periods:forecast.rows.length,origin:'projected' as const,scope:acquisitionScope(d)};
}
export function normalFinancialSource(d:Record<string,unknown>){const raw=rawNormalFinancialSource(d),input=normalBook(d).periodsFx;return input?mapNormalPeriodFx(d,input,raw).source:raw;}
function store(d:Record<string,unknown>){const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books||typeof s.books!=='object'||Array.isArray(s.books))throw Error('Livros fiscais não recuperáveis;originais preservados.');return s;}
export function validateNormal(b:NormalBook){
if(b?.periodsFx!==undefined)validateNormalPeriodFx(b.periodsFx);
if(b?.depreciation!==undefined)validateNormalDepreciation(b.depreciation);
if(b?.costs!==undefined)validateNormalCosts(b.costs);
if(b?.schedules!==undefined)validateSchedules(b.schedules);
@@ -34,23 +37,27 @@ export function validateNormal(b:NormalBook){
if(!b||b.version!==1||['year','mappingReference','scopeReference'].some(k=>typeof b[k as 'year']!=='string')||!['','resident_general','ep_general','other'].includes(b.scope)||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history))throw Error('Preparação IRPC normal não recuperável.');
for(const r of b.rows)if(!r||Object.keys(newNormalAdjustment('')).some(k=>typeof r[k as keyof NormalAdjustment]!=='string')||!['','noncontractual_fine','compensatory_interest','other'].includes(r.rule)||!['','yes','no'].includes(r.included))throw Error('Ajustamento IRPC não recuperável;original conservado.');
if(new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('Identificador de ajustamento duplicado.');
for(const r of [b.result,...(b.history??[])])if(r&&(!r.model||![irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion,normalDepreciationVersion].includes(r.model.version)||typeof r.key!=='string'||typeof r.createdAt!=='string'||!Array.isArray(r.model.rows)||!Array.isArray(r.model.pending)))throw Error('Histórico IRPC normal não recuperável.');
for(const r of [b.result,...(b.history??[])])if(r&&(!r.model||![irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion,normalDepreciationVersion,normalPeriodFxVersion].includes(r.model.version)||typeof r.key!=='string'||typeof r.createdAt!=='string'||!Array.isArray(r.model.rows)||!Array.isArray(r.model.pending)))throw Error('Histórico IRPC normal não recuperável.');
}
export function normalBook(d:Record<string,unknown>):NormalBook{
const m=store(d).irpcNormal;if(m===undefined)return newNormalBook();if(!m||typeof m!=='object'||Array.isArray(m))throw Error('Mapa IRPC normal não recuperável.');const b=m[acquisitionScope(d)]??newNormalBook();validateNormal(b);return b;
}
export function saveNormalBook(d:Record<string,unknown>,b:NormalBook){validateNormal(b);const s=store(d);if(s.irpcNormal!==undefined&&(!s.irpcNormal||typeof s.irpcNormal!=='object'||Array.isArray(s.irpcNormal)))throw Error('Mapa IRPC normal não recuperável.');return JSON.stringify({...s,irpcNormal:{...s.irpcNormal,[acquisitionScope(d)]:b}});}
function key(d:Record<string,unknown>,b:NormalBook){return JSON.stringify([b.depreciation?normalDepreciationVersion:b.costs?normalCostsVersion:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,acquisitionScope(d),d.business_plan_snapshot??'',b.year,b.mappingReference,b.scope,b.scopeReference,b.rows,...(b.profit?[b.profit]:[]),...(b.base?[b.base]:[]),...(b.assessment?[b.assessment]:[]),...(b.schedules?[b.schedules]:[]),...(b.costs?[b.costs]:[]),...(b.depreciation?[b.depreciation]:[])]);}
function key(d:Record<string,unknown>,b:NormalBook){return JSON.stringify([b.periodsFx?normalPeriodFxVersion:b.depreciation?normalDepreciationVersion:b.costs?normalCostsVersion:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,acquisitionScope(d),d.business_plan_snapshot??'',b.year,b.mappingReference,b.scope,b.scopeReference,b.rows,...(b.profit?[b.profit]:[]),...(b.base?[b.base]:[]),...(b.assessment?[b.assessment]:[]),...(b.schedules?[b.schedules]:[]),...(b.costs?[b.costs]:[]),...(b.depreciation?[b.depreciation]:[]),...(b.periodsFx?[b.periodsFx,normalPeriodFxDependency(d,b.periodsFx)]:[])]);}
export function normalCurrent(d:Record<string,unknown>,b:NormalBook){return !!b.result&&b.result.key===key(d,b);}
const zero=()=>fraction(0n,1n);
const cents=(s:string)=>{if(!/^-?\d+(?:\.\d{1,2})?$/.test(s))throw Error('Montante MZN inválido;máximo dois decimais.');const v=decimalFraction(s.startsWith('-')?s.slice(1):s);return s.startsWith('-')?fraction(-BigInt(v.numerator),BigInt(v.denominator)):v;};
const integerCents=(v:FiscalFraction)=>BigInt(v.numerator)*100n/BigInt(v.denominator);
export function calculateNormal(d:Record<string,unknown>,b:NormalBook):NormalModel{
validateNormal(b);const pending:string[]=[],source=normalFinancialSource(d),fiscalPeriod=b.schedules?normalSchedulePeriod(b.schedules,b.scope):undefined;
validateNormal(b);const pending:string[]=[],raw=rawNormalFinancialSource(d),periodsFx=b.periodsFx?mapNormalPeriodFx(d,b.periodsFx,raw):undefined,source=periodsFx?periodsFx.source:raw,fiscalPeriod=periodsFx?{kind:b.periodsFx!.kind,from:b.periodsFx!.from,to:b.periodsFx!.to,reference:b.periodsFx!.reference,pending:periodsFx.pending}:b.schedules?normalSchedulePeriod(b.schedules,b.scope):undefined;
if(periodsFx&&b.periodsFx!.kind==='annual'&&b.periodsFx!.from!=='2026-01-01'){
const formal=b.schedules?normalSchedulePeriod(b.schedules,b.scope):null;
if(!formal||formal.pending.length||formal.from!==b.periodsFx!.from||formal.to!==b.periodsFx!.to)fiscalPeriod!.pending.push('Ano diferente do civil exige o período autorizado/comunicado já documentado nos mapas,com as mesmas datas.');
}
if(fiscalPeriod)pending.push(...fiscalPeriod.pending);
if(!source)pending.push('Falta plano financeiro reconciliado.');
if(source?.currency!=='MZN')pending.push('Ponte em MZN necessária;sem conversão fiscal anual presumida.');
if(!/^2026$/.test(b.year)||!b.mappingReference.trim()||source?.periods!==12)pending.push('Documente correspondência dos 12 meses do plano ao exercício2026;outros exercícios/horizontes permanecem separados.');
if(!/^2026$/.test(b.year)||!b.mappingReference.trim()||source?.periods!==(periodsFx?.months??12))pending.push('Documente correspondência dos 12 meses do plano ao exercício2026;outros exercícios/horizontes permanecem separados.');
if(!(b.scope==='resident_general'||b.schedules&&b.scope==='ep_general')||!b.scopeReference.trim())pending.push('Identifique sujeito residente no regime geral,fora de transparência/regimes especiais;EP e outros âmbitos não abrangidos.');
const rows:NormalModel['rows']=b.rows.map(r=>({...structuredClone(r),status:'indeterminate',adjustment:null,reason:''}));
const amounts=new Map<string,FiscalFraction>(),groups=new Map<string,NormalModel['rows']>();
@@ -71,24 +78,25 @@ export function calculateNormal(d:Record<string,unknown>,b:NormalBook):NormalMod
for(const r of rows){if(!r.reason){r.status='calculated';r.adjustment=amounts.get(r.id)!;r.reason='Acréscimo integral de custo incluído no resultado de partida,qualificado pelo utilizador como '+(r.rule==='noncontractual_fine'?'multa/encargo de infracção sem origem contratual':'juros compensatórios')+';CIRPC36(1)c. Projecção pelos pressupostos do plano.';}else pending.push(r.document+': '+r.reason);}
const additions=addFractions(rows.flatMap(r=>r.adjustment?[r.adjustment]:[]));
const accountingProfit=source?.currency==='MZN'?cents(source.accountingProfitBeforeTax):null;
const contextReady=(!fiscalPeriod||fiscalPeriod.pending.length===0)&&!!source&&source.currency==='MZN'&&source.periods===12&&b.year==='2026'&&!!b.mappingReference.trim()&&(b.scope==='resident_general'||!!b.schedules&&b.scope==='ep_general')&&!!b.scopeReference.trim();
const contextReady=(!fiscalPeriod||fiscalPeriod.pending.length===0)&&!!source&&source.currency==='MZN'&&source.periods===(periodsFx?.months??12)&&b.year==='2026'&&!!b.mappingReference.trim()&&(b.scope==='resident_general'||!!b.schedules&&b.scope==='ep_general')&&!!b.scopeReference.trim();
if(rows.length===0&&!b.profit)pending.push('Sem ajustamentos documentados;ausência de registos não prova inexistência de correcções.');
if(!b.profit)pending.push('L6 parcial:resultado ajustado apenas destes acréscimos;restantes custos/variações patrimoniais,amortizações,perdas,deduções e colecta por tratar. Lucro tributável,matéria colectável e imposto indeterminados.');
const depreciation=b.depreciation?calculateNormalDepreciation(b.depreciation,source,fiscalPeriod??{from:b.year+'-01-01',to:b.year+'-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])]):undefined;
const depreciation=b.depreciation?calculateNormalDepreciation(b.depreciation,source,fiscalPeriod??{from:b.year+'-01-01',to:b.year+'-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])],!!periodsFx?.short):undefined;
const schedules=b.schedules?calculateSchedules(b.schedules,source,rows,depreciation?{value:depreciation.adjustment,pending:depreciation.pending}:undefined):undefined;
const costs=b.costs?calculateNormalCosts(b.costs,source,[...b.rows,...(b.schedules?.rows??[])],fiscalPeriod??{from:'2026-01-01',to:b.year+'-12-31'}):undefined;
const costs=b.costs?calculateNormalCosts(b.costs,source,[...b.rows,...(b.schedules?.rows??[])],fiscalPeriod??{from:'2026-01-01',to:b.year+'-12-31'},!!periodsFx?.short):undefined;
const profit=b.profit?closeNormalProfit(b.profit,source,rows,accountingProfit,additions,contextReady,schedules,costs,depreciation):undefined;
if(profit&&periodsFx)profit.source='CIRPC17/18/20/21/22/24/36;'+periodsFx.rule+' L7 mantém benefícios/casos especiais;L8 mantém saldo final.';
if(profit)pending.push(...profit.pending,...(!b.base?['Matéria colectável,perdas reportadas,deduções e imposto não apurados neste percurso.']:[]));
const base=b.base?calculateNormalBase(b.base,profit?.taxableProfit??null,b.year,b.schedules?.currentLossReference):undefined;
const base=b.base?calculateNormalBase(b.base,profit?.taxableProfit??null,b.year,b.schedules?.currentLossReference,!!periodsFx?.short):undefined;
if(base)pending.push(...base.pending,...(!b.assessment?['Colecta/imposto a pagar,créditos anuais eM20/M22 não determinados;matéria colectável não é imposto.']:[]));
const assessment=b.assessment?calculateNormalAssessment(b.assessment,base?.taxableBase??null,{year:b.year,scope:b.scope,extended:!!b.schedules,currency:source?.currency??'',...(fiscalPeriod?{period:{from:fiscalPeriod.from,to:fiscalPeriod.to}}:{})}):undefined;
const assessment=b.assessment?calculateNormalAssessment(b.assessment,base?.taxableBase??null,{year:b.year,scope:b.scope,extended:!!b.schedules||!!periodsFx,short:!!periodsFx?.short,currency:source?.currency??'',...(fiscalPeriod?{period:{from:fiscalPeriod.from,to:fiscalPeriod.to}}:{})}):undefined;
if(assessment)pending.push(...assessment.pending,assessment.finalBalance.reason);
const currentLoss=b.schedules&&profit?.taxableProfit&&BigInt(profit.taxableProfit.numerator)<0n?fraction(-BigInt(profit.taxableProfit.numerator),BigInt(profit.taxableProfit.denominator)):null;
return{...(depreciation?{depreciation}:{}),...(costs?{costs}:{}),...(schedules?{schedules,currentLoss,fiscalPeriod}:{}),...(assessment?{assessment,colecta:assessment.colecta}:{}),...(base?{base}:{}),...(profit?{profit}:{}),version:b.depreciation?normalDepreciationVersion:b.costs?normalCostsVersion:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,year:b.year,currency:source?.currency??'',origin:'projected',accountingProfit,additions,partialAdjustedResult:contextReady&&rows.length>0&&rows.every(r=>r.status==='calculated')&&accountingProfit?addFractions([accountingProfit,additions]):null,taxableProfit:profit?.taxableProfit??null,taxableBase:base?.taxableBase??null,tax:null,rows,pending,source:irpcNormalSource+(b.schedules?' Mapas complementares: CIRPC18/21/22/24/36; Decreto72/2013 art3–7. EP: CIRPC15(1)c, apenas resultado imputável ao estabelecimento no regime geral, anual2026/MZN.':'')};
return{...(periodsFx?{periodsFx}:{}),...(depreciation?{depreciation}:{}),...(costs?{costs}:{}),...(schedules?{schedules,currentLoss,fiscalPeriod}:{}),...(assessment?{assessment,colecta:assessment.colecta}:{}),...(base?{base}:{}),...(profit?{profit}:{}),version:b.periodsFx?normalPeriodFxVersion:b.depreciation?normalDepreciationVersion:b.costs?normalCostsVersion:b.schedules?normalSchedulesVersion:b.assessment?normalAssessmentVersion:b.base?normalBaseVersion:b.profit?normalProfitVersion:irpcNormalVersion,year:b.year,currency:source?.currency??'',origin:'projected',accountingProfit,additions,partialAdjustedResult:contextReady&&rows.length>0&&rows.every(r=>r.status==='calculated')&&accountingProfit?addFractions([accountingProfit,additions]):null,taxableProfit:profit?.taxableProfit??null,taxableBase:base?.taxableBase??null,tax:null,rows,pending,source:irpcNormalSource+(periodsFx?' '+periodsFx.rule:b.schedules?' Mapas complementares: CIRPC18/21/22/24/36; Decreto72/2013 art3–7. EP: CIRPC15(1)c, apenas resultado imputável ao estabelecimento no regime geral, anual2026/MZN.':'')};
}
export function recordNormal(d:Record<string,unknown>,b:NormalBook,now=new Date().toISOString()):NormalBook{return{...b,history:b.result?[...(b.history??[]),structuredClone(b.result)]:b.history??[],result:{key:key(d,b),createdAt:now,model:calculateNormal(d,b)}};}
const money=(v:FiscalFraction|null)=>v?fractionPresentation(v).decimal+' MZN':'Indeterminado';
function normalBaseReport(m:NormalBaseModel):Array<[string,string,string,string]>{return[['IRPC normal — L6','Matéria colectável — deduções/perdas',`Lucro tributável:${money(m.taxableProfit)};perdas propostas elegíveis:${money(m.requestedLosses)};perdas deduzidas nesta simulação:${money(m.deductedLosses)};benefícios deduzidos:${money(m.benefitDeduction)};matéria colectável simulada:${money(m.taxableBase)};imposto a pagar:Indeterminado. ${m.pending.join(' | ')}`,`NUIT:${m.input.subjectNuit};${m.input.identityReference};método:${m.input.method==='direct'?'Declarativo/directo':m.input.method==='indirect'?'Indirecto':'Por identificar'};${m.input.methodReference};perdas:${m.input.lossesState==='absent'?'Ausência declarada':m.input.lossesState==='known'?'Conhecidas':'Informação em falta'};${m.input.lossesReference};objecto:${m.input.unchangedObject==='yes'?'Sem alteração declarada':m.input.unchangedObject==='no'?'Alteração declarada':'Por esclarecer'};${m.input.objectReference};${m.source}`],['IRPC normal — L6','Benefícios à matéria colectável',`Estado:${m.input.benefits.state==='known'?'Valor conhecido':m.input.benefits.state==='absent'?'Ausência declarada':'Informação em falta'};montante original:${m.input.benefits.amount||'Não preenchido'};dedução:${money(m.benefitDeduction)};não inclui créditos à colecta ou retenções sofridas.`,m.input.benefits.reference],...m.losses.map(l=>['IRPC normal — L6','Perda fiscal de '+l.year,`Estado:${l.status==='eligible'?'Elegível neste recorte':l.status==='excluded'?'Excluída neste exercício':'Indeterminada'};apurada:${l.assessed};utilizada antes:${l.previouslyUsed};saldo:${money(l.available)};utilização proposta:${l.requested};dedução nesta simulação:${money(l.deducted)};saldo aritmético posterior:${money(l.remaining)};último exercício ordinário:${l.lastOrdinaryYear??'Por determinar'};${l.reason}`,`NUIT:${l.subjectNuit};${l.reference};sem consumo do saldo oficial nem reporte automático.`]as[string,string,string,string])];}
function normalAssessmentReport(m:NormalAssessmentModel):Array<[string,string,string,string]>{return[['IRPC normal — L6','Colecta — regime normal',`Matéria colectável:${money(m.taxableBase)};taxa:${m.rate?m.rate.numerator+'%':'Por identificar'};colecta simulada:${money(m.colecta)};${m.classification};${m.centExact===false?'Valor racional exacto com fracção de cêntimo;sem arredondamento fiscal aplicado.':''}${m.pending.join(' | ')}`,`Exercício:${m.period.year};período:${m.period.from}–${m.period.to};${m.input.reference};${m.rule};${m.source}`],['IRPC normal — L6','Saldo final — âmbito L8','A pagar:Indeterminado;a recuperar:Indeterminado. '+m.finalBalance.reason,'Sequência:lucro tributável → matéria colectável → colecta → saldo final;retenções,pagamentos e créditos anuais reservados aL8.']];}
export function normalReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=normalBook(d),r=b.result;if(!r)return[];const m=r.model;return[['IRPC normal — L6','Ajustamentos ao resultado financeiro',`Exercício:${m.year};resultado contabilístico antes de imposto:${money(m.accountingProfit)};acréscimos demonstrados:${money(m.additions)};resultado parcial ajustado:${money(m.partialAdjustedResult)};${m.profit?'lucro tributável simulado:'+money(m.taxableProfit)+(m.base?';matéria colectável simulada:'+money(m.taxableBase)+(m.assessment?';colecta simulada:'+money(m.colecta??null)+';saldo final a pagar ou recuperar:Indeterminado (L8).':';imposto a pagar:Indeterminado.'):';matéria colectável/imposto:Indeterminados.'):'lucro tributável/matéria colectável/imposto:Indeterminados.'} Projecção com pressupostos;${m.pending.join(' | ')}`,`${normalCurrent(d,b)?'Entradas correspondentes':'Histórico;origem ou entradas alteradas'};${r.createdAt};${m.source}`],...(m.depreciation?normalDepreciationReport(m.depreciation):[]),...(m.costs?normalCostReport(m.costs):[]),...(m.base?normalBaseReport(m.base):[]),...(m.assessment?normalAssessmentReport(m.assessment):[]),...(m.schedules?[['IRPC normal — L6','Mapas complementares',`Efeito líquido adicional:${money(m.schedules.net)};perda fiscal do próprio exercício:${money(m.currentLoss??null)};sem reporte ou consumo automático. ${m.schedules.pending.join(' | ')}`,m.schedules.source]as[string,string,string,string],...m.schedules.effects.map(e=>{const i=m.schedules!.input.rows.find(r=>r.id===e.id)!;return['IRPC normal — L6',i.document+'/'+i.line,`Montante:${money(e.amount)};efeito:${money(e.effect)};${e.reason}`,`${i.reference};exercício de imputação:${i.fiscalYear||'Não aplicável'}`]as[string,string,string,string];}),...m.schedules.assets.map(a=>['IRPC normal — L6',a.description,`Depreciação contabilística:${money(a.accountingValue)};quota fiscal:${money(a.fiscal)};acréscimo:${money(a.adjustment)};${a.reason}`,`${a.tableReference};${a.reference}`]as[string,string,string,string]),['IRPC normal — L6','Juros financeiros ordinários',`Remanescente aceite no recorte:${money(m.schedules.interest)};não corresponde a juros de mora/compensatórios L5.`,m.schedules.input.interest.reference]as[string,string,string,string]]:[]),...(m.schedules?[...normalFormFields(m).fields.map(f=>['IRPC normal — L6','M22 campo '+f.code+' — '+f.label,`${f.display};exacto:${f.value?f.value.numerator+'/'+f.value.denominator:'Indeterminado'};transcrição:${f.transcriptionReady?'Preparação parcial com valor exacto ao cêntimo':'Pendente ou sem aplicação'};${f.reason}`,normalFormFields(m).source]as[string,string,string,string]),...normalFormFields(m).m20.fields.map(f=>['IRPC normal — L6','M20 A.1 — conta '+f.account+' — '+f.label,`${f.display};representação em meticais inteiros:${f.transcriptionReady?'Exacta':'Pendente'};${f.reason}`,`${f.reference};${normalFormFields(m).m20.source}`]as[string,string,string,string]),['IRPC normal — L6','M20 — correspondência',normalFormFields(m).m20.reason,normalFormFields(m).m20.source]as[string,string,string,string]]:[]),...m.rows.map(r=>['IRPC normal — L6',r.document+'/'+r.line,`${({calculated:'Calculado neste recorte',indeterminate:'Indeterminado',outside_scope:'Fora deste recorte'})[r.status]};montante:${r.amount};acréscimo:${money(r.adjustment)};${r.reason}`,`Origem:${({revenue:'Receitas',cost_of_sales:'Custo das vendas',operating_expenses:'Despesas operacionais',personnel:'Pessoal',depreciation:'Depreciação contabilística',interest_expense:'Juros reconhecidos'}as Record<string,string>)[r.sourceCode]??'Por corresponder'};incluído:${r.included==='yes'?'Sim':r.included==='no'?'Não':'Por demonstrar'};suporte:${r.reference}`]as[string,string,string,string]),...(m.profit?[...m.profit.lines.map(l=>['IRPC normal — L6','Conferência: '+l.label,`Estado:${l.state==='known'?'Valor conhecido':l.state==='absent'?'Ausência declarada':'Informação em falta'};origem:${l.amount};acréscimos:${money(l.addedBack)};remanescente:${money(l.remainder)};${l.reason}`,l.reference]as[string,string,string,string]),...m.profit.topics.map(t=>['IRPC normal — L6',t.label,`Estado:${t.state==='known'?'Valor conhecido':t.state==='absent'?'Ausência declarada':'Informação em falta'};montante:${money(t.value)};${t.reason}`,t.reference]as[string,string,string,string]),['IRPC normal — L6','Âmbito da conferência',m.profit.input.completenessReference,m.profit.source]as[string,string,string,string]]:[])];}catch(e){return[['IRPC normal — L6','Não recuperável',String(e),'Originais conservados.']];}}
export function normalReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=normalBook(d),r=b.result;if(!r)return[];const m=r.model;return[['IRPC normal — L6','Ajustamentos ao resultado financeiro',`Exercício:${m.year};resultado contabilístico antes de imposto:${money(m.accountingProfit)};acréscimos demonstrados:${money(m.additions)};resultado parcial ajustado:${money(m.partialAdjustedResult)};${m.profit?'lucro tributável simulado:'+money(m.taxableProfit)+(m.base?';matéria colectável simulada:'+money(m.taxableBase)+(m.assessment?';colecta simulada:'+money(m.colecta??null)+';saldo final a pagar ou recuperar:Indeterminado (L8).':';imposto a pagar:Indeterminado.'):';matéria colectável/imposto:Indeterminados.'):'lucro tributável/matéria colectável/imposto:Indeterminados.'} Projecção com pressupostos;${m.pending.join(' | ')}`,`${normalCurrent(d,b)?'Entradas correspondentes':'Histórico;origem ou entradas alteradas'};${r.createdAt};${m.source}`],...(m.periodsFx?normalPeriodFxReport(m.periodsFx):[]),...(m.depreciation?normalDepreciationReport(m.depreciation):[]),...(m.costs?normalCostReport(m.costs):[]),...(m.base?normalBaseReport(m.base):[]),...(m.assessment?normalAssessmentReport(m.assessment):[]),...(m.schedules?[['IRPC normal — L6','Mapas complementares',`Efeito líquido adicional:${money(m.schedules.net)};perda fiscal do próprio exercício:${money(m.currentLoss??null)};sem reporte ou consumo automático. ${m.schedules.pending.join(' | ')}`,m.schedules.source]as[string,string,string,string],...m.schedules.effects.map(e=>{const i=m.schedules!.input.rows.find(r=>r.id===e.id)!;return['IRPC normal — L6',i.document+'/'+i.line,`Montante:${money(e.amount)};efeito:${money(e.effect)};${e.reason}`,`${i.reference};exercício de imputação:${i.fiscalYear||'Não aplicável'}`]as[string,string,string,string];}),...m.schedules.assets.map(a=>['IRPC normal — L6',a.description,`Depreciação contabilística:${money(a.accountingValue)};quota fiscal:${money(a.fiscal)};acréscimo:${money(a.adjustment)};${a.reason}`,`${a.tableReference};${a.reference}`]as[string,string,string,string]),['IRPC normal — L6','Juros financeiros ordinários',`Remanescente aceite no recorte:${money(m.schedules.interest)};não corresponde a juros de mora/compensatórios L5.`,m.schedules.input.interest.reference]as[string,string,string,string]]:[]),...(m.schedules?[...normalFormFields(m).fields.map(f=>['IRPC normal — L6','M22 campo '+f.code+' — '+f.label,`${f.display};exacto:${f.value?f.value.numerator+'/'+f.value.denominator:'Indeterminado'};transcrição:${f.transcriptionReady?'Preparação parcial com valor exacto ao cêntimo':'Pendente ou sem aplicação'};${f.reason}`,normalFormFields(m).source]as[string,string,string,string]),...normalFormFields(m).m20.fields.map(f=>['IRPC normal — L6','M20 A.1 — conta '+f.account+' — '+f.label,`${f.display};representação em meticais inteiros:${f.transcriptionReady?'Exacta':'Pendente'};${f.reason}`,`${f.reference};${normalFormFields(m).m20.source}`]as[string,string,string,string]),['IRPC normal — L6','M20 — correspondência',normalFormFields(m).m20.reason,normalFormFields(m).m20.source]as[string,string,string,string]]:[]),...m.rows.map(r=>['IRPC normal — L6',r.document+'/'+r.line,`${({calculated:'Calculado neste recorte',indeterminate:'Indeterminado',outside_scope:'Fora deste recorte'})[r.status]};montante:${r.amount};acréscimo:${money(r.adjustment)};${r.reason}`,`Origem:${({revenue:'Receitas',cost_of_sales:'Custo das vendas',operating_expenses:'Despesas operacionais',personnel:'Pessoal',depreciation:'Depreciação contabilística',interest_expense:'Juros reconhecidos'}as Record<string,string>)[r.sourceCode]??'Por corresponder'};incluído:${r.included==='yes'?'Sim':r.included==='no'?'Não':'Por demonstrar'};suporte:${r.reference}`]as[string,string,string,string]),...(m.profit?[...m.profit.lines.map(l=>['IRPC normal — L6','Conferência: '+l.label,`Estado:${l.state==='known'?'Valor conhecido':l.state==='absent'?'Ausência declarada':'Informação em falta'};origem:${l.amount};acréscimos:${money(l.addedBack)};remanescente:${money(l.remainder)};${l.reason}`,l.reference]as[string,string,string,string]),...m.profit.topics.map(t=>['IRPC normal — L6',t.label,`Estado:${t.state==='known'?'Valor conhecido':t.state==='absent'?'Ausência declarada':'Informação em falta'};montante:${money(t.value)};${t.reason}`,t.reference]as[string,string,string,string]),['IRPC normal — L6','Âmbito da conferência',m.profit.input.completenessReference,m.profit.source]as[string,string,string,string]]:[])];}catch(e){return[['IRPC normal — L6','Não recuperável',String(e),'Originais conservados.']];}}
export function normalHtml(d:Record<string,unknown>){const b=normalBook(d);if(!normalCurrent(d,b))throw Error('Calcule e guarde as entradas actuais antes de exportar.');const esc=(s:string)=>s.replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]!));return'<!doctype html><html lang="pt"><meta charset="utf-8"><title>IRPC normal — ajustamentos</title><h1>L6 — mapa parcial de ajustamentos</h1><p>Projecção pelos pressupostos do plano. Não é demonstração contabilística,liquidação ou Modelo20/22.</p>'+normalReportRows(d).map(r=>'<section><h2>'+esc(r[1])+'</h2><p>'+esc(r[2])+'</p><small>'+esc(r[3])+'</small></section>').join('')+'</html>';}
@@ -0,0 +1,25 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tests\TestCase;
final class IrpcNormalPeriodFxDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_period_and_currency_source_evidence_recover_with_original_plan_and_histories():void
{
$db=DB::selectOne('select current_database() as name,inet_server_port() as port');$this->assertSame('simulator_fiscal_delta_20261021_test',$db->name);$this->assertSame(55441,(int)$db->port);
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false]);$this->actingAs(User::factory()->create(['email_verified_at'=>now()]));$id=null;$etag=null;
foreach(['short','fx','missing','loss']as$case){
$d=json_decode(file_get_contents(base_path('outputs/irpc-l6-periods-fx/'.$case.'.json')),true,flags:JSON_THROW_ON_ERROR);$personal=['person_type'=>$d['person_type']];unset($d['person_type']);
if($id===null)$id=$this->postJson('/api/v1/simulation-drafts',['personal_snapshot'=>$personal,'economic_snapshot'=>$d],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated()->json('data.id');else$this->putJson('/api/v1/simulation-drafts/'.$id,['personal_snapshot'=>$personal,'economic_snapshot'=>$d],['If-Match'=>$etag])->assertOk();
$r=$this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();$etag=$r->headers->get('ETag');$back=$r->json('data.economic_snapshot');$this->assertSame($d['business_plan_snapshot'],$back['business_plan_snapshot']);$this->assertSame($d['modelo_a_snapshot'],$back['modelo_a_snapshot']);
$s=json_decode($back['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);$b=$s['irpcNormal'][$d['context_opportunity']];$m=$b['result']['model'];$this->assertSame('IRPC-normal-adjustments/8',$m['version']);$this->assertSame($b['periodsFx'],$m['periodsFx']['input']);$this->assertContains('IRPC-normal-adjustments/7',array_column(array_column($b['history'],'model'),'version'));$this->assertArrayHasKey('irpcWithholdings',$s);$this->assertNull($m['assessment']['finalBalance']['payable']);$this->assertNull($m['tax']);
if($case==='short'||$case==='fx'){$this->assertSame($case==='short'?'3130':'200320',$m['taxableProfit']['numerator']);$this->assertSame($m['taxableProfit'],$m['taxableBase']);$this->assertSame(['numerator'=>$case==='short'?'5008':'320512','denominator'=>'5'],$m['colecta']);$this->assertSame(6,$m['periodsFx']['months']);$this->assertSame([],$m['periodsFx']['pending']);}
else{$this->assertNull($m['colecta']);if($case==='loss'){$this->assertSame('3130',$m['taxableProfit']['numerator']);$this->assertNull($m['taxableBase']);}else{$this->assertNull($m['accountingProfit']);$this->assertNotEmpty($m['periodsFx']['pending']);}}
file_put_contents(base_path('outputs/irpc-l6-periods-fx/recovered-'.$case.'.json'),json_encode($back,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
import test from'node:test';import assert from'node:assert/strict';import fs from'node:fs';
import{normalBook,normalCurrent,calculateNormal,recordNormal,saveNormalBook,normalHtml,normalFinancialSource}from'../../resources/js/lib/irpc-normal.ts';
import{newNormalPeriodFx,mapNormalPeriodFx}from'../../resources/js/lib/irpc-normal-period-fx.ts';
import{emptyFxSelection,selectFx}from'../../resources/js/lib/iva-fx-selection.ts';
import{createProjectDossierReport}from'../../resources/js/lib/project-dossier-report.ts';
import{newCurrencyEvent}from'../../resources/js/lib/iva-currency-events.ts';
import{irpcFxSources}from'../../resources/js/lib/irpc-fx.ts';
const seed=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-depreciation-preserved.json',import.meta.url),'utf8')),val=f=>f?Number(f.numerator)/Number(f.denominator):null;
function fx(direction='mzn_per_unit',rate='64'){const s={...emptyFxSelection(),nature:'commercial',submissionDate:'2026-10-26',bulletinDate:'2026-10-26',bulletinRef:'QA pressuposto sintético64; não boletim real',source:'https://www.bancomoc.mz/',sourceCurrency:'USD',direction,mean:rate};return{...s,applied:selectFx(s)};}
function setup(currency='MZN',kind='start'){
const d=seed(),b=normalBook(d),p=JSON.parse(d.business_plan_snapshot);p.plan.currency=currency;d.business_plan_snapshot=JSON.stringify(p);
b.rows=[];b.costs=undefined;b.schedules.rows=[];b.schedules.interest.amount=currency==='USD'?'7680':'120';
for(const t of Object.values(b.profit.topics)){t.state='absent';t.amount='';t.reference='QA ausência declarada no período seleccionado';}
b.base.losses=[];b.base.lossesState='absent';b.base.lossesReference='QA ausência de perdas reportadas';
b.depreciation.assets[0].accounting=currency==='USD'?'3840':'60';b.depreciation.assets[0].cost=currency==='USD'?'32000':'500';b.depreciation.assets[0].start='2026-07-01';b.depreciation.assets[0].timing='months';
b.periodsFx={...newNormalPeriodFx(),kind,from:'2026-07-01',to:'2026-12-31',planStart:'2026-01-01',reference:'QA início documentado1Julho;correspondência completa dos meses7–12 ao período do sujeito',mznMapsReference:'QA activo/quotas/juros e demais mapas reconciliados explicitamente emMZN no período',conversionReference:currency==='USD'?'QA pressuposto de simulação64MZN/USD aplicado aos meses seleccionados;sem regra fiscal anual confirmada':''};
if(currency==='USD')b.periodsFx.months=Array.from({length:6},(_,n)=>({period:n+7,currency:'USD',fxSelection:fx()}));
return{d,b};
}
test('installed7 stays current without opt-in; optional8 preserves original result and plan while selecting6 months',()=>{const d=seed(),old=normalBook(d);assert.equal(normalCurrent(d,old),true);const {d:next,b}=setup(),m=calculateNormal(next,b);assert.equal(m.version,'IRPC-normal-adjustments/8');assert.equal(val(m.accountingProfit),3120);assert.equal(val(m.depreciation.assets[0].quota),50);assert.equal(val(m.taxableProfit),3130);assert.equal(val(m.taxableBase),3130);assert.equal(val(m.colecta),1001.6);assert.equal(m.assessment.period.from,'2026-07-01');assert.equal(m.assessment.finalBalance.payable,null);assert.equal(JSON.parse(next.business_plan_snapshot).plan.periods,12);});
test('monthly conversion reuses delivered selection and direction; originalUSD and assumptions remain reportable',()=>{const{d,b}=setup('USD'),m=calculateNormal(d,b);assert.equal(val(m.accountingProfit),199680);assert.equal(val(m.depreciation.adjustment),640);assert.equal(val(m.taxableProfit),200320);assert.equal(val(m.colecta),64102.4);assert.equal(m.periodsFx.originalCurrency,'USD');assert.equal(m.periodsFx.rows[0].evidence.column,'mean');assert.match(m.periodsFx.rule,/simulação/);const inv=setup('USD');for(const e of inv.b.periodsFx.months)e.fxSelection=fx('units_per_mzn','0.015625');assert.equal(val(calculateNormal(inv.d,inv.b).colecta),64102.4);});
test('annual plan in another currency and shorterplan aligned to July both reconcile without annualising',()=>{const a=setup('USD');a.b.periodsFx.kind='annual';a.b.periodsFx.from='2026-01-01';a.b.periodsFx.months=Array.from({length:12},(_,n)=>({period:n+1,currency:'USD',fxSelection:fx()}));a.b.schedules.interest.amount='15360';a.b.depreciation.assets[0].accounting='7680';a.b.depreciation.assets[0].start='2026-01-01';assert.equal(val(calculateNormal(a.d,a.b).colecta),128204.8);const{d,b}=setup();const p=JSON.parse(d.business_plan_snapshot);p.plan.periods=6;p.plan.assetDepreciation='60';p.plan.depreciationLifeMonths=6;d.business_plan_snapshot=JSON.stringify(p);b.periodsFx.planStart='2026-07-01';assert.equal(val(calculateNormal(d,b).colecta),1001.6);});
test('cessation and same-year start/end use documented dates; unsupportedpartial months and contradictions remain pending',()=>{const{d,b}=setup();b.periodsFx.kind='cessation';b.periodsFx.from='2026-01-01';b.periodsFx.to='2026-06-30';b.depreciation.assets[0].start='2026-01-01';assert.equal(val(calculateNormal(d,b).colecta),1001.6);b.periodsFx.kind='same_year';b.periodsFx.from='2026-04-01';b.periodsFx.to='2026-09-30';assert.equal(val(calculateNormal(d,b).colecta),1001.6);for(const patch of[{from:'2026-07-15'},{to:'2026-12-30'},{planStart:''},{from:'2026-01-01',kind:'start'},{to:'2026-02-31'},{reference:''},{mznMapsReference:''}]){const x=setup();Object.assign(x.b.periodsFx,patch);assert.equal(calculateNormal(x.d,x.b).colecta,null);}});
test('missingmonth selection,sourcecurrency mismatch and editedapplied rate never become defaultFX',()=>{for(const mutate of[i=>i.months.pop(),i=>i.months[0].currency='EUR',i=>i.months[0].fxSelection.mean='65',i=>i.conversionReference='',i=>i.months.push({...i.months[0],period:1})]){const{d,b}=setup('USD');mutate(b.periodsFx);const m=calculateNormal(d,b);assert.equal(m.accountingProfit,null);assert.equal(m.colecta,null);assert.ok(m.periodsFx.rows.some(r=>r.value));}});
test('fractionalcent conversion blocks dependenttotals while retaining exact source and otherknownmonths',()=>{const{d,b}=setup('USD');b.periodsFx.months[0].fxSelection=fx('mzn_per_unit','0.33333333');const m=calculateNormal(d,b);assert.equal(m.colecta,null);assert.ok(m.periodsFx.pending.some(s=>s.includes('fracção de cêntimo')));assert.equal(m.periodsFx.rows[0].originalCurrency,'USD');});
test('shortperiod priorloss expiry and annualcaps stay explicitlypending; no change to independent arithmetic',()=>{const x=setup();x.b.base=structuredClone(normalBook(seed()).base);const m=calculateNormal(x.d,x.b);assert.equal(val(m.taxableProfit),3130);assert.equal(m.taxableBase,null);assert.ok(m.base.pending.some(s=>s.includes('41(5)')));const y=setup();y.b.costs=structuredClone(normalBook(seed()).costs);assert.equal(calculateNormal(y.d,y.b).colecta,null);assert.ok(calculateNormal(y.d,y.b).costs.pending.some(s=>s.includes('Limite anual neste período curto')));y.b.costs=undefined;y.b.depreciation.assets[0].timing='annual';assert.equal(calculateNormal(y.d,y.b).colecta,null);});
test('save/recovery/report preserves7 history and sourceplan; changed period makes8 historical',()=>{const{d,b}=setup('USD'),original=d.business_plan_snapshot,old=structuredClone(b.result);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T10:00:00+02:00'));const back=normalBook(JSON.parse(JSON.stringify(d)));assert.equal(normalCurrent(d,back),true);assert.deepEqual(back.history.at(-1),old);assert.equal(d.business_plan_snapshot,original);assert.match(normalHtml(d),/64102.40/);assert.match(normalHtml(d),/regra fiscal anual não confirmada/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/USD/);back.periodsFx.reference+='changed';assert.equal(normalCurrent(d,back),false);});
test('newcases prepare privateAPI persistence and dossiers including insufficientinformation',()=>{const cases={short:setup(),fx:setup('USD'),missing:setup('USD'),loss:setup()};cases.missing.b.periodsFx.months.pop();cases.loss.b.base=structuredClone(normalBook(seed()).base);fs.mkdirSync('outputs/irpc-l6-periods-fx',{recursive:true});for(const[name,{d,b}]of Object.entries(cases)){d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T10:00:00+02:00'));fs.writeFileSync('outputs/irpc-l6-periods-fx/'+name+'.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l6-periods-fx/'+name+'.html',normalHtml(d));}});
test('different monthrates require MZN map reconciliation; UI source uses converted subset without relabellingUSD credit balances',()=>{const{d,b}=setup('USD');b.periodsFx.months[0].fxSelection=fx('mzn_per_unit','65');assert.equal(calculateNormal(d,b).colecta,null);b.depreciation.assets[0].accounting='3850';b.schedules.interest.amount='7700';assert.equal(val(calculateNormal(d,b).colecta),64272);d.modelo_a_snapshot=saveNormalBook(d,b);const source=normalFinancialSource(d);assert.equal(source.currency,'MZN');assert.equal(source.periods,6);assert.equal(source.accountingLines.find(l=>l.code==='depreciation').total,'3850.00');assert.deepEqual(source.cashSchedule,[]);});
test('noncalendar annual mapping cannot bypass existing authorised period and unsupportedshortdates stay pending',()=>{const{d,b}=setup();b.periodsFx.kind='annual';b.periodsFx.from='2025-07-01';b.periodsFx.to='2026-06-30';b.periodsFx.planStart='2025-07-01';assert.equal(calculateNormal(d,b).colecta,null);assert.ok(calculateNormal(d,b).pending.some(s=>s.includes('autorizado/comunicado')));});
test('reusedFX link invalidates current result when existing source changes and never changes historical result',()=>{const{d,b}=setup('USD'),snapshot=JSON.parse(d.modelo_a_snapshot);snapshot.currencyEvents??={};snapshot.currencyEvents[d.context_opportunity]={version:1,rows:[{...newCurrencyEvent('fx-shared','MZN'),sourceCurrency:'USD',fxSelection:fx()}]};d.modelo_a_snapshot=JSON.stringify(snapshot);const linked=irpcFxSources(d)[0];assert.ok(linked);b.periodsFx.months[0].fxLink={id:linked.id,fingerprint:linked.fingerprint};b.periodsFx.months[0].fxSelection=linked.selection;d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));const book=normalBook(d),old=structuredClone(book.result);assert.equal(normalCurrent(d,book),true);const changed=JSON.parse(d.modelo_a_snapshot);changed.currencyEvents[d.context_opportunity].rows[0].description='New source context';d.modelo_a_snapshot=JSON.stringify(changed);assert.equal(normalCurrent(d,normalBook(d)),false);assert.deepEqual(normalBook(d).result,old);assert.equal(calculateNormal(d,normalBook(d)).colecta,null);});
test('known nondepreciable asset in shortperiod keeps full addback without asking irrelevant timing',()=>{const{d,b}=setup();Object.assign(b.depreciation.assets[0],{category:'nondepreciable',timing:'',method:''});const m=calculateNormal(d,b);assert.equal(val(m.depreciation.adjustment),60);assert.equal(val(m.colecta),1017.6);});
+21
View File
@@ -0,0 +1,21 @@
<?php
$root=dirname(__DIR__);
$operation=$argv[1]??'';
if(!in_array($operation,['routes','test'],true)||basename($root)!=='portal-investidor-irpc-l6-periods-fx-qa-20261026')throw new RuntimeException('Explicit reliefs QA workspace and routes/test operation required.');
foreach(['storage/framework/views','storage/framework/cache','storage/framework/sessions','bootstrap/cache','storage/logs','outputs/irpc-l6-periods-fx']as$dir)if(!is_dir($root.'/'.$dir))mkdir($root.'/'.$dir,0700,true);
foreach(['APP_BASE_PATH'=>$root,'APP_ENV'=>'testing','APP_KEY'=>'base64:'.base64_encode(str_repeat('Q',32)),'DB_CONNECTION'=>'pgsql','DB_HOST'=>'127.0.0.1','DB_PORT'=>'55441','DB_DATABASE'=>'simulator_fiscal_delta_20261021_test','DB_USERNAME'=>'postgres','DB_PASSWORD'=>'','DB_URL'=>'','CACHE_STORE'=>'array','SESSION_DRIVER'=>'array','QUEUE_CONNECTION'=>'sync','MAIL_MAILER'=>'array'] as $k=>$v){putenv($k.'='.$v);$_ENV[$k]=$_SERVER[$k]=$v;}
$loader=require $root.'/vendor/autoload.php';
spl_autoload_register(function($class)use($root){foreach(['App\\'=>'app/','Tests\\'=>'tests/','Database\\Factories\\'=>'database/factories/']as$prefix=>$dir){if(str_starts_with($class,$prefix)){$file=$root.'/'.$dir.str_replace('\\','/',substr($class,strlen($prefix))).'.php';if(is_file($file)){require $file;return;}}}},true,true);
$app=require $root.'/bootstrap/app.php';
(new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app);
(new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app);
$c=config('database.connections.pgsql');
if($app->configurationIsCached()||$c['host']!=='127.0.0.1'||(int)$c['port']!==55441||$c['database']!=='simulator_fiscal_delta_20261021_test'||!empty($c['url']))throw new RuntimeException('QA guard');
$pdo=new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$c['database'],$c['username'],$c['password']);
if($pdo->query('select current_database()')->fetchColumn()!==$c['database'])throw new RuntimeException('QA identity');
if($operation==='routes'){
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$status=Illuminate\Support\Facades\Artisan::call('wayfinder:generate',['--with-form'=>true]);
echo Illuminate\Support\Facades\Artisan::output();exit($status);
}
$argv=['phpunit','-c','phpunit.investmoz.xml','tests/Feature/IrpcNormalPeriodFxDraftTest.php'];$argc=count($argv);$_SERVER['argv']=$argv;$_SERVER['argc']=$argc;require $root.'/vendor/phpunit/phpunit/phpunit';