feat(informes): manage manual GEDO responses and shared due date
DH V2 CI / WEB · typecheck, build (push) Successful in 27s
Production dependency audit / API · production dependencies (push) Successful in 15s
Production dependency audit / WEB · production dependencies (push) Successful in 15s
DH V2 CI / API · typecheck, tests, build (push) Successful in 2m3s
DH V2 CI / Docker / migrations / production images (push) Successful in 2m58s
DH V2 CI / Promote verified main to deploy (push) Successful in 3s
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 5m55s

This commit is contained in:
2026-09-15 23:35:49 -03:00
parent 69aeb52040
commit c9575cc520
20 changed files with 485 additions and 161 deletions
@@ -11,6 +11,8 @@ import {
import { ConfigService } from '@nestjs/config';
import { DataSource, EntityManager } from 'typeorm';
import { administrationAuditContext } from '../administration/common/administration-audit';
import { ActAdministrationService, type UploadedActResponseFile } from '../act-administration/act-administration.service';
import type { CreateActCompanyResponseDto } from '../act-administration/dto/create-act-company-response.dto';
import { AuditService } from '../audit/audit.service';
import type { AuthPrincipal, RequestWithContext } from '../common/http/request-context';
import {
@@ -20,6 +22,7 @@ import {
import type { CreateInspectionReportFollowUpDto } from './dto/create-inspection-report-follow-up.dto';
import type { OfficializeInspectionReportDto } from './dto/officialize-inspection-report.dto';
import type { UpdateInspectionReportDto } from './dto/update-inspection-report.dto';
import type { SetInspectionReportResponseDeadlineDto } from './dto/set-inspection-report-response-deadline.dto';
export const MAX_INSPECTION_REPORT_FILE_BYTES = 40 * 1024 * 1024;
@@ -56,6 +59,7 @@ export class InspectionReportWorkflowService {
constructor(
private readonly dataSource: DataSource,
private readonly audit: AuditService,
private readonly administration: ActAdministrationService,
config: ConfigService,
) {
const configured = config.get<string>('INSPECTION_REPORT_UPLOAD_ROOT')
@@ -220,6 +224,42 @@ export class InspectionReportWorkflowService {
}
}
async setResponseDeadline(
reportId: string,
dto: SetInspectionReportResponseDeadlineDto,
principal: AuthPrincipal,
request: RequestWithContext,
) {
const report = await this.requireOfficializedReport(reportId);
const reason = dto.reason ?? `Vencimiento general de respuestas definido desde el Informe ${report.code}`;
await this.administration.setDeadline(
report.actId,
{ responseDueOn: dto.responseDueOn, reason },
principal,
request,
report.id,
);
return this.getWorkflowView(this.dataSource.manager, reportId);
}
async addCompanyResponse(
reportId: string,
dto: CreateActCompanyResponseDto,
file: UploadedActResponseFile | undefined,
principal: AuthPrincipal,
request: RequestWithContext,
) {
const report = await this.requireOfficializedReport(reportId);
await this.administration.addResponse(report.actId, dto, file, principal, request, report.id);
return this.getWorkflowView(this.dataSource.manager, reportId);
}
async companyResponseContent(reportId: string, responseId: string) {
const report = await this.getReport(this.dataSource.manager, reportId);
if (!report) throw reportNotFound();
return this.administration.responseContent(responseId, reportId);
}
async officialPdfContent(reportId: string): Promise<{ filePath: string; originalName: string; mimeType: string }> {
const [row] = await this.dataSource.query(`
SELECT
@@ -300,6 +340,15 @@ export class InspectionReportWorkflowService {
try {
return await this.dataSource.transaction(async (manager) => {
const report = await this.lockReport(manager, reportId);
if (
(dto.type === 'COMPANY_NOTE' || dto.type === 'COMPANY_DOCUMENT')
&& report.status !== InspectionReportStatus.OFFICIALIZED
) {
throw new ConflictException({
code: 'INSPECTION_REPORT_GEDO_REQUIRED_FOR_COMPANY_RESPONSE',
message: 'Las respuestas de la empresa se registran después de cargar el PDF oficial de GEDO',
});
}
const occurredAt = new Date(dto.occurredAt);
await manager.query(`
INSERT INTO inspection_report_follow_ups (
@@ -407,6 +456,18 @@ export class InspectionReportWorkflowService {
return /^\.[a-z0-9]{1,10}$/.test(ext) ? ext : '';
}
private async requireOfficializedReport(reportId: string): Promise<ReportRow> {
const report = await this.getReport(this.dataSource.manager, reportId);
if (!report) throw reportNotFound();
if (report.status !== InspectionReportStatus.OFFICIALIZED) {
throw new ConflictException({
code: 'INSPECTION_REPORT_GEDO_REQUIRED',
message: 'Primero debe cargarse manualmente el PDF oficial de GEDO',
});
}
return report;
}
private async lockReport(manager: EntityManager, id: string): Promise<ReportRow> {
const [row] = await manager.query(`
SELECT id,act_id AS "actId",visit_id AS "visitId",code,status,