Skip to content

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


MétodoEndpointRolesAuditLog
GET/admin/usersSUPER_ADMIN
POST/admin/usersSUPER_ADMINUSER_CREATED · WARNING
PATCH/admin/users/:idSUPER_ADMINUSER_ROLE_CHANGED · WARNING
GET/admin/users/exportSUPER_ADMINUSER_EXPORTED · WARNING
GET/admin/pathsCONTENT_ADMIN, SUPER_ADMIN
PATCH/admin/paths/:idCONTENT_ADMIN, SUPER_ADMINPATH_UPDATED · INFO
PATCH/admin/paths/:id/publishCONTENT_ADMIN, SUPER_ADMINPATH_PUBLISHED · WARNING
DELETE/admin/paths/:idCONTENT_ADMIN, SUPER_ADMINPATH_DELETED · WARNING
GET/admin/audit-logsSUPER_ADMIN
GET/admin/audit-logs/:idSUPER_ADMIN
GET/admin/audit-logs/exportSUPER_ADMIN
GET/admin/audit-logs/statsSUPER_ADMIN

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


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


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 autenticado
Cuando llamo a GET /admin/users
Entonces recibo un array con todos los usuarios de la plataforma
Y cada elemento incluye: id, email, fullName, role, isSuspended, createdAt, lastActiveAt, enrollmentsCount, certificatesCount

AC-ADM-CONT-001.2 — Filtrado por nombre o email (client-side)

Dado que el listado de usuarios ha cargado en /admin/users
Cuando 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 texto

AC-ADM-CONT-001.3 — Drawer de detalle

Dado que estoy viendo la tabla de usuarios en /admin/users
Cuando hago clic en una fila
Entonces se abre el drawer de detalle con: rol actual, estado (activo/suspendido), AI Fluency Score, certificados, plan y última actividad

AC-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á abierto
Cuando selecciono un rol diferente en el selector de roles del drawer
Entonces la API recibe PATCH /admin/users/:id con el nuevo rol en el body
Y la respuesta incluye el usuario actualizado con el nuevo rol
Y la tabla refleja el cambio sin recargar la página
Y 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_ADMIN
Cuando abro el drawer de mi propio usuario
Entonces el selector de roles aparece deshabilitado
Y no existe botón de suspensión para mi propia cuenta

AC-ADM-CONT-002.3 — Cambio a rol corporativo requiere organización

Dado que soy SUPER_ADMIN y selecciono el rol CORPORATE_ADMIN para un usuario
Cuando no he seleccionado una organización en el selector
Entonces el botón "Confirmar cambio" permanece deshabilitado
Y se muestra el selector de organización antes de confirmar

US: US-ADM-CONT-003

AC-ADM-CONT-003.1 — Suspensión exitosa

Dado que soy SUPER_ADMIN y el usuario objetivo no está suspendido
Cuando hago clic en "Suspender cuenta" en el drawer
Entonces la API recibe PATCH /admin/users/:id con { isSuspended: true }
Y el estado del usuario cambia a "Suspendido" en la UI
Y 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 suspendido
Cuando ese usuario intenta iniciar sesión con sus credenciales correctas
Entonces la API responde con status 401 o 403
Y el usuario no recibe token JWT

AC-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á suspendido
Cuando hago clic en "Reactivar cuenta" en el drawer
Entonces la API recibe PATCH /admin/users/:id con { isSuspended: false }
Y el estado del usuario cambia a "Activo" en la UI

AC-ADM-CONT-004.2 — Usuario restaurado puede volver a iniciar sesión

Dado que el usuario ha sido reactivado
Cuando ese usuario intenta iniciar sesión con sus credenciales correctas
Entonces recibe un token JWT válido
Y puede acceder a las rutas protegidas de su rol

AC-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_ADMIN
Y hago clic en "Crear usuario"
Entonces la API recibe POST /admin/users con los datos del formulario
Y la respuesta es 201 con el nuevo usuario
Y el modal se cierra y el nuevo usuario aparece en la tabla
Y se genera un audit log con action="USER_CREATED" y captureBody=true

AC-ADM-CONT-005.2 — Email duplicado

Dado que ya existe un usuario con el email introducido
Cuando intento crear otro usuario con ese mismo email
Entonces la API responde con status 409
Y el modal muestra el mensaje de error sin cerrarse

AC-ADM-CONT-005.3 — Rol corporativo requiere organización

Dado que selecciono el rol CORPORATE_USER en el formulario de creación
Cuando no hay organizaciones creadas en el sistema
Entonces se muestra un aviso indicando que primero debe crearse una organización
Y el botón "Crear usuario" permanece deshabilitado

