diff --git a/docs/investmoz/consolidated-review-matrix-2026-10-03.md b/docs/investmoz/consolidated-review-matrix-2026-10-03.md index 052fdb55..c04f0e00 100644 --- a/docs/investmoz/consolidated-review-matrix-2026-10-03.md +++ b/docs/investmoz/consolidated-review-matrix-2026-10-03.md @@ -2130,3 +2130,14 @@ No editor, os documentos ainda por associar mantêm-se visíveis; os já associa Passaram oito testes frontend dirigidos: seis existentes e dois novos para encaminhamento conforme contexto, distinção entre associação/aprovação/indeterminação, controlos recuperados editáveis, retirada explícita, recuperação sem mutação do original e estado desactivado dos controlos. Tipos, lint, formatação e compilação QA passaram. Recibo e hashes no campo `adaptive_documents_increment` de `local-validation-20261029.json`; provas integrais aceites não foram repetidas. **Visual continua pendente, execução pela equipa:** na candidata, conferir contagens e destino com/sem dados em falta; abrir documentos já associados, alterar/retirar uma associação, comparar impacto antes de confirmar e verificar recuperação. Incluir também âmbito/período não cobertos e exigências indeterminadas, sem interpretar associação como aprovação. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. Sem instalação ou avanço de main; requisitos transversais continuam parciais. + + +### 29/10/2026 — comparação de impacto: alterações das associações documentais + +Continuação sobre `181a9883`, nos requisitos existentes V2-07/V2-10 e na decisão adaptativa de 23/10: explicar divergências e impacto antes da confirmação. Lacuna reproduzida na revisão anterior: a comparação mostrava apenas alterações de estado, omitindo a troca de documento quando ambos mantinham «associado». O texto de vínculo preservado também não distinguia substituição de conservação. + +Corrigido no componente existente: comparação por estado e identificador documental, com substituição, associação e retirada explícitas; nomes anterior/novo quando disponíveis, nome indisponível identificado sem inventar o ficheiro, e vínculo preservado apenas quando o identificador permanece. A ausência de alterações é expressamente limitada às exigências/associações documentais. O plano anterior/posterior já fornecido pelo backend e a confirmação de impacto mantêm-se; não há escrita em dados, mudança de schema, permissões ou cálculo. + +Dois novos casos falharam contra o código anterior e passaram na candidata. Dez testes frontend dirigidos passaram, cobrindo também troca com o mesmo nome, retirada, nova associação, nome indisponível, histórico sem aplicação, ausência de alterações e preservação da origem. Tipos, lint, formatação e compilação QA passaram. Provas históricas aceites reutilizadas, sem repetição integral. Recibo e hashes no campo `adaptive_document_impact_increment` de `local-validation-20261029.json`. + +**Visual pendente pela equipa:** na candidata e num dossier autorizado, preparar troca/retirada de documento, comparar antes de confirmar e conferir nomes/estado anterior e posterior; confirmar que associação mantida não surge como substituição e que o histórico permanece. Não é necessário confirmar uma alteração real para observar a prévia. Registar revisão, percurso, perfil, esperado/observado e evidência nesta matriz. Nenhuma instalação ou avanço de main; o incremento não encerra o percurso adaptativo transversal. diff --git a/docs/investmoz/unification/local-validation-20261029.json b/docs/investmoz/unification/local-validation-20261029.json index b48ee301..881e009d 100644 --- a/docs/investmoz/unification/local-validation-20261029.json +++ b/docs/investmoz/unification/local-validation-20261029.json @@ -1254,5 +1254,41 @@ "preservation": "Presentation and navigation only. Existing impact comparison, document data/history, backend, schema, permissions and fiscal rules unchanged. Historical accepted suites reused, not rerun in full.", "visual_acceptance": "Pending team execution and recording on candidate; component tests do not constitute visual acceptance.", "installation": "Not triggered; main and active release unchanged." + }, + "adaptive_document_impact_increment": { + "recorded_at": "2026-10-29T20:58:29.863158+00:00", + "base_commit": "181a9883b383d11226a84ea0a7ff64844543982d", + "requirements": [ + "V2-07", + "V2-10", + "Tempo adaptive journey decision 2026-10-23: explain divergences and impacts before confirmation" + ], + "defect": "Impact comparison filtered only by requirement state, hiding a changed document association when both old and new states were associated; preservation text could misdescribe a replacement.", + "behavior": "Compare both state and document identifier. Expose replacement, attachment and detachment with prior/new filenames when available, explicit fallback when a linked name is unavailable, and preserved-link text only for unchanged identifiers. Explicitly report no requirement/association changes without implying no other context impact.", + "checks": { + "previous_revision_reproduced": true, + "new_frontend_cases": 2, + "directed_frontend_passed": 10, + "types": "passed", + "lint": "passed", + "format": "passed", + "qa_build": "passed" + }, + "source_sha256": { + "resources/js/components/private-investment-preparation.tsx": "ec387317c8a4b0c82bf6339d3e3d898e9fdae3682419fbbc4b6a09c1b248a9f9", + "tests/frontend/investor-usability-components.test.mjs": "e6f4dcc4727f8ad8650cf50223c8d0aed1ac9f6b195568da3490e65cf93ecc50" + }, + "evidence": { + "outputs/validation-20261029/adaptive-impact-red.log": "879cffff2d7b197b3e25caa80d41c203263c017823bb54ed1ff10e31dbf01d58", + "outputs/validation-20261029/adaptive-impact-tests.log": "e62231b8f3087a30781d59ebfab3255264a842ca205c5e627a448eb2000df566", + "outputs/validation-20261029/adaptive-impact-types.log": "82962651e47f564de653ad2883f8fd34e7dae4b4468e4ac459cb3331962a3879", + "outputs/validation-20261029/adaptive-impact-lint.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "outputs/validation-20261029/adaptive-impact-format-check.log": "69ac262099e4710ddfdef82edaac4b6cc0548322385ae66c10852453cee79eb3", + "outputs/validation-20261029/adaptive-impact-build.log": "21247f3e6ebce90ebd4c68d5877dbe9606eaaa4f26eb86c4e439755ca26cf61d", + "outputs/validation-20261029/adaptive-impact-build-exit.json": "124fd50cb6d223cdd18fedc166d00af7254199b6e6dedf9be82f71db87af6d01" + }, + "preservation": "Existing before/after backend preview and confirmation flow retained; no data writes, schema, permissions, tax rules or backend changes. Historical accepted suites reused.", + "visual_acceptance": "Pending team execution and recording against candidate.", + "installation": "Not triggered; main and active release unchanged." } } diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 50492dd1..3cb36f4c 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -7667,3 +7667,14 @@ No editor, os documentos ainda por associar mantêm-se visíveis; os já associa Passaram oito testes frontend dirigidos: seis existentes e dois novos para encaminhamento conforme contexto, distinção entre associação/aprovação/indeterminação, controlos recuperados editáveis, retirada explícita, recuperação sem mutação do original e estado desactivado dos controlos. Tipos, lint, formatação e compilação QA passaram. Recibo e hashes no campo `adaptive_documents_increment` de `local-validation-20261029.json`; provas integrais aceites não foram repetidas. **Visual continua pendente, execução pela equipa:** na candidata, conferir contagens e destino com/sem dados em falta; abrir documentos já associados, alterar/retirar uma associação, comparar impacto antes de confirmar e verificar recuperação. Incluir também âmbito/período não cobertos e exigências indeterminadas, sem interpretar associação como aprovação. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. Sem instalação ou avanço de main; requisitos transversais continuam parciais. + + +### 29/10/2026 — comparação de impacto: alterações das associações documentais + +Continuação sobre `181a9883`, nos requisitos existentes V2-07/V2-10 e na decisão adaptativa de 23/10: explicar divergências e impacto antes da confirmação. Lacuna reproduzida na revisão anterior: a comparação mostrava apenas alterações de estado, omitindo a troca de documento quando ambos mantinham «associado». O texto de vínculo preservado também não distinguia substituição de conservação. + +Corrigido no componente existente: comparação por estado e identificador documental, com substituição, associação e retirada explícitas; nomes anterior/novo quando disponíveis, nome indisponível identificado sem inventar o ficheiro, e vínculo preservado apenas quando o identificador permanece. A ausência de alterações é expressamente limitada às exigências/associações documentais. O plano anterior/posterior já fornecido pelo backend e a confirmação de impacto mantêm-se; não há escrita em dados, mudança de schema, permissões ou cálculo. + +Dois novos casos falharam contra o código anterior e passaram na candidata. Dez testes frontend dirigidos passaram, cobrindo também troca com o mesmo nome, retirada, nova associação, nome indisponível, histórico sem aplicação, ausência de alterações e preservação da origem. Tipos, lint, formatação e compilação QA passaram. Provas históricas aceites reutilizadas, sem repetição integral. Recibo e hashes no campo `adaptive_document_impact_increment` de `local-validation-20261029.json`. + +**Visual pendente pela equipa:** na candidata e num dossier autorizado, preparar troca/retirada de documento, comparar antes de confirmar e conferir nomes/estado anterior e posterior; confirmar que associação mantida não surge como substituição e que o histórico permanece. Não é necessário confirmar uma alteração real para observar a prévia. Registar revisão, percurso, perfil, esperado/observado e evidência nesta matriz. Nenhuma instalação ou avanço de main; o incremento não encerra o percurso adaptativo transversal. diff --git a/resources/js/components/private-investment-preparation.tsx b/resources/js/components/private-investment-preparation.tsx index 59fc89f4..ba51d03f 100644 --- a/resources/js/components/private-investment-preparation.tsx +++ b/resources/js/components/private-investment-preparation.tsx @@ -494,6 +494,22 @@ export function PreparationImpact({ before: Preparation; after: Preparation; }) { + const changedRequirements = after.requirements.filter((requirement) => { + const previous = before.requirements.find( + (item) => item.key === requirement.key, + ); + return ( + previous?.state !== requirement.state || + (previous?.document_id ?? null) !== requirement.document_id + ); + }); + const documentName = ( + requirement: Preparation['requirements'][number] | undefined, + ) => + requirement?.document_id + ? (requirement.document?.file_name ?? + 'Documento associado — nome indisponível') + : 'Sem documento associado'; return (

