feat(f6.9): consolidate act and report documents and simplify follow-up
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m24s
DH V2 CI / API · typecheck, tests, build (push) Successful in 40s
DH V2 CI / WEB · typecheck, build (push) Successful in 18s
Production dependency audit / API · production dependencies (push) Successful in 8s
Production dependency audit / WEB · production dependencies (push) Successful in 9s
DH V2 CI / Docker / scripts contract (push) Successful in 1m22s

This commit is contained in:
DH V2
2026-09-15 18:48:12 -03:00
parent 079728aa6d
commit 5cf99442a8
39 changed files with 1604 additions and 566 deletions
@@ -36,15 +36,14 @@ test('F4 document center and inspection detail consume the F4 act client', () =>
test('F4 closure UI treats sealing as the definitive act state', () => {
assert.match(closurePanel, /getInspectionClosureF4/);
assert.match(closurePanel, /act\.status === 'SEALED'/);
assert.match(closurePanel, /ACTA SELLADA/);
assert.match(closurePanel, /manifestación de la empresa puede completarse/);
assert.match(closurePanel, /Pendiente de evento válido/);
assert.match(closurePanel, /Participantes y firmas/);
assert.doesNotMatch(closurePanel, /Pendiente de evento válido/);
assert.doesNotMatch(closurePanel, /reabrir ni cerrar el acta/);
});
test('F4 presentation keeps new lifecycle labels while retaining explicit legacy readability', () => {
assert.match(presentation, /LOCKED: 'Bloqueada/);
assert.match(presentation, /SEALED: 'Sellada'/);
assert.match(presentation, /LOCKED: 'Para firmar/);
assert.match(presentation, /SEALED: 'Firmada y cerrada'/);
assert.match(presentation, /READY: 'Lista para cerrar · legado'/);
assert.match(presentation, /CLOSED: 'Cerrada · legado'/);
assert.match(presentation, /CLOSED: 'Firmada y cerrada'/);
});
+3 -3
View File
@@ -4,9 +4,9 @@ import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { API_PHASE, API_VERSION } from '../../src/version';
test('health metadata reports the current F6.8 release', () => {
assert.equal(API_PHASE, 'F6.8');
test('health metadata reports the current F6.9 release', () => {
assert.equal(API_PHASE, 'F6.9');
const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as { version: string };
assert.equal(API_VERSION, pkg.version);
assert.equal(API_VERSION, '0.29.0-8');
assert.equal(API_VERSION, '0.29.0-9');
});
@@ -13,7 +13,7 @@ test('F6.1 presentation metadata keeps the visible WEB version aligned with pack
const visibleVersion = version.match(/APP_VERSION\s*=\s*'([^']+)'/)?.[1];
assert.equal(visibleVersion, pkg.version);
assert.match(version, /APP_PHASE\s*=\s*'F6\.8/);
assert.match(version, /APP_PHASE\s*=\s*'F6\.9/);
});
test('F6.1 presentation keeps Relevamientos retired from WEB navigation and routes', () => {
@@ -40,6 +40,7 @@ test('F6.2 exposes provisional PDF data contracts for Actas and Informes', () =>
assert.match(projection, /Firmas y manifestaciones/);
assert.match(projection, /GEDO e integridad/);
assert.match(projection, /Anexo final/);
assert.match(actPage, /<DocumentPdfProjection kind="act" \/>/);
assert.match(reportPage, /<DocumentPdfProjection kind="report" \/>/);
assert.match(actPage, /getInspectionActConsolidatedPdfBlob/);
assert.doesNotMatch(actPage, /DocumentPdfProjection/);
assert.doesNotMatch(reportPage, /DocumentPdfProjection/);
});
@@ -65,9 +65,9 @@ test('F6.8 makes the real Acta PDF and photographic record first-class in Dashbo
assert.match(api('src/inspection-acts/inspection-acts.service.ts'), /capture\.visit_id=act\.visit_id/);
assert.match(page, /Abrir PDF del Acta/);
assert.match(page, /Descargar PDF/);
assert.match(media, /Fotos y Hallazgos/);
assert.match(media, /Evidencia fotográfica/);
assert.match(media, /Fotos tomadas durante esta inspección/);
assert.match(media, /Hallazgos del Acta/);
assert.match(media, /act-finding-photos/);
assert.match(media, /listInspectionFindingEvidence/);
});
test('F6.8 presents technical families to users as installation and subinstallation types', () => {
@@ -0,0 +1,72 @@
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import test from 'node:test';
import { buildInspectionActPdf } from '../../src/inspection-reports/inspection-act-pdf-builder';
import { buildInspectionReportWord } from '../../src/inspection-reports/inspection-report-word-builder';
import sharp from 'sharp';
import { renderableInspectionImage } from '../../src/inspection-reports/inspection-document-images';
const digest = (data: Buffer) => createHash('sha256').update(data).digest('hex');
const photo = readFileSync(resolve(process.cwd(), 'assets/logo-mendoza.png'));
const locked = {
act: { code: 'ACT-TEST-01', occurredAt: '2026-09-14T18:30:00Z',
summary: 'Se inspeccionó un tanque.', inspection: { code: 'INSP-TEST-01' } },
responsible: { fullName: 'Responsable de prueba', documentNumber: '12345678' },
inventories: [{ id: 'asset-1', code: 'CAM-TEST-01', name: 'Tanque de prueba' }],
findings: [{ id: 'finding-1', assetId: 'asset-1', code: 'ACT-TEST-H01',
title: 'Pérdida', description: 'Se constató una pérdida visible.', severity: 3 }],
};
const sealed = { lockedSnapshot: locked, finalSha256: 'a'.repeat(64),
lockedSha256: 'b'.repeat(64), signatures: [], seal: { serverSealedAt: '2026-09-15T18:30:00Z' } };
const evidence = { id: 'photo-1', findingId: 'finding-1', sha256: digest(photo), buffer: photo };
test('F6.9 consolidated Acta is a hashed PDF 1.4 with actual evidence embedded', async () => {
const without = await buildInspectionActPdf(sealed);
const withEvidence = await buildInspectionActPdf(sealed, [evidence]);
assert.ok(withEvidence.buffer.subarray(0, 8).equals(Buffer.from('%PDF-1.4')));
assert.equal(withEvidence.sha256, digest(withEvidence.buffer));
assert.ok(withEvidence.buffer.length > without.buffer.length + 3_000);
assert.notEqual(withEvidence.sha256, without.sha256);
});
test('F6.9 technical Informe embeds evidence and its institutional source hash', () => {
const input = { code: 'INF-TEST-01', title: 'Informe de prueba', generatedAt: new Date('2026-09-15'),
frozenSha256: 'c'.repeat(64), frozenSnapshot: { sealedAct: sealed, source: { actCode: locked.act.code } } };
const plain = buildInspectionReportWord(input);
const withEvidence = buildInspectionReportWord({ ...input, photos: [evidence] });
assert.equal(withEvidence.sha256, digest(withEvidence.buffer));
assert.ok(withEvidence.buffer.subarray(0, 4).equals(Buffer.from('PK\x03\x04')));
assert.ok(withEvidence.buffer.includes(Buffer.from('word/media/photo-1.png')));
assert.ok(withEvidence.buffer.includes(photo));
assert.ok(withEvidence.buffer.includes(Buffer.from('ACT-TEST-H01')));
assert.ok(withEvidence.buffer.includes(Buffer.from('SHA-256 de la fuente del Informe')));
assert.ok(withEvidence.buffer.length > plain.buffer.length + photo.length);
assert.ok(!withEvidence.buffer.includes(Buffer.from('[Completar')));
});
test('F6.9 keeps prior sealed PDF and prior company responses available for audit', () => {
const pdf = readFileSync(resolve(process.cwd(), 'src/inspection-reports/inspection-act-pdf.service.ts'), 'utf8');
const controller = readFileSync(resolve(process.cwd(), 'src/act-administration/act-administration.controller.ts'), 'utf8');
assert.match(pdf, /inspection_act_consolidated_pdf_artifacts/);
const word = readFileSync(resolve(process.cwd(), 'src/inspection-reports/inspection-report-word.service.ts'), 'utf8');
assert.match(word, /inspection_report_consolidated_word_artifacts/);
assert.match(pdf, /ON CONFLICT \(act_id\) DO NOTHING/);
assert.match(controller, /responseContent\(responseId\)/);
assert.doesNotMatch(controller, /@Post\('responses'\)/);
});
test('F6.9 renders WebP field photographs in both documents without changing the source hash', async () => {
const original = await sharp(photo).webp().toBuffer();
const originalSha = digest(original);
const display = await renderableInspectionImage(original);
assert.ok(display.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])));
assert.equal(digest(original), originalSha);
const pdf = await buildInspectionActPdf(sealed, [{ ...evidence, sha256: originalSha, buffer: display }]);
assert.ok(pdf.buffer.subarray(0, 8).equals(Buffer.from('%PDF-1.4')));
const word = buildInspectionReportWord({ code: 'INF-WEBP', title: 'Informe', generatedAt: new Date(),
frozenSha256: 'c'.repeat(64), frozenSnapshot: { sealedAct: sealed }, photos: [{ ...evidence, sha256: originalSha, buffer: display }] });
assert.ok(word.buffer.includes(display));
assert.ok(word.buffer.includes(Buffer.from(originalSha)));
});