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
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:
@@ -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'/);
|
||||
});
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
Reference in New Issue
Block a user