AC-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 autenticado
Cuando llamo a GET /admin/paths
Entonces recibo todos los paths de la plataforma independientemente del creator
Y cada path incluye: id, title, slug, isPublished, isFeatured, accessLevel, checklistStatus

AC-ADM-CONT-006.2 — CONTENT_ADMIN solo ve sus propios paths

Dado que soy CONTENT_ADMIN autenticado
Cuando llamo a GET /admin/paths
Entonces recibo únicamente los paths cuyo creatorProfileId coincide con el mío
Y no veo los paths de otros creators

AC-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 publicado
Y la respuesta incluye el path actualizado con isPublished=false
Y 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 creator
Cuando llamo a PATCH /admin/paths/:id/publish con { isPublished: false }
Entonces la API responde con status 403
Y el path permanece con su estado original

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_ADMIN
Cuando llamo a PATCH /admin/paths/:id con { isFeatured: true }
Entonces el path queda con isFeatured=true en la base de datos
Y la respuesta incluye el path actualizado
Y 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=true
Cuando llamo a PATCH /admin/paths/:id con { isFeatured: false }
Entonces el path deja de aparecer como destacado

AC-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 autenticado
Cuando llamo a GET /admin/audit-logs con page=1 y limit=25
Entonces recibo un objeto con { data: AuditLogRecord[], meta: { page, limit, total, totalPages } }
Y los registros están ordenados por createdAt DESC

AC-ADM-CONT-009.2 — Filtro por categoría USER_MANAGEMENT

Dado que soy SUPER_ADMIN
Cuando llamo a GET /admin/audit-logs?category=USER_MANAGEMENT
Entonces recibo solo logs cuya categoría es USER_MANAGEMENT
Y en el listado aparecen acciones como USER_ROLE_CHANGED y USER_CREATED

AC-ADM-CONT-009.3 — Filtro por severidad CRITICAL

Dado que soy SUPER_ADMIN
Cuando llamo a GET /admin/audit-logs?severity=CRITICAL
Entonces recibo solo logs con severity=CRITICAL

AC-ADM-CONT-009.4 — Filtro por rango de fechas

Dado que soy SUPER_ADMIN
Cuando llamo a GET /admin/audit-logs?from=2026-01-01&to=2026-01-31
Entonces recibo solo logs con createdAt entre esas fechas (inclusive)

AC-ADM-CONT-009.5 — Detalle completo de un log

Dado que soy SUPER_ADMIN
Cuando llamo a GET /admin/audit-logs/:id con un ID válido
Entonces recibo el registro completo incluyendo: actorEmail, actorRole, actorIp, before, after, metadata

AC-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 autenticado
Cuando llamo a GET /admin/users
Entonces la API responde con status 403
Y no recibo ningún dato de usuarios

AC-ADM-CONT-010.2 — CONTENT_ADMIN no puede acceder a audit logs

Dado que soy CONTENT_ADMIN autenticado
Cuando llamo a GET /admin/audit-logs
Entonces la API responde con status 403

AC-ADM-CONT-010.3 — Usuario no autenticado recibe 401

Dado que no estoy autenticado
Cuando llamo a GET /admin/users sin token
Entonces la API responde con status 401


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 elemento
const 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 300ms
await page.waitForTimeout(400)
// Solo el usuario buscado debe ser visible en la tabla
const 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 STARTER
const 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 generado
const 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)
// Restaurar
await 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 deshabilitado
const rolePills = page.locator('[class*="au-role-pill"]')
// Todos los pills del propio usuario deben estar deshabilitados
for (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)
// Cleanup
await 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 primero
await apiClient.patch(`/admin/users/${fixtures.free.id}`, { isSuspended: true }, superAdminToken)
// Intentar login
const loginRes = await apiClient.post('/auth/login', {
email: fixtures.free.email,
password: fixtures.free.password,
})
expect([401, 403]).toContain(loginRes.status)
// Restaurar siempre
await 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. Suspender
await apiClient.patch(`/admin/users/${fixtures.free.id}`, { isSuspended: true }, superAdminToken)
// 2. Restaurar
const 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 login
const 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 estructura
if (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.contentAdmin
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/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 test
const 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)
// Restaurar
await 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 contentAdmin
const 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 log
const 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)
// Restaurar
await 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 solicitada
for (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 reciente
const listRes = await apiClient.get('/admin/audit-logs?limit=1', superAdminToken)
expect(listRes.status).toBe(200)
const logId = listRes.data.data[0]?.id
expect(logId).toBeDefined()
// Obtener el detalle
const 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