+
+
+ Consulta: documento disponível
+ nesta ficha pelas condições de acesso existentes.
+ Referências pendentes não impedem a consulta
+ autorizada.
+
+
+ Situação jurídica:{' '}
+ {legalStatusLabels[resource.legal_status] ??
+ 'Por confirmar'}
+ . A autorização de consulta não valida vigência ou
+ aplicabilidade.
+
+
+ Utilização em cálculos: esta ficha
+ e as suas associações não ativam regras fiscais. A
+ utilização efetiva exige verificação independente no
+ calculador.
+
+
+ Versão do registo:{' '}
+ {resource.version_no} ·{' '}
+ {resource.version_label ?? 'Edição por confirmar'}
+
+
+ Proveniência registada:{' '}
+ {resource.source_url ??
+ 'Não indicada; confirmação pendente'}
+
+
{resource.category} · {resource.topic}
diff --git a/resources/js/pages/portal/services.tsx b/resources/js/pages/portal/services.tsx
index e65455b4..ffbc22b8 100644
--- a/resources/js/pages/portal/services.tsx
+++ b/resources/js/pages/portal/services.tsx
@@ -5,6 +5,7 @@ import { newSubmissionKey } from '@/lib/portal-api';
import { ProvinceSelect } from '@/components/investment-territory';
import { SimulationForm } from '@/components/simulation-form';
import { InvestmentRoadmap } from '@/components/investment-roadmap';
+import { JourneyDocumentResources } from '@/components/journey-document-resources';
const input =
'box-border w-full min-w-0 max-w-full rounded-lg border border-slate-300 bg-white p-3 text-slate-900';
@@ -590,6 +591,9 @@ export default function Services(p: Props) {
>
)}
+ {['procedures', 'documents', 'legislation'].includes(
+ p.section,
+ ) && }
{p.section === 'procedures' && }
{(p.section === 'documents' || p.section === 'legislation') && (
<>
diff --git a/tools/investmoz-v2-align-master.cjs b/tools/investmoz-v2-align-master.cjs
new file mode 100644
index 00000000..2335b152
--- /dev/null
+++ b/tools/investmoz-v2-align-master.cjs
@@ -0,0 +1,91 @@
+const fs = require('fs');
+const d = 'storage/app/private/investmoz-v2/';
+const read = (f) =>
+ JSON.parse(fs.readFileSync(d + f, 'utf8').replace(/^\uFEFF/, ''));
+const norm = (s) =>
+ s
+ .replace(/[\u200b\ufeff]/g, '')
+ .replace(/\s+/g, ' ')
+ .trim()
+ .replace(
+ '2. Visão, objectivos e princípios',
+ '2. Visão, objetivos e princípios',
+ );
+const clean = (a) =>
+ a
+ .filter((p) => !/(ndice|toc)/i.test(p.style) && norm(p.text))
+ .map((p) => ({ ...p, text: norm(p.text) }));
+const a = clean(read('master-paragraphs.json')),
+ b = clean(read('master-paragraphs-reopened.json'));
+const pairs = [];
+function row(x0, x1, y0, y1, reverse) {
+ const size = y1 - y0;
+ let prev = new Uint32Array(size + 1),
+ curr = new Uint32Array(size + 1);
+ for (let i = 0; i < x1 - x0; i++) {
+ curr[0] = 0;
+ const text = a[reverse ? x1 - 1 - i : x0 + i].text;
+ for (let j = 1; j <= size; j++)
+ curr[j] =
+ text === b[reverse ? y1 - j : y0 + j - 1].text
+ ? prev[j - 1] + 1
+ : Math.max(prev[j], curr[j - 1]);
+ [prev, curr] = [curr, prev];
+ }
+ return prev;
+}
+function align(x0, x1, y0, y1) {
+ while (x0 < x1 && y0 < y1 && a[x0].text === b[y0].text)
+ pairs.push([x0++, y0++]);
+ const tail = [];
+ while (x0 < x1 && y0 < y1 && a[x1 - 1].text === b[y1 - 1].text)
+ tail.push([--x1, --y1]);
+ if (x0 < x1 && y0 < y1) {
+ if (x1 - x0 === 1) {
+ for (let j = y0; j < y1; j++)
+ if (a[x0].text === b[j].text) {
+ pairs.push([x0, j]);
+ break;
+ }
+ } else {
+ let mid = (x0 + x1) >> 1;
+ let f = row(x0, mid, y0, y1, false),
+ r = row(mid, x1, y0, y1, true),
+ split = 0,
+ best = -1;
+ for (let j = 0; j <= y1 - y0; j++)
+ if (f[j] + r[y1 - y0 - j] > best) {
+ best = f[j] + r[y1 - y0 - j];
+ split = j;
+ }
+ align(x0, mid, y0, y0 + split);
+ align(mid, x1, y0 + split, y1);
+ }
+ }
+ pairs.push(...tail.reverse());
+}
+align(0, a.length, 0, b.length);
+const ma = new Set(pairs.map((p) => p[0])),
+ mb = new Set(pairs.map((p) => p[1]));
+const removed = a.filter((p, i) => !ma.has(i)),
+ added = b.filter((p, i) => !mb.has(i));
+const result = {
+ method: 'LCS paragraph alignment; exclude TOC and empty paragraphs; normalize heading orthography only',
+ matched: pairs.length,
+ removed,
+ added,
+ mapping: pairs.map(([i, j]) => ({ old: a[i].p, current: b[j].p })),
+};
+fs.writeFileSync(
+ d + 'master-aligned-changes.json',
+ JSON.stringify(result, null, 2),
+);
+console.log(
+ JSON.stringify({
+ matched: pairs.length,
+ removed: removed.length,
+ added: added.length,
+ addedTexts: added.slice(0, 8),
+ removedFirst: removed.slice(0, 4),
+ }),
+);
diff --git a/tools/investmoz-v2-consolidate-variants.cjs b/tools/investmoz-v2-consolidate-variants.cjs
new file mode 100644
index 00000000..cbf7c2aa
--- /dev/null
+++ b/tools/investmoz-v2-consolidate-variants.cjs
@@ -0,0 +1,150 @@
+const fs = require('node:fs');
+const dir = 'storage/app/private/investmoz-v2/';
+const p = JSON.parse(fs.readFileSync(dir + 'master-paragraphs.json', 'utf8'));
+const diff = JSON.parse(
+ fs.readFileSync('docs/investmoz/v2/repeticoes-reconciliacao.json', 'utf8'),
+).repeated.filter((x) => x.comparison === 'historical_content_differs');
+const proposals = [
+ [
+ 'Índices e paginação',
+ 'Os intervalos abrangem sumários extensos; a diferença não equivale a regra funcional.',
+ 'Preservar ambos os índices e usar títulos/identificadores de parágrafo, sem páginas impressas como chave.',
+ ],
+ [
+ 'Linhas vazias',
+ 'Não altera o fluxo BM/AT.',
+ 'Conservar o texto comum; interoperabilidade permanece fora da fase.',
+ ],
+ [
+ 'Linhas vazias',
+ 'Não altera benefícios nem ativa cálculos.',
+ 'Unir o conteúdo comum, conservando referências de ambas as ocorrências.',
+ ],
+ [
+ 'Fluxo deslocado e texto introdutório',
+ 'A primeira ocorrência termina com a introdução de outro documento; a segunda contém a tabela de 14 etapas, também repetida após §13.',
+ 'Separar introdução e tabela; reutilizar a tabela como guia de preparação, sem conceder benefícios nem executar atos externos.',
+ ],
+ [
+ 'Comprimento dos espaços de preenchimento',
+ 'Não altera nomes dos campos.',
+ 'Reutilizar os mesmos campos; preservar os exemplares originais, sem escolher por número de ocorrência.',
+ ],
+ [
+ 'Vazios/estrutura de apresentação',
+ 'Não prova alteração de condições jurídicas.',
+ 'Conservar todas as condições comuns e referências; nenhuma validação jurídica automática.',
+ ],
+ [
+ 'Comprimento dos espaços de preenchimento',
+ 'Não altera a cobertura pedida.',
+ 'Consolidar os cabeçalhos/campos; preservar linhas e proveniências.',
+ ],
+ [
+ 'Selecção / Seleção',
+ 'Diferença ortográfica, sem alteração da obrigatoriedade indicada na fonte.',
+ 'Usar Seleção na orientação; não impor requisito ao ato sem fundamento específico.',
+ ],
+ [
+ 'Tabela de fluxo anexada a §13',
+ 'A tabela de 14 etapas foi incluída no intervalo da primeira ocorrência; as ações de governação comuns permanecem.',
+ 'Manter §13 com ações comuns; referenciar separadamente o guia do fluxo. Nomeações/acordos não são executados pelo portal.',
+ ],
+];
+const esc = (s) =>
+ String(s)
+ .replaceAll('&', '&')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>');
+let html =
+ 'Nove diferenças do mestreNove diferenças lado a lado
Extração histórica identificada pelo hash do índice. Não certifica o original atualmente aberto. As ocorrências são A/B, não versões oficiais sucessivas. Sem seleção automática da última ocorrência.
';
+const summary = [];
+diff.forEach((g, i) => {
+ const a = g.occurrences.map((o) =>
+ p.filter((x) => x.p >= o.start && x.p <= o.end),
+ );
+ html += `${i + 1}. ${esc(g.title)}
Natureza: ${esc(proposals[i][0])}
Impacto: ${esc(proposals[i][1])}
Proposta fundamentada: ${esc(proposals[i][2])}
${g.occurrences.map((o) => `| Ocorrência p.${o.start}–${o.end} | `).join('')}
${a.map((rows) => '| ' + rows.map((x) => ` p.${x.p} ${esc(x.text) || '[parágrafo vazio]'} `).join('') + ' | ').join('')}
`;
+ summary.push({
+ section: g.title,
+ occurrences: g.occurrences,
+ kind: proposals[i][0],
+ functionalImpact: proposals[i][1],
+ proposal: proposals[i][2],
+ decision:
+ 'compatible_content_preserved; no unresolved behavior contradiction identified in these nine spans',
+ });
+});
+fs.writeFileSync(dir + 'nove-diferencas-lado-a-lado.html', html + '');
+fs.writeFileSync(
+ 'docs/investmoz/v2/consolidacao-nove-diferencas.json',
+ JSON.stringify(summary, null, 2),
+);
+const register = JSON.parse(
+ fs.readFileSync(dir + 'document-reconciliation-register.json', 'utf8'),
+);
+const groups = new Map();
+for (const r of register.records) {
+ if (!r.sha256) continue;
+ const h = r.sha256.toLowerCase();
+ groups.set(h, [...(groups.get(h) ?? []), r]);
+}
+const duplicateProposals = [...groups.entries()]
+ .filter(([, g]) => g.length > 1)
+ .map(([h, g]) => {
+ const sorted = [...g].sort((a, b) => {
+ const score = (x) =>
+ x.registeredDocuments?.length
+ ? 0
+ : x.identifier.includes('\\Relatorios\\')
+ ? 1
+ : x.identifier.length;
+ return (
+ score(a) - score(b) || a.identifier.localeCompare(b.identifier)
+ );
+ });
+ return {
+ sha256: h,
+ reference: sorted[0].identifier,
+ allProvenances: g.map((x) => x.identifier),
+ basis: 'Exact byte equality; prefer registered record, then report archive, then shortest stable identifier. No later-version selection.',
+ scope: g[0].identifier.includes('::')
+ ? 'embedded_asset_only_not_parent_document'
+ : 'whole_file',
+ authorization: 'unchanged_not_inferred',
+ action: 'proposal_only_no_delete_or_move',
+ };
+ });
+fs.writeFileSync(
+ dir + 'duplicate-reference-proposals.json',
+ JSON.stringify(duplicateProposals, null, 2),
+);
+const steps = [];
+for (let n = 1782; n <= 1847; n += 5) {
+ const get = (k) => p.find((x) => x.p === k)?.text ?? '';
+ steps.push({
+ title: get(n),
+ inputs: get(n + 2),
+ sourceParagraph: n,
+ href:
+ n === 1797
+ ? '/simulador-fiscal'
+ : n === 1802 || n === 1807
+ ? '/balcao-virtual/painel'
+ : '/documentos',
+ });
+}
+fs.writeFileSync(
+ fs.existsSync(dir + 'docx-reopened.json')
+ ? dir + 'fiscal-preparation-guide-historical.ts'
+ : 'resources/js/lib/fiscal-preparation-guide.ts',
+ '// Historical source paragraphs; orientation only, never executable legal rules.\nexport const fiscalPreparationGuide = ' +
+ JSON.stringify(steps, null, 2) +
+ ';\n',
+);
+console.log(
+ JSON.stringify({
+ comparisons: diff.length,
+ duplicates: duplicateProposals.length,
+ guideSteps: steps.length,
+ }),
+);
diff --git a/tools/investmoz-v2-document-reconciliation.cjs b/tools/investmoz-v2-document-reconciliation.cjs
new file mode 100644
index 00000000..b9ae7bec
--- /dev/null
+++ b/tools/investmoz-v2-document-reconciliation.cjs
@@ -0,0 +1,170 @@
+const fs = require('node:fs');
+const crypto = require('node:crypto');
+const dir = 'storage/app/private/investmoz-v2/';
+const read = (p) =>
+ JSON.parse(fs.readFileSync(dir + p, 'utf8').replace(/^\uFEFF/, ''));
+const inventory = read('legal-journey-inventory.json');
+const active = read('document-reconciliation-active.json');
+const documents = active.tables.documents;
+const resources = active.tables.portal_resources;
+const records = [];
+for (const item of inventory.entries) {
+ records.push({
+ identifier: item.path ?? item.root,
+ origin: {
+ inventoryPath: item.path,
+ status: 'local_provenance_not_official_validation',
+ },
+ availability: item.status ?? 'inventoried',
+ failure: item.reason ?? null,
+ sha256: item.sha256 ?? null,
+ version: 'not_identified_from_bytes',
+ consultationAuthorization: 'not_determined_by_inventory',
+ legalStatus: 'not_determined_by_inventory',
+ calculationUse: 'not_activated_by_inventory',
+ registeredDocuments: documents
+ .filter(
+ (d) => d.sha256?.toLowerCase() === item.sha256?.toLowerCase(),
+ )
+ .map((d) => ({
+ metadata: d,
+ resources: resources
+ .filter((r) => r.document_id === d.id)
+ .map((r) => ({
+ ...r,
+ links: active.tables.portal_resource_links.filter(
+ (l) => l.resource_id === r.id,
+ ),
+ })),
+ })),
+ });
+ for (const attachment of item.attachments ?? [])
+ records.push({
+ identifier: item.path + '::' + attachment.identifier,
+ origin: {
+ parent: item.path,
+ embeddedIdentifier: attachment.identifier,
+ },
+ sha256: attachment.sha256,
+ availability: 'inventoried_embedded',
+ version: 'not_identified',
+ consultationAuthorization: 'not_determined',
+ legalStatus: 'not_determined',
+ calculationUse: 'not_activated_by_inventory',
+ });
+}
+const groups = new Map();
+for (const r of records) {
+ if (!r.sha256) continue;
+ const key = r.sha256.toLowerCase();
+ groups.set(key, [...(groups.get(key) ?? []), r.identifier]);
+}
+for (const r of records)
+ r.byteDuplicates = r.sha256
+ ? (groups.get(r.sha256.toLowerCase()) ?? []).filter(
+ (p) => p !== r.identifier,
+ )
+ : [];
+fs.writeFileSync(
+ dir + 'document-reconciliation-register.json',
+ JSON.stringify(
+ {
+ checked_at: new Date().toISOString(),
+ activeReadOnly: active.target,
+ records,
+ },
+ null,
+ 2,
+ ),
+);
+function csv(text) {
+ let rows = [],
+ row = [],
+ field = '',
+ quoted = false;
+ for (let i = 0; i < text.length; i++) {
+ let c = text[i];
+ if (c === '"') {
+ if (quoted && text[i + 1] === '"') {
+ field += '"';
+ i++;
+ } else quoted = !quoted;
+ } else if (c === ',' && !quoted) {
+ row.push(field);
+ field = '';
+ } else if (c === '\n' && !quoted) {
+ row.push(field.replace(/\r$/, ''));
+ rows.push(row);
+ row = [];
+ field = '';
+ } else field += c;
+ }
+ if (field || row.length) {
+ row.push(field);
+ rows.push(row);
+ }
+ return rows;
+}
+const index = csv(
+ fs.readFileSync('docs/investmoz/v2/indice-documento-mestre.csv', 'utf8'),
+);
+const paragraphs = read('master-paragraphs.json');
+const headings = new Map();
+for (const row of index.slice(1)) {
+ const title = row[5];
+ if (!title) continue;
+ const start = Number(row[2]),
+ end = Number(row[3]);
+ const body = paragraphs
+ .filter((p) => p.p >= start && p.p <= end)
+ .map((p) => p.text.replace(/\s+/g, ' ').trim())
+ .join('\n');
+ const key = title.trim().toLowerCase();
+ headings.set(key, [
+ ...(headings.get(key) ?? []),
+ {
+ title,
+ start,
+ end,
+ paragraphs: end - start + 1,
+ contentSha256: crypto
+ .createHash('sha256')
+ .update(body)
+ .digest('hex'),
+ },
+ ]);
+}
+const repeated = [...headings.values()]
+ .filter((g) => g.length > 1)
+ .map((g) => ({
+ title: g[0].title,
+ occurrences: g,
+ comparison:
+ new Set(g.map((r) => r.contentSha256)).size > 1
+ ? 'historical_content_differs'
+ : 'historical_content_identical',
+ decision: 'pending_human_reconciliation_no_requirement_discarded',
+ }));
+fs.writeFileSync(
+ 'docs/investmoz/v2/repeticoes-reconciliacao.json',
+ JSON.stringify(
+ {
+ sourceSha256: index[1][1],
+ basis: 'historical_saved_extraction_not_current_locked_original',
+ repeated,
+ },
+ null,
+ 2,
+ ),
+);
+console.log(
+ JSON.stringify({
+ records: records.length,
+ duplicateGroups: [...groups.values()].filter((g) => g.length > 1)
+ .length,
+ repeatedGroups: repeated.length,
+ differentGroups: repeated.filter(
+ (g) => g.comparison === 'historical_content_differs',
+ ).length,
+ }),
+);
diff --git a/tools/investmoz-v2-legal-journey-browser.cjs b/tools/investmoz-v2-legal-journey-browser.cjs
new file mode 100644
index 00000000..b1a1b92d
--- /dev/null
+++ b/tools/investmoz-v2-legal-journey-browser.cjs
@@ -0,0 +1,135 @@
+const {
+ chromium,
+} = require('C:/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+(async () => {
+ const b = await chromium.launch({
+ headless: true,
+ executablePath:
+ 'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe',
+ });
+ const checks = [];
+ try {
+ for (const width of [1440, 390]) {
+ const c = await b.newContext({ viewport: { width, height: 850 } });
+ await c.route('**/*', (r) =>
+ r.request().url().startsWith('http://127.0.0.1:18092')
+ ? r.continue()
+ : r.abort(),
+ );
+ const p = await c.newPage();
+ const errors = [];
+ p.on('pageerror', (e) => errors.push(e.message));
+ for (const path of [
+ '/documentos',
+ '/legislacao',
+ '/como-investir',
+ ]) {
+ const r = await p.goto('http://127.0.0.1:18092' + path);
+ assert.equal(
+ r.headers()['x-investmoz-qa-target'],
+ 'investmoz_v2_20261008_test:55441',
+ );
+ const section = p.getByRole('region', {
+ name: 'Fontes e guias para a jornada',
+ exact: true,
+ });
+ await section.waitFor();
+ await section
+ .getByText(
+ 'Guia de preparação fiscal — 14 etapas da fonte',
+ { exact: true },
+ )
+ .click();
+ assert.equal(
+ await section.locator('details ol > li').count(),
+ 14,
+ );
+ assert.match(
+ await section.locator('details').innerText(),
+ /p\.973–1042/,
+ );
+ assert.match(
+ await section
+ .locator('details ol > li')
+ .first()
+ .innerText(),
+ /973 \(histórico: 1782\)/,
+ );
+ assert.match(
+ await section.innerText(),
+ /não concede benefícios/,
+ );
+ assert.match(
+ await section.innerText(),
+ /estados independentes/,
+ );
+ await section
+ .getByRole('link', {
+ name: 'Consultar exemplares de formulários',
+ exact: true,
+ })
+ .click();
+ await p.waitForURL(
+ (u) =>
+ u.pathname === '/documentos' &&
+ u.searchParams.get('document_type') === 'official_form',
+ );
+ await p
+ .getByRole('region', {
+ name: 'Fontes e guias para a jornada',
+ exact: true,
+ })
+ .getByRole('link', {
+ name: 'Consultar guias disponíveis',
+ exact: true,
+ })
+ .click();
+ await p.waitForURL(
+ (u) => u.searchParams.get('document_type') === 'guide',
+ );
+ await p
+ .getByRole('region', {
+ name: 'Fontes e guias para a jornada',
+ exact: true,
+ })
+ .getByRole('link', {
+ name: 'Explorar todo o acervo autorizado',
+ exact: true,
+ })
+ .click();
+ await p.waitForURL(
+ (u) => u.pathname === '/documentos' && !u.search,
+ );
+ checks.push({
+ width,
+ path,
+ formFilter: true,
+ guideFilter: true,
+ unrestrictedLibrary: true,
+ });
+ }
+ assert.deepEqual(errors, []);
+ await p.screenshot({
+ path: `storage/app/private/investmoz-v2/legal-journey-${width}.png`,
+ fullPage: true,
+ });
+ await c.close();
+ }
+ fs.writeFileSync(
+ 'storage/app/private/investmoz-v2/legal-journey-browser.json',
+ JSON.stringify(
+ { qa: 'investmoz_v2_20261008_test:55441', checks },
+ null,
+ 2,
+ ),
+ );
+ console.log('Legal journey QA navigation passed');
+ } finally {
+ await b.close();
+ }
+})().catch((e) => {
+ console.error(e);
+ process.exitCode = 1;
+});
diff --git a/tools/investmoz-v2-refresh-master.cjs b/tools/investmoz-v2-refresh-master.cjs
new file mode 100644
index 00000000..3a2be721
--- /dev/null
+++ b/tools/investmoz-v2-refresh-master.cjs
@@ -0,0 +1,127 @@
+const fs = require('fs'),
+ crypto = require('crypto');
+const d = 'storage/app/private/investmoz-v2/';
+const r = (f) =>
+ JSON.parse(fs.readFileSync(d + f, 'utf8').replace(/^\uFEFF/, ''));
+const p = r('master-paragraphs-reopened.json'),
+ source = r('docx-reopened.json');
+const heads = p.filter((x) => /^(Ttulo|Title|Heading)/i.test(x.style));
+const rows = heads.map((x, i) => ({
+ title: x.text,
+ start: x.p,
+ end: heads[i + 1] ? heads[i + 1].p - 1 : p.length,
+ style: x.style,
+}));
+const q = (s) => '"' + String(s).replaceAll('"', '""') + '"';
+fs.writeFileSync(
+ 'docs/investmoz/v2/indice-documento-mestre-atual.csv',
+ [
+ 'fonte,sha256,paragrafo_inicio,paragrafo_fim,estilo,secção,estado',
+ ...rows.map((x) =>
+ [
+ 'melhorar e transformar o Portal do Investidor atual.docx',
+ source.sha256,
+ x.start,
+ x.end,
+ x.style,
+ x.title,
+ 'Leitura atual; implementação individual não certificada',
+ ]
+ .map(q)
+ .join(','),
+ ),
+ ].join('\n') + '\n',
+);
+const groups = new Map();
+for (const x of rows) {
+ const text = p
+ .filter((v) => v.p >= x.start && v.p <= x.end)
+ .map((v) => v.text.replace(/\s+/g, ' ').trim())
+ .join('\n');
+ const row = {
+ ...x,
+ contentSha256: crypto.createHash('sha256').update(text).digest('hex'),
+ };
+ const key = x.title.trim().toLowerCase();
+ groups.set(key, [...(groups.get(key) ?? []), row]);
+}
+const repeated = [...groups.values()]
+ .filter((g) => g.length > 1)
+ .map((g) => ({
+ title: g[0].title,
+ occurrences: g,
+ comparison:
+ new Set(g.map((v) => v.contentSha256)).size > 1
+ ? 'current_content_differs'
+ : 'current_content_identical',
+ }));
+fs.writeFileSync(
+ 'docs/investmoz/v2/repeticoes-reconciliacao-atual.json',
+ JSON.stringify(
+ { sha256: source.sha256, checked_at: source.checked_at, repeated },
+ null,
+ 2,
+ ),
+);
+const changes = r('master-aligned-changes.json');
+const map = new Map(changes.mapping.map((v) => [v.old, v.current]));
+const historic = r('master-paragraphs.json');
+const steps = [];
+for (let old = 1782; old <= 1847; old += 5) {
+ const title = historic.find((x) => x.p === old).text;
+ const inputs = historic.find((x) => x.p === old + 2).text;
+ const current = map.get(old);
+ if (
+ !current ||
+ p.find((v) => v.p === current).text !== title ||
+ p.find((v) => v.p === map.get(old + 2)).text !== inputs
+ )
+ throw Error('Guide text changed ' + old);
+ steps.push({
+ title,
+ inputs,
+ sourceParagraph: current,
+ historicalSourceParagraph: old,
+ href:
+ old === 1797
+ ? '/simulador-fiscal'
+ : old === 1802 || old === 1807
+ ? '/balcao-virtual/painel'
+ : '/documentos',
+ });
+}
+if (!fs.existsSync(d + 'fiscal-preparation-guide-before-reread.ts'))
+ fs.copyFileSync(
+ 'resources/js/lib/fiscal-preparation-guide.ts',
+ d + 'fiscal-preparation-guide-before-reread.ts',
+ );
+fs.writeFileSync(
+ 'resources/js/lib/fiscal-preparation-guide.ts',
+ '// Current source verified against preserved historical paragraphs; orientation only.\nexport const fiscalPreparationSource = ' +
+ JSON.stringify({ sha256: source.sha256, start: 973, end: 1042 }) +
+ ';\nexport const fiscalPreparationGuide = ' +
+ JSON.stringify(steps, null, 2) +
+ ';\n',
+);
+fs.writeFileSync(
+ d + 'master-reread-verification.json',
+ JSON.stringify(
+ {
+ sha256: source.sha256,
+ paragraphs: p.length,
+ headingCount: heads.length,
+ repeatedGroups: repeated.length,
+ differentGroups: repeated.filter(
+ (g) => g.comparison === 'current_content_differs',
+ ).length,
+ bodyAdded: changes.added.length,
+ bodyRemoved: changes.removed.length,
+ guideStepsVerified: steps.length,
+ guideTextUnchanged: true,
+ newFunctionalRequirementsIdentified: false,
+ },
+ null,
+ 2,
+ ),
+);
+console.log(fs.readFileSync(d + 'master-reread-verification.json', 'utf8'));