Cambios incluidos: - INDICE-DIRECTIVAS-WORKSPACE.yml actualizado - Perfiles de agentes: PERFIL-ML.md, PERFIL-SECURITY.md - Directivas SIMCO actualizadas: - SIMCO-ASIGNACION-PERFILES.md - SIMCO-CCA-SUBAGENTE.md - SIMCO-CONTEXT-ENGINEERING.md - SIMCO-CONTEXT-RESOLUTION.md - SIMCO-DELEGACION-PARALELA.md - Inventarios actualizados: DEVENV-MASTER, DEVENV-PORTS - Documentos de analisis agregados: - Analisis y planes de fix student portal - Analisis scripts BD - Analisis achievements, duplicados, gamification - Auditoria documentacion gamilit - Backlog discrepancias NEXUS - Planes maestros de resolucion - Reportes de ejecucion agregados - Knowledge base gamilit README actualizado - Referencia submodulo gamilit actualizada (commit beb94f7) Validaciones: - Plan validado contra directivas SIMCO-GIT - Dependencias verificadas - Build gamilit: EXITOSO
692 lines
15 KiB
Markdown
692 lines
15 KiB
Markdown
# 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<ConfigValidationResultDto> {
|
|
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<string, unknown>;
|
|
}
|
|
|
|
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<ConfigValidationResultDto> {
|
|
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<ConfigCategoryDto[]> {
|
|
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<ConfigCategoryDto[]> {
|
|
// 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<PaginatedSystemLogsDto> {
|
|
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<string, unknown>;
|
|
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<PaginatedSystemLogsDto> {
|
|
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<ReportDto> {
|
|
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
|