feat(portal): deliver ordinary national credit fraction and education continuity
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-11-10 12:47:57 +02:00
parent ff726e577f
commit c359364de3
14 changed files with 749 additions and 126 deletions
@@ -1,5 +1,29 @@
# Rastreabilidade, utilização e formação
## 10/11/2026 — L8: fracção nacional ordinária demonstrada até ao relatório em QA
Pendência funcional encerrada **em QA no recorte ordinário**: opção explícita de derivar a fracção nacional do crédito internacional da colecta L6 corrente e da parcela líquida estrangeira correspondida à matéria colectável. Fonte aceite reutilizada: CIRPC65(1)(a/b),65(2),Lei34/2007,PDF18/p768-(72),e ordem66/RIRPC21. Não se exige nova validação da fonte oficial. A fórmula técnica `colecta antes de créditos × parcela líquida / matéria colectável` fica limitada à colecta normal corrente, base positiva, inclusão documentada, mesmo titular/exercício/país e afectação líquida identificada. O portal não inventa nem reparte custos/perdas/deduções; valores acima da base ou parcelas por país que em conjunto excedam a base impedem o saldo dependente.
O modo documentado anterior continua por omissão nos livros existentes. Limite/suporte manuais ficam conservados quando se escolhe derivação, mas não são utilizados como limite activo nesse modo. Nova correspondência fica ligada à chave da colecta L6; origem alterada exige nova conferência. Método inválido é recusado. Os resultados antigos congelados não são reescritos. Convenção/aplicabilidade/limite continuam documentados por caso; efeitos L7 e outros regimes/períodos/moedas conservam correspondência própria. Não há consumo de crédito oficial, pagamento ou submissão fiscal.
Percurso demonstrado: cálculo → gravação/actualização/GET pelos endpoints privados existentes → recuperação com snapshot íntegro → recálculo corrente → HTML e relatório financeiro do dossier. Exemplo exclusivamente sintético:base4400/colecta1408/parcela100 produz fracção32;imposto estrangeiro100 limitado32,PEC200/PPC500 deixam676MZN de saldo simulado. Parcela0,01 produz fracção exacta2/625,sem arredondamento fiscal presumido. Correspondência em falta deixa resultado indeterminado. LimiteCDT6 continua a reduzir a dedução e a produzir702 no cenário documentado. Provas em `outputs/irpc-national-fraction-20261110/recovered-reports.json`,HTMLs/relatórios no mesmo directório.
Seis casos frontend novos (cinco iniciais e um adicional de afectação por países),um caso API/23 asserções apenas do delta, com transacção revertida na QA existente `proactive_investment_20261027_test:55441`,actor sintético existente1021. Sem novas bases,concessões,migrations ou alterações aos documentos/históricos anteriores. Tipos e lint dirigidos aprovados; compilação QA final acompanhada no log próprio. Provas aceites de IVA/IRPC/PEC/transporte/económico não repetidas. Reprodução dirigida: `node --test tests/frontend/irpc-national-fraction.test.mjs`; gerador de amostras `tools/irpc-national-fraction-fixtures.mjs`; writer QA `php tools/irpc-national-fraction-qa.php test`; conferência dos relatórios recuperados `node tools/irpc-national-fraction-recovered.mjs`.
**L8 continua parcial; fiscal não se limita a L8.** Transporte ordinário e crédito económico já entregues em569a1cd7 preservados. Resíduos L1–L7 e lotes L9–L16 estão explicitados no saldo por módulo existente, sem presumir fecho integral a partir de uma entrada ou taxa. Novo delta L8 e continuidade de Educação são compatíveis (sem schema/rotas/writers novos), agrupados em `outputs/entrega-educacao-l8-20261110.zip`, com manifesto/fontes/patch/reversão contra ff726e57; ainda não instalados. Tipos/lint/Pint/compilação QA final aprovados. O índice de trabalho e o pacote anterior ficam preservados; conferência em índices temporários verifica aplicação e reversão, sem instalar ficheiros. Homologação mantém ff726e57; conferência visual permanece separada. O próximo trabalho é outra pendência funcional do saldo, não repetir esta prova.
## 10/11/2026 — execução de conclusão integral após instalação ff726e57
Entrega de fontes até `ff726e57` instalada e verificada por HTTPS na homologação; inclui S08 e os restantes ajustes do pacote selado. Disponibilidade de código não é uma nova atribuição real nem prova de todos os percursos privados. Recibo `outputs/reviewed-install-20261110/verification.json`: revisão servida, 44 hashes do delta, configuração preservada, zero migrations/importações e release anterior conservada. As anotações antigas «sem instalação» continuam históricas, superadas neste âmbito. Conferência visual S08 permanece no registo próprio, sem reinstalação ou bloqueio transversal.
Saldo actual por módulo/percurso reconciliado na visão consolidada existente e em 35 IDs da matriz, sem criar novas linhas ou apagar estados/critérios anteriores. Os 3.555 registos incluem cláusulas/campos sobrepostos, não 3.555 funcionalidades independentes. Restantes células preservadas; recibos aceites reutilizados, sem nova auditoria geral.
**Utilização avançada nesta passagem — Educação:** no detalhe privado de uma declaração, preparar o fundamento da revisão e consultar Anterior/Seguinte no histórico deixa de exigir abandonar a revisão. As ligações conservam estado/posição; resposta nova actualiza a declaração mas não adopta silenciosamente a versão analisada. Um envio sem confirmação continua recuperável com a chave original. Alterar declaração/tipo/actor mantém a chave de isolamento do componente. Sair para outra página continua protegido pela guarda existente. Não houve novo endpoint, decisão automática, writer ou efeito na base.
Quatro verificações novas de comportamento em `tests/frontend/education-review.test.mjs`, seleccionadas por `history paging`, aprovadas. Tipos globais, lint dirigido e compilação QA concluídos com código 0. O build QA existente foi actualizado, sem novo ambiente ou dados. Logs `storage/logs/education-history-continuity-20261110-{types,build}.log`. Sem repetir testes aceites ou suites PHP. O delta é local e ainda não instalado remotamente; não há prova de navegador nem persistência durável do motivo entre sessões. Saldo no módulo: essa retoma durável, jornadas externas com representação/vínculos documentados e conferência visual. O proprietário já indicou que não existe cadastro/documento aprovado desses vínculos; isso bloqueia apenas os ramos externos dependentes.
Próximo trabalho funcional na ordem preservada: saldo fiscal L8 com fontes aceites, seguido de modalidades de Imobiliário/Bens de consumo/franquias e Estatística/IA, sem reabrir os fechos aceites. Não há exclusão de funcionalidades previstas nem conclusão global presumida.
## 10/11/2026 — preparação conjunta de contador e aliases
[Entrega conjunta](../v2/auditoria-portal/entrega-conjunta-contador-aliases-2026-11-10.md) preparada contra `main`/`origin/main` em `2209b838`, com patch único, reversão e manifesto. Oito casos novos/56 asserções conferem contexto sem base de dados nos dois aliases, GET/HEAD e com/sem barra final. Hashes das fontes e do build privado do contador continuam iguais ao recibo aceite; provas anteriores reutilizadas, não repetidas.
@@ -1,3 +1,21 @@
## Continuação integral — saldo e execução em 10/11/2026
Actualização L8 desta passagem: recorte ordinário de derivação da fracção nacional implementado e demonstrado em QA até gravação/recuperação/relatório pelo writer privado existente. Base4400/colecta1408/parcela100 → fracção32 → saldo simulado676,com limites/provas próprios; caso exacto2/625 e correspondência em falta demonstrados. Nenhuma determinação automática de CDT,repartição de custos/perdas ou consumo oficial. Modo documentado e históricos preservados. Delta agrupável com Educação,ainda sem instalação. Fonte e percurso no início do manual; provas `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
Correcção do saldo global: preservar explicitamente resíduos L1–L7 e L9–L16 (INSS,IRPS mensal/anual,Selo,direitos,ICE,SISA,IPRA),conforme tabela actualizada na visão existente. Transporte/crédito económico entregues em569a1cd7 não são trabalho ausente. Nenhum lote agregado é fechado por número de testes;fecho comunicado nesta passagem é apenas a fracção nacional ordinária em QA.
Entrega agrupada de fontes: `outputs/entrega-educacao-l8-20261110.zip`, base ff726e57, manifesto e patch/reversão conferidos em índices temporários; Educação e L8 sem alterações de schema/rotas/writers. Tipos, lint, Pint e compilação QA finais aprovados. Sem instalação/publicação nova, sem alterar o pacote selado anterior ou o índice de trabalho. Homologação permanece ff726e57.
Instalação `ff726e57` concluída e verificada na homologação: contador/aliases, contactos, pré-dossier, curadoria, sitemap e S08 incluídos. As pendências de instalação dessas entregas nas entradas antigas abaixo são históricas, não trabalho a repetir. Prova: `outputs/reviewed-install-20261110/verification.json`. Conferência visual S08 conserva cinco pontos pendentes no mesmo registo; não bloqueia trabalhos independentes.
Saldo por percursos no início de [visao-consolidada-modulos.md](visao-consolidada-modulos.md), separado em implementação, instalação/conferência e dependência concreta. Matriz existente reconciliada em 35 IDs agregados, conservando 3.555 linhas, estados, critérios originais e todas as células fora das três colunas actualizadas. Não se converteu falta de prova em falta de implementação nem se criou novo inventário.
Primeiro trabalho executado nesta passagem: `AUD-EDU-01/03`, continuidade da revisão de procura/capacidade ao consultar histórico. A paginação normal obrigava a abandonar a preparação para navegar; os dois sentidos passam a conservar o estado e a posição, no mesmo detalhe. Chave por declaração/tipo/actor continua a separar contextos. Quatro verificações novas aprovadas: fundamento conservado, tentativa incerta repetida com a mesma chave, versão nova exige comparação e actor diferente não recebe o fundamento anterior. Tipos globais e lint dos dois ficheiros alterados aprovados. Nenhum teste anterior reexecutado; nenhuma alteração de writer, schema, permissões, dados ou histórico. Delta novo local, sem instalação remota ou prova visual presumida; não confundir com `ff726e57` já instalada.
Compilação QA do delta concluída com código 0; apenas `public/build-qa-opportunities` actualizado para a instalação local existente. Logs de tipos/build: `storage/logs/education-history-continuity-20261110-{types,build}.log`. Build servido de produção, ZIP selado, dados e cluster QA preservados. Não foi feita instalação remota deste delta.
Saldo de Educação: retoma durável da preparação de revisão, jornadas externas/representação sem cadastro aprovado e conferência visual. O delta resolve a perda de continuidade dentro da mesma página, não persistência entre sessões. Próximo trabalho na ordem acordada: continuar a funcionalidade fiscal L8 com fonte executável; preservar IVA/IRPC/PEC e exportações aceites, bloquear apenas os casos cuja regra/prova específica falta. O portal inteiro não fica concluído por este incremento.
## Entrega conjunta preparada em 10/11/2026
[Contador e aliases na mesma entrega](entrega-conjunta-contador-aliases-2026-11-10.md): base `main` e `origin/main` actualizadas e coincidentes em `2209b838`; pacote de fontes com manifesto, patch conjunto e inverso, conferidos num índice temporário. Oito verificações novas de contexto/rotas, 56 asserções; provas aceites do contador e aliases reutilizadas, sem repetir suites anteriores. Sem instalação, push, dados de negócio, configuração ou build servido alterados.
File diff suppressed because one or more lines are too long
@@ -1,3 +1,58 @@
## Saldo operacional reconciliado — 10/11/2026, após `ff726e57`
### Actualização fiscal: recorte L8 demonstrado em QA; saldo L1–L16 preservado
**Pendência encerrada nesta passagem em QA:** derivação proporcional da fracção nacional do crédito internacional no caso L6 ordinário corrente, com parcela líquida já incluída e correspondida à matéria colectável. Percurso cálculo → gravação/recuperação pela API privada existente → recálculo → HTML/relatório do dossier demonstrado. Não é fecho integral de L8, determinação automática de CDT ou repartição de custos/perdas. Delta agrupável com a continuidade de Educação, ainda não instalado na homologação, que mantém `ff726e57`.
O resumo anterior centrado em L8 era incompleto. Conservam-se explicitamente os seguintes resíduos e lotes; os recibos posteriores identificados neste manual prevalecem sobre anotações antigas de ausência/instalação pendente. Nenhuma estimativa histórica ou contagem de testes representa conclusão do módulo.
| Lote fiscal | Saldo efectivo preservado |
| --- | --- |
| L1 — enquadramento IVA | Casos residuais de P058 e P001 fora dos tratamentos já entregues, incluindo o incremento P001 `d1551dab`/`63147312`. P084/P085 e restantes fechos operacionais aceites não são reabertos. |
| L2 — dedução/regularizações IVA | Art.18(6)/afectação mista, regularizações sucessivas/plurianuais, erros/incobráveis e variantes fora dos recortes entregues. |
| L3 — saldo/Modelo A | Sequência/reversões de deduções, apuramentos próprios de fornecimentos/margem/viagens/eventos e formalidades C/D/E não cobertas; entregas posteriores de restituição preservadas. |
| L4 — ISPC | Operações ocasionais, aquisições acima do limite e formulários/instruções/regulamentação de 2026 aplicáveis; apuramento habitual entregue preservado. |
| L5 — retenções IRPC | Casos documentais/AT e variantes de dispensa/CDT, juros/rectificações/guias não encerrados pelos recibos. Taxas gerais/especiais, câmbio e retenções sofridas já entregues conservados. |
| L6 — IRPC normal | Grupos de código e documentais discriminados no manual: perdas/deduções, períodos/moedas/representação e casos fora do recorte ordinário demonstrado. Colecta ordinária utilizável não é IRPC integral. |
| L7 — tratamentos específicos IRPC | Famílias/combinações/mistos/transporte e documentos por caso ainda abertos. Cumulações e ramos específicos entregues preservados. |
| L8 — liquidação IRPC | Derivação nacional ordinária encerrada em QA nesta passagem. Restam correspondência CDT automática, inclusão/correcção económica e quota automáticas, art.64(3/4)/benefícios, obrigações PPC/PEC, juros/guias, autónomos/períodos/moedas/M20M22 e casos transitórios/especiais. Transporte ordinário de créditos/PEC já entregue em `569a1cd7` não é tratado como ausente. |
| L9 — INSS | Percurso contributivo por trabalhador/vínculo/base/período e recuperação/declaração; referências de taxas e massa salarial não são fecho integral. |
| L10 — IRPS mensal | Tabela mensal do período, condições pessoais aplicáveis, remunerações/benefícios, retenção e líquido ligados a L9; preservar o caso anteriormente demonstrado, sem o extrapolar. |
| L11 — IRPS anual/outras categorias | Categorias, englobamento/tratamento próprio, deduções, retenções, saldo e formulários; não copiar regras IRPC. |
| L12 — Selo | Verbas por operação/contrato, incidência/base/taxa/isenção/obrigação com fonte e período; não aplicar exemplo genérico a todas as facturas. |
| L13 — direitos aduaneiros | Código pautal/origem/valor/despesas/câmbio/benefícios e percurso documental; não inferir código pela descrição nem benefício pela localização. |
| L14 — ICE | Produtos/operações, base/unidade e tributação específica/ad valorem/mista, período e ligações pertinentes; produção interna não depende de calcular direitos aduaneiros. |
| L15 — SISA | Delimitação legal/autárquica, transmissão onerosa/direitos, incidência/isenções/base/período e percurso; DUAT não é propriedade da terra. |
| L16 — IPRA/encargos imobiliários | Posse/uso/valor patrimonial, autarquia/exercício/isenções e separação de impostos e taxas administrativas; preço de compra não substitui valor patrimonial. |
Não foram identificados recibos de fecho integral de L9–L16 nesta reconciliação dirigida. Isso conserva o saldo; não apaga código, fontes ou casos parciais anteriores. Dúvidas/fontes em falta afectam apenas os casos dependentes. Detalhes e prova L8 na entrada actual do manual existente e em `outputs/irpc-national-fraction-20261110/recovered-reports.json`.
Esta é a actualização da visão e da matriz existentes, não um novo inventário. A matriz conserva 3.555 linhas sobrepostas de requisitos, cláusulas e campos; a contagem não representa funcionalidades independentes nem trabalho por implementar. Preservam-se todos os fechos e provas aceites. As secções datadas de 18/10 abaixo são históricas e não reabrem implementações posteriores.
Instalação de fontes até `ff726e57` concluída na homologação, incluindo S08, contador, aliases, contactos, pré-dossier, curadoria e sitemap. Prova: `outputs/reviewed-install-20261110/verification.json`, revisão HTTPS, 44 hashes do delta, configuração preservada, zero migrations/importações. Não reinstalar esse pacote. Código instalado não comprova por si só cada flag, ledger de instalação modular, canal externo ou percurso privado em uso real; as habilitações anteriores e as suas provas mantêm-se. Um módulo não é declarado desactivado apenas por falta de nova observação.
| Módulo / percurso utilizável a completar | Implementação que falta | Instalação e conferência | Dependência concreta / critério de conclusão |
| --- | --- | --- | --- |
| Descoberta, oportunidades e conteúdos públicos → interesse/dossier | Completar cláusulas e modalidades especializadas ainda abertas; não refazer cadastro, revisão, publicação, pesquisa ou curadoria entregues. | Fontes até ff726e57 instaladas; conferir apresentação/percursos ainda sem prova visual. | Dados efectivos de cada anúncio, divulgação e representação existentes. Fecho: localizar uma ficha, preparar/publicar pelo workflow aplicável e continuar no mesmo dossier sem perder contexto. |
| Territórios, mapa, âncoras/clusters → selecção contextual | Consumidores territoriais e correspondências ainda não cobertos; preservar mapa único, filtros e observações quantitativas entregues. | Não reabrir instalação dos deltas incluídos; conferir consumidores sem prova, sem repetir percursos aceites. | Geometrias, coordenadas, associações e fontes reais em falta por item. Fecho: pesquisar/seleccionar/guardar/recuperar a referência correcta, sem inventar equivalências ou pontos. |
| Balcão, percurso adaptativo e S08 → pedido/resultado | Restantes consumidores e parametrização por âmbito, obrigações/registo/licenciamento sustentados; núcleo de dossier, pedido, histórico e fecho pelo dono preservado. | S08 instalada. Conferência restante: #S08, estado, botão, gravação/recuperação, janela estreita. | Requisitos de cada serviço e actos externos próprios; bloquear só esse serviço/caso quando faltarem. Fecho: preparar/submeter/acompanhar/recuperar resultado no dossier certo. |
| CRM, responsáveis e tarefas → acompanhamento | Restantes formulários/consumidores e preparações não confirmadas; não recriar conversão, próxima acção, edição e preparação de tarefas já demonstradas. | Fontes instaladas; restantes variantes por perfil e uso visual a conferir. | Designações/representação do caso e integrações externas apenas onde usadas. Fecho: converter uma vez, atribuir, acompanhar e retomar sem duplicar tarefa/interacção. |
| Biblioteca, documentos, scanner e suporte → consulta privada | Cobertura do acervo e consumidores/variantes ainda não individualizados; preservar importação idempotente, originais, scanner, revogação e incidentes demonstrados. | Não reinstalar consulta pública/editor aceites; conferir anexos e consumidores pendentes. Operação de scanner/worker é distinta de código. | Bytes/fontes autorizados e disponibilidade efectiva dos serviços quando necessários. Fecho: importar/consultar/partilhar/revogar com original, autoria e histórico íntegros. |
| Simulador fiscal/contributivo → cálculo/rascunho/relatório | Resíduos L1–L7, restante L8 e L9–L16 conforme tabela acima. Fracção nacional ordinária agora demonstrada em QA; transporte/crédito económico já entregues preservados. Não limitar saldo fiscal a L8. | Fontes até ff726e57 instaladas; delta de fracção nacional ainda QA. Conferir apenas novos consumidores/deltas. | Diploma/período/limite/prova/CDT concretos faltantes bloqueiam só o cálculo dependente. Fecho por ramo: cenário calculado, guardado, recuperado e exportado com fonte/versão/valores coerentes. |
| Negócios, empresas e duplicados → ficha/publicação/comparação | Consolidação rastreável de entidades inequívocas, outros formatos e variantes contratuais ainda abertos. Pesquisa de semelhantes por raio/sector/classificação e consulta dos lotes já existem. | Fontes instaladas; visual/variantes ainda não observadas separados. | Pontos/fontes/classificações e prova do mandato por caso; não fundir apenas por nome. Fecho: localizar/importar/reconciliar, publicar e comparar sem perder originais/relações. |
| Fundos e alertas → referência/avaliação informativa/acompanhamento | Vínculos restantes da referência ao cadastro e canais/fontes externas; catálogo, editor, versões, critérios e gerador local já implementados. | Código instalado; confirmar operação do agendador/canal apenas no âmbito configurado e observação visual restante. | Fontes/prazos efectivos e credencial/destinatário do canal concreto. Fecho: consultar uma chamada, responder aos critérios, guardar e receber o aviso configurado sem decisão de crédito presumida. |
| Forma societária e preparação da empresa → modelo | Casos especiais; ingestão/versionamento/vínculo dos sete modelos exactos e derivadas; actividade/licenciamento integrado e percursos de assinatura/submissão/registo ainda não cobertos. | Comparador e preparação factual têm código instalado; não confundir ficha factual com contrato. Conferência visual pendente. | Exemplares exactos dos modelos e intervenientes externos de assinatura/registo. Fecho: comparar com fontes, preparar a forma escolhida e recuperar o modelo/derivada versionados; ausência de um modelo não bloqueia o comparador. |
| Capitalização e BVM → orientação/referência no dossier | Casos especiais e procedimentos CVM ainda não codificados; os guiões base de capitalização e cinco percursos BVM já existem. | Fontes instaladas; visual/login dos recortes pendentes. | Regulamento anual de montantes, preçário/encargos e condições da contraparte aplicáveis. Fecho: seleccionar percurso, perceber requisitos/fontes/indeterminações e recuperar a preparação no dossier. |
| Checklists e formulários → versão publicada/submissão | Outros consumidores e parametrização transversal de limites, formatos/mensagens e camadas documentais por fonte/âmbito. Editor, vigência, tipos, anexos e selector sem UUID manual já existem. | Fontes instaladas; habilitação nominal quando exigida pelo instalador existente, sem a presumir ausente; visual restante. | Texto/requisito/contexto publicado por administrador autorizado. Fecho: publicar v2, novas submissões usam v2 e respostas v1 continuam recuperáveis. |
| Educação → declaração/revisão/histórico | Retoma durável da preparação de revisão e jornadas externas de estudantes/formadoras/empregadores ainda abertas. **Avançado nesta passagem:** paginação do histórico preserva a revisão em preparação e o envio incerto, mantendo comparação/actor. | Delta novo local, ainda não instalado; quatro verificações novas, sem repetir provas anteriores. Conferência visual separada. | Não existe cadastro/documento aprovado dos vínculos externos, conforme decisão já registada; bloqueia só esses vínculos. Fecho interno: preparar/submeter, outro gestor rever e consultar histórico sem perder preparação ou duplicar decisão. |
| Bolsa/parcerias → candidatura/sala/acordo/marco | Variantes e consumidores não cobertos; preparação durável dos passos ainda só em memória. Editores, candidaturas, acordos, resumos e históricos entregues não são ausência funcional. | Fontes instaladas; conferência de partilha/revogação e restantes variantes por actor pendente. | Participantes/documentos autorizados no caso; não inferir autorização de oportunidade a partir da sala. Fecho: candidatura, colaboração, acordo/marco e revogação sem perda de original/histórico. |
| Perfis, gestão, indicadores e painéis → operação por papel | Métricas/fontes/denominadores e restantes filas por perfil; contextos de país em consumidores ainda abertos. Preservar perfis, revogação, suporte e editores entregues. | Código instalado; atribuições reais não são criadas por instalação; observação por papel separada. | Designações operacionais e fontes dos indicadores concretos. Fecho: acção útil permitida/negada pelo papel, evento contado com âmbito/período explícitos e distinção zero/sem dados. |
| Acesso offline, línguas e IA → consumidores previstos | Consulta offline de fichas já existe; edição/sincronização e idiomas adicionais não concluídos; IA não substitui os percursos determinísticos. | Código entregue preservado; abertura sem rede, apresentação e novos deltas por conferir. | Línguas/traduções aprovadas e fornecedor/credencial/âmbito de IA quando necessários; só bloqueiam esses casos. Fecho por consumidor, sem prometer cobertura geral offline ou inteligência não implementada. |
**Ordem de continuação preservada:** primeiro defeitos que impeçam usar/concluir percursos existentes (estabilização); depois simulador fiscal delimitado, Imobiliário, Bens de consumo/franquias, Estatística/painéis e IA, conforme ordem já registada. A correcção da continuidade do histórico de Educação pertence à estabilização de um percurso existente, não cria uma frente paralela. Próximo trabalho nessa ordem: continuar o saldo fiscal L8 executável com as fontes aceites; faltas de prova/CDT/regulamento bloqueiam apenas os casos afectados. A conferência visual S08 não suspende essa implementação.
O portal inteiro permanece em conclusão, sem excluir requisitos por inferência. A matriz conserva os critérios originais e as provas por ID; este quadro organiza o trabalho por percursos, sem promover módulos agregados a concluídos por código instalado ou por um teste parcial.
<!-- saldo-atual-20261018 -->
## Saldo atual por requisito em 18 de outubro de 2026
@@ -116,6 +116,15 @@ export function SimulationIrpcInternational({
<fieldset className="space-y-3 rounded border p-3">
<legend>Crédito por dupla tributação internacional</legend>
<p>{internationalCreditSource}</p>
{c.positions.some((p) => p.nationalMethod === 'proportional') && (
<p>
No modo de derivação ordinária abaixo, o portal calcula a
fracção nacional a partir da colecta e matéria colectável L6
correntes e da parcela líquida correspondida. Mantêm-se as
restantes condições e limites do modo documentado; não
determina a convenção aplicável.
</p>
)}
<p>
Reúna os comprovativos de cada país numa posição por exercício.
Indique a fracção nacional e a convenção conforme o suporte do
@@ -220,6 +229,59 @@ export function SimulationIrpcInternational({
onChange={(country) => patch({ country })}
allowLegacyValue
/>
<label className="block">
Fracção nacional
<select
aria-label={
'Internacional ' +
(index + 1) +
' — Método da fracção nacional'
}
value={p.nationalMethod ?? 'documented'}
onChange={(e) =>
patch({
nationalMethod: e.target
.value as InternationalPosition['nationalMethod'],
})
}
>
<option value="documented">
Limite documentado
</option>
<option value="proportional">
Derivar da colecta L6 ordinária
</option>
</select>
</label>
{p.nationalMethod === 'proportional' && (
<label className="block">
Correspondência da parcela líquida estrangeira
<textarea
aria-label={
'Internacional ' +
(index + 1) +
' — Correspondência da parcela líquida'
}
value={p.nationalBasisReference ?? ''}
onChange={(e) =>
patch({
nationalBasisReference:
e.target.value,
})
}
/>
<span className="block">
Indique abaixo a parcela líquida já incluída
na matéria colectável, após os custos,
perdas e deduções correspondentes. A fracção
será colecta L6 × parcela líquida / matéria
colectável L6, antes de créditos. O portal
não reparte custos ou perdas nem determina
convenções. O limite manual anterior é
conservado, mas não usado neste modo.
</span>
</label>
)}
{(
[
['subjectNuit', 'NUIT titular'],
@@ -238,23 +300,33 @@ export function SimulationIrpcInternational({
'Suporte da fracção nacional e exercício',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={
'Internacional ' +
(index + 1) +
' — ' +
label
}
value={p[key]}
onChange={(e) =>
patch({ [key]: e.target.value })
}
/>
</label>
))}
)
.filter(
([key]) =>
p.nationalMethod !== 'proportional' ||
(key !== 'nationalLimit' &&
key !== 'nationalLimitReference'),
)
.map(([key, label]) => (
<label key={key} className="block">
{key === 'incomeAmount' &&
p.nationalMethod === 'proportional'
? 'Parcela líquida estrangeira incluída — MZN'
: label}
<input
aria-label={
'Internacional ' +
(index + 1) +
' — ' +
label
}
value={p[key]}
onChange={(e) =>
patch({ [key]: e.target.value })
}
/>
</label>
))}
<label className="block">
Rendimento incluído na matéria colectável
<select
+43 -2
View File
@@ -1154,12 +1154,28 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
b.subjectNuit,
b.year,
b.assessmentKey,
{
ordinary:
assessment?.kind === 'normal' &&
assessment.current &&
assessment.key === b.assessmentKey,
taxableBase: assessment?.record?.model.taxableBase ?? null,
colecta: assessment?.colecta ?? null,
},
);
pending.push(
...m.pending.map((e) => 'País ' + p.country + ': ' + e),
);
return m;
}) ?? [];
const derivedNational = positions.flatMap((p) =>
p.nationalDerivation ? [p.nationalDerivation.income] : [],
);
const currentBase = assessment?.record?.model.taxableBase;
if (currentBase && cmp(addFractions(derivedNational), currentBase) > 0n)
pending.push(
'As parcelas estrangeiras com fracção derivada excedem em conjunto a matéria colectável corrente; reconcilie a afectação por país.',
);
const economicRows =
b.economicCredits?.rows.map((r) => {
const issues = claims
@@ -1384,7 +1400,9 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
: []),
...(b.internationalCredits
? [
'Crédito internacional do próprio exercício:fracção nacional,convenção e montantes MZN são documentados;sem cálculo presumido,transporte ou consumo oficial. ' +
(positions.some((p) => p.nationalDerivation)
? 'Crédito internacional do próprio exercício:fracção nacional derivada no caso L6 ordinário com parcela líquida correspondida;CDT e montantes MZN documentados;sem repartição presumida de custos/perdas,transporte ou consumo oficial. '
: 'Crédito internacional do próprio exercício:fracção nacional,convenção e montantes MZN são documentados;sem cálculo presumido,transporte ou consumo oficial. ') +
(b.economicCredits
? 'Benefícios positivos ficam separados;crédito económico no detalhe próprio.'
: 'Créditos económicos/benefícios positivos ficam separados.'),
@@ -1630,6 +1648,21 @@ export function annualReportRows(
show(p.paid) +
'; fracção nacional: ' +
show(p.nationalLimit) +
(p.nationalDerivation
? '; derivação L6: colecta ' +
show(p.nationalDerivation.colecta) +
' × parcela líquida ' +
show(p.nationalDerivation.income) +
' / matéria colectável ' +
show(
p.nationalDerivation
.taxableBase,
) +
'; exacta ' +
p.nationalLimit?.numerator +
'/' +
p.nationalLimit?.denominator
: '; limite documentado') +
'; limite CDT: ' +
(p.input.treaty === 'no'
? 'Não aplicável documentado'
@@ -1646,7 +1679,15 @@ export function annualReportRows(
p.pending.join(' | '),
p.input.incomeReference +
'; ' +
p.input.nationalLimitReference +
(p.nationalDerivation
? 'Limite manual anterior conservado, não utilizado nesta derivação'
: p.input.nationalLimitReference) +
(p.nationalDerivation
? '; correspondência: ' +
p.nationalDerivation.reference +
'; ' +
p.nationalDerivation.version
: '') +
'; ' +
p.input.treatyReference +
'; ' +
+64 -3
View File
@@ -38,6 +38,8 @@ export type InternationalPosition = {
incomeReference: string;
nationalLimit: string;
nationalLimitReference: string;
nationalMethod?: 'documented' | 'proportional';
nationalBasisReference?: string;
treaty: '' | 'yes' | 'no';
treatyLimit: string;
treatyReference: string;
@@ -149,6 +151,10 @@ export function validateInternational(c: InternationalCredits) {
) ||
!yesNo.includes(p.incomeIncluded) ||
!yesNo.includes(p.treaty) ||
(p.nationalMethod !== undefined &&
!['documented', 'proportional'].includes(p.nationalMethod)) ||
(p.nationalBasisReference !== undefined &&
typeof p.nationalBasisReference !== 'string') ||
!evidenceValid(p.previousUse) ||
!Array.isArray(p.payments)
)
@@ -278,6 +284,11 @@ export function internationalPosition(
subjectNuit: string,
year: string,
assessmentKey: string,
nationalBasis?: {
ordinary: boolean;
taxableBase: FiscalFraction | null;
colecta: FiscalFraction | null;
},
) {
const pending = [...proofIssues];
let nationalLimit: FiscalFraction | null = null,
@@ -286,6 +297,13 @@ export function internationalPosition(
eligible: FiscalFraction | null = null,
available: FiscalFraction | null = null,
aboveLimit: FiscalFraction | null = null;
let nationalDerivation: {
version: string;
income: FiscalFraction;
taxableBase: FiscalFraction;
colecta: FiscalFraction;
reference: string;
} | null = null;
try {
if (
!p.id.trim() ||
@@ -311,8 +329,46 @@ export function internationalPosition(
'Falta inclusão documentada do rendimento estrangeiro na matéria colectável.',
);
const income = creditMoney(p.incomeAmount);
nationalLimit = creditLimit(p.nationalLimit);
if (!p.nationalLimitReference.trim())
if (p.nationalMethod === 'proportional') {
const basis = nationalBasis;
if (
!basis?.ordinary ||
!basis.taxableBase ||
!basis.colecta ||
creditCompare(basis.taxableBase, zero()) <= 0n ||
creditCompare(basis.colecta, zero()) < 0n
)
throw Error(
'Derivação da fracção nacional exige matéria colectável positiva e colecta L6 ordinária corrente; efeitos L7 e casos especiais conservam correspondência própria.',
);
if (!p.nationalBasisReference?.trim())
throw Error(
'Identifique a correspondência da parcela líquida estrangeira na matéria colectável, incluindo custos/perdas/deduções aplicáveis; o portal não reparte esses valores por presunção.',
);
if (creditCompare(income, basis.taxableBase) > 0n)
throw Error(
'Parcela estrangeira excede a matéria colectável corrente; reconcilie custos, perdas e deduções antes de derivar a fracção.',
);
nationalLimit = fraction(
BigInt(basis.colecta.numerator) *
BigInt(income.numerator) *
BigInt(basis.taxableBase.denominator),
BigInt(basis.colecta.denominator) *
BigInt(income.denominator) *
BigInt(basis.taxableBase.numerator),
);
nationalDerivation = {
version: 'IRPC-national-fraction/1',
income,
taxableBase: structuredClone(basis.taxableBase),
colecta: structuredClone(basis.colecta),
reference: p.nationalBasisReference,
};
} else nationalLimit = creditLimit(p.nationalLimit);
if (
p.nationalMethod !== 'proportional' &&
!p.nationalLimitReference.trim()
)
throw Error(
'Fracção nacional exige suporte próprio; não é calculada nem presumida pelo portal.',
);
@@ -363,6 +419,7 @@ export function internationalPosition(
country: p.country,
paid,
nationalLimit,
...(nationalDerivation ? { nationalDerivation } : {}),
treatyLimit,
previousUse,
eligible,
@@ -403,7 +460,11 @@ export function internationalDeduction(
: null,
}
: {}),
source: internationalCreditSource,
source:
internationalCreditSource +
(positions.some((p) => p.nationalDerivation)
? ' Modo IRPC-national-fraction/1: no caso L6 ordinário corrente com parcela líquida documentada e correspondida, fracção = colecta antes de créditos × parcela líquida estrangeira / matéria colectável. Não efectua repartição de custos/perdas ou qualificação automática de CDT.'
: ''),
countryAllocationCreated: false,
carryCreated: false,
officialBalancesConsumed: false,
+31 -2
View File
@@ -1,7 +1,7 @@
import { Link } from '@inertiajs/react';
import EducationReview from '@/components/education-review';
import type { EducationReviewProps } from '@/lib/education-review';
import { Management, Pagination } from '../management/shared';
import { Management } from '../management/shared';
import type { Paginated } from '../management/shared';
type ReviewEntry = {
@@ -146,7 +146,36 @@ export default function Declaration(
)}
</article>
))}
<Pagination page={props.history} />
<nav
aria-label="Paginação do histórico de revisão"
className="flex flex-wrap items-center gap-4"
>
{props.history.prev_page_url && (
<Link
className="rounded border p-2"
href={props.history.prev_page_url}
preserveState
preserveScroll
>
Anterior
</Link>
)}
<span>
{props.history.total} registos · Página{' '}
{props.history.current_page} de{' '}
{props.history.last_page}
</span>
{props.history.next_page_url && (
<Link
className="rounded border p-2"
href={props.history.next_page_url}
preserveState
preserveScroll
>
Seguinte
</Link>
)}
</nav>
</section>
)}
</Management>
@@ -0,0 +1,52 @@
<?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 IrpcNationalFractionDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_derived_national_fraction_round_trips_through_the_existing_private_draft_writer(): void
{
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('proactive_investment_20261027_test', $target->name);
$this->assertSame(55441, (int) $target->port);
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
$this->actingAs(User::findOrFail(1021));
$id = null;
$etag = null;
foreach (['ordinary', 'exact', 'missing'] as $name) {
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-national-fraction-20261110/'.$name.'.json')), true, flags: JSON_THROW_ON_ERROR);
$personal = ['person_type' => $snapshot['person_type']];
unset($snapshot['person_type']);
$payload = ['personal_snapshot' => $personal, 'economic_snapshot' => $snapshot];
if ($id === null) {
$id = $this->postJson('/api/v1/simulation-drafts', $payload, ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
} else {
$this->putJson('/api/v1/simulation-drafts/'.$id, $payload, ['If-Match' => $etag])->assertOk();
}
$response = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();
$etag = $response->headers->get('ETag');
$back = $response->json('data.economic_snapshot');
$this->assertSame($snapshot['modelo_a_snapshot'], $back['modelo_a_snapshot']);
$this->assertSame($snapshot['business_plan_snapshot'], $back['business_plan_snapshot']);
$book = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcAnnual'][$snapshot['context_opportunity']];
$this->assertSame('proportional', $book['internationalCredits']['positions'][0]['nationalMethod']);
$model = $book['result']['model'];
if ($name === 'missing') {
$this->assertSame('indeterminate', $model['status']);
$this->assertNull($model['arithmeticBalance']);
} else {
$this->assertSame(['numerator' => $name === 'ordinary' ? '32' : '2', 'denominator' => $name === 'ordinary' ? '1' : '625'], $model['internationalCredit']['positions'][0]['nationalLimit']);
$this->assertSame('IRPC-national-fraction/1', $model['internationalCredit']['positions'][0]['nationalDerivation']['version']);
}
file_put_contents(base_path('outputs/irpc-national-fraction-20261110/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
}
}
+63
View File
@@ -3,6 +3,69 @@ import assert from 'node:assert/strict';
import { harness, props, text } from './helpers/education-review.mjs';
const form = 'education-review';
function historyPage(page = 1) {
return {
data: [], total: 24, current_page: page, last_page: 2,
prev_page_url: page === 2 ? '/management/education/demands/one?scope=review&history_page=1' : null,
next_page_url: page === 1 ? '/management/education/demands/one?scope=review&history_page=2' : null,
};
}
test('history paging keeps the prepared review and does not ask to discard it', () => {
const h = harness(props({ history: historyPage() }));
h.change(form, reason, 'SYNTHETIC reasoning still being prepared.');
const next = h.all().find(node => node.type === 'a' && text(node) === 'Seguinte');
assert.equal(next.props.preserveState, true);
assert.equal(next.props.preserveScroll, true);
assert.match(next.props.href, /scope=review&history_page=2$/);
assert.equal(h.leave(next.props.preserveState), false);
assert.equal(h.confirmCalls, 0);
h.render({ ...h.props, history: historyPage(2) });
assert.equal(h.field(form, reason).props.value, 'SYNTHETIC reasoning still being prepared.');
const previous = h.all().find(node => node.type === 'a' && text(node) === 'Anterior');
assert.equal(previous.props.preserveState, true);
assert.equal(previous.props.preserveScroll, true);
assert.equal(h.sent.length, 0);
assert.equal(h.leave(), true); // Leaving the declaration still protects unsaved work.
});
test('history paging keeps an uncertain attempt recoverable under the same command key', () => {
const h = harness(props({ history: historyPage() }));
const request = prepared(h);
h.fail(request);
h.render({ ...h.props, history: historyPage(2) });
const retry = h.submit(form);
assert.deepEqual(retry.data, request.data);
assert.equal(h.sent.length, 2); // One explicit retry; paging itself sends no decision.
});
test('history paging cannot silently approve a newer declaration version', () => {
const h = harness(props({ history: historyPage() }));
h.change(form, reason, 'SYNTHETIC review prepared against version one.');
h.render({
...h.props, history: historyPage(2),
declaration: { ...h.props.declaration, version_no: 2, quantity: 31 },
});
h.submit(form);
assert.equal(h.sent.length, 0);
assert.equal(h.field(form, reason).props.value, 'SYNTHETIC review prepared against version one.');
assert.match(text(h.tree), /A declaração mudou/);
h.click('Confirmar a leitura desta versão');
const request = h.submit(form);
assert.equal(request.data.version_no, 2);
});
test('history paging with a different actor remounts instead of disclosing the prepared reason', () => {
const h = harness(props({ history: historyPage() }));
h.change(form, reason, 'SYNTHETIC private reasoning of the previous actor.');
h.render({
...h.props, actorId: 3, history: historyPage(2),
declaration: { ...h.props.declaration, review_context: { actor_id: 3, commands: [] } },
});
assert.equal(h.field(form, reason).props.value, '');
assert.equal(h.sent.length, 0);
});
test('the private detail renders preserved reasons, unavailable entries and contextual history pagination', () => {
const h = harness(
props({
@@ -0,0 +1,141 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { internationalCase } from './helpers/irpc-international-case.mjs';
import {
calculateAnnual,
annualBook,
annualCurrent,
recordAnnual,
saveAnnualBook,
annualHtml,
annualReportRows,
} from '../../resources/js/lib/irpc-annual-settlement.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
export function nationalCase(income = '100') {
const c = internationalCase('100');
Object.assign(c.b.internationalCredits.positions[0], {
nationalMethod: 'proportional',
incomeAmount: income,
nationalBasisReference:
'SYNTHETIC QA: net taxable foreign component, costs/losses/deductions allocated and included in this L6 base; not a real tax determination',
});
return c;
}
const position = (c) => c.b.internationalCredits.positions[0];
void test('national fraction aggregate cannot allocate more than the current base across countries', () => {
const c = nationalCase('3000');
const second = structuredClone(position(c));
Object.assign(second, {
id: 'country-pt',
country: 'PT',
qualificationCountry: 'PT',
});
Object.assign(second.payments[0], {
id: 'tax-pt',
document: 'SYNTHETIC foreign-tax-pt',
});
c.b.internationalCredits.positions.push(second);
const m = calculateAnnual(c.d, c.b);
assert.equal(m.arithmeticBalance, null);
assert.ok(m.pending.some((message) => message.includes('em conjunto')));
});
void test('national fraction derives 1408 x 100 / 4400 before credits and retains the manual entry', () => {
const c = nationalCase();
const m = calculateAnnual(c.d, c.b);
assert.deepEqual(m.pending, []);
const p = m.internationalCredit.positions[0];
assert.deepEqual(p.nationalLimit, { numerator: '32', denominator: '1' });
assert.equal(p.input.nationalLimit, '8');
assert.equal(
p.nationalDerivation.reference,
position(c).nationalBasisReference,
);
assert.deepEqual(m.payable, { numerator: '676', denominator: '1' });
assert.equal(m.internationalCredit.officialBalancesConsumed, false);
});
void test('national fraction calculation survives serialization, retrieval and both existing report renderers', () => {
const c = nationalCase('0.01');
const historical = structuredClone(c.b.result);
const b = recordAnnual(c.d, c.b, '2026-11-10T13:00:00+02:00');
const saved = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, b) };
const back = JSON.parse(JSON.stringify(saved));
const recovered = annualBook(back);
assert.equal(annualCurrent(back, recovered), true);
assert.deepEqual(
recovered.result.model.internationalCredit.positions[0].nationalLimit,
{ numerator: '2', denominator: '625' },
);
assert.deepEqual(
calculateAnnual(back, recovered).internationalCredit,
recovered.result.model.internationalCredit,
);
assert.deepEqual(c.b.result, historical);
assert.match(annualHtml(back), /2\/625/);
assert.match(
JSON.stringify(annualReportRows(back)),
/IRPC-national-fraction\/1/,
);
assert.match(
JSON.stringify(
createProjectDossierReport(back, {
selectedSections: new Set(['finance']),
generatedAt: '2026-11-10T13:00:00+02:00',
}),
),
/2\/625/,
);
});
void test('national fraction leaves a case indeterminate without net-income correspondence or with too much income', () => {
for (const patch of [
{ nationalBasisReference: '' },
{ incomeAmount: '4400.01' },
{ nationalMethod: 'unknown' },
]) {
const c = nationalCase();
Object.assign(position(c), patch);
if (patch.nationalMethod)
assert.throws(() => calculateAnnual(c.d, c.b));
else {
const m = calculateAnnual(c.d, c.b);
assert.equal(m.status, 'indeterminate');
assert.equal(m.arithmeticBalance, null);
}
}
});
void test('national fraction rejects stale assessment binding and still applies a documented treaty ceiling', () => {
const c = nationalCase();
Object.assign(position(c), {
treaty: 'yes',
treatyLimit: '6',
treatyReference:
'SYNTHETIC QA case-specific ceiling, no actual treaty assertion',
});
assert.deepEqual(calculateAnnual(c.d, c.b).payable, {
numerator: '702',
denominator: '1',
});
position(c).assessmentKey = 'obsolete';
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
});
void test('national fraction keeps historical documented mode unchanged when the optional method is absent', () => {
const c = internationalCase();
const original = calculateAnnual(c.d, c.b);
position(c).nationalMethod = 'documented';
const m = calculateAnnual(c.d, c.b);
assert.deepEqual(
m.internationalCredit.deducted,
original.internationalCredit.deducted,
);
assert.deepEqual(m.payable, { numerator: '700', denominator: '1' });
assert.equal(
m.internationalCredit.positions[0].nationalDerivation,
undefined,
);
});
+16
View File
@@ -0,0 +1,16 @@
import fs from 'node:fs';
import { internationalCase } from '../tests/frontend/helpers/irpc-international-case.mjs';
import { recordAnnual, saveAnnualBook } from '../resources/js/lib/irpc-annual-settlement.ts';
const out = new URL('../outputs/irpc-national-fraction-20261110/', import.meta.url);
fs.mkdirSync(out, { recursive: true });
for (const [name, amount, reference] of [
['ordinary', '100', 'SYNTHETIC QA: net foreign taxable component included in this ordinary base after corresponding costs/losses/deductions'],
['exact', '0.01', 'SYNTHETIC QA: exact net component correspondence'],
['missing', '100', ''],
]) {
const c = internationalCase('100');
Object.assign(c.b.internationalCredits.positions[0], { nationalMethod: 'proportional', incomeAmount: amount, nationalBasisReference: reference });
const book = recordAnnual(c.d, c.b, '2026-11-10T13:00:00+02:00');
const snapshot = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, book) };
fs.writeFileSync(new URL(name + '.json', out), JSON.stringify(snapshot));
}
+26
View File
@@ -0,0 +1,26 @@
<?php
$root = dirname(__DIR__);
if (($argv[1] ?? '') !== 'test' || basename($root) !== 'portal-investidor') {
throw new RuntimeException('Explicit local test operation required.');
}
foreach (['APP_ENV' => 'testing', 'APP_KEY' => 'base64:'.base64_encode(str_repeat('Q', 32)),
'APP_CONFIG_CACHE' => $root.'/outputs/irpc-national-fraction-20261110/no-config.php',
'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
'DB_DATABASE' => 'proactive_investment_20261027_test', 'DB_USERNAME' => 'investmoz_test', 'DB_PASSWORD' => '',
'DB_URL' => '', 'DATABASE_URL' => '', 'CACHE_STORE' => 'array', 'SESSION_DRIVER' => 'array',
'QUEUE_CONNECTION' => 'sync', 'MAIL_MAILER' => 'array', 'SUITECRM_ENABLED' => 'false'] as $key => $value) {
putenv($key.'='.$value);
$_ENV[$key] = $_SERVER[$key] = $value;
}
$pdo = new PDO('pgsql:host=127.0.0.1;port=55441;dbname=proactive_investment_20261027_test', 'investmoz_test', '');
$identity = $pdo->query('select current_database() as name,inet_server_port() as port')->fetch(PDO::FETCH_ASSOC);
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
throw new RuntimeException('Isolated QA identity required.');
}
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/IrpcNationalFractionDraftTest.php'];
$argc = count($argv);
$_SERVER['argv'] = $argv;
$_SERVER['argc'] = $argc;
require $root.'/vendor/autoload.php';
require $root.'/vendor/phpunit/phpunit/phpunit';
@@ -0,0 +1,25 @@
import fs from 'node:fs';
import assert from 'node:assert/strict';
import { annualBook, annualCurrent, calculateAnnual, annualHtml, annualReportRows } from '../resources/js/lib/irpc-annual-settlement.ts';
import { createProjectDossierReport } from '../resources/js/lib/project-dossier-report.ts';
const out = new URL('../outputs/irpc-national-fraction-20261110/', import.meta.url);
const cases = [];
for (const name of ['ordinary', 'exact', 'missing']) {
const d = JSON.parse(fs.readFileSync(new URL('recovered-' + name + '.json', out), 'utf8'));
const b = annualBook(d), m = b.result.model, fresh = calculateAnnual(d, b);
assert.equal(annualCurrent(d, b), true);
assert.deepEqual(fresh.internationalCredit, m.internationalCredit);
assert.deepEqual(fresh.arithmeticBalance, m.arithmeticBalance);
const html = annualHtml(d);
const rows = annualReportRows(d);
const dossier = createProjectDossierReport(d, { selectedSections: new Set(['finance']), generatedAt: '2026-11-10T13:00:00+02:00' });
if (name !== 'missing') {
assert.match(html, /derivação L6/);
assert.match(JSON.stringify(dossier), /IRPC-national-fraction\/1/);
} else assert.equal(m.arithmeticBalance, null);
fs.writeFileSync(new URL('report-' + name + '.html', out), html);
fs.writeFileSync(new URL('dossier-' + name + '.json', out), JSON.stringify(dossier));
cases.push({ name, status: m.status, nationalLimit: m.internationalCredit.positions[0].nationalLimit, balance: m.arithmeticBalance, apiRecovered: true, current: true, html: true, dossier: true, reportRows: rows.length });
}
fs.writeFileSync(new URL('recovered-reports.json', out), JSON.stringify({ synthetic: true, activeDatabaseChanged: false, cases }, null, 2));
console.log(JSON.stringify(cases));