# PLAN DE IMPLEMENTACION: Endpoints Pendientes Admin Portal **Fecha:** 2026-01-07 **Proyecto:** GAMILIT - Admin Portal Backend **Agente:** Claude Code (Opus 4.5) **Tipo:** Plan de Implementacion Detallado --- ## RESUMEN EJECUTIVO ```yaml total_pendientes: 5 p1_criticos: 3 p2_mejoras: 2 archivos_a_modificar: controladores: 2 servicios: 2 dtos: 4 (nuevos) archivos_nuevos: dtos: 4 tablas_bd: 0 # No se requieren nuevas tablas verificacion_duplicados: validate_config: "No existe - CREAR" config_categories: "No existe - CREAR" system_logs: "admin-logs.controller.ts existe pero retorna audit_logs, no system_logs - CREAR" reports_schedule: "No existe - CREAR" ``` --- ## 1. TASK-SETTINGS-VALIDATE-CONFIG (P1) ### 1.1 Analisis ```yaml endpoint: "POST /admin/system/config/validate" frontend_espera: "/admin/system/config/validate" estado_actual: "NO IMPLEMENTADO" duplicados_encontrados: "Ninguno" proposito: | Validar configuraciones ANTES de aplicarlas. Retorna errores de validacion sin persistir cambios. ``` ### 1.2 Verificacion de Duplicados | Archivo | Patron Buscado | Encontrado | Accion | |---------|----------------|------------|--------| | admin-system.controller.ts | validateConfig | NO | Crear endpoint | | admin-system.service.ts | validateConfig | NO | Crear metodo | | dto/system/*.ts | ValidateConfigDto | NO | Crear DTO | ### 1.3 Implementacion Propuesta **Archivo:** `admin-system.controller.ts` ```typescript @Post('config/validate') @ApiOperation({ summary: 'Validate system configuration', description: 'Validate configuration values before applying. Returns validation errors if any.', }) async validateConfig( @Body() configDto: ValidateConfigDto, ): Promise { return this.adminSystemService.validateConfig(configDto); } ``` **Nuevo DTO:** `validate-config.dto.ts` ```typescript export class ValidateConfigDto { @IsOptional() @IsBoolean() maintenance_mode?: boolean; @IsOptional() @IsString() maintenance_message?: string; @IsOptional() @IsBoolean() allow_registrations?: boolean; @IsOptional() @IsNumber() @Min(1) @Max(100) max_login_attempts?: number; @IsOptional() @IsNumber() @Min(1) @Max(1440) lockout_duration_minutes?: number; @IsOptional() @IsNumber() @Min(5) @Max(10080) session_timeout_minutes?: number; @IsOptional() @IsObject() custom_settings?: Record; } export class ConfigValidationResultDto { @ApiProperty() valid: boolean; @ApiProperty() errors: ConfigValidationError[]; @ApiProperty() warnings: string[]; } export class ConfigValidationError { @ApiProperty() field: string; @ApiProperty() message: string; @ApiProperty() value?: unknown; } ``` **Metodo en Service:** ```typescript async validateConfig(configDto: ValidateConfigDto): Promise { const errors: ConfigValidationError[] = []; const warnings: string[] = []; // Validar maintenance_mode if (configDto.maintenance_mode === true && !configDto.maintenance_message) { warnings.push('Se recomienda proporcionar un mensaje de mantenimiento'); } // Validar login attempts if (configDto.max_login_attempts && configDto.max_login_attempts < 3) { warnings.push('Un numero bajo de intentos puede bloquear usuarios legitimos'); } // Validar session timeout if (configDto.session_timeout_minutes && configDto.session_timeout_minutes > 1440) { warnings.push('Sesiones largas pueden ser un riesgo de seguridad'); } // Validar custom_settings si existen if (configDto.custom_settings) { for (const [key, value] of Object.entries(configDto.custom_settings)) { if (typeof key !== 'string' || key.length > 100) { errors.push({ field: `custom_settings.${key}`, message: 'Key debe ser string de maximo 100 caracteres', value: key, }); } } } return { valid: errors.length === 0, errors, warnings, }; } ``` --- ## 2. TASK-SETTINGS-CONFIG-CATEGORIES (P1) ### 2.1 Analisis ```yaml endpoint: "GET /admin/system/config/categories" frontend_espera: "/admin/system/config/categories" estado_actual: "NO IMPLEMENTADO" duplicados_encontrados: "Ninguno" proposito: | Retornar lista de categorias de configuracion disponibles. El frontend lo usa para renderizar tabs/secciones. ``` ### 2.2 Verificacion de Duplicados | Archivo | Patron Buscado | Encontrado | Accion | |---------|----------------|------------|--------| | admin-system.controller.ts | getCategories | NO | Crear endpoint | | admin-system.service.ts | getCategories | NO | Crear metodo | | system_settings tabla | setting_category CHECK | SI | Usar valores existentes | ### 2.3 Implementacion Propuesta **Archivo:** `admin-system.controller.ts` ```typescript @Get('config/categories') @ApiOperation({ summary: 'Get available configuration categories', description: 'Retrieve list of available configuration categories with metadata', }) async getConfigCategories(): Promise { return this.adminSystemService.getConfigCategories(); } ``` **Nuevo DTO:** `config-category.dto.ts` ```typescript export class ConfigCategoryDto { @ApiProperty() key: string; @ApiProperty() name: string; @ApiProperty() description: string; @ApiProperty() icon?: string; @ApiProperty() order: number; } ``` **Metodo en Service:** ```typescript async getConfigCategories(): Promise { // Categorias definidas en CHECK constraint de system_settings // Ver: system_settings_setting_category_check return [ { key: 'general', name: 'General', description: 'Configuracion general de la plataforma', icon: 'settings', order: 1, }, { key: 'security', name: 'Seguridad', description: 'Politicas de autenticacion y acceso', icon: 'security', order: 2, }, { key: 'email', name: 'Correo Electronico', description: 'Configuracion de correo y notificaciones', icon: 'email', order: 3, }, { key: 'gamification', name: 'Gamificacion', description: 'Parametros del sistema de gamificacion', icon: 'stars', order: 4, }, { key: 'storage', name: 'Almacenamiento', description: 'Configuracion de archivos y media', icon: 'storage', order: 5, }, { key: 'analytics', name: 'Analiticas', description: 'Configuracion de metricas y reportes', icon: 'analytics', order: 6, }, { key: 'integrations', name: 'Integraciones', description: 'Servicios externos y APIs', icon: 'integration_instructions', order: 7, }, ]; } ``` --- ## 3. TASK-SETTINGS-LOGS-ENDPOINT (P1) ### 3.1 Analisis ```yaml endpoint: "GET /admin/system/logs" frontend_espera: "/admin/system/logs" estado_actual: "PARCIAL - admin-logs.controller retorna audit_logs (auth_attempts)" duplicados_encontrados: "admin-logs.controller.ts existe pero retorna auth attempts, no system logs" proposito: | Retornar system_logs de audit_logging.system_logs Diferente de audit_logs que son intentos de autenticacion. nota_importante: | - GET /admin/logs -> admin-logs.controller -> getAuditLog() -> auth_attempts - GET /admin/system/logs -> NUEVO -> getSystemLogs() -> system_logs Son endpoints DIFERENTES para datos DIFERENTES. ``` ### 3.2 Verificacion de Duplicados | Archivo | Patron Buscado | Encontrado | Proposito | |---------|----------------|------------|-----------| | admin-logs.controller.ts | getLogs | SI | Retorna auth_attempts (audit) | | admin-system.controller.ts | getSystemLogs | NO | Debe retornar system_logs | | entities/system-log.entity.ts | SystemLog | SI | Entity existe | ### 3.3 Tabla de Base de Datos ```sql -- audit_logging.system_logs ya existe -- Ver: ddl/schemas/audit_logging/tables/01-system_logs.sql CREATE TABLE audit_logging.system_logs ( id UUID PRIMARY KEY, log_level VARCHAR(20), message TEXT, context JSONB, source VARCHAR(100), user_id UUID, tenant_id UUID, ip_address INET, user_agent TEXT, request_path VARCHAR(255), request_method VARCHAR(10), response_status INTEGER, duration_ms INTEGER, stack_trace TEXT, created_at TIMESTAMPTZ ); ``` ### 3.4 Implementacion Propuesta **Archivo:** `admin-system.controller.ts` ```typescript @Get('logs') @ApiOperation({ summary: 'Get system logs', description: 'Retrieve paginated system logs with filtering options', }) async getSystemLogs( @Query() query: SystemLogsQueryDto, ): Promise { return this.adminSystemService.getSystemLogs(query); } ``` **Nuevo DTO:** `system-logs-query.dto.ts` ```typescript export class SystemLogsQueryDto { @IsOptional() @IsString() log_level?: 'debug' | 'info' | 'warn' | 'error' | 'fatal'; @IsOptional() @IsString() source?: string; @IsOptional() @IsUUID() user_id?: string; @IsOptional() @IsDateString() start_date?: string; @IsOptional() @IsDateString() end_date?: string; @IsOptional() @IsString() search?: string; @IsOptional() @Type(() => Number) @IsNumber() @Min(1) page?: number = 1; @IsOptional() @Type(() => Number) @IsNumber() @Min(1) @Max(100) limit?: number = 50; } export class SystemLogDto { id: string; log_level: string; message: string; context?: Record; source: string; user_id?: string; tenant_id?: string; ip_address?: string; user_agent?: string; request_path?: string; request_method?: string; response_status?: number; duration_ms?: number; stack_trace?: string; created_at: string; } export class PaginatedSystemLogsDto { data: SystemLogDto[]; total: number; page: number; limit: number; total_pages: number; } ``` **Metodo en Service:** ```typescript async getSystemLogs(query: SystemLogsQueryDto): Promise { const { log_level, source, user_id, start_date, end_date, search, page = 1, limit = 50 } = query; const skip = (page - 1) * limit; // Query audit_logging.system_logs let sql = ` SELECT * FROM audit_logging.system_logs WHERE 1=1 `; const params: any[] = []; let paramIndex = 1; if (log_level) { sql += ` AND log_level = $${paramIndex++}`; params.push(log_level); } if (source) { sql += ` AND source ILIKE $${paramIndex++}`; params.push(`%${source}%`); } if (user_id) { sql += ` AND user_id = $${paramIndex++}`; params.push(user_id); } if (start_date) { sql += ` AND created_at >= $${paramIndex++}`; params.push(start_date); } if (end_date) { sql += ` AND created_at <= $${paramIndex++}`; params.push(end_date); } if (search) { sql += ` AND (message ILIKE $${paramIndex} OR source ILIKE $${paramIndex})`; params.push(`%${search}%`); paramIndex++; } // Count total const countResult = await this.authConnection.query( `SELECT COUNT(*) as count FROM (${sql}) subq`, params, ); const total = parseInt(countResult[0].count, 10); // Get paginated data sql += ` ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex}`; params.push(limit, skip); const data = await this.authConnection.query(sql, params); return { data: data.map(row => ({ ...row, created_at: row.created_at?.toISOString(), })), total, page, limit, total_pages: Math.ceil(total / limit), }; } ``` --- ## 4. TASK-ADMIN-REPORTS-SCHEDULE (P2) ### 4.1 Analisis ```yaml endpoint: "POST /admin/reports/:id/schedule" frontend_espera: "/admin/reports/:id/schedule" estado_actual: "NO IMPLEMENTADO" duplicados_encontrados: "Ninguno" proposito: | Programar generacion automatica de reportes. Requiere tabla de scheduled_reports o campo en admin_reports. ``` ### 4.2 Opcion de Implementacion ```yaml opcion_a_nueva_tabla: pros: - Separacion de responsabilidades - Historial de ejecuciones contras: - Requiere migracion de BD - Mas complejidad opcion_b_campo_en_reports: pros: - Sin cambios en BD (campos JSONB) - Implementacion rapida contras: - Menos estructurado decision: "Opcion B - Usar campo schedule_config JSONB en admin_reports" ``` ### 4.3 Implementacion Propuesta **Archivo:** `admin-reports.controller.ts` ```typescript @Post(':id/schedule') @ApiOperation({ summary: 'Schedule report generation', description: 'Configure automatic periodic generation of a report', }) async scheduleReport( @Param('id') id: string, @Body() scheduleDto: ScheduleReportDto, @Request() req: AuthRequest, ): Promise { const userId = req.user!.id; return this.adminReportsService.scheduleReport(id, scheduleDto, userId); } ``` **Nuevo DTO:** `schedule-report.dto.ts` ```typescript export class ScheduleReportDto { @IsBoolean() enabled: boolean; @IsString() @IsIn(['daily', 'weekly', 'monthly']) frequency: 'daily' | 'weekly' | 'monthly'; @IsOptional() @IsNumber() @Min(0) @Max(23) hour?: number = 8; // Default 8 AM @IsOptional() @IsNumber() @Min(0) @Max(6) day_of_week?: number; // 0=Sunday, for weekly @IsOptional() @IsNumber() @Min(1) @Max(28) day_of_month?: number; // for monthly @IsOptional() @IsArray() @IsEmail({}, { each: true }) recipients?: string[]; // Emails para enviar reporte } ``` **Nota:** Esta implementacion P2 requiere modificar admin_reports para agregar schedule_config JSONB. Se documenta para sprint futuro. --- ## 5. RESUMEN DE ARCHIVOS A MODIFICAR ### 5.1 Archivos Existentes a Modificar | Archivo | Cambios | |---------|---------| | admin-system.controller.ts | +3 endpoints | | admin-system.service.ts | +3 metodos | | admin-reports.controller.ts | +1 endpoint (P2) | | admin-reports.service.ts | +1 metodo (P2) | | dto/system/index.ts | +4 exports | ### 5.2 Archivos Nuevos a Crear | Archivo | Proposito | |---------|-----------| | dto/system/validate-config.dto.ts | DTOs para validacion | | dto/system/config-category.dto.ts | DTO para categorias | | dto/system/system-logs.dto.ts | DTOs para system logs | | dto/reports/schedule-report.dto.ts | DTO para schedule (P2) | --- ## 6. PLAN DE EJECUCION ### Fase 1: Implementar P1 (Inmediato) ```yaml paso_1: accion: "Crear DTOs para system" archivos: - validate-config.dto.ts - config-category.dto.ts - system-logs.dto.ts validacion: "npm run build" paso_2: accion: "Agregar metodos a admin-system.service.ts" metodos: - validateConfig() - getConfigCategories() - getSystemLogs() validacion: "npm run build" paso_3: accion: "Agregar endpoints a admin-system.controller.ts" endpoints: - POST config/validate - GET config/categories - GET logs validacion: "npm run build && npm run test" ``` ### Fase 2: Implementar P2 (Sprint Futuro) ```yaml paso_1: accion: "Evaluar si admin_reports tiene schedule_config" decision: "Agregar columna o usar metadata existente" paso_2: accion: "Crear DTO schedule-report.dto.ts" paso_3: accion: "Agregar endpoint y service method" ``` --- ## 7. VALIDACION POST-IMPLEMENTACION ```yaml validaciones: - build_backend: "npm run build (0 errors)" - lint: "npm run lint (0 warnings)" - tests: "npm run test (all pass)" - swagger: "Verificar endpoints en /api/docs" - frontend: "Verificar llamadas desde AdminSettingsPage" documentacion: - Actualizar BACKEND_INVENTORY.yml - Crear reporte de ejecucion - Actualizar README de admin module ``` --- **Plan creado:** 2026-01-07 **Agente:** Claude Code (Opus 4.5) **Estado:** LISTO PARA IMPLEMENTACION