Gestión de Contenido
Módulos API: admin (AdminUsersController, AdminPathsController), audit-logs
Páginas web: AdminUsersPage, AdminAuditLogsPage
Spec Playwright: apps/e2e/tests/admin/content-management.spec.ts — 📋 pendiente
Endpoints cubiertos
Section titled “Endpoints cubiertos”| Método | Endpoint | Roles | AuditLog |
|---|---|---|---|
GET | /admin/users | SUPER_ADMIN | — |
POST | /admin/users | SUPER_ADMIN | USER_CREATED · WARNING |
PATCH | /admin/users/:id | SUPER_ADMIN | USER_ROLE_CHANGED · WARNING |
GET | /admin/users/export | SUPER_ADMIN | USER_EXPORTED · WARNING |
GET | /admin/paths | CONTENT_ADMIN, SUPER_ADMIN | — |
PATCH | /admin/paths/:id | CONTENT_ADMIN, SUPER_ADMIN | PATH_UPDATED · INFO |
PATCH | /admin/paths/:id/publish | CONTENT_ADMIN, SUPER_ADMIN | PATH_PUBLISHED · WARNING |
DELETE | /admin/paths/:id | CONTENT_ADMIN, SUPER_ADMIN | PATH_DELETED · WARNING |
GET | /admin/audit-logs | SUPER_ADMIN | — |
GET | /admin/audit-logs/:id | SUPER_ADMIN | — |
GET | /admin/audit-logs/export | SUPER_ADMIN | — |
GET | /admin/audit-logs/stats | SUPER_ADMIN | — |
User Stories
Section titled “User Stories”US-ADM-CONT-001: Buscar y ver el perfil completo de cualquier usuario
Section titled “US-ADM-CONT-001: Buscar y ver el perfil completo de cualquier usuario”Como SUPER_ADMIN
Quiero buscar usuarios por nombre o email y ver su perfil completo desde /admin/users
Para tener visibilidad total del estado, rol, plan y actividad de cualquier cuenta
Criterios: AC-ADM-CONT-001 · Casos: TC-ADM-CONT-001, TC-ADM-CONT-002 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-002: Cambiar el rol de un usuario
Section titled “US-ADM-CONT-002: Cambiar el rol de un usuario”Como SUPER_ADMIN Quiero cambiar el rol de cualquier usuario a cualquier nivel (excepto degradar SUPER_ADMIN desde la UI) desde el drawer de detalle Para promover a CONTENT_ADMIN, degradar a FREE o asignar un plan corporativo sin intervención técnica
Criterios: AC-ADM-CONT-002 · Casos: TC-ADM-CONT-003, TC-ADM-CONT-004 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-003: Suspender una cuenta por violación de términos
Section titled “US-ADM-CONT-003: Suspender una cuenta por violación de términos”Como SUPER_ADMIN Quiero suspender la cuenta de un usuario que ha violado los términos de servicio Para impedir su acceso inmediato a la plataforma sin eliminar sus datos
Criterios: AC-ADM-CONT-003 · Casos: TC-ADM-CONT-005, TC-ADM-CONT-006 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-004: Restaurar una cuenta suspendida
Section titled “US-ADM-CONT-004: Restaurar una cuenta suspendida”Como SUPER_ADMIN Quiero reactivar la cuenta de un usuario previamente suspendido Para restablecer su acceso cuando el motivo de suspensión ha sido resuelto
Criterios: AC-ADM-CONT-004 · Casos: TC-ADM-CONT-007 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-005: Crear un usuario manualmente
Section titled “US-ADM-CONT-005: Crear un usuario manualmente”Como SUPER_ADMIN Quiero crear un nuevo usuario directamente desde el panel con un rol y contraseña inicial Para incorporar a staff interno (CONTENT_ADMIN) o usuarios corporativos sin que pasen por el flujo de registro público
Criterios: AC-ADM-CONT-005 · Casos: TC-ADM-CONT-008, TC-ADM-CONT-009 · Prioridad: Media · ✅ Implementado
US-ADM-CONT-006: Ver todos los paths publicados y sus métricas
Section titled “US-ADM-CONT-006: Ver todos los paths publicados y sus métricas”Como SUPER_ADMIN
Quiero ver el listado completo de paths de todos los creators desde /admin/paths
Para tener visibilidad del catálogo y detectar contenido que requiera intervención
Criterios: AC-ADM-CONT-006 · Casos: TC-ADM-CONT-010, TC-ADM-CONT-011 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-007: Despublicar un path inapropiado de cualquier creator
Section titled “US-ADM-CONT-007: Despublicar un path inapropiado de cualquier creator”Como SUPER_ADMIN Quiero despublicar un path de otro creator desde el panel admin Para retirar contenido inapropiado o no conforme con las políticas de la plataforma de forma inmediata
Criterios: AC-ADM-CONT-007 · Casos: TC-ADM-CONT-012, TC-ADM-CONT-013 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-008: Destacar un path en la portada del catálogo
Section titled “US-ADM-CONT-008: Destacar un path en la portada del catálogo”Como SUPER_ADMIN
Quiero marcar un path como isFeatured = true desde el panel admin
Para impulsarlo en la portada del catálogo público sin necesidad de publicar una nueva versión
Criterios: AC-ADM-CONT-008 · Casos: TC-ADM-CONT-014 · Prioridad: Media · ✅ Implementado
US-ADM-CONT-009: Consultar el historial de acciones críticas en audit logs
Section titled “US-ADM-CONT-009: Consultar el historial de acciones críticas en audit logs”Como SUPER_ADMIN
Quiero consultar los audit logs con filtros por categoría, severidad, actor y rango de fechas desde /admin/audit-logs
Para auditar acciones sensibles (cambios de rol, suspensiones, eliminaciones de paths) y cumplir con los requisitos de trazabilidad
Criterios: AC-ADM-CONT-009 · Casos: TC-ADM-CONT-015, TC-ADM-CONT-016 · Prioridad: Alta · ✅ Implementado
US-ADM-CONT-010: Acceso denegado al portal admin para roles no autorizados
Section titled “US-ADM-CONT-010: Acceso denegado al portal admin para roles no autorizados”Como CONTENT_ADMIN (o cualquier rol distinto de SUPER_ADMIN) Quiero recibir un 403 claro al intentar acceder a endpoints exclusivos de SUPER_ADMIN Para garantizar que la separación de privilegios se cumple en la capa de API
Criterios: AC-ADM-CONT-010 · Casos: TC-ADM-CONT-017, TC-ADM-CONT-018 · Prioridad: Alta · ✅ Implementado
Acceptance Criteria
Section titled “Acceptance Criteria”AC-ADM-CONT-001: Buscar y ver el perfil completo de cualquier usuario
Section titled “AC-ADM-CONT-001: Buscar y ver el perfil completo de cualquier usuario”US: US-ADM-CONT-001
AC-ADM-CONT-001.1 — Listado y búsqueda
Dado que soy SUPER_ADMIN autenticadoCuando llamo a GET /admin/usersEntonces recibo un array con todos los usuarios de la plataformaY cada elemento incluye: id, email, fullName, role, isSuspended, createdAt, lastActiveAt, enrollmentsCount, certificatesCountAC-ADM-CONT-001.2 — Filtrado por nombre o email (client-side)
Dado que el listado de usuarios ha cargado en /admin/usersCuando introduzco texto en el campo de búsqueda data-testid="admin-users-search"Entonces la tabla filtra en tiempo real mostrando solo usuarios cuyo nombre o email contiene el textoAC-ADM-CONT-001.3 — Drawer de detalle
Dado que estoy viendo la tabla de usuarios en /admin/usersCuando hago clic en una filaEntonces se abre el drawer de detalle con: rol actual, estado (activo/suspendido), AI Fluency Score, certificados, plan y última actividadAC-ADM-CONT-002: Cambiar el rol de un usuario
Section titled “AC-ADM-CONT-002: Cambiar el rol de un usuario”US: US-ADM-CONT-002
AC-ADM-CONT-002.1 — Cambio de rol exitoso
Dado que soy SUPER_ADMIN y el drawer de un usuario está abiertoCuando selecciono un rol diferente en el selector de roles del drawerEntonces la API recibe PATCH /admin/users/:id con el nuevo rol en el bodyY la respuesta incluye el usuario actualizado con el nuevo rolY la tabla refleja el cambio sin recargar la páginaY se genera un audit log con action="USER_ROLE_CHANGED" y severity="WARNING"AC-ADM-CONT-002.2 — No se puede cambiar el propio rol
Dado que soy SUPER_ADMINCuando abro el drawer de mi propio usuarioEntonces el selector de roles aparece deshabilitadoY no existe botón de suspensión para mi propia cuentaAC-ADM-CONT-002.3 — Cambio a rol corporativo requiere organización
Dado que soy SUPER_ADMIN y selecciono el rol CORPORATE_ADMIN para un usuarioCuando no he seleccionado una organización en el selectorEntonces el botón "Confirmar cambio" permanece deshabilitadoY se muestra el selector de organización antes de confirmarAC-ADM-CONT-003: Suspender una cuenta
Section titled “AC-ADM-CONT-003: Suspender una cuenta”US: US-ADM-CONT-003
AC-ADM-CONT-003.1 — Suspensión exitosa
Dado que soy SUPER_ADMIN y el usuario objetivo no está suspendidoCuando hago clic en "Suspender cuenta" en el drawerEntonces la API recibe PATCH /admin/users/:id con { isSuspended: true }Y el estado del usuario cambia a "Suspendido" en la UIY se genera un audit log con action="USER_ROLE_CHANGED" y severity="WARNING"AC-ADM-CONT-003.2 — Usuario suspendido no puede iniciar sesión
Dado que el usuario ha sido suspendidoCuando ese usuario intenta iniciar sesión con sus credenciales correctasEntonces la API responde con status 401 o 403Y el usuario no recibe token JWTAC-ADM-CONT-004: Restaurar una cuenta suspendida
Section titled “AC-ADM-CONT-004: Restaurar una cuenta suspendida”US: US-ADM-CONT-004
AC-ADM-CONT-004.1 — Restauración exitosa
Dado que soy SUPER_ADMIN y el usuario objetivo está suspendidoCuando hago clic en "Reactivar cuenta" en el drawerEntonces la API recibe PATCH /admin/users/:id con { isSuspended: false }Y el estado del usuario cambia a "Activo" en la UIAC-ADM-CONT-004.2 — Usuario restaurado puede volver a iniciar sesión
Dado que el usuario ha sido reactivadoCuando ese usuario intenta iniciar sesión con sus credenciales correctasEntonces recibe un token JWT válidoY puede acceder a las rutas protegidas de su rolAC-ADM-CONT-005: Crear un usuario manualmente
Section titled “AC-ADM-CONT-005: Crear un usuario manualmente”US: US-ADM-CONT-005
AC-ADM-CONT-005.1 — Creación exitosa de CONTENT_ADMIN
Dado que soy SUPER_ADMIN y he abierto el modal "Crear usuario"Cuando relleno email, nombre completo, contraseña (≥8 chars) y selecciono rol CONTENT_ADMINY hago clic en "Crear usuario"Entonces la API recibe POST /admin/users con los datos del formularioY la respuesta es 201 con el nuevo usuarioY el modal se cierra y el nuevo usuario aparece en la tablaY se genera un audit log con action="USER_CREATED" y captureBody=trueAC-ADM-CONT-005.2 — Email duplicado
Dado que ya existe un usuario con el email introducidoCuando intento crear otro usuario con ese mismo emailEntonces la API responde con status 409Y el modal muestra el mensaje de error sin cerrarseAC-ADM-CONT-005.3 — Rol corporativo requiere organización
Dado que selecciono el rol CORPORATE_USER en el formulario de creaciónCuando no hay organizaciones creadas en el sistemaEntonces se muestra un aviso indicando que primero debe crearse una organizaciónY el botón "Crear usuario" permanece deshabilitadoAC-ADM-CONT-006: Ver todos los paths publicados
Section titled “AC-ADM-CONT-006: Ver todos los paths publicados”US: US-ADM-CONT-006
AC-ADM-CONT-006.1 — SUPER_ADMIN ve todos los paths
Dado que soy SUPER_ADMIN autenticadoCuando llamo a GET /admin/pathsEntonces recibo todos los paths de la plataforma independientemente del creatorY cada path incluye: id, title, slug, isPublished, isFeatured, accessLevel, checklistStatusAC-ADM-CONT-006.2 — CONTENT_ADMIN solo ve sus propios paths
Dado que soy CONTENT_ADMIN autenticadoCuando llamo a GET /admin/pathsEntonces recibo únicamente los paths cuyo creatorProfileId coincide con el míoY no veo los paths de otros creatorsAC-ADM-CONT-007: Despublicar un path inapropiado
Section titled “AC-ADM-CONT-007: Despublicar un path inapropiado”US: US-ADM-CONT-007
AC-ADM-CONT-007.1 — Despublicación exitosa por SUPER_ADMIN
Dado que soy SUPER_ADMIN y el path objetivo está publicado (isPublished=true)Cuando llamo a PATCH /admin/paths/:id/publish con { isPublished: false }Entonces el path deja de estar publicadoY la respuesta incluye el path actualizado con isPublished=falseY se genera un audit log con action="PATH_PUBLISHED" y severity="WARNING"AC-ADM-CONT-007.2 — CONTENT_ADMIN no puede despublicar paths ajenos
Dado que soy CONTENT_ADMIN y el path objetivo pertenece a otro creatorCuando llamo a PATCH /admin/paths/:id/publish con { isPublished: false }Entonces la API responde con status 403Y el path permanece con su estado originalAC-ADM-CONT-008: Destacar un path en el catálogo
Section titled “AC-ADM-CONT-008: Destacar un path en el catálogo”US: US-ADM-CONT-008
AC-ADM-CONT-008.1 — Marcado como destacado exitoso
Dado que soy SUPER_ADMINCuando llamo a PATCH /admin/paths/:id con { isFeatured: true }Entonces el path queda con isFeatured=true en la base de datosY la respuesta incluye el path actualizadoY se genera un audit log con action="PATH_UPDATED" y severity="INFO"AC-ADM-CONT-008.2 — Retirar destaque
Dado que el path está marcado como isFeatured=trueCuando llamo a PATCH /admin/paths/:id con { isFeatured: false }Entonces el path deja de aparecer como destacadoAC-ADM-CONT-009: Consultar audit logs con filtros
Section titled “AC-ADM-CONT-009: Consultar audit logs con filtros”US: US-ADM-CONT-009
AC-ADM-CONT-009.1 — Listado paginado
Dado que soy SUPER_ADMIN autenticadoCuando llamo a GET /admin/audit-logs con page=1 y limit=25Entonces recibo un objeto con { data: AuditLogRecord[], meta: { page, limit, total, totalPages } }Y los registros están ordenados por createdAt DESCAC-ADM-CONT-009.2 — Filtro por categoría USER_MANAGEMENT
Dado que soy SUPER_ADMINCuando llamo a GET /admin/audit-logs?category=USER_MANAGEMENTEntonces recibo solo logs cuya categoría es USER_MANAGEMENTY en el listado aparecen acciones como USER_ROLE_CHANGED y USER_CREATEDAC-ADM-CONT-009.3 — Filtro por severidad CRITICAL
Dado que soy SUPER_ADMINCuando llamo a GET /admin/audit-logs?severity=CRITICALEntonces recibo solo logs con severity=CRITICALAC-ADM-CONT-009.4 — Filtro por rango de fechas
Dado que soy SUPER_ADMINCuando llamo a GET /admin/audit-logs?from=2026-01-01&to=2026-01-31Entonces recibo solo logs con createdAt entre esas fechas (inclusive)AC-ADM-CONT-009.5 — Detalle completo de un log
Dado que soy SUPER_ADMINCuando llamo a GET /admin/audit-logs/:id con un ID válidoEntonces recibo el registro completo incluyendo: actorEmail, actorRole, actorIp, before, after, metadataAC-ADM-CONT-010: Acceso denegado para roles no autorizados
Section titled “AC-ADM-CONT-010: Acceso denegado para roles no autorizados”US: US-ADM-CONT-010
AC-ADM-CONT-010.1 — CONTENT_ADMIN no puede acceder a gestión de usuarios
Dado que soy CONTENT_ADMIN autenticadoCuando llamo a GET /admin/usersEntonces la API responde con status 403Y no recibo ningún dato de usuariosAC-ADM-CONT-010.2 — CONTENT_ADMIN no puede acceder a audit logs
Dado que soy CONTENT_ADMIN autenticadoCuando llamo a GET /admin/audit-logsEntonces la API responde con status 403AC-ADM-CONT-010.3 — Usuario no autenticado recibe 401
Dado que no estoy autenticadoCuando llamo a GET /admin/users sin tokenEntonces la API responde con status 401Test Cases
Section titled “Test Cases”TC-ADM-CONT-001: Listado de usuarios devuelve todos los usuarios
Section titled “TC-ADM-CONT-001: Listado de usuarios devuelve todos los usuarios”AC: AC-ADM-CONT-001.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/admin/content-management.spec.ts
const res = await apiClient.get('/admin/users', superAdminToken)expect(res.status).toBe(200)expect(Array.isArray(res.data)).toBe(true)expect(res.data.length).toBeGreaterThan(0)// Verificar estructura mínima de cada elementoconst user = res.data[0]expect(user).toHaveProperty('id')expect(user).toHaveProperty('email')expect(user).toHaveProperty('role')expect(user).toHaveProperty('isSuspended')expect(user).toHaveProperty('enrollmentsCount')TC-ADM-CONT-002: Búsqueda por email en la UI filtra la tabla
Section titled “TC-ADM-CONT-002: Búsqueda por email en la UI filtra la tabla”AC: AC-ADM-CONT-001.2 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/admin/content-management.spec.ts
Precondiciones: fixtures.free existe en la BD
await loginAs(page, { email: process.env.SUPER_ADMIN_EMAIL!, password: process.env.SUPER_ADMIN_PASSWORD! })await page.goto('/admin/users')await page.getByTestId('admin-users-search').fill(fixtures.free.email)// Esperar debounce 300msawait page.waitForTimeout(400)// Solo el usuario buscado debe ser visible en la tablaconst rows = page.locator('[data-testid="admin-user-row"]')await expect(rows).toHaveCount(1)await expect(rows.first()).toContainText(fixtures.free.email)TC-ADM-CONT-003: Cambio de rol vía API actualiza el usuario
Section titled “TC-ADM-CONT-003: Cambio de rol vía API actualiza el usuario”AC: AC-ADM-CONT-002.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/admin/content-management.spec.ts
Precondiciones: fixtures.free existe; restaurar rol al finalizar el test
const userId = fixtures.free.id
// Promover a STARTERconst res = await apiClient.patch(`/admin/users/${userId}`, { role: 'STARTER' }, superAdminToken)expect(res.status).toBe(200)expect(res.data.role).toBe('STARTER')
// Verificar que el audit log fue generadoconst logs = await apiClient.get( `/admin/audit-logs?actorId=${/* SUPER_ADMIN id */''}&action=USER_ROLE_CHANGED`, superAdminToken,)expect(logs.data.data.some((l: { entityId: string }) => l.entityId === userId)).toBe(true)
// Restaurarawait apiClient.patch(`/admin/users/${userId}`, { role: 'FREE' }, superAdminToken)TC-ADM-CONT-004: No se puede cambiar el propio rol
Section titled “TC-ADM-CONT-004: No se puede cambiar el propio rol”AC: AC-ADM-CONT-002.2 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/admin/content-management.spec.ts
await loginAs(page, { email: process.env.SUPER_ADMIN_EMAIL!, password: process.env.SUPER_ADMIN_PASSWORD! })await page.goto('/admin/users')// Buscar mi propio usuario (email del SUPER_ADMIN)await page.getByTestId('admin-users-search').fill(process.env.SUPER_ADMIN_EMAIL!)await page.waitForTimeout(400)await page.locator('[data-testid="admin-user-row"]').first().click()// El selector de roles debe estar deshabilitadoconst rolePills = page.locator('[class*="au-role-pill"]')// Todos los pills del propio usuario deben estar deshabilitadosfor (const pill of await rolePills.all()) { await expect(pill).toBeDisabled()}TC-ADM-CONT-005: Suspender cuenta vía API
Section titled “TC-ADM-CONT-005: Suspender cuenta vía API”AC: AC-ADM-CONT-003.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Precondiciones: fixtures.free está activo; restaurar al finalizar
const res = await apiClient.patch( `/admin/users/${fixtures.free.id}`, { isSuspended: true }, superAdminToken,)expect(res.status).toBe(200)expect(res.data.isSuspended).toBe(true)
// Cleanupawait apiClient.patch(`/admin/users/${fixtures.free.id}`, { isSuspended: false }, superAdminToken)TC-ADM-CONT-006: Usuario suspendido no puede iniciar sesión
Section titled “TC-ADM-CONT-006: Usuario suspendido no puede iniciar sesión”AC: AC-ADM-CONT-003.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Precondiciones: fixtures.free ha sido suspendido por el TC-ADM-CONT-005 (o configurado previamente)
// Suspender primeroawait apiClient.patch(`/admin/users/${fixtures.free.id}`, { isSuspended: true }, superAdminToken)
// Intentar loginconst loginRes = await apiClient.post('/auth/login', { email: fixtures.free.email, password: fixtures.free.password,})expect([401, 403]).toContain(loginRes.status)
// Restaurar siempreawait apiClient.patch(`/admin/users/${fixtures.free.id}`, { isSuspended: false }, superAdminToken)TC-ADM-CONT-007: Restaurar cuenta suspendida recupera el acceso
Section titled “TC-ADM-CONT-007: Restaurar cuenta suspendida recupera el acceso”AC: AC-ADM-CONT-004.1, AC-ADM-CONT-004.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
// 1. Suspenderawait apiClient.patch(`/admin/users/${fixtures.free.id}`, { isSuspended: true }, superAdminToken)
// 2. Restaurarconst restoreRes = await apiClient.patch( `/admin/users/${fixtures.free.id}`, { isSuspended: false }, superAdminToken,)expect(restoreRes.status).toBe(200)expect(restoreRes.data.isSuspended).toBe(false)
// 3. Verificar que puede hacer loginconst loginRes = await apiClient.post('/auth/login', { email: fixtures.free.email, password: fixtures.free.password,})expect(loginRes.status).toBe(200)expect(loginRes.data).toHaveProperty('accessToken')TC-ADM-CONT-008: Crear usuario manual con rol CONTENT_ADMIN
Section titled “TC-ADM-CONT-008: Crear usuario manual con rol CONTENT_ADMIN”AC: AC-ADM-CONT-005.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const email = `qa-content-admin-${Date.now()}@nappai.test`const res = await apiClient.post( '/admin/users', { email, fullName: 'QA Creator Test', password: 'TestPass123!', role: 'CONTENT_ADMIN', }, superAdminToken,)expect(res.status).toBe(201)expect(res.data.role).toBe('CONTENT_ADMIN')expect(res.data.email).toBe(email)TC-ADM-CONT-009: Crear usuario manual con email duplicado devuelve 409
Section titled “TC-ADM-CONT-009: Crear usuario manual con email duplicado devuelve 409”AC: AC-ADM-CONT-005.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient.post( '/admin/users', { email: fixtures.free.email, // email ya existente fullName: 'Duplicate User', password: 'TestPass123!', role: 'FREE', }, superAdminToken,)expect(res.status).toBe(409)TC-ADM-CONT-010: SUPER_ADMIN ve todos los paths de la plataforma
Section titled “TC-ADM-CONT-010: SUPER_ADMIN ve todos los paths de la plataforma”AC: AC-ADM-CONT-006.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient.get('/admin/paths', superAdminToken)expect(res.status).toBe(200)expect(Array.isArray(res.data)).toBe(true)// Verificar estructuraif (res.data.length > 0) { const path = res.data[0] expect(path).toHaveProperty('id') expect(path).toHaveProperty('title') expect(path).toHaveProperty('isPublished') expect(path).toHaveProperty('isFeatured') expect(path).toHaveProperty('checklistStatus')}TC-ADM-CONT-011: CONTENT_ADMIN solo ve sus propios paths
Section titled “TC-ADM-CONT-011: CONTENT_ADMIN solo ve sus propios paths”AC: AC-ADM-CONT-006.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
// Necesita token de fixtures.contentAdminconst contentAdminToken = await apiClient .post('/auth/login', { email: fixtures.contentAdmin.email, password: fixtures.contentAdmin.password }) .then((r) => r.data.accessToken)
const res = await apiClient.get('/admin/paths', contentAdminToken)expect(res.status).toBe(200)// Todos los paths devueltos deben pertenecer al CONTENT_ADMIN// (Verificar contra el creatorProfileId del contentAdmin)TC-ADM-CONT-012: SUPER_ADMIN despublica path de otro creator
Section titled “TC-ADM-CONT-012: SUPER_ADMIN despublica path de otro creator”AC: AC-ADM-CONT-007.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Precondiciones: Existe un path publicado (isPublished=true) con un ID conocido en el entorno de test
// Requiere un pathId de prueba configurado en el entorno de testconst pathId = process.env.TEST_PATH_ID!
const res = await apiClient.patch( `/admin/paths/${pathId}/publish`, { isPublished: false }, superAdminToken,)expect(res.status).toBe(200)expect(res.data.isPublished).toBe(false)
// Restaurarawait apiClient.patch(`/admin/paths/${pathId}/publish`, { isPublished: true }, superAdminToken)TC-ADM-CONT-013: CONTENT_ADMIN no puede despublicar path ajeno
Section titled “TC-ADM-CONT-013: CONTENT_ADMIN no puede despublicar path ajeno”AC: AC-ADM-CONT-007.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const contentAdminToken = await apiClient .post('/auth/login', { email: fixtures.contentAdmin.email, password: fixtures.contentAdmin.password }) .then((r) => r.data.accessToken)
// pathId de un path que NO pertenece al contentAdminconst foreignPathId = process.env.TEST_FOREIGN_PATH_ID!
const res = await apiClient.patch( `/admin/paths/${foreignPathId}/publish`, { isPublished: false }, contentAdminToken,)expect(res.status).toBe(403)TC-ADM-CONT-014: Marcar path como destacado
Section titled “TC-ADM-CONT-014: Marcar path como destacado”AC: AC-ADM-CONT-008.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
const pathId = process.env.TEST_PATH_ID!
const res = await apiClient.patch( `/admin/paths/${pathId}`, { isFeatured: true }, superAdminToken,)expect(res.status).toBe(200)expect(res.data.isFeatured).toBe(true)
// Verificar audit logconst logs = await apiClient.get( `/admin/audit-logs?action=PATH_UPDATED&entityType=PATH`, superAdminToken,)expect(logs.data.data.some((l: { entityId: string }) => l.entityId === pathId)).toBe(true)
// Restaurarawait apiClient.patch(`/admin/paths/${pathId}`, { isFeatured: false }, superAdminToken)TC-ADM-CONT-015: Audit logs paginados con filtro de categoría
Section titled “TC-ADM-CONT-015: Audit logs paginados con filtro de categoría”AC: AC-ADM-CONT-009.1, AC-ADM-CONT-009.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient.get( '/admin/audit-logs?category=USER_MANAGEMENT&page=1&limit=10', superAdminToken,)expect(res.status).toBe(200)expect(res.data).toHaveProperty('data')expect(res.data).toHaveProperty('meta')expect(res.data.meta).toMatchObject({ page: 1, limit: 10,})// Todos los registros deben ser de la categoría solicitadafor (const log of res.data.data) { expect(log.category).toBe('USER_MANAGEMENT')}TC-ADM-CONT-016: Detalle de audit log incluye metadatos técnicos
Section titled “TC-ADM-CONT-016: Detalle de audit log incluye metadatos técnicos”AC: AC-ADM-CONT-009.5 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
// Obtener el ID de un log recienteconst listRes = await apiClient.get('/admin/audit-logs?limit=1', superAdminToken)expect(listRes.status).toBe(200)const logId = listRes.data.data[0]?.idexpect(logId).toBeDefined()
// Obtener el detalleconst detailRes = await apiClient.get(`/admin/audit-logs/${logId}`, superAdminToken)expect(detailRes.status).toBe(200)expect(detailRes.data).toHaveProperty('actorEmail')expect(detailRes.data).toHaveProperty('actorRole')expect(detailRes.data).toHaveProperty('actorIp')expect(detailRes.data).toHaveProperty('action')expect(detailRes.data).toHaveProperty('category')expect(detailRes.data).toHaveProperty('severity')expect(detailRes.data).toHaveProperty('success')TC-ADM-CONT-017: CONTENT_ADMIN recibe 403 en GET /admin/users
Section titled “TC-ADM-CONT-017: CONTENT_ADMIN recibe 403 en GET /admin/users”AC: AC-ADM-CONT-010.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Nota: Este test es candidato a ser parte de la suite access-control (BLOCKING en CI).
const contentAdminToken = await apiClient .post('/auth/login', { email: fixtures.contentAdmin.email, password: fixtures.contentAdmin.password }) .then((r) => r.data.accessToken)
const res = await apiClient.get('/admin/users', contentAdminToken)expect(res.status).toBe(403)TC-ADM-CONT-018: CONTENT_ADMIN recibe 403 en GET /admin/audit-logs
Section titled “TC-ADM-CONT-018: CONTENT_ADMIN recibe 403 en GET /admin/audit-logs”AC: AC-ADM-CONT-010.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Nota: Candidato a suite access-control (BLOCKING en CI).
const contentAdminToken = await apiClient .post('/auth/login', { email: fixtures.contentAdmin.email, password: fixtures.contentAdmin.password }) .then((r) => r.data.accessToken)
const res = await apiClient.get('/admin/audit-logs', contentAdminToken)expect(res.status).toBe(403)Última actualización: 2026-05-29 · Agente de Documentación QA