- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
66 KiB
Guía de Desarrollo - Portal Admin
Fecha de creación: 2025-11-29 Versión: 1.0.0 Estado: VIGENTE Aplica a: apps/frontend/src/apps/admin/ + apps/backend/src/modules/admin/
1. Visión General
1.1 Propósito
El Portal Admin es la interfaz principal para administradores del sistema GAMILIT. Proporciona herramientas para:
- Gestión de Usuarios: Crear, editar, suspender y eliminar usuarios del sistema
- Gestión de Organizaciones: Administrar tenants, instituciones y sus suscripciones
- Configuración del Sistema: Settings globales, feature flags y mantenimiento
- Moderación de Contenido: Aprobar/rechazar contenido creado por teachers
- Configuración de Gamificación: Parámetros de ML Coins, rangos Maya, achievements
- Monitoreo del Sistema: Salud del sistema, métricas, logs y performance
- Alertas del Sistema: Gestión de alertas críticas y de intervención
- Reportes y Analytics: Dashboards, estadísticas y exportación de datos
- Operaciones Masivas: Bulk operations (suspend, delete, role updates)
- Gestión de Roles y Permisos: Configuración de permisos por rol
- Asignación de Aulas: Asignar teachers a classrooms
1.2 Usuarios Objetivo
| Rol | Acceso | Funcionalidades |
|---|---|---|
| Admin | Completo | Todas las funcionalidades del portal |
| Super Admin | Completo + System | Incluye configuración crítica del sistema |
2. Arquitectura
2.1 Estructura de Carpetas
Frontend (apps/frontend/src/apps/admin/)
admin/
├── index.ts # Barrel export principal
├── layouts/
│ └── AdminLayout.tsx # Layout principal con navegación
├── pages/ # Páginas del portal (14 páginas)
│ ├── AdminDashboardPage.tsx # Dashboard principal
│ ├── AdminUsersPage.tsx # Gestión de usuarios
│ ├── AdminInstitutionsPage.tsx # Gestión de organizaciones
│ ├── AdminRolesPage.tsx # Roles y permisos
│ ├── AdminContentPage.tsx # Moderación de contenido
│ ├── AdminGamificationPage.tsx # Config gamificación
│ ├── AdminSettingsPage.tsx # Configuración del sistema
│ ├── AdminMonitoringPage.tsx # Monitoreo del sistema
│ ├── AdminAlertsPage.tsx # Alertas del sistema
│ ├── AdminAnalyticsPage.tsx # Analytics y métricas
│ ├── AdminReportsPage.tsx # Generación de reportes
│ ├── AdminProgressPage.tsx # Progreso general
│ ├── AdminClassroomTeacherPage.tsx # Asignación de aulas
│ └── AdminAdvancedPage.tsx # Configuración avanzada
├── components/ # Componentes organizados por dominio
│ ├── dashboard/ # Dashboard components
│ │ └── AdminDashboardHero.tsx
│ ├── users/ # Gestión de usuarios
│ ├── alerts/ # Sistema de alertas
│ ├── analytics/ # Gráficas y métricas
│ ├── monitoring/ # Monitoreo del sistema
│ ├── content/ # Moderación de contenido
│ ├── gamification/ # Configuración gamificación
│ ├── reports/ # Generación de reportes
│ ├── progress/ # Vistas de progreso
│ ├── classroom-teacher/ # Asignación de aulas
│ ├── settings/ # Configuración
│ ├── advanced/ # Configuración avanzada
│ └── index.ts
├── hooks/ # Custom hooks (20+ hooks)
│ ├── useAdminDashboard.ts # Dashboard data
│ ├── useAdminData.ts # General admin data
│ ├── useUserManagement.ts # CRUD usuarios
│ ├── useOrganizations.ts # CRUD organizaciones
│ ├── useRoles.ts # Gestión de roles
│ ├── useRolePermissions.ts # Permisos por rol
│ ├── useContentManagement.ts # Moderación contenido
│ ├── useGamificationConfig.ts # Config gamificación
│ ├── useSystemConfig.ts # Config sistema
│ ├── useSettings.ts # Settings generales
│ ├── useMonitoring.ts # Monitoreo
│ ├── useSystemMonitoring.ts # Monitoreo avanzado
│ ├── useSystemMetrics.ts # Métricas del sistema
│ ├── useAlerts.ts # Alertas
│ ├── useAnalytics.ts # Analytics
│ ├── useReports.ts # Reportes
│ ├── useProgress.ts # Progreso
│ ├── useClassroomTeacher.ts # Asignaciones aulas
│ ├── useAuditLogs.ts # Logs de auditoría
│ └── index.ts
└── types/
└── index.ts # 50+ interfaces/types
Backend (apps/backend/src/modules/admin/)
admin/
├── admin.module.ts # Módulo NestJS principal
├── index.ts # Barrel exports
├── controllers/ # 17 controllers
│ ├── admin-dashboard.controller.ts # Dashboard general
│ ├── admin-users.controller.ts # CRUD usuarios
│ ├── admin-organizations.controller.ts # CRUD organizaciones
│ ├── admin-roles.controller.ts # Gestión de roles
│ ├── admin-content.controller.ts # Moderación contenido
│ ├── admin-gamification-config.controller.ts # Config gamificación
│ ├── admin-system.controller.ts # Config sistema
│ ├── admin-monitoring.controller.ts # Monitoreo
│ ├── admin-alerts.controller.ts # Alertas sistema
│ ├── admin-interventions.controller.ts # Alertas intervención
│ ├── admin-analytics.controller.ts # Analytics
│ ├── admin-reports.controller.ts # Reportes
│ ├── admin-progress.controller.ts # Progreso general
│ ├── admin-logs.controller.ts # Audit logs
│ ├── admin-bulk-operations.controller.ts # Operaciones masivas
│ ├── classroom-assignments.controller.ts # Asignación aulas
│ └── classroom-teachers-rest.controller.ts
├── services/ # 15 services
│ ├── admin-dashboard.service.ts
│ ├── admin-users.service.ts
│ ├── admin-organizations.service.ts
│ ├── admin-roles.service.ts
│ ├── admin-content.service.ts
│ ├── gamification-config.service.ts
│ ├── admin-system.service.ts
│ ├── admin-monitoring.service.ts
│ ├── admin-alerts.service.ts
│ ├── admin-interventions.service.ts
│ ├── admin-analytics.service.ts
│ ├── admin-reports.service.ts
│ ├── admin-progress.service.ts
│ ├── bulk-operations.service.ts
│ └── classroom-assignments.service.ts
├── dto/ # Data Transfer Objects (15 categorías)
│ ├── dashboard/ # Dashboard DTOs
│ ├── users/ # User management DTOs
│ ├── organizations/ # Organization DTOs
│ ├── roles/ # Role management DTOs
│ ├── content/ # Content moderation DTOs
│ ├── gamification-config/ # Gamification config DTOs
│ ├── system/ # System config DTOs
│ ├── monitoring/ # Monitoring DTOs
│ ├── alerts/ # Alerts DTOs
│ ├── interventions/ # Intervention alerts DTOs
│ ├── analytics/ # Analytics DTOs
│ ├── reports/ # Reports DTOs
│ ├── progress/ # Progress tracking DTOs
│ ├── bulk-operations/ # Bulk operations DTOs
│ └── classroom-assignments/ # Classroom assignment DTOs
├── entities/ # Entidades TypeORM
│ ├── system-setting.entity.ts
│ ├── feature-flag.entity.ts
│ ├── notification-settings.entity.ts
│ ├── bulk-operation.entity.ts
│ ├── system-alert.entity.ts
│ └── index.ts
├── guards/ # Guards de autorización
│ └── admin.guard.ts
└── __tests__/ # Tests unitarios
2.2 Diagrama de Dependencias
┌─────────────────────────────────────────────────────────────────┐
│ FRONTEND │
├─────────────────────────────────────────────────────────────────┤
│ Pages ──────► Components ──────► Hooks ──────► API Services │
│ │ │ │
│ └───────────────────┼─────► Types │
└────────────────────────────────────────┼─────────────────────────┘
│
HTTP/REST
│
┌────────────────────────────────────────▼─────────────────────────┐
│ BACKEND │
├──────────────────────────────────────────────────────────────────┤
│ Controllers ──────► Services ──────► Repositories ──────► DB │
│ │ │ │
│ └──────────────────┼─────► Guards (AdminGuard) │
│ │ │
│ └─────► External Modules │
│ (Auth, Social, Educational, │
│ Gamification, Progress, etc.) │
└──────────────────────────────────────────────────────────────────┘
2.3 Flujo de Datos
┌──────────────┐
│ AdminPage │ Renderiza la UI
└──────┬───────┘
│ Usa
▼
┌──────────────┐
│ useAdminXXX │ Hook personalizado que maneja lógica
└──────┬───────┘
│ Llama
▼
┌──────────────┐
│ adminAPI.ts │ Servicio API (axios)
└──────┬───────┘
│ HTTP
▼
┌──────────────────┐
│ AdminController │ Backend NestJS
└──────┬───────────┘
│ Usa
▼
┌──────────────────┐
│ AdminService │ Lógica de negocio
└──────┬───────────┘
│ Query
▼
┌──────────────────┐
│ TypeORM Repo │ Acceso a base de datos
└──────┬───────────┘
│
▼
┌──────────────────┐
│ PostgreSQL DB │ Múltiples schemas (auth, social, educational, etc.)
└──────────────────┘
3. Módulos Principales
3.1 Dashboard Administrativo
Propósito: Vista general del estado del sistema y métricas clave.
Componentes:
AdminDashboardPage.tsx- Página principalAdminDashboardHero.tsx- Hero con métricas principalesuseAdminDashboard.ts- Hook con auto-refresh
Funcionalidades:
- Estadísticas del sistema (usuarios, organizaciones, contenido)
- Salud del sistema (CPU, memoria, uptime)
- Alertas recientes
- Actividad de usuarios
- Acciones recientes de admins
Endpoints principales:
GET /admin/dashboard // Dashboard completo
GET /admin/dashboard/stats // Estadísticas
GET /admin/dashboard/recent-activity
GET /admin/dashboard/user-stats
GET /admin/dashboard/organization-stats
GET /admin/dashboard/actions/recent
GET /admin/dashboard/alerts
GET /admin/dashboard/analytics/user-activity
Ejemplo de uso:
// useAdminDashboard.ts
export function useAdminDashboard() {
const {
systemHealth,
metrics,
recentActions,
alerts,
userActivity,
loading,
error,
refreshAll,
} = useAdminDashboard();
return {
systemHealth, // CPU, memory, uptime
metrics, // Total users, orgs, sessions
recentActions, // Últimas acciones de admins
alerts, // Alertas del sistema
userActivity, // Actividad de usuarios
loading,
error,
refreshAll,
};
}
3.2 Gestión de Usuarios
Propósito: CRUD completo de usuarios del sistema.
Componentes:
AdminUsersPage.tsx- Lista y gestión de usuarios- Componentes: filtros, tabla, modales de edición
Funcionalidades:
- Listar usuarios con filtros (rol, status, búsqueda)
- Crear nuevos usuarios
- Editar información de usuario
- Suspender/reactivar usuarios
- Eliminar usuarios (soft delete)
- Resetear contraseñas
- Ver detalles y estadísticas por usuario
- Operaciones masivas (bulk suspend, bulk delete, bulk role update)
Endpoints principales:
GET /admin/users // Lista paginada
GET /admin/users/stats // Estadísticas
GET /admin/users/:id // Detalles
PUT /admin/users/:id // Actualizar
DELETE /admin/users/:id // Eliminar
POST /admin/users/:id/suspend // Suspender
POST /admin/users/:id/activate // Activar
POST /admin/users/:id/reset-password // Reset password
POST /admin/users/bulk/suspend // Bulk suspend
POST /admin/users/bulk/delete // Bulk delete
POST /admin/users/bulk/role // Bulk role update
GET /admin/users/bulk/:operationId // Status operación
DTOs clave:
// ListUsersDto
interface ListUsersDto {
page?: number;
limit?: number;
role?: string;
status?: 'active' | 'suspended' | 'inactive';
search?: string;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}
// UpdateUserDto
interface UpdateUserDto {
username?: string;
email?: string;
role?: string;
status?: string;
profile?: {
first_name?: string;
last_name?: string;
};
}
// SuspendUserDto
interface SuspendUserDto {
reason: string;
duration_days?: number;
}
3.3 Gestión de Organizaciones
Propósito: Administrar tenants e instituciones educativas.
Componentes:
AdminInstitutionsPage.tsx- Gestión de organizaciones
Funcionalidades:
- Listar organizaciones/tenants
- Crear nueva organización
- Editar información de organización
- Configurar suscripción (plan, features)
- Ver usuarios por organización
- Activar/desactivar features
- Ver estadísticas por organización
Endpoints principales:
GET /admin/organizations // Lista
POST /admin/organizations // Crear
GET /admin/organizations/:id // Detalles
PUT /admin/organizations/:id // Actualizar
DELETE /admin/organizations/:id // Eliminar
GET /admin/organizations/:id/users // Usuarios
GET /admin/organizations/:id/stats // Estadísticas
PATCH /admin/organizations/:id/subscription // Actualizar suscripción
PATCH /admin/organizations/:id/features // Actualizar features
3.4 Configuración de Gamificación
Propósito: Configurar parámetros del sistema de gamificación.
Componentes:
AdminGamificationPage.tsx- Configuración completa
Funcionalidades:
- Configurar parámetros de ML Coins (rewards, costs)
- Configurar rangos Maya (thresholds, nombres, colores)
- Configurar achievements (criterios, recompensas)
- Preview de impacto de cambios
- Historial de cambios de configuración
Endpoints principales:
GET /admin/gamification/settings // Settings actuales
PATCH /admin/gamification/settings // Actualizar settings
GET /admin/gamification/parameters // Lista parámetros
PATCH /admin/gamification/parameters/:id // Actualizar parámetro
GET /admin/gamification/maya-ranks // Rangos Maya
PATCH /admin/gamification/maya-ranks/:id // Actualizar rango
POST /admin/gamification/preview-impact // Preview de cambios
Parámetros configurables:
// Rewards
- exercise_completion_xp: number
- exercise_completion_coins: number
- daily_login_coins: number
- assignment_completion_bonus_xp: number
// Costs
- comodin_cost: number
- hint_cost: number
- retry_cost: number
// Maya Ranks
- ajaw_threshold: number
- kinich_threshold: number
- kukulkan_threshold: number
3.5 Moderación de Contenido
Propósito: Aprobar/rechazar contenido creado por teachers.
Componentes:
AdminContentPage.tsx- Cola de moderación
Funcionalidades:
- Ver cola de contenido pendiente
- Aprobar contenido
- Rechazar contenido (con razón)
- Ver historial de aprobaciones
- Filtrar por tipo (ejercicios, módulos, media)
- Ver versiones de contenido
Endpoints principales:
GET /admin/content // Lista contenido
GET /admin/content/pending // Pendiente moderación
GET /admin/content/:id // Detalles
POST /admin/content/:id/approve // Aprobar
POST /admin/content/:id/reject // Rechazar
GET /admin/content/:id/versions // Versiones
POST /admin/content/:id/versions // Crear versión
GET /admin/content/:id/approval-history // Historial
GET /admin/media // Lista media files
3.6 Alertas del Sistema
Propósito: Gestionar alertas críticas y de intervención.
Componentes:
AdminAlertsPage.tsx- Gestión de alertas
Funcionalidades:
- Ver alertas del sistema (critical, high, medium, low)
- Ver alertas de intervención estudiantil
- Crear alertas manuales
- Resolver/cerrar alertas
- Acknowledge alertas
- Filtrar por tipo, severidad, estado
- Ver estadísticas de alertas
Endpoints principales:
// System Alerts
GET /admin/alerts // Lista
POST /admin/alerts // Crear
GET /admin/alerts/stats // Estadísticas
PATCH /admin/alerts/:id/acknowledge // Acknowledge
PATCH /admin/alerts/:id/resolve // Resolver
// Intervention Alerts (estudiantes en riesgo)
GET /admin/interventions // Lista
GET /admin/interventions/:id // Detalles
PATCH /admin/interventions/:id/acknowledge
PATCH /admin/interventions/:id/resolve
Tipos de alertas:
// System Alerts
- high_error_rate
- database_connection
- high_memory_usage
- high_cpu_usage
- disk_space_low
// Intervention Alerts
- declining_trend // Estudiante con tendencia decreciente
- low_engagement // Baja participación
- failing_exercises // Fallos repetidos
- no_activity // Sin actividad reciente
3.7 Monitoreo del Sistema
Propósito: Monitorear salud y performance del sistema.
Componentes:
AdminMonitoringPage.tsx- Dashboard de monitoreo
Funcionalidades:
- Salud del sistema (health check)
- Métricas en tiempo real (CPU, memoria, uptime)
- Logs de errores recientes
- Tendencias de errores
- Historial de métricas
- Performance de endpoints
Endpoints principales:
GET /admin/monitoring/health // Health check
GET /admin/monitoring/metrics // Métricas actuales
GET /admin/monitoring/metrics/history // Historial
GET /admin/monitoring/errors/recent // Errores recientes
GET /admin/monitoring/errors/stats // Estadísticas errores
GET /admin/monitoring/errors/trends // Tendencias
3.8 Analytics y Reportes
Propósito: Dashboards analytics y generación de reportes.
Componentes:
AdminAnalyticsPage.tsx- Analytics avanzadosAdminReportsPage.tsx- Generación de reportes
Funcionalidades Analytics:
- Overview general
- Engagement analytics
- Retention analytics
- Gamification analytics
- Activity timeline
- Top users
- Exportar datos
Funcionalidades Reportes:
- Generar reportes en PDF/Excel
- Reportes predefinidos (usuarios, organizaciones, contenido)
- Reportes personalizados
- Programar reportes recurrentes
Endpoints Analytics:
GET /admin/analytics/overview
GET /admin/analytics/engagement
GET /admin/analytics/retention
GET /admin/analytics/gamification
GET /admin/analytics/activity-timeline
GET /admin/analytics/top-users
POST /admin/analytics/export
Endpoints Reportes:
GET /admin/reports // Lista reportes
POST /admin/reports/generate // Generar reporte
GET /admin/reports/:id // Descargar reporte
GET /admin/reports/templates // Templates disponibles
3.9 Configuración del Sistema
Propósito: Configuración global del sistema.
Componentes:
AdminSettingsPage.tsx- Settings generalesAdminAdvancedPage.tsx- Configuración avanzada
Funcionalidades:
- Configurar system settings (globales)
- Feature flags (habilitar/deshabilitar features)
- Notification settings
- Modo mantenimiento
- Limpiar cache
- Ejecutar tareas de mantenimiento
- Ver audit logs
Endpoints principales:
GET /admin/system/config // Config actual
PATCH /admin/system/config // Actualizar config
GET /admin/system/health // Salud sistema
POST /admin/system/maintenance/toggle // Toggle mantenimiento
POST /admin/system/maintenance/clear-cache
POST /admin/system/maintenance/rebuild-indexes
GET /admin/logs // Audit logs
3.10 Gestión de Roles y Permisos
Propósito: Configurar permisos por rol.
Componentes:
AdminRolesPage.tsx- Gestión de roles
Funcionalidades:
- Listar roles
- Ver permisos por rol
- Actualizar permisos de un rol
- Crear roles personalizados (futuro)
Endpoints principales:
GET /admin/roles // Lista roles
GET /admin/roles/:id // Detalles rol
GET /admin/roles/:id/permissions // Permisos del rol
PATCH /admin/roles/:id/permissions // Actualizar permisos
3.11 Asignación de Aulas
Propósito: Asignar teachers a classrooms.
Componentes:
AdminClassroomTeacherPage.tsx- Gestión de asignaciones
Funcionalidades:
- Listar todas las asignaciones
- Asignar teacher a classroom
- Reasignar classroom a otro teacher
- Remover asignación
- Bulk assign (múltiples classrooms)
- Ver teachers disponibles
- Ver classrooms sin asignar
Endpoints principales:
GET /admin/classroom-assignments // Lista todas
GET /admin/classroom-assignments/teachers/:teacherId
GET /admin/classroom-assignments/classrooms/:classroomId
POST /admin/classroom-assignments/assign // Asignar
POST /admin/classroom-assignments/bulk-assign
PATCH /admin/classroom-assignments/:id/reassign
DELETE /admin/classroom-assignments/:id // Remover
// REST endpoints alternativos
GET /admin/classrooms/:classroomId/teachers
POST /admin/classrooms/:classroomId/teachers
DELETE /admin/classrooms/:classroomId/teachers/:teacherId
3.12 Progreso General
Propósito: Vista global del progreso de todos los usuarios.
Componentes:
AdminProgressPage.tsx- Dashboard de progreso
Funcionalidades:
- Overview de progreso global
- Progreso por estudiante
- Progreso por classroom
- Progreso por módulo
- Logros más comunes
- Submissions recientes
- Exportar datos de progreso
Endpoints principales:
GET /admin/progress/overview
GET /admin/progress/students
GET /admin/progress/classrooms
GET /admin/progress/modules
GET /admin/progress/export
4. Patrones de Diseño
4.1 Frontend Patterns
4.1.1 Page + Hook Pattern
Cada página tiene un hook correspondiente que encapsula la lógica:
// Pattern: Page con Hook dedicado
// AdminUsersPage.tsx
export default function AdminUsersPage() {
const {
users,
stats,
loading,
error,
filters,
setFilters,
createUser,
updateUser,
deleteUser,
suspendUser,
} = useUserManagement();
if (loading) return <LoadingSpinner />;
if (error) return <ErrorDisplay error={error} />;
return (
<AdminLayout>
<UserFilters filters={filters} onChange={setFilters} />
<UserStats stats={stats} />
<UserTable
users={users}
onEdit={updateUser}
onDelete={deleteUser}
onSuspend={suspendUser}
/>
</AdminLayout>
);
}
// useUserManagement.ts
export function useUserManagement() {
const [filters, setFilters] = useState<UserFilters>({});
const { data: users, isLoading } = useQuery({
queryKey: ['admin', 'users', filters],
queryFn: () => adminAPI.listUsers(filters),
});
const { data: stats } = useQuery({
queryKey: ['admin', 'users', 'stats'],
queryFn: () => adminAPI.getUserStats(),
});
const { mutate: createUser } = useMutation({
mutationFn: adminAPI.createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
},
});
// ... más mutations
return {
users,
stats,
loading: isLoading,
filters,
setFilters,
createUser,
updateUser,
deleteUser,
suspendUser,
};
}
4.1.2 Auto-Refresh Pattern
Para datos que requieren actualización frecuente:
// Pattern: Auto-refresh con control manual
export function useAdminDashboard(customIntervals?: RefreshIntervals) {
const [isPaused, setIsPaused] = useState(false);
// Fetch functions
const fetchHealth = useCallback(async () => {
const data = await adminAPI.getSystemHealth();
setHealth(data);
}, []);
// Auto-refresh intervals
useEffect(() => {
if (isPaused) return;
const healthInterval = setInterval(fetchHealth, 10000);
const metricsInterval = setInterval(fetchMetrics, 30000);
return () => {
clearInterval(healthInterval);
clearInterval(metricsInterval);
};
}, [isPaused, fetchHealth, fetchMetrics]);
return {
health,
metrics,
pauseRefresh: () => setIsPaused(true),
resumeRefresh: () => setIsPaused(false),
isPaused,
};
}
4.1.3 Bulk Operations Pattern
Para operaciones masivas con feedback de progreso:
// Pattern: Bulk operations con progress tracking
export function useBulkOperations() {
const [operation, setOperation] = useState<BulkOperation | null>(null);
const { mutate: bulkSuspend } = useMutation({
mutationFn: async (userIds: string[]) => {
const result = await adminAPI.bulkSuspendUsers({
user_ids: userIds,
reason: 'Admin bulk suspend',
});
// Poll operation status
return pollOperationStatus(result.operation_id);
},
onSuccess: (operation) => {
setOperation(operation);
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
},
});
// Poll para status updates
const pollOperationStatus = async (operationId: string) => {
let status = await adminAPI.getBulkOperationStatus(operationId);
while (status.status === 'in_progress') {
await new Promise(resolve => setTimeout(resolve, 2000));
status = await adminAPI.getBulkOperationStatus(operationId);
}
return status;
};
return {
bulkSuspend,
bulkDelete,
bulkUpdateRole,
operation,
};
}
4.1.4 Modal Pattern
Modales para operaciones complejas:
// Pattern: Modal con confirmación
interface ConfirmDeleteModalProps {
isOpen: boolean;
onClose: () => void;
user: User;
onConfirm: (userId: string) => void;
}
export const ConfirmDeleteModal: React.FC<ConfirmDeleteModalProps> = ({
isOpen,
onClose,
user,
onConfirm,
}) => {
const [confirmText, setConfirmText] = useState('');
const canDelete = confirmText === user.username;
return (
<Modal isOpen={isOpen} onClose={onClose}>
<div className="p-6">
<h2>Confirmar eliminación</h2>
<p>Para eliminar al usuario {user.username}, escribe su nombre:</p>
<input
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
placeholder="Nombre de usuario"
/>
<div className="flex gap-2">
<Button variant="secondary" onClick={onClose}>
Cancelar
</Button>
<Button
variant="danger"
onClick={() => onConfirm(user.id)}
disabled={!canDelete}
>
Eliminar
</Button>
</div>
</div>
</Modal>
);
};
4.2 Backend Patterns
4.2.1 Guard-Based Authorization
// Pattern: AdminGuard para proteger rutas
@Controller('admin')
@UseGuards(JwtAuthGuard, AdminGuard) // Requiere autenticación + rol admin
@ApiTags('Admin')
export class AdminUsersController {
@Get('users')
async listUsers(@Query() query: ListUsersDto): Promise<PaginatedUsersDto> {
return this.adminUsersService.listUsers(query);
}
}
4.2.2 AdminGuard Implementation
// guards/admin.guard.ts
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
throw new ForbiddenException('User not authenticated');
}
// Verificar rol admin o super_admin
const isAdmin = user.role === 'admin' || user.role === 'super_admin';
if (!isAdmin) {
throw new ForbiddenException('Access denied. Admin privileges required.');
}
return true;
}
}
4.2.3 Service Layer with Pagination
// Pattern: Service con paginación y filtros
@Injectable()
export class AdminUsersService {
constructor(
@InjectRepository(User, 'auth')
private userRepo: Repository<User>,
) {}
async listUsers(query: ListUsersDto): Promise<PaginatedUsersDto> {
const { page = 1, limit = 20, role, status, search, sortBy = 'created_at', sortOrder = 'DESC' } = query;
const qb = this.userRepo.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.leftJoinAndSelect('user.roles', 'userRoles')
.leftJoinAndSelect('userRoles.role', 'role');
// Aplicar filtros
if (role) {
qb.andWhere('role.name = :role', { role });
}
if (status) {
qb.andWhere('user.status = :status', { status });
}
if (search) {
qb.andWhere(
'(user.username ILIKE :search OR user.email ILIKE :search OR profile.first_name ILIKE :search OR profile.last_name ILIKE :search)',
{ search: `%${search}%` }
);
}
// Sorting
qb.orderBy(`user.${sortBy}`, sortOrder);
// Paginación
const skip = (page - 1) * limit;
qb.skip(skip).take(limit);
const [users, total] = await qb.getManyAndCount();
return {
data: users,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
}
4.2.4 Bulk Operations Pattern
// Pattern: Operaciones masivas con tracking
@Injectable()
export class BulkOperationsService {
constructor(
@InjectRepository(BulkOperation, 'auth')
private bulkOpRepo: Repository<BulkOperation>,
@InjectRepository(User, 'auth')
private userRepo: Repository<User>,
) {}
async bulkSuspendUsers(dto: BulkSuspendUsersDto): Promise<BulkOperationStatusDto> {
// Crear registro de operación
const operation = this.bulkOpRepo.create({
operation_type: 'suspend_users',
target_count: dto.user_ids.length,
status: 'in_progress',
initiated_by: dto.admin_id,
});
await this.bulkOpRepo.save(operation);
// Ejecutar operación en background
this.executeBulkSuspend(operation.id, dto).catch(err => {
this.logger.error(`Bulk suspend failed: ${err.message}`);
});
return {
operation_id: operation.id,
status: 'in_progress',
total: dto.user_ids.length,
completed: 0,
failed: 0,
};
}
private async executeBulkSuspend(operationId: string, dto: BulkSuspendUsersDto): Promise<void> {
const operation = await this.bulkOpRepo.findOne({ where: { id: operationId } });
let completed = 0;
let failed = 0;
for (const userId of dto.user_ids) {
try {
await this.userRepo.update(userId, { status: 'suspended' });
completed++;
} catch (error) {
failed++;
}
// Actualizar progreso
operation.completed_count = completed;
operation.failed_count = failed;
await this.bulkOpRepo.save(operation);
}
// Marcar como completado
operation.status = 'completed';
operation.completed_at = new Date();
await this.bulkOpRepo.save(operation);
}
}
4.2.5 Audit Logging Pattern
// Pattern: Audit logging automático
@Injectable()
export class AdminUsersService {
constructor(
@InjectRepository(User, 'auth')
private userRepo: Repository<User>,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
private auditLogService: AuditLogService,
) {}
async updateUser(userId: string, dto: UpdateUserDto, adminId: string): Promise<User> {
const user = await this.userRepo.findOne({ where: { id: userId } });
if (!user) {
throw new NotFoundException('User not found');
}
// Guardar estado anterior para audit
const previousState = { ...user };
// Actualizar
Object.assign(user, dto);
await this.userRepo.save(user);
// Log de auditoría
await this.auditLogService.log({
event_type: 'user_updated',
actor_id: adminId,
target_id: userId,
target_type: 'user',
changes: {
before: previousState,
after: user,
},
metadata: {
updated_fields: Object.keys(dto),
},
});
return user;
}
}
5. Rutas y Navegación
5.1 Rutas Frontend
// Estructura de rutas del portal admin
const adminRoutes = [
{
path: '/admin',
element: <AdminLayout />,
children: [
{ path: '', element: <AdminDashboardPage /> },
{ path: 'dashboard', element: <AdminDashboardPage /> },
// Gestión
{ path: 'users', element: <AdminUsersPage /> },
{ path: 'institutions', element: <AdminInstitutionsPage /> },
{ path: 'roles', element: <AdminRolesPage /> },
// Contenido
{ path: 'content', element: <AdminContentPage /> },
{ path: 'classroom-teacher', element: <AdminClassroomTeacherPage /> },
// Configuración
{ path: 'gamification', element: <AdminGamificationPage /> },
{ path: 'settings', element: <AdminSettingsPage /> },
{ path: 'advanced', element: <AdminAdvancedPage /> },
// Monitoreo
{ path: 'monitoring', element: <AdminMonitoringPage /> },
{ path: 'alerts', element: <AdminAlertsPage /> },
// Analytics
{ path: 'analytics', element: <AdminAnalyticsPage /> },
{ path: 'reports', element: <AdminReportsPage /> },
{ path: 'progress', element: <AdminProgressPage /> },
],
},
];
5.2 Navegación Lateral
// Sidebar navigation items
const navigationItems = [
{
label: 'Dashboard',
icon: <LayoutDashboard />,
path: '/admin/dashboard',
},
{
label: 'Gestión',
icon: <Users />,
children: [
{ label: 'Usuarios', path: '/admin/users' },
{ label: 'Instituciones', path: '/admin/institutions' },
{ label: 'Roles', path: '/admin/roles' },
],
},
{
label: 'Contenido',
icon: <BookOpen />,
children: [
{ label: 'Moderación', path: '/admin/content' },
{ label: 'Asignación Aulas', path: '/admin/classroom-teacher' },
],
},
{
label: 'Configuración',
icon: <Settings />,
children: [
{ label: 'Gamificación', path: '/admin/gamification' },
{ label: 'Sistema', path: '/admin/settings' },
{ label: 'Avanzado', path: '/admin/advanced' },
],
},
{
label: 'Monitoreo',
icon: <Activity />,
children: [
{ label: 'Sistema', path: '/admin/monitoring' },
{ label: 'Alertas', path: '/admin/alerts' },
],
},
{
label: 'Analytics',
icon: <BarChart />,
children: [
{ label: 'Estadísticas', path: '/admin/analytics' },
{ label: 'Reportes', path: '/admin/reports' },
{ label: 'Progreso', path: '/admin/progress' },
],
},
];
6. APIs del Portal Admin
6.1 Tabla Resumen de Endpoints
| Categoría | Método | Endpoint | Descripción | Guard |
|---|---|---|---|---|
| Dashboard | GET | /admin/dashboard |
Dashboard completo | AdminGuard |
| GET | /admin/dashboard/stats |
Estadísticas | AdminGuard | |
| GET | /admin/dashboard/recent-activity |
Actividad reciente | AdminGuard | |
| GET | /admin/dashboard/actions/recent |
Acciones de admins | AdminGuard | |
| GET | /admin/dashboard/alerts |
Alertas | AdminGuard | |
| Users | GET | /admin/users |
Lista de usuarios | AdminGuard |
| GET | /admin/users/stats |
Estadísticas usuarios | AdminGuard | |
| GET | /admin/users/:id |
Detalles usuario | AdminGuard | |
| PUT | /admin/users/:id |
Actualizar usuario | AdminGuard | |
| DELETE | /admin/users/:id |
Eliminar usuario | AdminGuard | |
| POST | /admin/users/:id/suspend |
Suspender usuario | AdminGuard | |
| POST | /admin/users/:id/activate |
Activar usuario | AdminGuard | |
| POST | /admin/users/bulk/suspend |
Suspender múltiples | AdminGuard | |
| POST | /admin/users/bulk/delete |
Eliminar múltiples | AdminGuard | |
| Organizations | GET | /admin/organizations |
Lista organizaciones | AdminGuard |
| POST | /admin/organizations |
Crear organización | AdminGuard | |
| GET | /admin/organizations/:id |
Detalles organización | AdminGuard | |
| PUT | /admin/organizations/:id |
Actualizar organización | AdminGuard | |
| PATCH | /admin/organizations/:id/subscription |
Actualizar suscripción | AdminGuard | |
| Roles | GET | /admin/roles |
Lista de roles | AdminGuard |
| GET | /admin/roles/:id/permissions |
Permisos del rol | AdminGuard | |
| PATCH | /admin/roles/:id/permissions |
Actualizar permisos | AdminGuard | |
| Content | GET | /admin/content |
Lista contenido | AdminGuard |
| GET | /admin/content/pending |
Pendiente moderación | AdminGuard | |
| POST | /admin/content/:id/approve |
Aprobar contenido | AdminGuard | |
| POST | /admin/content/:id/reject |
Rechazar contenido | AdminGuard | |
| Gamification | GET | /admin/gamification/settings |
Config gamificación | AdminGuard |
| PATCH | /admin/gamification/settings |
Actualizar config | AdminGuard | |
| GET | /admin/gamification/parameters |
Parámetros | AdminGuard | |
| PATCH | /admin/gamification/parameters/:id |
Actualizar parámetro | AdminGuard | |
| System | GET | /admin/system/config |
Config sistema | AdminGuard |
| PATCH | /admin/system/config |
Actualizar config | AdminGuard | |
| GET | /admin/system/health |
Salud del sistema | AdminGuard | |
| POST | /admin/system/maintenance/toggle |
Toggle mantenimiento | AdminGuard | |
| Monitoring | GET | /admin/monitoring/health |
Health check | AdminGuard |
| GET | /admin/monitoring/metrics |
Métricas actuales | AdminGuard | |
| GET | /admin/monitoring/errors/recent |
Errores recientes | AdminGuard | |
| Alerts | GET | /admin/alerts |
Lista alertas | AdminGuard |
| POST | /admin/alerts |
Crear alerta | AdminGuard | |
| PATCH | /admin/alerts/:id/acknowledge |
Acknowledge | AdminGuard | |
| PATCH | /admin/alerts/:id/resolve |
Resolver | AdminGuard | |
| Analytics | GET | /admin/analytics/overview |
Overview | AdminGuard |
| GET | /admin/analytics/engagement |
Engagement | AdminGuard | |
| GET | /admin/analytics/retention |
Retention | AdminGuard | |
| POST | /admin/analytics/export |
Exportar datos | AdminGuard | |
| Reports | GET | /admin/reports |
Lista reportes | AdminGuard |
| POST | /admin/reports/generate |
Generar reporte | AdminGuard | |
| Progress | GET | /admin/progress/overview |
Overview progreso | AdminGuard |
| GET | /admin/progress/students |
Progreso estudiantes | AdminGuard | |
| GET | /admin/progress/export |
Exportar progreso | AdminGuard | |
| Classroom | GET | /admin/classroom-assignments |
Lista asignaciones | AdminGuard |
| POST | /admin/classroom-assignments/assign |
Asignar | AdminGuard | |
| POST | /admin/classroom-assignments/bulk-assign |
Bulk assign | AdminGuard | |
| Logs | GET | /admin/logs |
Audit logs | AdminGuard |
6.2 Frontend API Services
services/api/admin/
├── adminAPI.ts # Main admin API (dashboard, general)
├── adminUsersAPI.ts # User management
├── adminOrganizationsAPI.ts # Organizations management
├── adminRolesAPI.ts # Roles and permissions
├── adminContentAPI.ts # Content moderation
├── adminGamificationAPI.ts # Gamification config
├── adminSystemAPI.ts # System configuration
├── adminMonitoringAPI.ts # System monitoring
├── adminAlertsAPI.ts # Alerts management
├── adminAnalyticsAPI.ts # Analytics
├── adminReportsAPI.ts # Reports generation
├── adminProgressAPI.ts # Progress tracking
├── adminClassroomAPI.ts # Classroom assignments
├── adminLogsAPI.ts # Audit logs
└── index.ts # Barrel export
7. Estado y Stores
7.1 Zustand Stores (Opcional)
El portal admin usa principalmente React Query para state management, pero puede usar Zustand para estado global:
// stores/adminStore.ts
interface AdminState {
// Global admin state
selectedOrganization: Organization | null;
maintenanceMode: boolean;
// Actions
setSelectedOrganization: (org: Organization | null) => void;
setMaintenanceMode: (mode: boolean) => void;
}
export const useAdminStore = create<AdminState>((set) => ({
selectedOrganization: null,
maintenanceMode: false,
setSelectedOrganization: (org) => set({ selectedOrganization: org }),
setMaintenanceMode: (mode) => set({ maintenanceMode: mode }),
}));
7.2 React Query Cache
// Configuración de React Query para admin
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutos
cacheTime: 10 * 60 * 1000, // 10 minutos
refetchOnWindowFocus: true,
retry: 1,
},
},
});
// Query keys structure
const adminQueryKeys = {
all: ['admin'] as const,
dashboard: () => [...adminQueryKeys.all, 'dashboard'] as const,
dashboardStats: () => [...adminQueryKeys.dashboard(), 'stats'] as const,
users: () => [...adminQueryKeys.all, 'users'] as const,
usersList: (filters: UserFilters) => [...adminQueryKeys.users(), 'list', filters] as const,
usersStats: () => [...adminQueryKeys.users(), 'stats'] as const,
organizations: () => [...adminQueryKeys.all, 'organizations'] as const,
organizationsList: () => [...adminQueryKeys.organizations(), 'list'] as const,
monitoring: () => [...adminQueryKeys.all, 'monitoring'] as const,
monitoringHealth: () => [...adminQueryKeys.monitoring(), 'health'] as const,
monitoringMetrics: () => [...adminQueryKeys.monitoring(), 'metrics'] as const,
};
8. Seguridad
8.1 Autorización
- JwtAuthGuard: Verifica token JWT válido
- AdminGuard: Verifica rol admin/super_admin
8.2 Reglas de Acceso
Admin puede:
- Ver todos los usuarios del sistema
- Crear/editar/eliminar usuarios
- Suspender/reactivar usuarios
- Gestionar organizaciones
- Moderar contenido
- Configurar gamificación
- Ver audit logs completos
- Ejecutar bulk operations
- Configurar sistema
- Ver todas las métricas
Admin NO puede:
- Modificar datos de super_admin (solo super_admin puede)
- Eliminar su propia cuenta
- Desactivar modo mantenimiento si no lo activó
8.3 Audit Logging
Todas las acciones de admin se registran:
// Eventos auditados
const AUDIT_EVENTS = {
USER_CREATED: 'user_created',
USER_UPDATED: 'user_updated',
USER_DELETED: 'user_deleted',
USER_SUSPENDED: 'user_suspended',
ORG_CREATED: 'organization_created',
ORG_UPDATED: 'organization_updated',
CONTENT_APPROVED: 'content_approved',
CONTENT_REJECTED: 'content_rejected',
CONFIG_UPDATED: 'config_updated',
MAINTENANCE_TOGGLED: 'maintenance_toggled',
BULK_OPERATION: 'bulk_operation',
};
8.4 Rate Limiting
Endpoints de admin tienen rate limiting más permisivo:
// Rate limits por rol
const RATE_LIMITS = {
admin: {
windowMs: 15 * 60 * 1000, // 15 minutos
maxRequests: 1000, // 1000 requests
},
default: {
windowMs: 15 * 60 * 1000,
maxRequests: 100,
},
};
9. Flujos Principales
9.1 Flujo: Crear Usuario
1. Admin navega a /admin/users
2. Click en "Nuevo Usuario"
3. Modal de creación se abre
4. Admin completa form:
- Username
- Email
- Password
- Role
- Profile info
5. Submit → POST /admin/users
6. Backend valida datos
7. Backend crea usuario en DB
8. Backend registra audit log
9. Backend retorna usuario creado
10. Frontend actualiza lista (invalidate query)
11. Modal se cierra
12. Toast de éxito
9.2 Flujo: Suspender Usuario
1. Admin ve lista de usuarios
2. Selecciona usuario problemático
3. Click "Suspender"
4. Modal de confirmación:
- Razón (requerido)
- Duración (opcional)
5. Confirm → POST /admin/users/:id/suspend
6. Backend suspende usuario
7. Backend registra audit log
8. Backend envía notificación al usuario
9. Frontend actualiza UI
10. Toast de confirmación
9.3 Flujo: Aprobar Contenido
1. Admin navega a /admin/content
2. Ve lista de contenido pendiente
3. Click en item para ver detalles
4. Revisa contenido:
- Preview del ejercicio
- Metadata
- Author
5. Decisión:
a) Aprobar → POST /admin/content/:id/approve
- Contenido pasa a published
- Creator recibe notificación
b) Rechazar → POST /admin/content/:id/reject
- Modal para razón
- Contenido pasa a rejected
- Creator recibe notificación con feedback
6. Frontend actualiza cola de moderación
9.4 Flujo: Configurar Gamificación
1. Admin navega a /admin/gamification
2. Ve config actual de parámetros
3. Modifica valores:
- ML Coins por ejercicio
- XP por logro
- Costos de comodines
4. Click "Preview Impact"
→ POST /admin/gamification/preview-impact
- Backend simula impacto en usuarios
- Muestra estadísticas proyectadas
5. Admin revisa preview
6. Confirm → PATCH /admin/gamification/settings
7. Backend actualiza config
8. Backend registra audit log
9. Config nueva aplica a partir de ahora
10. Toast de éxito con resumen de cambios
9.5 Flujo: Operación Masiva (Bulk Suspend)
1. Admin selecciona múltiples usuarios (checkboxes)
2. Click "Suspender seleccionados"
3. Modal de confirmación:
- Lista de usuarios
- Razón global
- Duración
4. Confirm → POST /admin/users/bulk/suspend
5. Backend:
- Crea BulkOperation record
- Retorna operation_id inmediatamente
- Ejecuta suspensión en background
6. Frontend:
- Muestra progress modal
- Poll GET /admin/users/bulk/:operationId cada 2s
7. Backend actualiza progreso:
- completed_count incrementa
- failed_count si hay errores
8. Al completar:
- Frontend muestra resumen
- Invalidate users query
- Cierra modal después de 3s
9.6 Flujo: Modo Mantenimiento
1. Admin navega a /admin/settings
2. Toggle "Maintenance Mode"
3. Modal de confirmación:
- Mensaje personalizado para usuarios
- Duración estimada
4. Confirm → POST /admin/system/maintenance/toggle
5. Backend:
- Actualiza system_settings
- Broadcast WebSocket a todos los usuarios
6. Frontend (todos los portales):
- Reciben notificación
- Muestran banner de mantenimiento
- Bloquean acciones críticas
7. Admin puede seguir usando el portal
8. Para desactivar:
- Admin toggle OFF
- Backend notifica a todos
- Sistema vuelve a normal
10. Testing
10.1 Tests Unitarios Backend
// admin-users.controller.spec.ts
describe('AdminUsersController', () => {
let controller: AdminUsersController;
let service: AdminUsersService;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [AdminUsersController],
providers: [
{ provide: AdminUsersService, useValue: mockService },
],
}).compile();
controller = module.get(AdminUsersController);
service = module.get(AdminUsersService);
});
it('should list users with pagination', async () => {
const query: ListUsersDto = { page: 1, limit: 20 };
mockService.listUsers.mockResolvedValue(mockPaginatedUsers);
const result = await controller.listUsers(query);
expect(result.data).toHaveLength(20);
expect(result.meta.total).toBe(100);
expect(mockService.listUsers).toHaveBeenCalledWith(query);
});
it('should suspend user', async () => {
const userId = 'user-123';
const dto: SuspendUserDto = { reason: 'Violation', duration_days: 7 };
await controller.suspendUser(userId, dto);
expect(mockService.suspendUser).toHaveBeenCalledWith(userId, dto);
});
});
10.2 Tests Frontend
// useUserManagement.test.ts
describe('useUserManagement', () => {
it('should fetch users list', async () => {
const { result } = renderHook(() => useUserManagement(), {
wrapper: QueryClientProvider,
});
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.users).toBeDefined();
expect(result.current.users.length).toBeGreaterThan(0);
});
it('should suspend user', async () => {
const { result } = renderHook(() => useUserManagement(), {
wrapper: QueryClientProvider,
});
await act(async () => {
await result.current.suspendUser('user-123', {
reason: 'Test suspension',
});
});
await waitFor(() => {
expect(mockAPI.suspendUser).toHaveBeenCalled();
});
});
});
10.3 E2E Tests
// admin-users.e2e.spec.ts
describe('Admin Users Management (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/admin/users (GET) - should require admin role', () => {
return request(app.getHttpServer())
.get('/admin/users')
.set('Authorization', `Bearer ${studentToken}`)
.expect(403);
});
it('/admin/users (GET) - should return users for admin', () => {
return request(app.getHttpServer())
.get('/admin/users')
.set('Authorization', `Bearer ${adminToken}`)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.meta).toBeDefined();
});
});
});
11. Buenas Prácticas
11.1 Frontend
// DO: Hooks específicos por funcionalidad
export function useUserManagement() { ... }
export function useOrganizations() { ... }
export function useBulkOperations() { ... }
// DON'T: Un hook gigante
export function useAdmin() { ... } // Evitar
// DO: Query keys jerárquicas y descriptivas
const queryKey = ['admin', 'users', 'list', { role: 'teacher', page: 1 }];
// DO: Invalidar queries relacionadas
const { mutate } = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
queryClient.invalidateQueries({ queryKey: ['admin', 'dashboard', 'stats'] });
},
});
// DO: Error boundaries para secciones críticas
<ErrorBoundary fallback={<ErrorDisplay />}>
<AdminUsersPage />
</ErrorBoundary>
11.2 Backend
// DO: DTOs con validación completa
export class CreateUserDto {
@IsString()
@MinLength(3)
@MaxLength(50)
username!: string;
@IsEmail()
email!: string;
@IsString()
@MinLength(8)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
password!: string;
}
// DO: Logging completo en servicios admin
@Injectable()
export class AdminUsersService {
private readonly logger = new Logger(AdminUsersService.name);
async suspendUser(userId: string, dto: SuspendUserDto): Promise<void> {
this.logger.log(`Suspending user ${userId}: ${dto.reason}`);
try {
await this.userRepo.update(userId, { status: 'suspended' });
await this.auditLogService.log({ ... });
} catch (error) {
this.logger.error(`Failed to suspend user ${userId}: ${error.message}`);
throw new InternalServerErrorException('Failed to suspend user');
}
}
}
// DO: Transacciones para operaciones críticas
async createOrganization(dto: CreateOrganizationDto): Promise<Organization> {
return this.dataSource.transaction(async (manager) => {
const org = await manager.save(Organization, dto);
await manager.save(Tenant, { organization_id: org.id });
await manager.save(FeatureFlag, { tenant_id: tenant.id });
return org;
});
}
12. Troubleshooting
12.1 Problemas Comunes
| Problema | Causa | Solución |
|---|---|---|
| 403 Forbidden | AdminGuard rechaza | Verificar rol del usuario (admin/super_admin) |
| Bulk operation timeout | Operación muy larga | Usar background jobs, no esperar respuesta |
| Dashboard lento | Queries N+1 | Usar eager loading en TypeORM |
| Métricas desactualizadas | Cache | Invalidar cache manualmente o reducir TTL |
| Audit logs faltantes | Error en middleware | Verificar AuditLogInterceptor está aplicado |
12.2 Debugging
// Habilitar logs verbose en development
if (process.env.NODE_ENV === 'development') {
// Frontend
apiClient.interceptors.request.use((config) => {
console.log(`[Admin API] ${config.method?.toUpperCase()} ${config.url}`);
return config;
});
// Backend
@Injectable()
export class AdminUsersService {
async listUsers(query: ListUsersDto) {
this.logger.debug(`ListUsers called with:`, query);
const result = await this.userRepo.find(...);
this.logger.debug(`Returned ${result.length} users`);
return result;
}
}
}
13. Checklist de Desarrollo
13.1 Nueva Funcionalidad Admin
- Definir tipos en
admin/types/index.ts - Crear DTOs en backend
admin/dto/ - Implementar service en
admin/services/ - Crear/modificar controller
- Aplicar AdminGuard a endpoints
- Agregar audit logging
- Crear API service en frontend
- Implementar hook en
admin/hooks/ - Crear componentes necesarios
- Integrar en página correspondiente
- Agregar tests unitarios
- Documentar en Swagger
- Actualizar esta guía
13.2 Code Review Admin
- AdminGuard aplicado correctamente
- Validación de DTOs completa
- Error handling implementado
- Audit logging presente
- Logs apropiados en services
- Types alineados frontend/backend
- Query keys descriptivas
- Invalidación de cache correcta
- Permisos verificados
- Tests passing
14. Referencias
Documentos Complementarios
| Documento | Descripción |
|---|---|
| PORTAL-TEACHER-GUIDE.md | Guía del portal Teacher (estructura similar) |
| COMPONENT-PATTERNS.md | Patrones de componentes |
| HOOK-PATTERNS.md | Patrones de hooks |
| DTO-CONVENTIONS.md | Convenciones de DTOs |
| ESTRUCTURA-MODULOS.md | Estructura de módulos |
| ESTANDARES-API-ROUTES.md | Estándares de rutas API |
ADRs Relevantes
- ADR-001: Separación de portales (student/teacher/admin)
- ADR-002: Uso de AdminGuard para autorización
- ADR-003: Bulk operations con background jobs
- ADR-004: Audit logging obligatorio para acciones admin
15. Ejemplos de Código Completos
15.1 Hook Completo - useUserManagement
// hooks/useUserManagement.ts
import { useState, useCallback } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as adminAPI from '@/services/api/adminAPI';
import type {
User,
ListUsersDto,
UpdateUserDto,
SuspendUserDto
} from '../types';
export interface UseUserManagementResult {
// Data
users: User[];
stats: UserStatsDto | null;
loading: boolean;
error: string | null;
// Filters
filters: ListUsersDto;
setFilters: (filters: ListUsersDto) => void;
// Actions
createUser: (data: CreateUserDto) => Promise<void>;
updateUser: (id: string, data: UpdateUserDto) => Promise<void>;
deleteUser: (id: string) => Promise<void>;
suspendUser: (id: string, data: SuspendUserDto) => Promise<void>;
activateUser: (id: string) => Promise<void>;
resetPassword: (id: string) => Promise<void>;
}
export function useUserManagement(): UseUserManagementResult {
const queryClient = useQueryClient();
const [filters, setFilters] = useState<ListUsersDto>({
page: 1,
limit: 20,
});
// Queries
const {
data: usersData,
isLoading: usersLoading,
error: usersError
} = useQuery({
queryKey: ['admin', 'users', 'list', filters],
queryFn: () => adminAPI.listUsers(filters),
});
const { data: stats } = useQuery({
queryKey: ['admin', 'users', 'stats'],
queryFn: () => adminAPI.getUserStats(),
});
// Mutations
const { mutateAsync: createUser } = useMutation({
mutationFn: adminAPI.createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
queryClient.invalidateQueries({ queryKey: ['admin', 'dashboard'] });
},
});
const { mutateAsync: updateUser } = useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) =>
adminAPI.updateUser(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
},
});
const { mutateAsync: deleteUser } = useMutation({
mutationFn: adminAPI.deleteUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
queryClient.invalidateQueries({ queryKey: ['admin', 'dashboard'] });
},
});
const { mutateAsync: suspendUser } = useMutation({
mutationFn: ({ id, data }: { id: string; data: SuspendUserDto }) =>
adminAPI.suspendUser(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
},
});
const { mutateAsync: activateUser } = useMutation({
mutationFn: adminAPI.activateUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
},
});
const { mutateAsync: resetPassword } = useMutation({
mutationFn: adminAPI.resetUserPassword,
onSuccess: () => {
// No need to invalidate, just show success toast
},
});
return {
users: usersData?.data || [],
stats: stats || null,
loading: usersLoading,
error: usersError?.message || null,
filters,
setFilters,
createUser,
updateUser: useCallback(
(id: string, data: UpdateUserDto) => updateUser({ id, data }),
[updateUser]
),
deleteUser,
suspendUser: useCallback(
(id: string, data: SuspendUserDto) => suspendUser({ id, data }),
[suspendUser]
),
activateUser,
resetPassword,
};
}
15.2 Service Backend Completo - AdminUsersService
// services/admin-users.service.ts
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '@modules/auth/entities/user.entity';
import { AuditLogService } from '@modules/audit/audit-log.service';
import {
ListUsersDto,
UpdateUserDto,
SuspendUserDto,
PaginatedUsersDto,
UserStatsDto,
} from '../dto/users';
@Injectable()
export class AdminUsersService {
private readonly logger = new Logger(AdminUsersService.name);
constructor(
@InjectRepository(User, 'auth')
private userRepo: Repository<User>,
private auditLogService: AuditLogService,
) {}
async listUsers(query: ListUsersDto): Promise<PaginatedUsersDto> {
const {
page = 1,
limit = 20,
role,
status,
search,
sortBy = 'created_at',
sortOrder = 'DESC'
} = query;
this.logger.debug(`Listing users: page=${page}, limit=${limit}, role=${role}, status=${status}`);
const qb = this.userRepo.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.leftJoinAndSelect('user.roles', 'userRoles')
.leftJoinAndSelect('userRoles.role', 'role');
// Filtros
if (role) {
qb.andWhere('role.name = :role', { role });
}
if (status) {
qb.andWhere('user.status = :status', { status });
}
if (search) {
qb.andWhere(
'(user.username ILIKE :search OR user.email ILIKE :search OR profile.first_name ILIKE :search OR profile.last_name ILIKE :search)',
{ search: `%${search}%` }
);
}
// Sorting
qb.orderBy(`user.${sortBy}`, sortOrder);
// Paginación
const skip = (page - 1) * limit;
qb.skip(skip).take(limit);
const [users, total] = await qb.getManyAndCount();
this.logger.debug(`Found ${total} users, returning page ${page}`);
return {
data: users,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
async getUserDetails(id: string): Promise<User> {
const user = await this.userRepo.findOne({
where: { id },
relations: ['profile', 'roles', 'roles.role'],
});
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async updateUser(
userId: string,
dto: UpdateUserDto,
adminId: string
): Promise<User> {
const user = await this.getUserDetails(userId);
this.logger.log(`Admin ${adminId} updating user ${userId}`);
// Guardar estado anterior
const previousState = { ...user };
// Actualizar
Object.assign(user, dto);
await this.userRepo.save(user);
// Audit log
await this.auditLogService.log({
event_type: 'user_updated',
actor_id: adminId,
target_id: userId,
target_type: 'user',
changes: {
before: previousState,
after: user,
},
metadata: {
updated_fields: Object.keys(dto),
},
});
return user;
}
async deleteUser(userId: string, adminId: string): Promise<void> {
const user = await this.getUserDetails(userId);
this.logger.log(`Admin ${adminId} deleting user ${userId}`);
// Soft delete
user.deleted_at = new Date();
user.status = 'deleted';
await this.userRepo.save(user);
// Audit log
await this.auditLogService.log({
event_type: 'user_deleted',
actor_id: adminId,
target_id: userId,
target_type: 'user',
});
}
async suspendUser(
userId: string,
dto: SuspendUserDto,
adminId: string
): Promise<void> {
const user = await this.getUserDetails(userId);
this.logger.log(`Admin ${adminId} suspending user ${userId}: ${dto.reason}`);
user.status = 'suspended';
user.suspended_at = new Date();
user.suspension_reason = dto.reason;
if (dto.duration_days) {
const until = new Date();
until.setDate(until.getDate() + dto.duration_days);
user.suspended_until = until;
}
await this.userRepo.save(user);
// Audit log
await this.auditLogService.log({
event_type: 'user_suspended',
actor_id: adminId,
target_id: userId,
target_type: 'user',
metadata: {
reason: dto.reason,
duration_days: dto.duration_days,
},
});
}
async getUserStats(): Promise<UserStatsDto> {
const total = await this.userRepo.count();
const active = await this.userRepo.count({ where: { status: 'active' } });
const suspended = await this.userRepo.count({ where: { status: 'suspended' } });
// Roles breakdown
const roleBreakdown = await this.userRepo
.createQueryBuilder('user')
.leftJoin('user.roles', 'userRoles')
.leftJoin('userRoles.role', 'role')
.select('role.name', 'role')
.addSelect('COUNT(user.id)', 'count')
.groupBy('role.name')
.getRawMany();
return {
total_users: total,
active_users: active,
suspended_users: suspended,
inactive_users: total - active - suspended,
role_breakdown: roleBreakdown,
};
}
}
Changelog
| Versión | Fecha | Cambios |
|---|---|---|
| 1.0.0 | 2025-11-29 | Creación inicial completa |
Fin del documento - PORTAL-ADMIN-GUIDE.md