# 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 ```typescript @Controller('api/v1/compliance/programs') @ApiTags('compliance-programs') export class ProgramController { // GET /api/v1/compliance/programs @Get() async findAll(@Query() query: ProgramQueryDto): Promise>; // GET /api/v1/compliance/programs/:id @Get(':id') async findOne(@Param('id') id: UUID): Promise; // POST /api/v1/compliance/programs @Post() async create(@Body() dto: CreateProgramDto): Promise; // GET /api/v1/compliance/programs/:id/requirements @Get(':id/requirements') async getRequirements(@Param('id') id: UUID): Promise; // POST /api/v1/compliance/programs/:id/requirements @Post(':id/requirements') async addRequirement(@Param('id') id: UUID, @Body() dto: CreateRequirementDto): Promise; } ``` ### 2. ComplianceController ```typescript @Controller('api/v1/compliance') @ApiTags('compliance') export class ComplianceController { // POST /api/v1/compliance/register-project @Post('register-project') async registerProject(@Body() dto: RegisterProjectDto): Promise; // GET /api/v1/compliance/project/:projectId @Get('project/:projectId') async getProjectCompliance(@Param('projectId') projectId: UUID): Promise; // GET /api/v1/compliance/project/:projectId/items @Get('project/:projectId/items') async getComplianceItems( @Param('projectId') projectId: UUID, @Query() query: ComplianceItemQueryDto ): Promise; // PUT /api/v1/compliance/items/:id/status @Put('items/:id/status') async updateItemStatus( @Param('id') id: UUID, @Body() dto: UpdateItemStatusDto ): Promise; // 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; // GET /api/v1/compliance/items/:id/evidence @Get('items/:id/evidence') async getEvidence(@Param('id') id: UUID): Promise; // DELETE /api/v1/compliance/evidence/:id @Delete('evidence/:id') async removeEvidence(@Param('id') id: UUID): Promise; // GET /api/v1/compliance/project/:projectId/dashboard @Get('project/:projectId/dashboard') async getDashboard(@Param('projectId') projectId: UUID): Promise; } ``` ### 3. AuditController ```typescript @Controller('api/v1/compliance/audits') @ApiTags('compliance-audits') export class AuditController { // GET /api/v1/compliance/audits @Get() async findAll(@Query() query: AuditQueryDto): Promise>; // GET /api/v1/compliance/audits/:id @Get(':id') async findOne(@Param('id') id: UUID): Promise; // POST /api/v1/compliance/audits @Post() async create(@Body() dto: CreateAuditDto): Promise; // PUT /api/v1/compliance/audits/:id @Put(':id') async update(@Param('id') id: UUID, @Body() dto: UpdateAuditDto): Promise; // POST /api/v1/compliance/audits/:id/start @Post(':id/start') async startAudit(@Param('id') id: UUID): Promise; // POST /api/v1/compliance/audits/:id/complete @Post(':id/complete') async completeAudit(@Param('id') id: UUID, @Body() dto: CompleteAuditDto): Promise; // GET /api/v1/compliance/audits/:id/findings @Get(':id/findings') async getFindings(@Param('id') id: UUID): Promise; // POST /api/v1/compliance/audits/:id/findings @Post(':id/findings') async addFinding(@Param('id') id: UUID, @Body() dto: CreateFindingDto): Promise; // PUT /api/v1/compliance/findings/:id @Put('findings/:id') async updateFinding(@Param('id') id: UUID, @Body() dto: UpdateFindingDto): Promise; // POST /api/v1/compliance/findings/:id/corrective-actions @Post('findings/:id/corrective-actions') async addCorrectiveAction( @Param('id') id: UUID, @Body() dto: CreateCorrectiveActionDto ): Promise; // PUT /api/v1/compliance/corrective-actions/:id/complete @Put('corrective-actions/:id/complete') async completeAction(@Param('id') id: UUID): Promise; // PUT /api/v1/compliance/corrective-actions/:id/verify @Put('corrective-actions/:id/verify') async verifyAction(@Param('id') id: UUID, @Body() dto: VerifyActionDto): Promise; } ``` ### 4. ReportController ```typescript @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; // GET /api/v1/compliance/reports/audit/:projectId @Get('audit/:projectId') async getAuditReport(@Param('projectId') projectId: UUID): Promise; // GET /api/v1/compliance/reports/executive/:projectId @Get('executive/:projectId') async getExecutiveReport(@Param('projectId') projectId: UUID): Promise; // GET /api/v1/compliance/reports/evidence-package/:projectId @Get('evidence-package/:projectId') async downloadEvidencePackage(@Param('projectId') projectId: UUID): Promise; } ``` --- ## Services ### ComplianceService ```typescript @Injectable() export class ComplianceService { constructor( @InjectRepository(ProjectProgram) private readonly projectProgramRepo: Repository, @InjectRepository(ComplianceItem) private readonly itemRepo: Repository, private readonly eventEmitter: EventEmitter2 ) {} async registerProject(tenantId: UUID, userId: UUID, dto: RegisterProjectDto): Promise; async getProjectCompliance(tenantId: UUID, projectId: UUID): Promise; async getComplianceItems(projectProgramId: UUID, query: ComplianceItemQueryDto): Promise; async updateItemStatus(tenantId: UUID, itemId: UUID, userId: UUID, dto: UpdateItemStatusDto): Promise; async calculateCompliancePercentage(projectProgramId: UUID): Promise; async getDashboard(tenantId: UUID, projectId: UUID): Promise; } ``` ### EvidenceService ```typescript @Injectable() export class EvidenceService { constructor( @InjectRepository(ComplianceEvidence) private readonly evidenceRepo: Repository, private readonly storageService: StorageService ) {} async upload(itemId: UUID, userId: UUID, file: Express.Multer.File, dto: UploadEvidenceDto): Promise; async getByItem(itemId: UUID): Promise; async remove(tenantId: UUID, evidenceId: UUID): Promise; async replace(evidenceId: UUID, file: Express.Multer.File): Promise; } ``` ### AuditService ```typescript @Injectable() export class AuditService { constructor( @InjectRepository(Audit) private readonly auditRepo: Repository, @InjectRepository(AuditFinding) private readonly findingRepo: Repository, @InjectRepository(CorrectiveAction) private readonly actionRepo: Repository, private readonly eventEmitter: EventEmitter2 ) {} async findAll(tenantId: UUID, query: AuditQueryDto): Promise>; async findOne(tenantId: UUID, id: UUID): Promise; async create(tenantId: UUID, userId: UUID, dto: CreateAuditDto): Promise; async startAudit(tenantId: UUID, id: UUID): Promise; async completeAudit(tenantId: UUID, id: UUID, dto: CompleteAuditDto): Promise; async addFinding(auditId: UUID, userId: UUID, dto: CreateFindingDto): Promise; async updateFinding(findingId: UUID, dto: UpdateFindingDto): Promise; async addCorrectiveAction(findingId: UUID, userId: UUID, dto: CreateCorrectiveActionDto): Promise; async completeAction(actionId: UUID, userId: UUID): Promise; async verifyAction(actionId: UUID, userId: UUID, dto: VerifyActionDto): Promise; async calculateFindingStats(auditId: UUID): Promise<{ critical: number; major: number; minor: number }>; } ``` --- ## DTOs ### Program DTOs ```typescript 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 ```typescript 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 ```typescript 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 ```typescript @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 ```typescript 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 - [DDL-SPEC-compliance.md](../../04-modelado/database-design/schemas/DDL-SPEC-compliance.md) - [COMPLIANCE-CONTEXT.md](../../04-modelado/domain-models/COMPLIANCE-CONTEXT.md) - [EPIC-MAI-011](../../08-epicas/EPIC-MAI-011-infonavit.md) --- *Ultima actualizacion: 2025-12-05*