15 KiB
15 KiB
Backend Specification: Compliance Module (INFONAVIT)
Version: 1.0.0 Fecha: 2025-12-05 Modulos: MAI-011 (INFONAVIT Cumplimiento)
Resumen
| Metrica | Valor |
|---|---|
| Controllers | 4 |
| Services | 5 |
| Entities | 10 |
| Endpoints | 30+ |
| Tests Requeridos | 40+ |
Estructura del Modulo
modules/compliance/
+-- compliance.module.ts
+-- controllers/
| +-- program.controller.ts
| +-- compliance.controller.ts
| +-- audit.controller.ts
| +-- report.controller.ts
+-- services/
| +-- program.service.ts
| +-- compliance.service.ts
| +-- evidence.service.ts
| +-- audit.service.ts
| +-- report.service.ts
+-- entities/
| +-- infonavit-program.entity.ts
| +-- program-requirement.entity.ts
| +-- project-program.entity.ts
| +-- compliance-item.entity.ts
| +-- compliance-evidence.entity.ts
| +-- audit.entity.ts
| +-- audit-finding.entity.ts
| +-- corrective-action.entity.ts
| +-- compliance-alert.entity.ts
| +-- compliance-history.entity.ts
+-- dto/
+-- events/
+-- jobs/
+-- due-date-alerts.job.ts
Controllers
1. ProgramController
@Controller('api/v1/compliance/programs')
@ApiTags('compliance-programs')
export class ProgramController {
// GET /api/v1/compliance/programs
@Get()
async findAll(@Query() query: ProgramQueryDto): Promise<PaginatedResponse<ProgramDto>>;
// GET /api/v1/compliance/programs/:id
@Get(':id')
async findOne(@Param('id') id: UUID): Promise<ProgramDetailDto>;
// POST /api/v1/compliance/programs
@Post()
async create(@Body() dto: CreateProgramDto): Promise<ProgramDto>;
// GET /api/v1/compliance/programs/:id/requirements
@Get(':id/requirements')
async getRequirements(@Param('id') id: UUID): Promise<RequirementTreeDto[]>;
// POST /api/v1/compliance/programs/:id/requirements
@Post(':id/requirements')
async addRequirement(@Param('id') id: UUID, @Body() dto: CreateRequirementDto): Promise<RequirementDto>;
}
2. ComplianceController
@Controller('api/v1/compliance')
@ApiTags('compliance')
export class ComplianceController {
// POST /api/v1/compliance/register-project
@Post('register-project')
async registerProject(@Body() dto: RegisterProjectDto): Promise<ProjectProgramDto>;
// GET /api/v1/compliance/project/:projectId
@Get('project/:projectId')
async getProjectCompliance(@Param('projectId') projectId: UUID): Promise<ProjectComplianceDto>;
// GET /api/v1/compliance/project/:projectId/items
@Get('project/:projectId/items')
async getComplianceItems(
@Param('projectId') projectId: UUID,
@Query() query: ComplianceItemQueryDto
): Promise<ComplianceItemDto[]>;
// PUT /api/v1/compliance/items/:id/status
@Put('items/:id/status')
async updateItemStatus(
@Param('id') id: UUID,
@Body() dto: UpdateItemStatusDto
): Promise<ComplianceItemDto>;
// POST /api/v1/compliance/items/:id/evidence
@Post('items/:id/evidence')
@UseInterceptors(FileInterceptor('file'))
async uploadEvidence(
@Param('id') id: UUID,
@UploadedFile() file: Express.Multer.File,
@Body() dto: UploadEvidenceDto
): Promise<EvidenceDto>;
// GET /api/v1/compliance/items/:id/evidence
@Get('items/:id/evidence')
async getEvidence(@Param('id') id: UUID): Promise<EvidenceDto[]>;
// DELETE /api/v1/compliance/evidence/:id
@Delete('evidence/:id')
async removeEvidence(@Param('id') id: UUID): Promise<void>;
// GET /api/v1/compliance/project/:projectId/dashboard
@Get('project/:projectId/dashboard')
async getDashboard(@Param('projectId') projectId: UUID): Promise<ComplianceDashboardDto>;
}
3. AuditController
@Controller('api/v1/compliance/audits')
@ApiTags('compliance-audits')
export class AuditController {
// GET /api/v1/compliance/audits
@Get()
async findAll(@Query() query: AuditQueryDto): Promise<PaginatedResponse<AuditDto>>;
// GET /api/v1/compliance/audits/:id
@Get(':id')
async findOne(@Param('id') id: UUID): Promise<AuditDetailDto>;
// POST /api/v1/compliance/audits
@Post()
async create(@Body() dto: CreateAuditDto): Promise<AuditDto>;
// PUT /api/v1/compliance/audits/:id
@Put(':id')
async update(@Param('id') id: UUID, @Body() dto: UpdateAuditDto): Promise<AuditDto>;
// POST /api/v1/compliance/audits/:id/start
@Post(':id/start')
async startAudit(@Param('id') id: UUID): Promise<AuditDto>;
// POST /api/v1/compliance/audits/:id/complete
@Post(':id/complete')
async completeAudit(@Param('id') id: UUID, @Body() dto: CompleteAuditDto): Promise<AuditDto>;
// GET /api/v1/compliance/audits/:id/findings
@Get(':id/findings')
async getFindings(@Param('id') id: UUID): Promise<FindingDto[]>;
// POST /api/v1/compliance/audits/:id/findings
@Post(':id/findings')
async addFinding(@Param('id') id: UUID, @Body() dto: CreateFindingDto): Promise<FindingDto>;
// PUT /api/v1/compliance/findings/:id
@Put('findings/:id')
async updateFinding(@Param('id') id: UUID, @Body() dto: UpdateFindingDto): Promise<FindingDto>;
// POST /api/v1/compliance/findings/:id/corrective-actions
@Post('findings/:id/corrective-actions')
async addCorrectiveAction(
@Param('id') id: UUID,
@Body() dto: CreateCorrectiveActionDto
): Promise<CorrectiveActionDto>;
// PUT /api/v1/compliance/corrective-actions/:id/complete
@Put('corrective-actions/:id/complete')
async completeAction(@Param('id') id: UUID): Promise<CorrectiveActionDto>;
// PUT /api/v1/compliance/corrective-actions/:id/verify
@Put('corrective-actions/:id/verify')
async verifyAction(@Param('id') id: UUID, @Body() dto: VerifyActionDto): Promise<CorrectiveActionDto>;
}
4. ReportController
@Controller('api/v1/compliance/reports')
@ApiTags('compliance-reports')
export class ReportController {
// GET /api/v1/compliance/reports/status/:projectId
@Get('status/:projectId')
async getStatusReport(@Param('projectId') projectId: UUID): Promise<StatusReportDto>;
// GET /api/v1/compliance/reports/audit/:projectId
@Get('audit/:projectId')
async getAuditReport(@Param('projectId') projectId: UUID): Promise<AuditReportDto>;
// GET /api/v1/compliance/reports/executive/:projectId
@Get('executive/:projectId')
async getExecutiveReport(@Param('projectId') projectId: UUID): Promise<ExecutiveReportDto>;
// GET /api/v1/compliance/reports/evidence-package/:projectId
@Get('evidence-package/:projectId')
async downloadEvidencePackage(@Param('projectId') projectId: UUID): Promise<StreamableFile>;
}
Services
ComplianceService
@Injectable()
export class ComplianceService {
constructor(
@InjectRepository(ProjectProgram)
private readonly projectProgramRepo: Repository<ProjectProgram>,
@InjectRepository(ComplianceItem)
private readonly itemRepo: Repository<ComplianceItem>,
private readonly eventEmitter: EventEmitter2
) {}
async registerProject(tenantId: UUID, userId: UUID, dto: RegisterProjectDto): Promise<ProjectProgramDto>;
async getProjectCompliance(tenantId: UUID, projectId: UUID): Promise<ProjectComplianceDto>;
async getComplianceItems(projectProgramId: UUID, query: ComplianceItemQueryDto): Promise<ComplianceItemDto[]>;
async updateItemStatus(tenantId: UUID, itemId: UUID, userId: UUID, dto: UpdateItemStatusDto): Promise<ComplianceItemDto>;
async calculateCompliancePercentage(projectProgramId: UUID): Promise<number>;
async getDashboard(tenantId: UUID, projectId: UUID): Promise<ComplianceDashboardDto>;
}
EvidenceService
@Injectable()
export class EvidenceService {
constructor(
@InjectRepository(ComplianceEvidence)
private readonly evidenceRepo: Repository<ComplianceEvidence>,
private readonly storageService: StorageService
) {}
async upload(itemId: UUID, userId: UUID, file: Express.Multer.File, dto: UploadEvidenceDto): Promise<EvidenceDto>;
async getByItem(itemId: UUID): Promise<EvidenceDto[]>;
async remove(tenantId: UUID, evidenceId: UUID): Promise<void>;
async replace(evidenceId: UUID, file: Express.Multer.File): Promise<EvidenceDto>;
}
AuditService
@Injectable()
export class AuditService {
constructor(
@InjectRepository(Audit)
private readonly auditRepo: Repository<Audit>,
@InjectRepository(AuditFinding)
private readonly findingRepo: Repository<AuditFinding>,
@InjectRepository(CorrectiveAction)
private readonly actionRepo: Repository<CorrectiveAction>,
private readonly eventEmitter: EventEmitter2
) {}
async findAll(tenantId: UUID, query: AuditQueryDto): Promise<PaginatedResponse<AuditDto>>;
async findOne(tenantId: UUID, id: UUID): Promise<AuditDetailDto>;
async create(tenantId: UUID, userId: UUID, dto: CreateAuditDto): Promise<AuditDto>;
async startAudit(tenantId: UUID, id: UUID): Promise<AuditDto>;
async completeAudit(tenantId: UUID, id: UUID, dto: CompleteAuditDto): Promise<AuditDto>;
async addFinding(auditId: UUID, userId: UUID, dto: CreateFindingDto): Promise<FindingDto>;
async updateFinding(findingId: UUID, dto: UpdateFindingDto): Promise<FindingDto>;
async addCorrectiveAction(findingId: UUID, userId: UUID, dto: CreateCorrectiveActionDto): Promise<CorrectiveActionDto>;
async completeAction(actionId: UUID, userId: UUID): Promise<CorrectiveActionDto>;
async verifyAction(actionId: UUID, userId: UUID, dto: VerifyActionDto): Promise<CorrectiveActionDto>;
async calculateFindingStats(auditId: UUID): Promise<{ critical: number; major: number; minor: number }>;
}
DTOs
Program DTOs
export class CreateProgramDto {
@IsString() @MaxLength(20)
code: string;
@IsString() @MaxLength(200)
name: string;
@IsEnum(ProgramType)
programType: ProgramType;
@IsOptional() @IsDateString()
validFrom?: string;
@IsOptional() @IsDateString()
validTo?: string;
}
export class CreateRequirementDto {
@IsString() @MaxLength(30)
code: string;
@IsString() @MaxLength(300)
name: string;
@IsEnum(RequirementCategory)
category: RequirementCategory;
@IsOptional() @IsString()
description?: string;
@IsOptional() @IsArray() @IsString({ each: true })
evidenceRequired?: string[];
@IsOptional() @IsUUID()
parentId?: string;
@IsOptional() @IsBoolean()
isMandatory?: boolean;
}
Compliance DTOs
export class RegisterProjectDto {
@IsUUID()
projectId: string;
@IsUUID()
programId: string;
@IsOptional() @IsString()
registrationNumber?: string;
@IsOptional() @IsDateString()
registrationDate?: string;
}
export class UpdateItemStatusDto {
@IsEnum(RequirementStatus)
status: RequirementStatus;
@IsOptional() @IsString()
notes?: string;
@IsOptional() @IsBoolean()
isNotApplicable?: boolean;
@IsOptional() @IsString()
naReason?: string;
}
export class ComplianceDashboardDto {
projectId: string;
programs: {
programId: string;
programName: string;
compliancePercentage: number;
status: 'green' | 'yellow' | 'red';
}[];
summary: {
totalRequirements: number;
compliantCount: number;
pendingCount: number;
nonCompliantCount: number;
overallPercentage: number;
};
byCategory: {
category: RequirementCategory;
total: number;
compliant: number;
percentage: number;
}[];
upcomingDueDates: {
itemId: string;
requirementName: string;
dueDate: string;
daysRemaining: number;
}[];
recentAudits: AuditSummaryDto[];
}
Audit DTOs
export class CreateAuditDto {
@IsUUID()
projectId: string;
@IsEnum(AuditType)
auditType: AuditType;
@IsDateString()
scheduledDate: string;
@IsOptional() @IsString()
scheduledTime?: string;
@IsOptional() @IsString()
auditorName?: string;
@IsOptional() @IsString()
auditorCompany?: string;
}
export class CreateFindingDto {
@IsString() @MaxLength(30)
findingNumber: string;
@IsEnum(FindingSeverity)
severity: FindingSeverity;
@IsString() @MaxLength(300)
title: string;
@IsString()
description: string;
@IsOptional() @IsUUID()
complianceItemId?: string;
@IsOptional() @IsString()
responsibleName?: string;
@IsDateString()
dueDate: string;
}
export class CreateCorrectiveActionDto {
@IsString()
description: string;
@IsEnum(ActionType)
actionType: ActionType;
@IsOptional() @IsUUID()
assignedTo?: string;
@IsDateString()
dueDate: string;
}
Scheduled Jobs
DueDateAlertsJob
@Injectable()
export class DueDateAlertsJob {
constructor(
private readonly complianceService: ComplianceService,
private readonly notificationService: NotificationService
) {}
@Cron('0 8 * * *') // Daily at 8 AM
async generateDueDateAlerts() {
// Find items due in next 7 days
const upcomingItems = await this.complianceService.getItemsDueSoon(7);
for (const item of upcomingItems) {
await this.notificationService.send({
type: 'requirement_due',
recipients: item.responsibleUsers,
data: {
requirementName: item.requirementName,
dueDate: item.dueDate,
daysRemaining: item.daysRemaining
}
});
}
// Find overdue findings
const overdueFindings = await this.complianceService.getOverdueFindings();
for (const finding of overdueFindings) {
await this.notificationService.send({
type: 'finding_overdue',
severity: finding.severity,
recipients: finding.responsibleUsers,
data: {
findingTitle: finding.title,
dueDate: finding.dueDate,
daysOverdue: finding.daysOverdue
}
});
}
}
}
Events
export class ProjectRegisteredInProgramEvent {
constructor(
public readonly projectProgramId: string,
public readonly projectId: string,
public readonly programId: string,
public readonly registrationNumber: string | null,
public readonly timestamp: Date = new Date()
) {}
}
export class ComplianceStatusChangedEvent {
constructor(
public readonly complianceItemId: string,
public readonly projectProgramId: string,
public readonly previousStatus: RequirementStatus,
public readonly newStatus: RequirementStatus,
public readonly changedBy: string,
public readonly timestamp: Date = new Date()
) {}
}
export class AuditCompletedEvent {
constructor(
public readonly auditId: string,
public readonly projectId: string,
public readonly overallCompliance: number,
public readonly findingsCount: number,
public readonly criticalCount: number,
public readonly timestamp: Date = new Date()
) {}
}
Referencias
Ultima actualizacion: 2025-12-05