changes for detail agents and erp-suite
Some checks are pending
CI Pipeline / changes (push) Waiting to run
CI Pipeline / core (push) Blocked by required conditions
CI Pipeline / trading-backend (push) Blocked by required conditions
CI Pipeline / trading-data-service (push) Blocked by required conditions
CI Pipeline / trading-frontend (push) Blocked by required conditions
CI Pipeline / erp-core (push) Blocked by required conditions
CI Pipeline / erp-mecanicas (push) Blocked by required conditions
CI Pipeline / gamilit-backend (push) Blocked by required conditions
CI Pipeline / gamilit-frontend (push) Blocked by required conditions

This commit is contained in:
rckrdmrd 2025-12-12 23:16:32 -06:00
parent 513a86ceee
commit 3a44cad22e
40 changed files with 232 additions and 182 deletions

View File

@ -7,7 +7,7 @@
* Sprint 0 - P0-008: Test Infrastructure
*/
import { Repository, SelectQueryBuilder } from 'typeorm';
import { Repository, SelectQueryBuilder, ObjectLiteral } from 'typeorm';
/**
* Creates a mock TypeORM Repository with all common methods
@ -15,7 +15,7 @@ import { Repository, SelectQueryBuilder } from 'typeorm';
* @template T - Entity type
* @returns Mocked Repository<T>
*/
export function createMockRepository<T>(): jest.Mocked<Repository<T>> {
export function createMockRepository<T extends ObjectLiteral>(): jest.Mocked<Repository<T>> {
return {
find: jest.fn(),
findOne: jest.fn(),
@ -58,7 +58,7 @@ export function createMockRepository<T>(): jest.Mocked<Repository<T>> {
* @template T - Entity type
* @returns Mocked SelectQueryBuilder<T>
*/
export function createMockQueryBuilder<T>(): jest.Mocked<SelectQueryBuilder<T>> {
export function createMockQueryBuilder<T extends ObjectLiteral>(): jest.Mocked<SelectQueryBuilder<T>> {
const queryBuilder = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),

View File

@ -24,6 +24,7 @@ import {
AlertsStatsDto,
PaginatedAlertsDto,
} from '../dto/alerts';
import { AuthRequest } from '@shared/types';
/**
* AdminAlertsController
@ -154,7 +155,7 @@ export class AdminAlertsController {
@Body() createDto: CreateAlertDto,
@Request() req: AuthRequest,
): Promise<AlertResponseDto> {
const userId = req.user?.id || req.user?.sub;
const userId = req.user!.id;
return this.alertsService.createAlert(createDto, userId);
}
@ -186,7 +187,7 @@ export class AdminAlertsController {
@Body() dto: AcknowledgeAlertDto,
@Request() req: AuthRequest,
): Promise<AlertResponseDto> {
const userId = req.user?.id || req.user?.sub;
const userId = req.user!.id;
return this.alertsService.acknowledgeAlert(id, dto.acknowledgment_note, userId);
}
@ -218,7 +219,7 @@ export class AdminAlertsController {
@Body() dto: ResolveAlertDto,
@Request() req: AuthRequest,
): Promise<AlertResponseDto> {
const userId = req.user?.id || req.user?.sub;
const userId = req.user!.id;
return this.alertsService.resolveAlert(id, dto.resolution_note, userId);
}

View File

@ -20,6 +20,7 @@ import {
BulkDeleteUsersDto,
BulkOperationStatusDto,
} from '../dto/bulk-operations';
import { AuthRequest } from '@shared/types';
/**
* AdminBulkOperationsController
@ -62,7 +63,7 @@ export class AdminBulkOperationsController {
@Body() dto: BulkSuspendUsersDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user.sub; // JWT payload contiene sub = user.id
const adminId = req.user!.id; // JWT payload contiene sub = user.id
return this.bulkOpsService.bulkSuspendUsers(dto, adminId);
}
@ -84,7 +85,7 @@ export class AdminBulkOperationsController {
@Body() dto: BulkActivateUsersDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.bulkOpsService.bulkActivateUsers(dto, adminId);
}
@ -106,7 +107,7 @@ export class AdminBulkOperationsController {
@Body() dto: BulkUpdateRoleDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.bulkOpsService.bulkUpdateRole(dto, adminId);
}
@ -128,7 +129,7 @@ export class AdminBulkOperationsController {
@Body() dto: BulkDeleteUsersDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.bulkOpsService.bulkDeleteUsers(dto, adminId);
}

View File

@ -28,6 +28,7 @@ import {
ListApprovalHistoryDto,
PaginatedApprovalHistoryDto,
} from '../dto/content';
import { AuthRequest } from '@shared/types';
@ApiTags('Admin - Content')
@Controller('admin/content')
@ -71,7 +72,7 @@ export class AdminContentController {
@Body() approvalDto: ApproveContentDto,
@Request() req: AuthRequest,
): Promise<ContentDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminContentService.approveContent(
id,
approvalDto,
@ -89,7 +90,7 @@ export class AdminContentController {
@Body() approvalDto: ApproveContentDto,
@Request() req: AuthRequest,
): Promise<ContentDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminContentService.approveContent(
id,
approvalDto,
@ -108,7 +109,7 @@ export class AdminContentController {
@Body() rejectionDto: RejectContentDto,
@Request() req: AuthRequest,
): Promise<ContentDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminContentService.rejectContent(
id,
rejectionDto,
@ -126,7 +127,7 @@ export class AdminContentController {
@Body() rejectionDto: RejectContentDto,
@Request() req: AuthRequest,
): Promise<ContentDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminContentService.rejectContent(
id,
rejectionDto,
@ -159,7 +160,7 @@ export class AdminContentController {
@Body() dto: CreateVersionDto,
@Request() req: AuthRequest,
): Promise<VersionResponseDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminContentService.createVersion(dto, adminId);
}

View File

@ -33,6 +33,7 @@ import {
UpdateMayaRankDto,
UpdateMayaRankResponseDto,
} from '../dto/gamification-config';
import { AuthRequest } from '@shared/types';
/**
* AdminGamificationConfigController
@ -177,7 +178,7 @@ export class AdminGamificationConfigController {
@Body() dto: UpdateGamificationSettingsDto,
@Request() req: AuthRequest,
): Promise<GamificationSettingsResponseDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.gamificationConfigService.updateGamificationSettings(
dto,
adminId,
@ -284,7 +285,7 @@ export class AdminGamificationConfigController {
async restoreDefaults(
@Request() req: AuthRequest,
): Promise<RestoreDefaultsResultDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.gamificationConfigService.restoreDefaults(adminId);
}
@ -325,7 +326,7 @@ export class AdminGamificationConfigController {
async restoreDefaultsAlternative(
@Request() req: AuthRequest,
): Promise<RestoreDefaultsResultDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.gamificationConfigService.restoreDefaults(adminId);
}
@ -505,7 +506,7 @@ export class AdminGamificationConfigController {
@Body() dto: UpdateParameterDto,
@Request() req: AuthRequest,
): Promise<UpdateParameterResponseDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.gamificationConfigService.updateParameterById(
id,
dto,
@ -637,7 +638,7 @@ export class AdminGamificationConfigController {
@Body() dto: UpdateMayaRankDto,
@Request() req: AuthRequest,
): Promise<UpdateMayaRankResponseDto> {
const adminId = req.user.sub;
const adminId = req.user!.id;
return this.gamificationConfigService.updateMayaRank(
rankName,
dto,

View File

@ -22,6 +22,7 @@ import {
InterventionAlertDto,
PaginatedInterventionsDto,
} from '../dto/interventions';
import { AuthRequest } from '@shared/types';
/**
* AdminInterventionsController
@ -165,7 +166,7 @@ export class AdminInterventionsController {
@Body() dto: AcknowledgeInterventionDto,
@Request() req: AuthRequest,
): Promise<InterventionAlertDto> {
const userId = req.user?.id || req.user?.sub;
const userId = req.user!.id;
return this.interventionsService.acknowledgeIntervention(id, dto.acknowledgment_note, userId);
}
@ -213,7 +214,7 @@ export class AdminInterventionsController {
@Body() dto: ResolveInterventionDto,
@Request() req: AuthRequest,
): Promise<InterventionAlertDto> {
const userId = req.user?.id || req.user?.sub;
const userId = req.user!.id;
return this.interventionsService.resolveIntervention(id, dto.resolution_notes, userId);
}

View File

@ -21,6 +21,7 @@ import {
ListReportsDto,
PaginatedReportsDto,
} from '../dto/reports';
import { AuthRequest } from '@shared/types';
@ApiTags('Admin - Reports')
@Controller('admin/reports')
@ -39,7 +40,7 @@ export class AdminReportsController {
@Body() generateDto: GenerateReportDto,
@Request() req: AuthRequest,
): Promise<ReportDto> {
const userId = req.user?.id || req.user?.sub;
const userId = req.user!.id;
return this.adminReportsService.generateReport(generateDto, userId);
}

View File

@ -20,6 +20,7 @@ import {
CacheClearResultDto,
SessionCleanupResultDto,
} from '../dto/system';
import { AuthRequest } from '@shared/types';
@ApiTags('Admin - System')
@Controller('admin/system')
@ -73,7 +74,7 @@ export class AdminSystemController {
@Body() configDto: UpdateSystemConfigDto,
@Request() req: AuthRequest,
): Promise<SystemConfigDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminSystemService.updateSystemConfig(configDto, adminId);
}
@ -109,7 +110,7 @@ export class AdminSystemController {
@Body() configDto: Record<string, unknown>,
@Request() req: AuthRequest,
): Promise<Record<string, unknown>> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminSystemService.updateConfigByCategory(
category,
configDto,
@ -127,7 +128,7 @@ export class AdminSystemController {
@Body() toggleDto: ToggleMaintenanceDto,
@Request() req: AuthRequest,
): Promise<MaintenanceStatusDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.adminSystemService.toggleMaintenance(
toggleDto,
adminId,

View File

@ -32,6 +32,7 @@ import {
BulkOperationStatusDto,
} from '../dto/bulk-operations';
import { User } from '@modules/auth/entities/user.entity';
import { AuthRequest } from '@shared/types';
@ApiTags('Admin - Users')
@Controller('admin/users')
@ -139,7 +140,7 @@ export class AdminUsersController {
@Body() dto: BulkSuspendUsersDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.bulkOpsService.bulkSuspendUsers(dto, adminId);
}
@ -153,7 +154,7 @@ export class AdminUsersController {
@Body() dto: BulkDeleteUsersDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.bulkOpsService.bulkDeleteUsers(dto, adminId);
}
@ -167,7 +168,7 @@ export class AdminUsersController {
@Body() dto: BulkUpdateRoleDto,
@Request() req: AuthRequest,
): Promise<BulkOperationStatusDto> {
const adminId = req.user?.id || req.user?.sub;
const adminId = req.user!.id;
return this.bulkOpsService.bulkUpdateRole(dto, adminId);
}
}

View File

@ -22,6 +22,7 @@ import {
FeatureFlagCheckResultDto
} from '../dto/feature-flags';
import { FeatureFlag } from '../entities/feature-flag.entity';
import { AuthRequest } from '@shared/types';
/**
* FeatureFlagsController

View File

@ -103,11 +103,11 @@ export class GamificationConfigService {
)?.updated_by;
return {
xp: config.xp || DEFAULT_GAMIFICATION_CONFIG.xp,
ranks: config.ranks || DEFAULT_GAMIFICATION_CONFIG.ranks,
coins: config.coins || DEFAULT_GAMIFICATION_CONFIG.coins,
xp: (config.xp as Record<string, unknown>) || (DEFAULT_GAMIFICATION_CONFIG.xp as Record<string, unknown>),
ranks: (config.ranks as Record<string, unknown>) || (DEFAULT_GAMIFICATION_CONFIG.ranks as Record<string, unknown>),
coins: (config.coins as Record<string, unknown>) || (DEFAULT_GAMIFICATION_CONFIG.coins as Record<string, unknown>),
achievements:
config.achievements || DEFAULT_GAMIFICATION_CONFIG.achievements,
(config.achievements as Record<string, unknown>) || (DEFAULT_GAMIFICATION_CONFIG.achievements as Record<string, unknown>),
defaults: defaults,
last_updated: lastUpdated.toISOString(),
updated_by: updatedBy,
@ -131,15 +131,15 @@ export class GamificationConfigService {
// Update each category
if (dto.xp) {
await this.updateXpSettings(dto.xp, adminId);
await this.updateXpSettings(dto.xp as unknown as Record<string, unknown>, adminId);
}
if (dto.ranks) {
await this.updateRankSettings(dto.ranks, adminId);
await this.updateRankSettings(dto.ranks as unknown as Record<string, unknown>, adminId);
}
if (dto.coins) {
await this.updateCoinsSettings(dto.coins, adminId);
await this.updateCoinsSettings(dto.coins as unknown as Record<string, unknown>, adminId);
}
if (dto.achievements) {
@ -456,7 +456,7 @@ export class GamificationConfigService {
// Assign to config structure
if (category === 'xp' || category === 'coins') {
config[category][key] = value;
(config[category] as Record<string, unknown>)[key] = value;
} else if (category === 'ranks' && key === 'thresholds') {
config.ranks = value;
} else if (category === 'achievements' && key === 'criteria') {

View File

@ -32,6 +32,7 @@ import { AssignmentGradeDto } from '../dto/grade-submission.dto';
import { PatchAssignmentDto } from '../dto/patch-assignment.dto';
import { DistributeAssignmentDto, DistributeAssignmentResponseDto } from '../dto/distribute-assignment.dto';
import { DuplicateAssignmentDto, DuplicateAssignmentResponseDto } from '../dto/duplicate-assignment.dto';
import { AuthRequest } from '@shared/types';
@Controller('teacher/assignments')
@ApiTags('Assignments')
@ -70,7 +71,7 @@ export class AssignmentsController {
})
@ApiResponse({ status: 400, description: 'Datos inválidos' })
async create(@Body() createDto: CreateAssignmentDto, @Request() req: AuthRequest) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.create(createDto, teacherId);
}
@ -117,7 +118,7 @@ export class AssignmentsController {
},
})
async findAll(@Query() query: any, @Request() req: AuthRequest) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.findAll(teacherId, {
isPublished: query.isPublished !== undefined ? query.isPublished === 'true' : undefined,
assignmentType: query.type,
@ -165,7 +166,7 @@ export class AssignmentsController {
description: 'Asignación no encontrada o acceso denegado',
})
async findOne(@Param('id') id: string, @Request() req: AuthRequest) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.findOne(id, teacherId);
}
@ -201,7 +202,7 @@ export class AssignmentsController {
@Body() updateDto: UpdateAssignmentDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.update(id, updateDto, teacherId);
}
@ -229,7 +230,7 @@ export class AssignmentsController {
description: 'Asignación no encontrada o acceso denegado',
})
async remove(@Param('id') id: string, @Request() req: AuthRequest) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
await this.assignmentsService.remove(id, teacherId);
}
@ -268,7 +269,7 @@ export class AssignmentsController {
@Body() dto: AssignToClassroomsDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.assignToClassrooms(id, dto, teacherId);
}
@ -322,7 +323,7 @@ export class AssignmentsController {
@Query() query: any,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.getSubmissions(id, teacherId, {
status: query.status,
classroomId: query.classroomId,
@ -375,7 +376,7 @@ export class AssignmentsController {
@Body() dto: AssignmentGradeDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.gradeSubmission(submissionId, dto, teacherId);
}
@ -409,7 +410,7 @@ export class AssignmentsController {
@Body() patchDto: PatchAssignmentDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.patchAssignment(id, patchDto, teacherId);
}
@ -443,7 +444,7 @@ export class AssignmentsController {
@Body() distributeDto: DistributeAssignmentDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.distributeAssignment(id, distributeDto, teacherId);
}
@ -474,7 +475,7 @@ export class AssignmentsController {
@Body() duplicateDto: DuplicateAssignmentDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.duplicateAssignment(id, duplicateDto, teacherId);
}
@ -530,7 +531,7 @@ export class AssignmentsController {
@Body('notifyStudents') notifyStudents: boolean = false,
@Request() req: AuthRequest,
) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.publishAssignment(id, teacherId, notifyStudents);
}
@ -570,7 +571,7 @@ export class AssignmentsController {
description: 'Assignment not found or access denied',
})
async close(@Param('id') id: string, @Request() req: AuthRequest) {
const teacherId = req.user?.userId || req.user?.sub;
const teacherId = req.user!.id;
return this.assignmentsService.closeAssignment(id, teacherId);
}
}

View File

@ -22,6 +22,7 @@ import { RolesGuard } from '../../auth/guards/roles.guard';
import { Roles } from '../../auth/decorators/roles.decorator';
import { GamilityRoleEnum } from '@/shared/constants';
import { AssignmentsService } from '../services/assignments.service';
import { AuthRequest } from '@shared/types';
@Controller('student/assignments')
@ApiTags('Student Assignments')
@ -74,7 +75,7 @@ export class StudentAssignmentsController {
},
})
async getMyAssignments(@Query() query: any, @Request() req: AuthRequest) {
const studentId = req.user?.userId || req.user?.sub;
const studentId = req.user!.id;
return this.assignmentsService.findStudentAssignments(studentId, {
status: query.status,
classroomId: query.classroomId,
@ -105,7 +106,7 @@ export class StudentAssignmentsController {
description: 'Tarea no encontrada o no asignada a este estudiante',
})
async getAssignmentDetails(@Param('id') id: string, @Request() req: AuthRequest) {
const studentId = req.user?.userId || req.user?.sub;
const studentId = req.user!.id;
return this.assignmentsService.findStudentAssignmentById(id, studentId);
}
@ -138,7 +139,7 @@ export class StudentAssignmentsController {
},
})
async getGradesSummary(@Request() req: AuthRequest) {
const studentId = req.user?.userId || req.user?.sub;
const studentId = req.user!.id;
return this.assignmentsService.getStudentGradesSummary(studentId);
}
}

View File

@ -29,6 +29,7 @@ import {
UserSessionResponseDto,
} from '../dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { AuthRequest } from '@shared/types';
/**
* AuthController
@ -131,8 +132,8 @@ export class AuthController {
@ApiResponse({ status: 401, description: 'No autenticado' })
async logout(@Request() req: AuthRequest): Promise<{ message: string }> {
// Extraer userId y sessionId del token JWT
const userId = req.user?.id;
const sessionId = req.user?.sessionId || 'current-session';
const userId = req.user!.id;
const sessionId = req.user!.sessionId || 'current-session';
await this.authService.logout(userId, sessionId);
return { message: 'Sesión cerrada exitosamente' };
@ -177,7 +178,7 @@ export class AuthController {
@ApiResponse({ status: 401, description: 'No autenticado' })
async getProfile(@Request() req: AuthRequest): Promise<UserResponseDto> {
// Extraer userId del token JWT
const userId = req.user?.id;
const userId = req.user!.id;
const user = await this.authService.validateUser(userId);
if (!user) {
@ -210,7 +211,7 @@ export class AuthController {
@Body() dto: UpdateProfileDto,
): Promise<UserResponseDto> {
// Extraer userId del token JWT
const userId = req.user?.id;
const userId = req.user!.id;
// Actualizar perfil usando el servicio de auth
const updatedUser = await this.authService.updateUserProfile(userId, dto);
@ -344,7 +345,7 @@ export class AuthController {
@Body('currentPassword') _currentPassword: string,
@Body('newPassword') _newPassword: string,
): Promise<{ message: string }> {
const _userId = req.user?.id;
const _userId = req.user!.id;
// TODO: Implementar lógica de cambio de contraseña
return { message: 'Contraseña cambiada exitosamente' };
}
@ -364,7 +365,7 @@ export class AuthController {
})
@ApiResponse({ status: 401, description: 'No autenticado' })
async getSessions(@Request() req: AuthRequest): Promise<UserSessionResponseDto[]> {
const userId = req.user?.id;
const userId = req.user!.id;
return this.sessionService.getSessions(userId);
}
@ -394,7 +395,7 @@ export class AuthController {
@Request() req: AuthRequest,
@Param('sessionId') sessionId: string,
): Promise<{ message: string }> {
const userId = req.user?.id;
const userId = req.user!.id;
return this.sessionService.revokeSession(sessionId, userId);
}
@ -421,8 +422,8 @@ export class AuthController {
})
@ApiResponse({ status: 401, description: 'No autenticado' })
async revokeAllSessions(@Request() req: AuthRequest): Promise<{ message: string; count: number }> {
const userId = req.user?.id;
const currentSessionId = req.user?.sessionId || 'unknown';
const userId = req.user!.id;
const currentSessionId = req.user!.sessionId || 'unknown';
return this.sessionService.revokeAllSessions(userId, currentSessionId);
}
}

View File

@ -26,6 +26,7 @@ import {
import { API_ROUTES, extractBasePath } from '@/shared/constants';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { AuthService } from '../services/auth.service';
import { AuthRequest } from '@shared/types';
/**
* PasswordController
@ -144,7 +145,7 @@ export class PasswordController {
@Body() dto: ChangePasswordDto,
): Promise<{ message: string }> {
// Extraer userId del token JWT
const userId = req.user?.id;
const userId = req.user!.id;
return this.authService.changePassword(
userId,
dto.current_password,
@ -203,7 +204,7 @@ export class PasswordController {
@ApiResponse({ status: 409, description: 'Email ya verificado' })
async resendVerification(@Request() req: AuthRequest): Promise<{ message: string }> {
// Extraer userId del token JWT
const userId = req.user?.id;
const userId = req.user!.id;
return this.emailVerificationService.resendVerification(userId);
}
@ -228,7 +229,7 @@ export class PasswordController {
@ApiResponse({ status: 401, description: 'No autenticado' })
async checkVerificationStatus(@Request() req: AuthRequest): Promise<{ verified: boolean }> {
// Extraer userId del token JWT
const userId = req.user?.id;
const userId = req.user!.id;
return this.emailVerificationService.checkVerificationStatus(userId);
}
}

View File

@ -27,6 +27,7 @@ import {
UserResponseDto,
} from '../dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { AuthRequest } from '@shared/types';
/**
* UsersController
@ -60,7 +61,7 @@ export class UsersController {
})
@ApiResponse({ status: 401, description: 'No autenticado' })
async getProfile(@Request() req: AuthRequest): Promise<UserResponseDto> {
const userId = req.user?.id;
const userId = req.user!.id;
const user = await this.authService.validateUser(userId);
if (!user) {
@ -91,7 +92,7 @@ export class UsersController {
@Request() req: AuthRequest,
@Body() dto: UpdateProfileDto,
): Promise<UserResponseDto> {
const userId = req.user?.id;
const userId = req.user!.id;
const updatedUser = await this.authService.updateUserProfile(userId, dto);
if (!updatedUser) {
@ -120,7 +121,7 @@ export class UsersController {
})
@ApiResponse({ status: 401, description: 'No autenticado' })
async getPreferences(@Request() req: AuthRequest): Promise<{ preferences: any }> {
const userId = req.user?.id;
const userId = req.user!.id;
const preferences = await this.authService.getUserPreferences(userId);
return { preferences };
}
@ -154,7 +155,7 @@ export class UsersController {
@Request() req: AuthRequest,
@Body('preferences') preferences: any,
): Promise<{ preferences: any }> {
const userId = req.user?.id;
const userId = req.user!.id;
const updatedPreferences = await this.authService.updateUserPreferences(
userId,
preferences,
@ -197,7 +198,7 @@ export class UsersController {
@Request() req: AuthRequest,
@UploadedFile() file: any,
): Promise<{ avatar_url: string }> {
const userId = req.user?.id;
const userId = req.user!.id;
if (!file) {
throw new UnauthorizedException('No se proporcionó archivo');
@ -231,7 +232,7 @@ export class UsersController {
})
@ApiResponse({ status: 401, description: 'No autenticado' })
async getStatistics(@Request() req: AuthRequest): Promise<any> {
const userId = req.user?.id;
const userId = req.user!.id;
const statistics = await this.authService.getUserStatistics(userId);
return statistics;
}

View File

@ -30,6 +30,7 @@ import { ExerciseSubmissionService, ExerciseAttemptService } from '@/modules/pro
import { ExerciseAnswerValidator } from '@/modules/progress/dto/answers/exercise-answer.validator';
import { JwtAuthGuard } from '@/modules/auth/guards/jwt-auth.guard';
import { Profile } from '@/modules/auth/entities';
import { AuthRequest } from '@shared/types';
/**
* ExercisesController
@ -221,8 +222,8 @@ export class ExercisesController {
description: 'Error interno del servidor',
})
async findAll(@Request() req: AuthRequest) {
const userId = req.user.id;
const userRole = req.user.role;
const userId = req.user!.id;
const userRole = req.user!.role;
// GAP-C06: Aplicar RLS según el rol del usuario
let exercises: any[];
@ -373,7 +374,7 @@ export class ExercisesController {
},
})
async findOne(@Param('id') id: string, @Request() req: AuthRequest) {
const userId = req.user.id;
const userId = req.user!.id;
// Obtener ejercicio
const exercise = await this.exercisesService.findById(id);
@ -676,7 +677,7 @@ export class ExercisesController {
const exercises = await this.exercisesService.findByModuleId(moduleId);
// FIX 2025-11-29: Convert auth.users.id → profiles.id
const userId = req.user.id;
const userId = req.user!.id;
const profileId = await this.getProfileId(userId);
// Obtener todas las submissions del usuario de una sola vez para eficiencia

View File

@ -18,6 +18,7 @@ import { UploadMediaDto } from '../dto/upload-media.dto';
import { MediaAttachment } from '../entities/media-attachment.entity';
import { JwtAuthGuard } from '@modules/auth/guards/jwt-auth.guard';
import { Response } from 'express';
import { AuthRequest } from '@shared/types';
/**
* Controller para gestión de archivos multimedia
@ -84,7 +85,7 @@ export class MediaUploadController {
@UploadedFile() file: any,
@Body() dto: UploadMediaDto,
): Promise<MediaAttachment> {
const userId = req.user.profileId;
const userId = req.user!.profile?.id || req.user!.id;
return this.mediaService.uploadFile(file, userId, dto);
}

View File

@ -18,6 +18,7 @@ import { CreateModuleDto, ModuleResponseDto, GetModulesQueryDto } from '../dto';
import { API_ROUTES, extractBasePath } from '@/shared/constants';
import { DifficultyLevelEnum } from '@/shared/constants/enums.constants';
import { JwtAuthGuard } from '@/modules/auth/guards/jwt-auth.guard';
import { AuthRequest } from '@shared/types';
/**
* ModulesController
@ -111,7 +112,7 @@ export class ModulesController {
description: 'Error interno del servidor',
})
async findAll(@Request() req: AuthRequest, @Query() query: GetModulesQueryDto) {
const userId = req.user.id;
const userId = req.user!.id;
// Reutilizar getUserModules que ya tiene la lógica correcta para ambas tablas
// Ahora acepta classroomId opcional para filtrar
return this.modulesService.getUserModules(userId, query.classroomId);

View File

@ -173,7 +173,7 @@ export class ExercisesService {
this.validateContentByExerciseType(exerciseType, content, config);
}
await this.exerciseRepo.update(id, exerciseData);
await this.exerciseRepo.update(id, exerciseData as any);
const updated = await this.findById(id);
if (!updated) {
throw new NotFoundException(`Exercise with ID ${id} not found after update`);
@ -230,8 +230,9 @@ export class ExercisesService {
}
// Validar que grid tenga dimensiones correctas
const rows = config?.gridSize?.rows || 0;
const cols = config?.gridSize?.cols || 0;
const gridSize = config?.gridSize as { rows?: number; cols?: number } | undefined;
const rows = gridSize?.rows || 0;
const cols = gridSize?.cols || 0;
if (content.grid.length !== rows) {
throw new BadRequestException(
@ -526,11 +527,13 @@ export class ExercisesService {
}
// ✅ FE-060: Use config.gridSize directly (passed as parameter)
const gridSize = config?.gridSize || { rows: 15, cols: 15 };
const gridSizeRaw = config?.gridSize as { rows?: number; cols?: number } | undefined;
const gridSize = gridSizeRaw || { rows: 15, cols: 15 };
const cluesArray = (sanitized.clues || []) as Array<{ startRow: number; startCol: number; direction: string; length: number; number?: number }>;
sanitized.grid = this.generateEmptyGrid(
gridSize.rows || 15,
gridSize.cols || 15,
sanitized.clues || [],
cluesArray,
);
sanitized.gridConfig = gridSize;
@ -539,11 +542,11 @@ export class ExercisesService {
hasConfig: !!config,
gridSizeFromConfig: config?.gridSize,
gridSizeUsed: gridSize,
gridGenerated: `${gridSize.rows}x${gridSize.cols}`,
gridGenerated: `${gridSize.rows || 15}x${gridSize.cols || 15}`,
hasGrid: !!sanitized.grid,
gridIsArray: Array.isArray(sanitized.grid),
gridDimensions: sanitized.grid?.length ? `${sanitized.grid.length}x${sanitized.grid[0]?.length}` : 'N/A',
cluesCount: sanitized.clues?.length,
gridDimensions: sanitized.grid?.length ? `${(sanitized.grid as unknown[]).length}x${(sanitized.grid as unknown[][])[0]?.length}` : 'N/A',
cluesCount: cluesArray.length,
});
break;
}

View File

@ -62,7 +62,7 @@ export class MediaService {
throw new NotFoundException(`Media resource with ID ${id} not found`);
}
await this.mediaRepo.update(id, mediaData);
await this.mediaRepo.update(id, mediaData as any);
const updated = await this.findById(id);
if (!updated) {
throw new NotFoundException(`Media resource with ID ${id} not found after update`);
@ -107,7 +107,7 @@ export class MediaService {
};
}
await this.mediaRepo.update(id, updateData);
await this.mediaRepo.update(id, updateData as any);
const updated = await this.findById(id);
if (!updated) {
throw new NotFoundException(`Media resource with ID ${id} not found after update`);

View File

@ -57,7 +57,7 @@ export class ModulesService {
* Actualizar un módulo existente
*/
async update(id: string, moduleData: Partial<Module>): Promise<Module | null> {
await this.moduleRepo.update(id, moduleData);
await this.moduleRepo.update(id, moduleData as any);
return this.findById(id);
}

View File

@ -16,6 +16,7 @@ import { ClassroomMissionsService } from '../services/classroom-missions.service
import { AssignClassroomMissionDto } from '../dto/missions/assign-classroom-mission.dto';
import { ClassroomMissionResponseDto } from '../dto/missions/classroom-mission-response.dto';
import { JwtAuthGuard } from '@/modules/auth/guards';
import { AuthRequest } from '@shared/types';
/**
* ClassroomMissionsController
@ -143,7 +144,7 @@ export class ClassroomMissionsController {
@Body() dto: AssignClassroomMissionDto,
@Request() req: AuthRequest,
) {
const teacherId = req.user.id;
const teacherId = req.user!.id;
return this.classroomMissionsService.assignMissionToClassroom(classroomId, teacherId, dto);
}

View File

@ -22,6 +22,7 @@ import {
} from '../dto/mission-templates';
import { JwtAuthGuard } from '@/modules/auth/guards';
import { AdminGuard } from '@/modules/admin/guards/admin.guard';
import { AuthRequest } from '@shared/types';
/**
* MissionTemplatesController
@ -205,7 +206,7 @@ export class MissionTemplatesController {
description: 'Datos inválidos',
})
async create(@Body() dto: CreateMissionTemplateDto, @Request() req: AuthRequest) {
const createdBy = req.user.id;
const createdBy = req.user!.id;
return this.templatesService.create(dto, createdBy);
}

View File

@ -18,6 +18,7 @@ import { MissionResponseDto } from '../dto/missions/mission-response.dto';
import { MissionStatsDto } from '../dto/missions/mission-stats.dto';
import { UpdateMissionProgressDto } from '../dto/missions/update-mission-progress.dto';
import { JwtAuthGuard } from '@/modules/auth/guards';
import { AuthRequest } from '@shared/types';
/**
* MissionsController
@ -111,7 +112,7 @@ export class MissionsController {
description: 'No autenticado - Token JWT inválido o ausente',
})
async getDailyMissions(@Request() req: AuthRequest) {
const userId = req.user.id;
const userId = req.user!.id;
return this.missionsService.findByTypeAndUser(userId, MissionTypeEnum.DAILY);
}
@ -180,7 +181,7 @@ export class MissionsController {
description: 'No autenticado - Token JWT inválido o ausente',
})
async getWeeklyMissions(@Request() req: AuthRequest) {
const userId = req.user.id;
const userId = req.user!.id;
return this.missionsService.findByTypeAndUser(userId, MissionTypeEnum.WEEKLY);
}
@ -235,7 +236,7 @@ export class MissionsController {
description: 'No autenticado - Token JWT inválido o ausente',
})
async getSpecialMissions(@Request() req: AuthRequest) {
const userId = req.user.id;
const userId = req.user!.id;
return this.missionsService.findByTypeAndUser(userId, MissionTypeEnum.SPECIAL);
}
@ -299,7 +300,7 @@ export class MissionsController {
})
async getStats(@Param('userId') userId: string, @Request() req: AuthRequest) {
// Validar que el userId coincide con el usuario autenticado
if (userId !== req.user.id) {
if (userId !== req.user!.id) {
throw new HttpException('Forbidden: Cannot access stats of another user', HttpStatus.FORBIDDEN);
}
@ -361,7 +362,7 @@ export class MissionsController {
description: 'Misión no encontrada',
})
async startMission(@Param('id') missionId: string, @Request() req: AuthRequest) {
const userId = req.user.id;
const userId = req.user!.id;
return this.missionsService.startMission(missionId, userId);
}
@ -449,7 +450,7 @@ export class MissionsController {
@Body() dto: UpdateMissionProgressDto,
@Request() req: AuthRequest,
) {
const userId = req.user.id;
const userId = req.user!.id;
return this.missionsService.updateProgress(
missionId,
userId,
@ -567,7 +568,7 @@ export class MissionsController {
description: 'Misión no encontrada',
})
async claimRewards(@Param('id') missionId: string, @Request() req: AuthRequest) {
const userId = req.user.id;
const userId = req.user!.id;
const result = await this.missionsService.claimRewards(missionId, userId);
return { success: true, data: result };
}

View File

@ -27,6 +27,7 @@ import { JwtAuthGuard } from '@modules/auth/guards/jwt-auth.guard';
import { RolesGuard } from '@shared/guards/roles.guard';
import { Roles } from '@shared/decorators/roles.decorator';
import { UserRank } from '../entities';
import { AuthRequest } from '@shared/types';
/**
* RankMetadataDto
@ -111,7 +112,7 @@ export class RanksController {
@ApiResponse({ status: 401, description: 'No autenticado' })
@ApiResponse({ status: 404, description: 'Usuario sin rango inicializado' })
async getCurrentRank(@Request() req: AuthRequest): Promise<UserRank> {
const userId = req.user.sub;
const userId = req.user!.id;
return this.ranksService.getCurrentRank(userId);
}

View File

@ -287,23 +287,25 @@ export class UserStatsController {
const stats = await this.userStatsService.findByUserId(userId);
// Handle total_xp_increment
if (updateData.total_xp_increment !== undefined) {
const newXP = stats.total_xp + updateData.total_xp_increment;
updateData.total_xp = newXP;
if (updateData.total_xp_increment !== undefined && updateData.total_xp_increment !== null) {
const increment = Number(updateData.total_xp_increment) || 0;
updateData.total_xp = stats.total_xp + increment;
delete updateData.total_xp_increment;
}
// Handle ml_coins_increment
if (updateData.ml_coins_increment !== undefined) {
updateData.ml_coins = stats.ml_coins + updateData.ml_coins_increment;
updateData.ml_coins_earned_total = stats.ml_coins_earned_total + updateData.ml_coins_increment;
if (updateData.ml_coins_increment !== undefined && updateData.ml_coins_increment !== null) {
const increment = Number(updateData.ml_coins_increment) || 0;
updateData.ml_coins = stats.ml_coins + increment;
updateData.ml_coins_earned_total = stats.ml_coins_earned_total + increment;
delete updateData.ml_coins_increment;
}
// Handle ml_coins_decrement
if (updateData.ml_coins_decrement !== undefined) {
updateData.ml_coins = stats.ml_coins - updateData.ml_coins_decrement;
updateData.ml_coins_spent_total = stats.ml_coins_spent_total + updateData.ml_coins_decrement;
if (updateData.ml_coins_decrement !== undefined && updateData.ml_coins_decrement !== null) {
const decrement = Number(updateData.ml_coins_decrement) || 0;
updateData.ml_coins = stats.ml_coins - decrement;
updateData.ml_coins_spent_total = stats.ml_coins_spent_total + decrement;
delete updateData.ml_coins_decrement;
}

View File

@ -228,8 +228,9 @@ export class AchievementsService {
const grantDto = new GrantAchievementDto();
grantDto.user_id = userId;
grantDto.achievement_id = achievement.id;
grantDto.progress = achievement.conditions.progress || achievement.conditions.max_progress || 100;
grantDto.max_progress = achievement.conditions.max_progress || 100;
const conditionsTyped = achievement.conditions as { progress?: number; max_progress?: number };
grantDto.progress = conditionsTyped.progress || conditionsTyped.max_progress || 100;
grantDto.max_progress = conditionsTyped.max_progress || 100;
grantDto.is_completed = true;
grantDto.progress_data = { auto_detected: true };
@ -245,32 +246,44 @@ export class AchievementsService {
* Evalúa si las estadísticas del usuario cumplen con las condiciones del logro
*/
private meetsConditions(userStats: UserStats, conditions: Record<string, unknown>): boolean {
const type = conditions.type || 'generic';
// Type cast for accessing condition properties
const cond = conditions as {
type?: string;
exercises_completed?: number;
modules_completed?: number;
min_streak?: number;
min_level?: number;
min_average_score?: number;
min_perfect_scores?: number;
target_rank?: string;
min_coins_earned?: number;
};
const type = cond.type || 'generic';
switch (type) {
case 'progress':
return (
userStats.exercises_completed >= (conditions.exercises_completed || 0) &&
userStats.modules_completed >= (conditions.modules_completed || 0)
userStats.exercises_completed >= (cond.exercises_completed || 0) &&
userStats.modules_completed >= (cond.modules_completed || 0)
);
case 'streak':
return userStats.current_streak >= (conditions.min_streak || 0);
return userStats.current_streak >= (cond.min_streak || 0);
case 'level':
return userStats.level >= (conditions.min_level || 0);
return userStats.level >= (cond.min_level || 0);
case 'score':
return (
(userStats.average_score || 0) >= (conditions.min_average_score || 0) &&
userStats.perfect_scores >= (conditions.min_perfect_scores || 0)
(userStats.average_score || 0) >= (cond.min_average_score || 0) &&
userStats.perfect_scores >= (cond.min_perfect_scores || 0)
);
case 'rank':
return this.userReachedRank(userStats.current_rank, conditions.target_rank);
return this.userReachedRank(userStats.current_rank, cond.target_rank || '');
case 'ml_coins':
return userStats.ml_coins_earned_total >= (conditions.min_coins_earned || 0);
return userStats.ml_coins_earned_total >= (cond.min_coins_earned || 0);
default:
return false;

View File

@ -108,16 +108,14 @@ export class MissionClaimService {
const mlCoinsReward = mission.rewards?.ml_coins || 0;
if (mlCoinsReward > 0) {
try {
await this.mlCoinsService.addTransaction({
user_id: profileId,
amount: mlCoinsReward,
type: TransactionTypeEnum.REWARD,
description: `Mission completed: ${mission.title}`,
metadata: {
mission_id: mission.id,
mission_type: mission.mission_type,
},
});
await this.mlCoinsService.addCoins(
profileId,
mlCoinsReward,
TransactionTypeEnum.EARNED_BONUS,
`Mission completed: ${mission.title}`,
mission.id,
'mission',
);
} catch (error) {
this.logger.error(
`Failed to add ML Coins for mission ${missionId}: ${error}`,
@ -132,7 +130,7 @@ export class MissionClaimService {
if (xpReward > 0) {
try {
const statsUpdate = await this.userStatsService.addXP(profileId, xpReward);
const statsUpdate = await this.userStatsService.addXp(profileId, xpReward);
if (statsUpdate.rankUp) {
rankUp = {

View File

@ -27,7 +27,7 @@ import {
MissionObjective,
MissionRewards,
} from '../../entities/mission.entity';
import { MissionTemplate } from '../../entities/mission-template.entity';
import { MissionTemplate, MissionTemplateTypeEnum } from '../../entities/mission-template.entity';
import { MissionTemplatesService } from '../mission-templates.service';
/**
@ -67,8 +67,8 @@ export class MissionGeneratorService {
await this.expireMissions(profileId, MissionTypeEnum.DAILY);
// Get available templates for user's level
const templates = await this.templatesService.getActiveByTypeAndLevel(
'daily',
const templates = await this.templatesService.getActiveByType(
'daily' as MissionTemplateTypeEnum,
userLevel,
);
@ -116,8 +116,8 @@ export class MissionGeneratorService {
await this.expireMissions(profileId, MissionTypeEnum.WEEKLY);
// Get available weekly templates
const templates = await this.templatesService.getActiveByTypeAndLevel(
'weekly',
const templates = await this.templatesService.getActiveByType(
'weekly' as MissionTemplateTypeEnum,
userLevel,
);

View File

@ -28,6 +28,7 @@ import {
DeviceResponseDto,
DevicesListResponseDto,
} from '../dto/devices';
import { AuthRequest } from '@shared/types';
/**
* NotificationDevicesController
@ -146,7 +147,7 @@ export class NotificationDevicesController {
@Body() registerDto: RegisterDeviceDto,
@Request() req: AuthRequest,
): Promise<DeviceResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const device = await this.deviceService.registerDevice({
userId,
@ -186,7 +187,7 @@ export class NotificationDevicesController {
description: 'No autenticado',
})
async getUserDevices(@Request() req: AuthRequest): Promise<DevicesListResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const devices = await this.deviceService.getUserDevices(userId, false);
// Ocultar parcialmente los tokens
@ -236,7 +237,7 @@ export class NotificationDevicesController {
@Param('id') deviceId: string,
@Request() req: AuthRequest,
): Promise<DeviceResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const device = await this.deviceService.getDeviceById(deviceId, userId);
return {
@ -288,7 +289,7 @@ export class NotificationDevicesController {
@Body() updateDto: UpdateDeviceNameDto,
@Request() req: AuthRequest,
): Promise<DeviceResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const device = await this.deviceService.updateDeviceName(
deviceId,
@ -347,7 +348,7 @@ export class NotificationDevicesController {
@Param('id') deviceId: string,
@Request() req: AuthRequest,
): Promise<void> {
const userId = req.user.sub;
const userId = req.user!.id;
await this.deviceService.deleteDevice(deviceId, userId);
}

View File

@ -20,6 +20,7 @@ import {
SendFromTemplateDto,
NotificationResponseDto,
} from '../dto/notifications';
import { AuthRequest } from '@shared/types';
/**
* NotificationMultiChannelController
@ -111,7 +112,7 @@ export class NotificationMultiChannelController {
): Promise<NotificationResponseDto> {
// Validar que el usuario solo crea notificaciones para sí mismo
// (a menos que sea admin - validación futura)
if (createDto.userId !== req.user.sub) {
if (createDto.userId !== req.user!.id) {
// TODO: Permitir si es admin
throw new Error('Cannot create notifications for other users');
}
@ -195,7 +196,7 @@ export class NotificationMultiChannelController {
@Request() req: AuthRequest,
): Promise<NotificationResponseDto> {
// Validar ownership
if (sendDto.userId !== req.user.sub) {
if (sendDto.userId !== req.user!.id) {
// TODO: Permitir si es admin
throw new Error('Cannot create notifications for other users');
}

View File

@ -24,6 +24,7 @@ import {
PreferenceResponseDto,
PreferencesListResponseDto,
} from '../dto/preferences';
import { AuthRequest } from '@shared/types';
/**
* NotificationPreferencesController
@ -84,7 +85,7 @@ export class NotificationPreferencesController {
async getUserPreferences(
@Request() req: AuthRequest,
): Promise<PreferencesListResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const preferences = await this.preferenceService.getUserPreferences(userId);
return {
@ -130,7 +131,7 @@ export class NotificationPreferencesController {
@Body() updateDto: UpdatePreferenceDto,
@Request() req: AuthRequest,
): Promise<PreferenceResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const preference = await this.preferenceService.updatePreference(
userId,
@ -177,7 +178,7 @@ export class NotificationPreferencesController {
@Body() updateDto: UpdateMultiplePreferencesDto,
@Request() req: AuthRequest,
): Promise<PreferencesListResponseDto> {
const userId = req.user.sub;
const userId = req.user!.id;
const preferences = await this.preferenceService.updateMultiple(
userId,

View File

@ -32,6 +32,7 @@ import { JwtAuthGuard } from '@/modules/auth/guards/jwt-auth.guard';
import { RolesGuard } from '@/shared/guards/roles.guard';
import { Roles } from '@/shared/decorators/roles.decorator';
import { GamilityRoleEnum } from '@/shared/constants/enums.constants';
import { AuthRequest } from '@shared/types';
/**
* ExerciseSubmissionController

View File

@ -31,6 +31,7 @@ import {
AttemptDetailDto,
AttemptsListResponseDto,
} from '../dto/exercise-responses.dto';
import { AuthRequest } from '@shared/types';
/**
* Controller for Teacher Exercise Responses

View File

@ -17,6 +17,7 @@ import { JwtAuthGuard } from '@modules/auth/guards/jwt-auth.guard';
import { RolesGuard } from '@modules/auth/guards/roles.guard';
import { Roles } from '@modules/auth/decorators/roles.decorator';
import { GamilityRoleEnum } from '@shared/constants/enums.constants';
import { AuthRequest } from '@shared/types';
/**
* Controller para gestión de evaluaciones manuales (ManualReview)
@ -114,7 +115,10 @@ export class ManualReviewController {
@Request() req: AuthRequest,
@Body() dto: CreateReviewDto,
): Promise<ManualReview> {
const teacherId = req.user.profileId;
const teacherId = req.user?.profile?.id || req.user?.sub || req.user?.id;
if (!teacherId) {
throw new Error('Teacher ID not found in request');
}
return this.reviewService.createReview(teacherId, dto);
}

View File

@ -28,6 +28,7 @@ import {
UpdatePermissionsDto,
StudentPermissionsResponseDto,
} from '../dto/student-blocking';
import { AuthRequest } from '@shared/types';
import {
CreateTeacherClassroomDto,
UpdateTeacherClassroomDto,
@ -119,7 +120,7 @@ export class TeacherClassroomsController {
@Query() query: GetClassroomsQueryDto,
@Request() req: AuthRequest,
): Promise<PaginatedTeacherClassroomsResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.getClassrooms(teacherId, query);
}
@ -154,7 +155,7 @@ export class TeacherClassroomsController {
@Body() dto: CreateTeacherClassroomDto,
@Request() req: AuthRequest,
): Promise<TeacherClassroomResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.createClassroom(teacherId, dto);
}
@ -194,7 +195,7 @@ export class TeacherClassroomsController {
@Param('id') id: string,
@Request() req: AuthRequest,
): Promise<TeacherClassroomDetailResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.getClassroomById(id, teacherId);
}
@ -240,7 +241,7 @@ export class TeacherClassroomsController {
@Body() dto: UpdateTeacherClassroomDto,
@Request() req: AuthRequest,
): Promise<TeacherClassroomResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.updateClassroom(id, teacherId, dto);
}
@ -289,7 +290,7 @@ export class TeacherClassroomsController {
@Param('id') id: string,
@Request() req: AuthRequest,
): Promise<{ success: boolean; message: string }> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.deleteClassroom(id, teacherId);
}
@ -345,7 +346,7 @@ export class TeacherClassroomsController {
@Query() query: GetClassroomStudentsQueryDto,
@Request() req: AuthRequest,
): Promise<PaginatedStudentsResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.getClassroomStudents(id, teacherId, query);
}
@ -385,7 +386,7 @@ export class TeacherClassroomsController {
@Param('id') id: string,
@Request() req: AuthRequest,
): Promise<ClassroomStatsDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.getClassroomStats(id, teacherId);
}
@ -425,7 +426,7 @@ export class TeacherClassroomsController {
@Param('classroomId') classroomId: string,
@Request() req: AuthRequest,
): Promise<TeacherInClassroomDto[]> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.getClassroomTeachers(classroomId, teacherId);
}
@ -490,7 +491,7 @@ export class TeacherClassroomsController {
@Param('id') id: string,
@Request() req: AuthRequest,
): Promise<any> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.classroomsCrudService.getClassroomProgress(id, teacherId);
}
@ -546,7 +547,7 @@ export class TeacherClassroomsController {
@Body() dto: BlockStudentDto,
@Request() req: AuthRequest,
): Promise<StudentPermissionsResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.studentBlockingService.blockStudent(
classroomId,
studentId,
@ -601,7 +602,7 @@ export class TeacherClassroomsController {
@Param('studentId') studentId: string,
@Request() req: AuthRequest,
): Promise<StudentPermissionsResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.studentBlockingService.unblockStudent(
classroomId,
studentId,
@ -651,7 +652,7 @@ export class TeacherClassroomsController {
@Param('studentId') studentId: string,
@Request() req: AuthRequest,
): Promise<StudentPermissionsResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.studentBlockingService.getStudentPermissions(
classroomId,
studentId,
@ -707,7 +708,7 @@ export class TeacherClassroomsController {
@Body() dto: UpdatePermissionsDto,
@Request() req: AuthRequest,
): Promise<StudentPermissionsResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.studentBlockingService.updateStudentPermissions(
classroomId,
studentId,

View File

@ -29,6 +29,7 @@ import {
import { JwtAuthGuard } from '@modules/auth/guards/jwt-auth.guard';
import { TeacherGuard } from '../guards';
import { TeacherContentService } from '../services/teacher-content.service';
import { AuthRequest } from '@shared/types';
import {
CreateTeacherContentDto,
UpdateTeacherContentDto,
@ -115,7 +116,7 @@ export class TeacherContentController {
@Query() query: GetTeacherContentQueryDto,
@Request() req: AuthRequest,
): Promise<PaginatedTeacherContentResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.findAll(teacherId, query);
}
@ -152,7 +153,7 @@ export class TeacherContentController {
description: 'Forbidden - Teacher is not the owner of this content',
})
async findOne(@Param('id') id: string, @Request() req: AuthRequest): Promise<TeacherContentResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.findOne(id, teacherId);
}
@ -195,7 +196,7 @@ export class TeacherContentController {
@Body() dto: CreateTeacherContentDto,
@Request() req: AuthRequest,
): Promise<TeacherContentResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.create(teacherId, dto);
}
@ -245,7 +246,7 @@ export class TeacherContentController {
@Body() dto: UpdateTeacherContentDto,
@Request() req: AuthRequest,
): Promise<TeacherContentResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.update(id, teacherId, dto);
}
@ -294,7 +295,7 @@ export class TeacherContentController {
@Param('id') id: string,
@Request() req: AuthRequest,
): Promise<{ success: boolean; message: string }> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.delete(id, teacherId);
}
@ -340,7 +341,7 @@ export class TeacherContentController {
@Body() dto: CloneTeacherContentDto,
@Request() req: AuthRequest,
): Promise<TeacherContentResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.clone(id, teacherId, dto);
}
@ -388,7 +389,7 @@ export class TeacherContentController {
@Param('id') id: string,
@Request() req: AuthRequest,
): Promise<TeacherContentResponseDto> {
const teacherId = req.user.sub;
const teacherId = req.user!.id;
return this.contentService.publish(id, teacherId);
}
}

View File

@ -22,6 +22,7 @@ import { JwtAuthGuard } from '@/modules/auth/guards/jwt-auth.guard';
import { RolesGuard } from '@/modules/auth/guards/roles.guard';
import { Roles } from '@/modules/auth/decorators/roles.decorator';
import { GamilityRoleEnum } from '@/shared/constants/enums.constants';
import { AuthRequest } from '@shared/types';
import {
TeacherDashboardService,
StudentProgressService,
@ -80,14 +81,14 @@ export class TeacherController {
@Get('dashboard/stats')
@ApiOperation({ summary: 'Get classroom statistics' })
async getClassroomStats(@Request() req: AuthRequest) {
const teacherId = req.user.id;
const teacherId = req.user!.id;
return this.dashboardService.getClassroomStats(teacherId);
}
@Get('dashboard/activities')
@ApiOperation({ summary: 'Get recent activities' })
async getRecentActivities(@Request() req: AuthRequest, @Query('limit') limit?: number) {
const teacherId = req.user.id;
const teacherId = req.user!.id;
return this.dashboardService.getRecentActivities(
teacherId,
limit ? parseInt(limit as any) : 10,
@ -97,14 +98,14 @@ export class TeacherController {
@Get('dashboard/alerts')
@ApiOperation({ summary: 'Get student alerts' })
async getStudentAlerts(@Request() req: AuthRequest) {
const teacherId = req.user.id;
const teacherId = req.user!.id;
return this.dashboardService.getStudentAlerts(teacherId);
}
@Get('dashboard/top-performers')
@ApiOperation({ summary: 'Get top performing students' })
async getTopPerformers(@Request() req: AuthRequest, @Query('limit') limit?: number) {
const teacherId = req.user.id;
const teacherId = req.user!.id;
return this.dashboardService.getTopPerformers(
teacherId,
limit ? parseInt(limit as any) : 5,
@ -114,7 +115,7 @@ export class TeacherController {
@Get('dashboard/module-progress')
@ApiOperation({ summary: 'Get module progress summary' })
async getModuleProgressSummary(@Request() req: AuthRequest) {
const teacherId = req.user.id;
const teacherId = req.user!.id;
return this.dashboardService.getModuleProgressSummary(teacherId);
}
@ -152,7 +153,7 @@ export class TeacherController {
@Param('studentId') studentId: string,
@Request() req: AuthRequest,
): Promise<StudentNoteResponseDto[]> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.studentProgressService.getStudentNotes(studentId, teacherId);
}
@ -166,7 +167,7 @@ export class TeacherController {
@Body() noteDto: AddTeacherNoteDto,
@Request() req: AuthRequest,
): Promise<StudentNoteResponseDto> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.studentProgressService.addStudentNote(
studentId,
teacherId,
@ -266,7 +267,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Query() query: GetEngagementMetricsDto,
) {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.analyticsService.getEngagementMetrics(teacherId, query);
}
@ -280,7 +281,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Query() query: GenerateReportsDto,
) {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.analyticsService.generateReports(teacherId, query);
}
@ -301,7 +302,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Query('classroom_id') classroomId?: string,
): Promise<EconomyAnalyticsDto> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.analyticsService.getEconomyAnalytics(teacherId, classroomId);
}
@ -322,7 +323,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Query('classroom_id') classroomId?: string,
): Promise<StudentsEconomyResponseDto> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.analyticsService.getStudentsEconomy(teacherId, classroomId);
}
@ -343,7 +344,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Query('classroom_id') classroomId?: string,
): Promise<AchievementsStatsResponseDto> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.analyticsService.getAchievementsStats(teacherId, classroomId);
}
@ -365,7 +366,7 @@ export class TeacherController {
@Body() dto: GenerateReportDto,
@Res() res: Response,
) {
const userId = req.user.profile.id;
const userId = req.user!.profile!.id;
const tenantId = req.user.tenantId || req.user.tenant_id || 'default';
// Generate report (now persists to storage and database)
@ -424,7 +425,7 @@ export class TeacherController {
@Body() dto: GrantBonusDto,
@Request() req: AuthRequest,
): Promise<GrantBonusResponseDto> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.bonusCoinsService.grantBonus(teacherId, studentId, dto);
}
@ -446,7 +447,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Query() query: GetRecentReportsQueryDto,
): Promise<ReportMetadataDto[]> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
const limit = query.limit || 10;
return this.teacherReportsService.getRecentReports(teacherId, limit);
}
@ -462,7 +463,7 @@ export class TeacherController {
type: ReportStatsDto,
})
async getReportStats(@Request() req: AuthRequest): Promise<ReportStatsDto> {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
return this.teacherReportsService.getReportStats(teacherId);
}
@ -489,7 +490,7 @@ export class TeacherController {
@Request() req: AuthRequest,
@Res() res: Response,
) {
const teacherId = req.user.profile.id;
const teacherId = req.user!.profile!.id;
// Get report with ownership validation
const report = await this.teacherReportsService.getReportById(reportId, teacherId);

View File

@ -76,6 +76,7 @@ export interface AuthUser {
rank?: string;
tenant_id?: string;
tenantId?: string; // Alternative naming
sessionId?: string; // Session ID from JWT payload
profile?: {
id: string;
tenant_id?: string;