Próximo passo: {after.next}.

+ {changedRequirements.length === 0 && ( +

Sem alterações às exigências ou associações documentais.

+ )}

{after.calculation_notice}

diff --git a/tests/frontend/investor-usability-components.test.mjs b/tests/frontend/investor-usability-components.test.mjs index 07d62579..ca3afe54 100644 --- a/tests/frontend/investor-usability-components.test.mjs +++ b/tests/frontend/investor-usability-components.test.mjs @@ -396,6 +396,79 @@ function preparationFixture() { ], }; } +void test('impact exposes document replacement even when its state and filename stay unchanged', () => { + const Impact = load( + '../../resources/js/components/private-investment-preparation.tsx', + preparationMocks, + 'PreparationImpact', + ); + const before = preparationFixture(); + before.requirements = [before.requirements[1]]; + before.requirements[0].document = { file_name: 'ORIGINAL.pdf' }; + const after = structuredClone(before); + after.requirements[0].document_id = 'replacement-document'; + const original = JSON.stringify({ before, after }); + const html = renderToStaticMarkup( + React.createElement(Impact, { before, after }), + ); + const impactList = html.slice(html.indexOf('')); + assert.match(impactList, /ASSOCIATED/); + assert.match( + impactList, + /Substituição de documento: ORIGINAL.pdf → ORIGINAL.pdf/, + ); + assert.doesNotMatch(impactList, /Vínculo documental preservado/); + assert.doesNotMatch(html, /Sem alterações às exigências/); + assert.equal(JSON.stringify({ before, after }), original); +}); +void test('impact distinguishes detachment, unavailable names, preserved history and unchanged associations', () => { + const Impact = load( + '../../resources/js/components/private-investment-preparation.tsx', + preparationMocks, + 'PreparationImpact', + ); + const before = preparationFixture(); + before.requirements = [before.requirements[1]]; + before.requirements[0].document = { file_name: 'ORIGINAL.pdf' }; + const render = (after) => + renderToStaticMarkup(React.createElement(Impact, { before, after })); + const detached = structuredClone(before); + detached.requirements[0] = { + ...detached.requirements[0], + document_id: null, + document: null, + state: 'missing', + }; + assert.match( + render(detached), + /Documento: ORIGINAL.pdf → Sem documento associado/, + ); + const replaced = structuredClone(before); + replaced.requirements[0] = { + ...replaced.requirements[0], + document_id: 'new', + document: null, + }; + assert.match( + render(replaced), + /Substituição de documento: ORIGINAL.pdf → Documento associado — nome indisponível/, + ); + const historical = structuredClone(before); + historical.requirements[0].state = 'not_applicable'; + const historicalList = render(historical).split('')[0]; + assert.match(historicalList, /Vínculo documental preservado/); + assert.doesNotMatch(historicalList, /Substituição de documento/); + assert.match( + render(structuredClone(before)), + /Sem alterações às exigências ou associações documentais/, + ); + const associated = structuredClone(detached); + const added = renderToStaticMarkup( + React.createElement(Impact, { before: associated, after: before }), + ); + assert.match(added, /Documento: Sem documento associado → ORIGINAL.pdf/); + assert.equal(before.requirements[0].document_id, 'saved-document'); +}); const preparationMocks = { '@inertiajs/react': { Link: ({ href, children }) =>