feat(f6.2): add per-act representative signing and user smtp
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Failing after 1m25s
DH V2 CI / API · typecheck, tests, build (push) Successful in 34s
DH V2 CI / WEB · typecheck, build (push) Successful in 20s
Production dependency audit / API · production dependencies (push) Successful in 9s
Production dependency audit / WEB · production dependencies (push) Successful in 9s
DH V2 CI / Docker / scripts contract (push) Successful in 1m16s

This commit is contained in:
2026-09-14 15:53:55 -03:00
parent 103ecf2fae
commit 7fe59bccd2
46 changed files with 907 additions and 154 deletions
@@ -25,11 +25,11 @@ test('F3.1 amplía el perfil personal y protege email del Inspector también en
assert.match(userMigration, /trg_users_inspector_email/);
});
test('F3.1 exige email al crear o asignar el rol Inspector', () => {
assert.match(usersService, /assertInspectorHasEmail\(roles, dto\.email/);
assert.match(usersService, /assertInspectorHasEmail\(roles, before\.email\)/);
assert.match(usersService, /Los usuarios con rol Inspector deben tener un email válido/);
assert.match(usersService, /dto\.email !== undefined && !dto\.email && before\.roles\.some/);
test('F3.1 email del Inspector sigue protegido y F6.2 endurece email para todo usuario', () => {
assert.match(usersService, /USER_EMAIL_REQUIRED/);
assert.match(usersService, /Cada usuario de Hidrocarburos debe tener un email válido/);
assert.match(usersService, /if \(!dto\.email\?\.trim\(\)\) throw userEmailRequired\(\)/);
assert.match(usersService, /dto\.email !== undefined && !dto\.email/);
});
test('F3.1 dossier canónico agrega alias sin borrar identidad histórica', () => {
+2 -2
View File
@@ -5,8 +5,8 @@ import { resolve } from 'node:path';
import { API_PHASE, API_VERSION } from '../../src/version';
test('health metadata reports the current F6.1 release', () => {
assert.equal(API_PHASE, 'F6.1');
assert.equal(API_PHASE, 'F6.2');
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-3');
assert.equal(API_VERSION, '0.29.0-4');
});
+2 -2
View File
@@ -10,8 +10,8 @@ function mountedRepoFile(path: string): string {
test('F6.3 Android test cut targets production API and has a distinct installable debug version', () => {
const gradle = mountedRepoFile('android-app/app/build.gradle.kts');
assert.match(gradle, /versionCode = 30/);
assert.match(gradle, /versionName = "0\.19\.2"/);
assert.match(gradle, /versionCode = 31/);
assert.match(gradle, /versionName = "0\.19\.3"/);
assert.match(gradle, /https:\/\/dhv2\.korexlabs\.com\/api\/v3\//);
assert.match(gradle, /applicationIdSuffix = "\.debug"/);
});
@@ -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\.1 · Contexto operativo ÁreaOperadora consolidado'/);
assert.match(version, /APP_PHASE\s*=\s*'F6\.2 · Firma por Acta y correo de usuario'/);
});
test('F6.1 presentation keeps Relevamientos retired from WEB navigation and routes', () => {
@@ -33,7 +33,7 @@ test('F6.1 presentation keeps the complete Inspector profile and documentary-cop
for (const field of ['dni', 'phone', 'jobTitle', 'employeeNumber']) {
assert.match(user, new RegExp(`name="${field}"`));
}
assert.match(user, /email es obligatorio para un Inspector/i);
assert.match(user, /email es obligatorio para todos los usuarios de Hidrocarburos/i);
assert.match(user, /la documentación se enviará también/i);
assert.match(delivery, /recipientKind:'INSPECTOR'/);
assert.match(delivery, /documentKind:'ACT_PDF'/);
@@ -0,0 +1,72 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const read = (path: string) => readFileSync(resolve(process.cwd(), path), 'utf8');
const migration = read('src/database/migrations/1790117400000-f6-2-user-smtp-and-act-representative.ts');
const closing = read('src/inspection-closing/inspection-closing.service.ts');
const responsibleDto = read('src/inspection-closing/dto/upsert-inspection-responsible.dto.ts');
const prepareDto = read('src/inspection-closing/dto/prepare-inspection-act.dto.ts');
const findingEntity = read('src/database/entities/inspection-finding.entity.ts');
const smtp = read('src/inspection-reports/smtp-delivery.service.ts');
const users = read('src/administration/users/users.controller.ts');
const delivery = read('src/inspection-reports/inspection-document-delivery.service.ts');
const android = read('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/ModernMobileActsScreen.kt');
const androidModel = read('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt');
const profilePage = read('../web-v2/src/pages/MyProfilePage.tsx');
test('F6.2 keeps urgency exclusively at Act close, never on individual findings', () => {
assert.match(prepareDto, /InspectionActUrgency/);
assert.match(prepareDto, /urgency!/);
assert.doesNotMatch(findingEntity, /\burgency\b/i);
assert.match(android, /Urgencia del Acta/);
assert.match(android, /urgencia no se asigna a cada Hallazgo/i);
});
test('F6.2 requires the company representative identity and email on every Act', () => {
assert.match(responsibleDto, /attendanceStatus === InspectionResponsibleAttendanceStatus\.PRESENT/);
assert.match(responsibleDto, /@IsEmail\(\)/);
assert.match(migration, /LENGTH\(TRIM\(COALESCE\(email, ''\)\)\) > 0/);
assert.match(closing, /representativeSuggestion/);
assert.match(closing, /previous_act\.visit_id=current_act\.visit_id/);
assert.match(androidModel, /representativeSuggestion/);
assert.match(android, /Nombres y apellidos \*/);
assert.match(android, /DNI \*/);
assert.match(android, /Cargo \/ función \*/);
assert.match(android, /Email \*/);
assert.match(android, /Firma del representante de la empresa/);
assert.match(android, /En disconformidad/);
assert.match(android, /Motivo de disconformidad \*/);
});
test('F6.2 gives every authenticated user a general SMTP default and encrypted custom override', () => {
assert.match(migration, /CREATE TABLE user_smtp_settings/);
assert.match(migration, /ALTER COLUMN email SET NOT NULL/);
assert.match(migration, /mode IN \('SYSTEM','CUSTOM'\)/);
assert.match(smtp, /publicUserSettings/);
assert.match(smtp, /saveUserSettings/);
assert.match(smtp, /source:\s*["']USER["']/);
assert.match(smtp, /encryptSecret/);
assert.match(smtp, /password_enc/);
});
test('F6.2 exposes personal mail settings in Mi perfil without granting administration permissions', () => {
assert.match(users, /@Get\('self\/profile'\)/);
assert.match(users, /@Patch\('self\/profile'\)/);
assert.match(users, /@Get\('self\/smtp'\)/);
assert.match(users, /@Put\('self\/smtp'\)/);
assert.match(users, /@Post\('self\/smtp\/test'\)/);
assert.match(profilePage, /MI PERFIL/);
assert.match(profilePage, /Usar SMTP general/);
assert.match(profilePage, /Usar SMTP propio/);
assert.match(profilePage, /Enviar correo de prueba/);
assert.doesNotMatch(profilePage, /document_delivery\.manage/);
});
test('F6.2 sends Act-related mail through the lead inspector transport selection', () => {
assert.match(delivery, /lead_inspector_user_id AS "userId"/);
assert.match(delivery, /configured\(senderUserId\)/);
assert.match(delivery, /}, senderUserId\);/);
});