workspace-v1/orchestration/analisis/PLAN-IMPLEMENTACION-PENDIENTES-ADMIN-2026-01-07.md
rckrdmrd e56e927a4d [MAINT-001] docs(orchestration): Actualizacion directivas SIMCO, perfiles y documentacion
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
2026-01-10 04:51:28 -06:00

15 KiB

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

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

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

@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

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:

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

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

@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

export class ConfigCategoryDto {
  @ApiProperty()
  key: string;

  @ApiProperty()
  name: string;

  @ApiProperty()
  description: string;

  @ApiProperty()
  icon?: string;

  @ApiProperty()
  order: number;
}

Metodo en Service:

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

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

-- 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

@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

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:

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

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

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

@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

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)

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)

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

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