Catálogo y Enrolamiento
Módulos API: paths
Páginas web: CatalogPage (/catalog), AppCatalogPage (/dashboard/catalog), PathDetailPage (/paths/:slug)
Spec Playwright: apps/e2e/tests/student/catalog.spec.ts — 📋 pendiente
Módulos API cubiertos
Section titled “Módulos API cubiertos”| Método | Endpoint | Auth | Descripción |
|---|---|---|---|
GET | /paths | No | Listado del catálogo (solo isPublished: true, sin organizationId) con filtros y ordenación |
GET | /paths/:slug | No | Detalle público de un path con módulos, instructor y stats en vivo |
GET | /paths/:slug/preview | Sí (CONTENT_ADMIN / SUPER_ADMIN) | Detalle con contenido no publicado para creators |
GET | /paths/:slug/preview/structure | Sí (CONTENT_ADMIN / SUPER_ADMIN) | Estructura lateral con lecciones no publicadas |
GET | /paths/:slug/preview/lessons/:lessonId | Sí (CONTENT_ADMIN / SUPER_ADMIN) | Lección individual en modo preview |
GET | /paths/:slug/learn | Sí | Estructura con progreso del usuario (sidebar del player) |
GET | /paths/:slug/enrollment | Sí | Estado de enrolamiento del usuario autenticado |
POST | /paths/:slug/enroll | Sí | Enrolar al usuario; valida límite de inscripciones del plan |
GET | /paths/:slug/reviews | No | Reviews paginadas del path (10 por página) |
GET | /paths/:slug/reviews/mine | Sí | Mi review para el path (null si no existe) |
POST | /paths/:slug/reviews | Sí | Crear review; requiere progreso >= 50 % y no haber revisado antes |
PATCH | /paths/:slug/reviews/mine | Sí | Editar mi review existente |
GET | /paths/:id/preview-image | No | Imagen de preview del path (binaria, con caché 24 h) |
GET | /paths/:id/banner-image | No | Imagen de banner del path (binaria, con caché 24 h) |
Parámetros de filtrado — GET /paths
Section titled “Parámetros de filtrado — GET /paths”| Param | Tipo | Valores |
|---|---|---|
role | string | rol profesional (ej. Ventas) |
industry | string | sector (ej. Tecnología) |
access | enum | free · paid · all |
level | string | Principiante · Intermedio · Avanzado |
q | string | búsqueda full-text en título y descripción |
sort | enum | popular (defecto) · az · hrs · new |
cert | enum | all · yes |
User Stories
Section titled “User Stories”US-CAT-001: Explorar el catálogo público sin autenticarse
Section titled “US-CAT-001: Explorar el catálogo público sin autenticarse”Como visitante no registrado Quiero ver el catálogo completo de rutas de aprendizaje Para descubrir qué ofrece la plataforma antes de registrarme
Criterios: AC-CAT-001 · Casos: TC-CAT-001, TC-CAT-002 · Prioridad: Alta · ✅ Implementado
US-CAT-002: Filtrar y buscar paths en el catálogo
Section titled “US-CAT-002: Filtrar y buscar paths en el catálogo”Como visitante o usuario autenticado Quiero filtrar el catálogo por rol, sector, nivel, duración, acceso y certificado, y buscar por texto libre Para encontrar rutas relevantes para mi perfil profesional sin revisar todo el catálogo
Criterios: AC-CAT-002 · Casos: TC-CAT-003, TC-CAT-004 · Prioridad: Alta · ✅ Implementado
US-CAT-003: Ver el detalle de un path antes de enrolarme
Section titled “US-CAT-003: Ver el detalle de un path antes de enrolarme”Como visitante o usuario autenticado Quiero ver la descripción completa, módulos, instructor, valoraciones y condiciones de acceso de un path Para tomar una decisión informada antes de inscribirme
Criterios: AC-CAT-003 · Casos: TC-CAT-005, TC-CAT-006 · Prioridad: Alta · ✅ Implementado
US-CAT-004: Enrolarme en un path según mi nivel de acceso
Section titled “US-CAT-004: Enrolarme en un path según mi nivel de acceso”Como usuario autenticado Quiero inscribirme en un path que esté incluido en mi plan Para comenzar a aprender y acceder al contenido de las lecciones
Criterios: AC-CAT-004 · Casos: TC-CAT-007, TC-CAT-008, TC-CAT-009 · Prioridad: Alta · ✅ Implementado
US-CAT-005: Ver CTA de upgrade cuando el path requiere plan superior
Section titled “US-CAT-005: Ver CTA de upgrade cuando el path requiere plan superior”Como usuario con plan FREE o STARTER Quiero ver claramente qué plan necesito para acceder a un path bloqueado Para saber qué tengo que contratar si quiero acceder al contenido
Criterios: AC-CAT-005 · Casos: TC-CAT-010 · Prioridad: Alta · ✅ Implementado
US-CAT-006: No poder enrolarme si he alcanzado el límite del plan
Section titled “US-CAT-006: No poder enrolarme si he alcanzado el límite del plan”Como usuario FREE con 0 inscripciones disponibles restantes Quiero recibir un mensaje claro indicando que he llegado al límite Para entender que debo mejorar mi plan para inscribirme en más rutas
Criterios: AC-CAT-006 · Casos: TC-CAT-011 · Prioridad: Alta · ✅ Implementado
US-CAT-007: Dejar una review en un path con suficiente progreso
Section titled “US-CAT-007: Dejar una review en un path con suficiente progreso”Como usuario enrolado que ha completado al menos el 50 % del path Quiero dejar una valoración de 1-5 estrellas con comentario opcional Para ayudar a otros usuarios a elegir contenido de calidad
Criterios: AC-CAT-007 · Casos: TC-CAT-012, TC-CAT-013 · Prioridad: Media · ✅ Implementado
US-CAT-008: Editar mi review existente
Section titled “US-CAT-008: Editar mi review existente”Como usuario que ya dejó una review Quiero poder editar mi puntuación o comentario Para reflejar un cambio de opinión después de completar el path
Criterios: AC-CAT-008 · Casos: TC-CAT-014 · Prioridad: Media · ✅ Implementado
US-CAT-009: Ver el modo preview de un path como creator
Section titled “US-CAT-009: Ver el modo preview de un path como creator”Como CONTENT_ADMIN o SUPER_ADMIN Quiero previsualizar el path con todos sus módulos (incluso no publicados) tal como lo verá el estudiante Para revisar la presentación del contenido antes de publicarlo
Criterios: AC-CAT-009 · Casos: TC-CAT-015, TC-CAT-016 · Prioridad: Media · ✅ Implementado
Criterios de Aceptación
Section titled “Criterios de Aceptación”AC-CAT-001: Catálogo público accesible sin autenticación
Section titled “AC-CAT-001: Catálogo público accesible sin autenticación”US: US-CAT-001
AC-CAT-001.1 — Catálogo carga sin sesión
Dado que soy un visitante no registradoCuando navego a "/catalog"Entonces veo la lista de rutas publicadas (isPublished: true)Y no se me solicita autenticaciónY cada tarjeta muestra: título, descripción (2 líneas), nivel, horas, número de módulos, access level badgeAC-CAT-001.2 — Solo paths publicados son visibles
Dado que existe un path con isPublished: falseCuando accedo a "GET /paths"Entonces ese path NO aparece en el listadoY la respuesta HTTP es 200AC-CAT-001.3 — Paths de organizaciones no aparecen en el catálogo público
Dado que existe un path con organizationId != nullCuando accedo a "GET /paths" sin autenticaciónEntonces ese path NO aparece en el catálogo públicoAC-CAT-002: Filtrado y búsqueda del catálogo
Section titled “AC-CAT-002: Filtrado y búsqueda del catálogo”US: US-CAT-002
AC-CAT-002.1 — Filtro por acceso (free/paid)
Dado que el catálogo contiene paths con accessLevel PUBLIC, FREE_REGISTERED y PAID_PROCuando aplico el filtro "Gratuito" (access=free)Entonces solo veo paths con accessLevel PUBLIC o FREE_REGISTEREDY los paths PAID_PRO no aparecen en los resultadosAC-CAT-002.2 — Búsqueda full-text
Dado que hay un path con título "IA para Ventas"Cuando escribo "ventas" en el campo de búsquedaEntonces ese path aparece en los resultadosY los paths sin "ventas" en título, descripción ni roles no aparecenAC-CAT-002.3 — Ordenación: sin resultados
Dado que busco un texto que no coincide con ningún pathEntonces veo el estado vacío con el mensaje "Sin resultados"Y el botón "Limpiar filtros" está visible y funcionalAC-CAT-002.4 — Cambio de filtro reinicia la paginación a página 1
Dado que estoy en la página 2 del catálogoCuando cambio cualquier filtroEntonces vuelvo a la página 1 automáticamenteAC-CAT-003: Detalle de un path
Section titled “AC-CAT-003: Detalle de un path”US: US-CAT-003
AC-CAT-003.1 — Página de detalle accesible sin autenticación
Dado que existe un path publicado con slug "ia-para-ventas"Cuando accedo a "/paths/ia-para-ventas" sin autenticaciónEntonces veo: título, descripción larga, access badge, nivel de dificultadY veo el accordion de módulos con conteo de lecciones por móduloY veo la información del instructor (si es público)Y el CTA muestra "Registrarse gratis"AC-CAT-003.2 — Path no encontrado devuelve 404
Dado que accedo a "GET /paths/slug-inexistente"Entonces la API responde con status 404Y en la UI veo el estado de error "Ruta no encontrada"AC-CAT-003.3 — Path no publicado no es accesible públicamente
Dado que existe un path con isPublished: false y slug "borrador-path"Cuando accedo a "GET /paths/borrador-path"Entonces la API responde con status 404AC-CAT-004: Enrolamiento en un path
Section titled “AC-CAT-004: Enrolamiento en un path”US: US-CAT-004
AC-CAT-004.1 — Enrolamiento exitoso en path gratuito
Dado que soy usuario FREE autenticadoY existe un path con accessLevel FREE_REGISTEREDY no estoy inscrito en ese pathCuando hago POST a "/paths/:slug/enroll"Entonces la API responde con status 201 y { enrolled: true, pathId, pathTitle }Y soy redirigido a "/learn/:slug"Y recibo una notificación de inscripción exitosaAC-CAT-004.2 — Doble enrolamiento retorna 409
Dado que ya estoy inscrito en el pathCuando intento hacer POST a "/paths/:slug/enroll" de nuevoEntonces la API responde con status 409 y código "Already enrolled"Y en la UI soy redirigido directamente a "/learn/:slug"AC-CAT-004.3 — Visitante no autenticado redirigido al registro
Dado que no tengo sesión activaCuando hago clic en "Registrarse gratis" en la página de detalle de un pathEntonces soy redirigido a "/auth/register"AC-CAT-005: CTA de upgrade para contenido bloqueado
Section titled “AC-CAT-005: CTA de upgrade para contenido bloqueado”US: US-CAT-005
AC-CAT-005.1 — Tarjeta muestra plan requerido cuando el usuario no tiene acceso
Dado que soy usuario FREE (nivel numérico 1)Y existe un path con accessLevel PAID_PRO (nivel numérico 3)Cuando veo la tarjeta de ese path en el catálogoEntonces el botón muestra "Requiere Plan Pro" con ícono de candadoY al hacer clic soy redirigido a "/pricing"AC-CAT-005.2 — Usuario con nivel suficiente no ve el candado
Dado que soy usuario PROY existe un path con accessLevel PAID_PROCuando veo la tarjeta de ese pathEntonces el botón muestra "Empezar →" sin candadoAC-CAT-006: Límite de inscripciones del plan
Section titled “AC-CAT-006: Límite de inscripciones del plan”US: US-CAT-006
AC-CAT-006.1 — Enrolamiento rechazado al alcanzar el límite
Dado que soy usuario FREE con maxEnrollments = 3Y ya tengo 3 paths inscritosCuando intento hacer POST a "/paths/:slug/enroll" en un path nuevoEntonces la API responde con status 403 y código "ENROLLMENT_LIMIT_REACHED"Y el body incluye { limit: 3, current: 3 }AC-CAT-006.2 — Banner de límite alcanzado en AppCatalogPage
Dado que soy usuario FREE y he alcanzado mi límite de inscripcionesCuando visito "/dashboard/catalog"Entonces veo un banner rojo indicando "Has alcanzado el límite de N inscripciones"Y aparece el enlace "Mejorar plan →" que apunta a "/pricing"Y los botones "Empezar" de paths no inscritos aparecen deshabilitados con tooltip "Límite de inscripciones alcanzado"AC-CAT-007: Crear review en un path
Section titled “AC-CAT-007: Crear review en un path”US: US-CAT-007
AC-CAT-007.1 — Review creada con progreso suficiente
Dado que soy usuario autenticado e inscrito en el path "ia-para-ventas"Y he completado al menos el 50 % de las lecciones publicadas del pathCuando hago POST a "/paths/ia-para-ventas/reviews" con { rating: 5, comment: "Excelente" }Entonces la API responde con status 201 y los datos de la reviewY el avgRating y reviewCount del path se recalculan automáticamenteAC-CAT-007.2 — Review rechazada con menos del 50 % de progreso
Dado que tengo menos del 50 % del path completadoCuando intento crear una reviewEntonces la API responde con status 403 y código "INSUFFICIENT_PROGRESS"Y el body incluye { currentPct: <número>, message: "Complete at least 50%..." }AC-CAT-007.3 — Review duplicada retorna 409
Dado que ya existe una review mía para este pathCuando intento crear otra reviewEntonces la API responde con status 409 y código "REVIEW_EXISTS"AC-CAT-008: Editar mi review existente
Section titled “AC-CAT-008: Editar mi review existente”US: US-CAT-008
AC-CAT-008.1 — Actualización exitosa de rating y comentario
Dado que tengo una review existente con rating 3 para el path "ia-para-ventas"Cuando hago PATCH a "/paths/ia-para-ventas/reviews/mine" con { rating: 4, comment: "Mejoró" }Entonces la API responde con status 200 y los datos actualizadosY el avgRating del path se recalcula con el nuevo valorAC-CAT-008.2 — Edición rechazada si no existe review previa
Dado que nunca he dejado review en este pathCuando intento PATCH "/paths/:slug/reviews/mine"Entonces la API responde con status 404AC-CAT-009: Preview de un path como creator
Section titled “AC-CAT-009: Preview de un path como creator”US: US-CAT-009
AC-CAT-009.1 — CONTENT_ADMIN puede ver preview solo de sus propios paths
Dado que soy CONTENT_ADMIN y el path "ia-para-ventas" me perteneceCuando accedo a "GET /paths/ia-para-ventas/preview"Entonces la API responde con status 200 e incluye módulos no publicadosAC-CAT-009.2 — CONTENT_ADMIN no puede previsualizar paths de otro creator
Dado que soy CONTENT_ADMIN y el path "otro-path" pertenece a otro creatorCuando accedo a "GET /paths/otro-path/preview"Entonces la API responde con status 403AC-CAT-009.3 — Estudiante no puede acceder al endpoint preview
Dado que soy usuario FREE autenticadoCuando accedo a "GET /paths/:slug/preview"Entonces la API responde con status 403Test Cases
Section titled “Test Cases”TC-CAT-001: Catálogo carga sin autenticación
Section titled “TC-CAT-001: Catálogo carga sin autenticación”AC: AC-CAT-001.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
Precondiciones: Existe al menos un path publicado en la BD de test
Pasos:
- Navegar a
/catalogsin sesión activa (contexto anónimo) - Esperar que los cards se rendericen
Resultado esperado: La página muestra al menos un PathCard. No hay redirección al login. El contador de resultados muestra al menos “1 ruta”.
// sin fixtures — contexto anónimo de Playwrightconst page = await context.newPage()await page.goto('/catalog')await expect(page.locator('[data-testid="path-card"]').first()).toBeVisible()await expect(page).not.toHaveURL(/\/login/)TC-CAT-002: Path de organización no aparece en catálogo público
Section titled “TC-CAT-002: Path de organización no aparece en catálogo público”AC: AC-CAT-001.3 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// Verificar a nivel API que organizationId != null excluye el pathconst res = await apiClient.get('/paths')expect(res.status).toBe(200)const corporateLeaked = res.data.some((p: { organizationId: string | null }) => p.organizationId !== null)expect(corporateLeaked).toBe(false)TC-CAT-003: Filtrar por acceso — solo gratuitos
Section titled “TC-CAT-003: Filtrar por acceso — solo gratuitos”AC: AC-CAT-002.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
const res = await apiClient.get('/paths?access=free')expect(res.status).toBe(200)const PAID_LEVELS = ['PAID_STARTER', 'PAID_PRO', 'PAID_ELITE', 'CORPORATE']const hasPaidPath = res.data.some((p: { accessLevel: string }) => PAID_LEVELS.includes(p.accessLevel))expect(hasPaidPath).toBe(false)TC-CAT-004: Búsqueda full-text sin resultados
Section titled “TC-CAT-004: Búsqueda full-text sin resultados”AC: AC-CAT-002.3 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// data-testid: 'catalog-search', 'catalog-empty-state', 'catalog-clear-filters'await page.getByTestId('catalog-search').fill('xyzw123nonexistent')await expect(page.getByTestId('catalog-empty-state')).toBeVisible()await page.getByTestId('catalog-clear-filters').click()await expect(page.getByTestId('catalog-empty-state')).not.toBeVisible()TC-CAT-005: Detalle de path accesible sin autenticación
Section titled “TC-CAT-005: Detalle de path accesible sin autenticación”AC: AC-CAT-003.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
Precondiciones: Existe al menos un path publicado con slug conocido en la BD de test
// Obtener slug del primer path del catálogoconst catalogRes = await apiClient.get('/paths')expect(catalogRes.status).toBe(200)const slug: string = catalogRes.data[0].slug
// Verificar detalle accesible sin authconst detailRes = await apiClient.get(`/paths/${slug}`)expect(detailRes.status).toBe(200)expect(detailRes.data).toMatchObject({ title: expect.any(String), slug, accessLevel: expect.any(String), modules: expect.any(Array),})TC-CAT-006: Path no encontrado — 404
Section titled “TC-CAT-006: Path no encontrado — 404”AC: AC-CAT-003.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
const res = await apiClient.get('/paths/slug-que-no-existe-jamas')expect(res.status).toBe(404)TC-CAT-007: Enrolamiento exitoso — path gratuito
Section titled “TC-CAT-007: Enrolamiento exitoso — path gratuito”AC: AC-CAT-004.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
Precondiciones: fixtures.free existe; hay al menos un path con accessLevel: FREE_REGISTERED; el usuario no está inscrito
await loginAs(page, fixtures.free)
// Obtener un path gratuito no inscritoconst res = await apiClient.get('/paths?access=free', { headers: authHeader(fixtures.free) })const freePath = res.data[0]
await page.goto(`/paths/${freePath.slug}`)// data-testid: 'enroll-cta-button'await page.getByTestId('enroll-cta-button').click()
// Debe redirigir al playerawait expect(page).toHaveURL(new RegExp(`/learn/${freePath.slug}`))
// Verificar estado en APIconst enrollment = await apiClient.get(`/paths/${freePath.slug}/enrollment`, { headers: authHeader(fixtures.free),})expect(enrollment.data.enrolled).toBe(true)TC-CAT-008: Doble enrolamiento redirige al player
Section titled “TC-CAT-008: Doble enrolamiento redirige al player”AC: AC-CAT-004.2 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// El fixture.starter ya está inscrito en el path de pruebaconst firstEnroll = await apiClient.post(`/paths/${testPathSlug}/enroll`, {}, { headers: authHeader(fixtures.starter),})// Primera inscripción — puede ser 201 o 409 si ya existía en un run anteriorexpect([201, 409]).toContain(firstEnroll.status)
const secondEnroll = await apiClient.post(`/paths/${testPathSlug}/enroll`, {}, { headers: authHeader(fixtures.starter),})expect(secondEnroll.status).toBe(409)TC-CAT-009: Visitante sin sesión redirigido al registro al intentar enrolarse
Section titled “TC-CAT-009: Visitante sin sesión redirigido al registro al intentar enrolarse”AC: AC-CAT-004.3 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// fixtures.anonymous — sin sesiónconst page = await context.newPage()await page.goto(`/paths/${testPathSlug}`)// data-testid: 'enroll-cta-button' — anónimo ve "Registrarse gratis"await page.getByTestId('enroll-cta-button').click()await expect(page).toHaveURL(/\/auth\/register/)TC-CAT-010: CTA de upgrade en tarjeta bloqueada
Section titled “TC-CAT-010: CTA de upgrade en tarjeta bloqueada”AC: AC-CAT-005.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
Precondiciones: Existe un path con accessLevel: PAID_PRO en el catálogo
await loginAs(page, fixtures.free) // rol FREE, nivel numérico 1
// Buscar un path de pago PROconst res = await apiClient.get('/paths?access=paid')const proPaths = res.data.filter((p: { accessLevel: string }) => p.accessLevel === 'PAID_PRO')expect(proPaths.length).toBeGreaterThan(0)
await page.goto('/catalog')// data-testid: 'path-card-upgrade-btn' (visible solo cuando locked=true)const upgradeBtn = page.getByTestId('path-card-upgrade-btn').first()await expect(upgradeBtn).toBeVisible()await upgradeBtn.click()await expect(page).toHaveURL(/\/pricing/)TC-CAT-011: Enrolamiento rechazado por límite de plan
Section titled “TC-CAT-011: Enrolamiento rechazado por límite de plan”AC: AC-CAT-006.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// Asumiendo que fixtures.free tiene el límite agotado en la BD de testconst res = await apiClient.post(`/paths/${anotherPathSlug}/enroll`, {}, { headers: authHeader(fixtures.free),})expect(res.status).toBe(403)expect(res.data.code).toBe('ENROLLMENT_LIMIT_REACHED')expect(res.data).toMatchObject({ limit: expect.any(Number), current: expect.any(Number),})TC-CAT-012: Crear review con progreso suficiente
Section titled “TC-CAT-012: Crear review con progreso suficiente”AC: AC-CAT-007.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// fixtures.pro asumido con >= 50% completado en testPathSlugconst res = await apiClient.post(`/paths/${testPathSlug}/reviews`, { rating: 4, comment: 'Muy útil para el día a día',}, { headers: authHeader(fixtures.pro) })
expect(res.status).toBe(201)expect(res.data).toMatchObject({ id: expect.any(String), rating: 4, comment: 'Muy útil para el día a día', pathId: expect.any(String),})TC-CAT-013: Review rechazada con progreso insuficiente
Section titled “TC-CAT-013: Review rechazada con progreso insuficiente”AC: AC-CAT-007.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// fixtures.starter asumido con < 50% completado en testPathSlugconst res = await apiClient.post(`/paths/${testPathSlug}/reviews`, { rating: 5, comment: 'Intentando dejar review sin completar',}, { headers: authHeader(fixtures.starter) })
expect(res.status).toBe(403)expect(res.data.code).toBe('INSUFFICIENT_PROGRESS')expect(res.data.currentPct).toBeLessThan(50)TC-CAT-014: Editar review existente
Section titled “TC-CAT-014: Editar review existente”AC: AC-CAT-008.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// fixtures.pro ya tiene review creada (ver TC-CAT-012)const res = await apiClient.patch(`/paths/${testPathSlug}/reviews/mine`, { rating: 5, comment: 'Actualización: aún mejor de lo que pensaba',}, { headers: authHeader(fixtures.pro) })
expect(res.status).toBe(200)expect(res.data.rating).toBe(5)expect(res.data.comment).toBe('Actualización: aún mejor de lo que pensaba')
// Verificar que la media del path se actualizóconst pathRes = await apiClient.get(`/paths/${testPathSlug}`)expect(pathRes.data.avgRating).toBeGreaterThan(0)TC-CAT-015: Preview bloqueado para usuario sin rol correcto
Section titled “TC-CAT-015: Preview bloqueado para usuario sin rol correcto”AC: AC-CAT-009.3 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/student/catalog.spec.ts
// fixtures.free — rol FREE, no puede acceder al preview de creatorconst res = await apiClient.get(`/paths/${testPathSlug}/preview`, { headers: authHeader(fixtures.free),})expect(res.status).toBe(403)TC-CAT-016: Preview retorna módulos no publicados para SUPER_ADMIN
Section titled “TC-CAT-016: Preview retorna módulos no publicados para SUPER_ADMIN”AC: AC-CAT-009.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/admin/paths-preview.spec.ts
// fixtures.superAdmin — SUPER_ADMIN puede previsualizar cualquier pathconst publicRes = await apiClient.get(`/paths/${testPathSlug}`)const previewRes = await apiClient.get(`/paths/${testPathSlug}/preview`, { headers: authHeader(fixtures.superAdmin),})
expect(previewRes.status).toBe(200)// El preview incluye módulos no publicados — el array puede ser más largoexpect(previewRes.data.modules.length).toBeGreaterThanOrEqual(publicRes.data.modules.length)