Reportes Corporativos
Módulos API: organizations · Páginas web: OrgReportsPage
Spec Playwright: apps/e2e/tests/corporate/reports.spec.ts — 📋 pendiente
Módulos API cubiertos
Section titled “Módulos API cubiertos”| Endpoint | Método | Descripción |
|---|---|---|
GET /corporate/dashboard | GET | Dashboard corporativo: métricas del equipo, alertas de churn, top miembros, paths asignados |
GET /organizations/:orgId/stats | GET | KPIs de la organización: miembros totales, activos, en riesgo, score promedio |
GET /organizations/:orgId/analytics/skills-gap | GET | Análisis de brecha de competencias por dimensión (requiere feature gapDetection) |
GET /organizations/:orgId/reports/:type | GET | Descarga de reporte por tipo (executive-summary, skills-gap, individual-progress, certificates, hr) |
GET /organizations/:orgId/activity/export | GET | Exportar log de actividad en CSV (encola job en BullMQ) |
GET /organizations/:orgId/members/export | GET | Exportar miembros con métricas como CSV |
GET /organizations/:orgId/features | GET | Features del plan activo (controla qué reportes están disponibles) |
User Stories
Section titled “User Stories”US-REP-001: Ver el dashboard corporativo con métricas del equipo
Section titled “US-REP-001: Ver el dashboard corporativo con métricas del equipo”Como CORPORATE_ADMIN Quiero ver un dashboard con las métricas clave de mi equipo (miembros activos, lecciones esta semana, certificados del mes, score promedio) Para tener una visión general del rendimiento de la organización sin necesidad de navegar por múltiples pantallas
Criterios: AC-REP-001 · Casos: TC-REP-001, TC-REP-002 · Prioridad: Alta · ✅ Implementado
US-REP-002: Ver KPIs de la organización
Section titled “US-REP-002: Ver KPIs de la organización”Como CORPORATE_ADMIN Quiero consultar los indicadores clave (total de miembros, activos en los últimos 7 días, miembros en riesgo, score de fluidez promedio) Para tomar decisiones de gestión formativa basadas en datos actualizados
Criterios: AC-REP-002 · Casos: TC-REP-003 · Prioridad: Alta · ✅ Implementado
US-REP-003: Ver análisis de brecha de competencias del equipo
Section titled “US-REP-003: Ver análisis de brecha de competencias del equipo”Como CORPORATE_ADMIN con plan que incluye gapDetection
Quiero ver el nivel actual vs. objetivo en cada dimensión de fluidez (Comprensión, Aplicación, Integración, Evaluación, Creación)
Para identificar en qué áreas el equipo está por debajo del objetivo y priorizar asignaciones de contenido
Criterios: AC-REP-003 · Casos: TC-REP-004, TC-REP-005 · Prioridad: Alta · ✅ Implementado
US-REP-004: Exportar listado de miembros con métricas en CSV
Section titled “US-REP-004: Exportar listado de miembros con métricas en CSV”Como CORPORATE_ADMIN Quiero descargar un CSV con todos los miembros del equipo incluyendo nombre, email, score, estado y paths asignados Para compartir el informe con RR.HH. o auditores sin necesidad de herramientas adicionales
Criterios: AC-REP-004 · Casos: TC-REP-006, TC-REP-007 · Prioridad: Alta · ✅ Implementado
US-REP-005: Descargar reportes formales por tipo y formato
Section titled “US-REP-005: Descargar reportes formales por tipo y formato”Como CORPORATE_ADMIN con plan que incluye advancedReports
Quiero solicitar la generación de reportes formales (resumen ejecutivo, progreso individual, certificados, RR.HH.) en formato PDF, XLSX o CSV según el tipo
Para presentar datos de formación a la dirección de la empresa en formatos profesionales
Criterios: AC-REP-005 · Casos: TC-REP-008, TC-REP-009 · Prioridad: Media · ✅ Implementado (stub — generación real encola via BullMQ)
US-REP-006: Ver alertas de miembros en riesgo de churn
Section titled “US-REP-006: Ver alertas de miembros en riesgo de churn”Como CORPORATE_ADMIN Quiero ver en el dashboard los miembros que llevan más de 14 días sin actividad Para detectar a tiempo quién necesita un nudge o una intervención antes de que abandone el programa
Criterios: AC-REP-006 · Casos: TC-REP-010 · Prioridad: Alta · ✅ Implementado
US-REP-007: Filtrar reportes por período de tiempo
Section titled “US-REP-007: Filtrar reportes por período de tiempo”Como CORPORATE_ADMIN Quiero seleccionar el período de análisis (últimos 7 días, 30 días, trimestral, anual) antes de generar un reporte Para comparar el rendimiento del equipo en distintos horizontes temporales
Criterios: AC-REP-007 · Casos: TC-REP-011 · Prioridad: Media · ✅ Implementado (UI selector; el parámetro period se pasa al endpoint)
US-REP-008: Acceso denegado a usuarios sin rol corporativo
Section titled “US-REP-008: Acceso denegado a usuarios sin rol corporativo”Como sistema
Quiero bloquear el acceso a la página de reportes y a los endpoints de analíticas para cualquier rol distinto de CORPORATE_ADMIN o SUPER_ADMIN
Para proteger datos sensibles del equipo corporativo
Criterios: AC-REP-008 · Casos: TC-REP-012, TC-REP-013 · Prioridad: Alta · ✅ Implementado
Criterios de Aceptación
Section titled “Criterios de Aceptación”AC-REP-001: Dashboard corporativo con métricas del equipo
Section titled “AC-REP-001: Dashboard corporativo con métricas del equipo”US: US-REP-001
AC-REP-001.1 — Métricas calculadas correctamente
Dado que soy CORPORATE_ADMIN con organizationId asignadoCuando llamo a "GET /corporate/dashboard"Entonces la API devuelve 200Y la respuesta incluye el objeto "org" con id, name, plan, totalSeats y activeMembersY la respuesta incluye el objeto "stats" con weekLessons, certificates, avgPerMember y newMembersThisMonthY la respuesta incluye el array "topMembers" con hasta 6 elementosY la respuesta incluye el array "assignedPaths" con los paths de la organizaciónY la respuesta incluye el objeto "fluidityScore" con total, goal y sectorAvgAC-REP-001.2 — activeMembers calcula miembros con actividad en los últimos 7 días
Dado que 4 miembros tienen lastActiveAt en los últimos 7 días y 2 no tienen actividadCuando consulto el dashboardEntonces "org.activeMembers" es 4AC-REP-001.3 — weekLessons calcula lecciones completadas en los últimos 7 días
Dado que el equipo completó 12 lecciones en los últimos 7 díasCuando consulto el dashboardEntonces "stats.weekLessons" es 12Y "stats.avgPerMember" es weekLessons dividido entre el número total de miembrosAC-REP-002: KPIs de la organización
Section titled “AC-REP-002: KPIs de la organización”US: US-REP-002
AC-REP-002.1 — Estructura de respuesta completa
Dado que soy CORPORATE_ADMINCuando llamo a "GET /organizations/:orgId/stats"Entonces la API devuelve 200Y el objeto contiene: totalMembers, activeCount, atRiskCount, avgScore, avgProgressAC-REP-002.2 — atRiskCount considera inactividad de 14 días
Dado que 3 miembros no tienen actividad en los últimos 14 díasCuando consulto los stats de la organizaciónEntonces "atRiskCount" es 3AC-REP-002.3 — avgScore es el promedio de fluidityScore del equipo (excluyendo nulls y ceros)
Dado que 3 miembros tienen fluidityScore de 70, 80, 90 y 1 miembro tiene score nullCuando consulto los statsEntonces "avgScore" es 80 (promedio de los 3 scores válidos)AC-REP-003: Análisis de brecha de competencias
Section titled “AC-REP-003: Análisis de brecha de competencias”US: US-REP-003
AC-REP-003.1 — Respuesta con 5 dimensiones cuando la feature está activa
Dado que el plan tiene "gapDetection: true"Y el equipo tiene un avgScore calculadoCuando llamo a "GET /organizations/:orgId/analytics/skills-gap"Entonces la API devuelve 200Y la respuesta incluye "dimensions" con exactamente 5 objetosY cada dimensión tiene: name, current, targetY los nombres son: "Comprensión", "Aplicación", "Integración", "Evaluación", "Creación"AC-REP-003.2 — Error cuando la feature no está activa en el plan
Dado que el plan de la organización tiene "gapDetection: false"Cuando llamo al endpoint de skills-gapEntonces la API devuelve un error indicando que la feature no está disponible en el planAC-REP-003.3 — UI muestra bloqueo si el plan no tiene gapDetection
Dado que soy CORPORATE_ADMIN y el plan no tiene "gapDetection"Cuando navego a "/corporate/reports"Entonces la sección de "Brecha de competencias" muestra el mensaje "Disponible en planes superiores"Y no se realiza la llamada al endpoint de skills-gapAC-REP-004: Exportar miembros como CSV
Section titled “AC-REP-004: Exportar miembros como CSV”US: US-REP-004
AC-REP-004.1 — Respuesta con cabeceras de descarga correctas
Dado que soy CORPORATE_ADMINCuando llamo a "GET /organizations/:orgId/members/export"Entonces la API devuelve 200Y la cabecera "Content-Type" es "text/csv"Y la cabecera "Content-Disposition" incluye "attachment; filename=\"equipo-YYYY-MM-DD.csv\""AC-REP-004.2 — CSV contiene todas las columnas esperadas
Dado que la organización tiene al menos 1 miembroCuando descargo el CSVEntonces la primera línea contiene: Name,Email,Score,Trend,Progress,Status,LastSeen,PathsAssignedY hay una fila por cada miembro de la organizaciónAC-REP-004.3 — Miembros sin actividad aparecen con LastSeen “Never”
Dado que un miembro nunca ha iniciado sesión (lastActiveAt es null)Cuando descargo el CSVEntonces su columna LastSeen muestra "Never"AC-REP-005: Descarga de reportes formales por tipo
Section titled “AC-REP-005: Descarga de reportes formales por tipo”US: US-REP-005
AC-REP-005.1 — Reporte encolado con parámetros correctos
Dado que el plan tiene "advancedReports: true"Cuando llamo a "GET /organizations/:orgId/reports/executive-summary?format=pdf&period=30d"Entonces la API devuelve 200Y la respuesta incluye { message: "Report executive-summary (pdf, 30d) generation queued", orgId }AC-REP-005.2 — Tipos de reporte disponibles y sus formatos
Dado que el plan tiene "advancedReports: true"Los tipos válidos son: executive-summary, skills-gap, individual-progress, team-evolution, certificates, hrY los formatos disponibles según tipo son: - executive-summary: PDF, PPT - skills-gap: PDF, XLSX - individual-progress: PDF, XLSX, CSV - team-evolution: PDF, PPT - certificates: PDF, CSV - hr: PDF, PPT, XLSXAC-REP-005.3 — UI bloquea exportación avanzada sin la feature
Dado que el plan no tiene "advancedReports: true"Cuando navego a "/corporate/reports"Entonces en lugar de las tarjetas de exportación se muestra el mensaje "Disponible en planes superiores"Y el botón "Descargar todos" está presente pero no activa ningún reporte bloqueadoAC-REP-006: Alertas de miembros en riesgo de churn
Section titled “AC-REP-006: Alertas de miembros en riesgo de churn”US: US-REP-006
AC-REP-006.1 — churnAlerts contiene miembros sin actividad en 14 días
Dado que 3 miembros tienen lastActiveAt hace más de 14 días y 2 han estado activos recientementeCuando consulto el dashboard corporativoEntonces "churnAlerts" contiene hasta 5 miembrosY cada alerta incluye: userId, name, initials, reason, daysSinceActivity, riskY "risk" es "high" para todos los incluidos en churnAlertsAC-REP-006.2 — daysSinceActivity es correcto
Dado que un miembro tiene lastActiveAt hace exactamente 20 díasCuando consulto el dashboardEntonces su alerta en churnAlerts muestra daysSinceActivity: 20AC-REP-006.3 — Miembros sin lastActiveAt aparecen en churnAlerts con daysSinceActivity: 999
Dado que un miembro nunca ha iniciado sesión (lastActiveAt es null)Cuando consulto el dashboardEntonces ese miembro aparece en churnAlertsY su daysSinceActivity es 999AC-REP-007: Selector de período en reportes
Section titled “AC-REP-007: Selector de período en reportes”US: US-REP-007
AC-REP-007.1 — El parámetro period se pasa al endpoint de descarga
Dado que soy CORPORATE_ADMIN y selecciono el período "quarterly"Cuando descargo un reporte de tipo "individual-progress"Entonces la llamada al endpoint incluye "?period=quarterly"Y la respuesta contiene el period "quarterly" en el campo messageAC-REP-007.2 — Período por defecto es 30d
Dado que accedo a la página de reportes sin cambiar el selector de períodoCuando se genera un reporteEntonces el endpoint recibe "?period=30d" como valor por defectoAC-REP-008: Control de acceso a reportes corporativos
Section titled “AC-REP-008: Control de acceso a reportes corporativos”US: US-REP-008
AC-REP-008.1 — API devuelve 403 para usuarios sin rol corporativo
Dado que soy usuario con rol FREE, STARTER, PRO, ELITE o CORPORATE_USERCuando llamo a "GET /organizations/:orgId/analytics/skills-gap"Entonces la API devuelve 403Y no recibo ningún dato de la organizaciónAC-REP-008.2 — API devuelve 403 para usuario sin rol corporativo en exportación
Dado que soy usuario con rol CORPORATE_USER (miembro del equipo, no admin)Cuando llamo a "GET /organizations/:orgId/members/export"Entonces la API devuelve 403AC-REP-008.3 — CorporateGuard redirige a /dashboard en el frontend
Dado que soy usuario con rol FREE o CORPORATE_USERCuando navego directamente a "/corporate/reports"Entonces el CorporateGuard me redirige a "/dashboard"Y no se renderiza ningún componente de reportesAC-REP-008.4 — SUPER_ADMIN puede acceder a reportes de cualquier organización
Dado que soy usuario con rol SUPER_ADMINCuando llamo a "GET /organizations/:orgId/analytics/skills-gap" con cualquier orgIdEntonces la API devuelve 200 (el guard acepta SUPER_ADMIN además de CORPORATE_ADMIN)Test Cases
Section titled “Test Cases”Fixtures utilizados en estos test cases:
// fixtures.corpAdmin → usuario con rol CORPORATE_ADMIN y organizationId asignado// fixtures.corpUser → usuario con rol CORPORATE_USER (miembro del equipo, no admin)// fixtures.free → usuario con rol FREE (sin acceso corporativo)TC-REP-001: Dashboard devuelve estructura completa
Section titled “TC-REP-001: Dashboard devuelve estructura completa”AC: AC-REP-001.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get('/corporate/dashboard')
expect(res.status).toBe(200)
// Estructura de alto nivelexpect(res.data).toHaveProperty('org')expect(res.data).toHaveProperty('stats')expect(res.data).toHaveProperty('fluidityScore')expect(res.data).toHaveProperty('churnAlerts')expect(res.data).toHaveProperty('topMembers')expect(res.data).toHaveProperty('assignedPaths')
// orgexpect(res.data.org).toHaveProperty('id')expect(res.data.org).toHaveProperty('name')expect(res.data.org).toHaveProperty('plan')expect(res.data.org).toHaveProperty('totalSeats')expect(res.data.org).toHaveProperty('activeMembers')expect(typeof res.data.org.activeMembers).toBe('number')
// statsexpect(res.data.stats).toHaveProperty('weekLessons')expect(res.data.stats).toHaveProperty('certificates')expect(res.data.stats).toHaveProperty('avgPerMember')expect(res.data.stats).toHaveProperty('newMembersThisMonth')
// topMembers — máximo 6 elementosexpect(Array.isArray(res.data.topMembers)).toBe(true)expect(res.data.topMembers.length).toBeLessThanOrEqual(6)TC-REP-002: topMembers incluye campos de progreso y actividad
Section titled “TC-REP-002: topMembers incluye campos de progreso y actividad”AC: AC-REP-001.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get('/corporate/dashboard')
expect(res.status).toBe(200)
for (const member of res.data.topMembers) { expect(member).toHaveProperty('id') expect(member).toHaveProperty('name') expect(member).toHaveProperty('initials') expect(member).toHaveProperty('score') expect(member).toHaveProperty('progress') expect(member).toHaveProperty('status') expect(['active', 'inactive', 'idle']).toContain(member.status)}TC-REP-003: KPIs de organización con estructura correcta
Section titled “TC-REP-003: KPIs de organización con estructura correcta”AC: AC-REP-002.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/stats`)
expect(res.status).toBe(200)expect(res.data).toHaveProperty('totalMembers')expect(res.data).toHaveProperty('activeCount')expect(res.data).toHaveProperty('atRiskCount')expect(res.data).toHaveProperty('avgScore')expect(res.data).toHaveProperty('avgProgress')
expect(typeof res.data.totalMembers).toBe('number')expect(typeof res.data.activeCount).toBe('number')expect(typeof res.data.atRiskCount).toBe('number')expect(typeof res.data.avgScore).toBe('number')
// activeCount no puede superar totalMembersexpect(res.data.activeCount).toBeLessThanOrEqual(res.data.totalMembers)// atRiskCount no puede superar totalMembersexpect(res.data.atRiskCount).toBeLessThanOrEqual(res.data.totalMembers)TC-REP-004: Skills gap devuelve 5 dimensiones con targets
Section titled “TC-REP-004: Skills gap devuelve 5 dimensiones con targets”AC: AC-REP-003.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
Precondiciones: El plan de la organización de test tiene gapDetection: true.
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/analytics/skills-gap`)
expect(res.status).toBe(200)expect(Array.isArray(res.data.dimensions)).toBe(true)expect(res.data.dimensions).toHaveLength(5)
const expectedNames = ['Comprensión', 'Aplicación', 'Integración', 'Evaluación', 'Creación']for (const [i, dim] of res.data.dimensions.entries()) { expect(dim.name).toBe(expectedNames[i]) expect(dim).toHaveProperty('current') expect(dim).toHaveProperty('target') expect(typeof dim.current).toBe('number') expect(typeof dim.target).toBe('number') // current y target deben ser valores razonables [0–100] expect(dim.current).toBeGreaterThanOrEqual(0) expect(dim.current).toBeLessThanOrEqual(100) expect(dim.target).toBeGreaterThanOrEqual(0) expect(dim.target).toBeLessThanOrEqual(100)}TC-REP-005: Skills gap bloqueado si el plan no tiene gapDetection (E2E)
Section titled “TC-REP-005: Skills gap bloqueado si el plan no tiene gapDetection (E2E)”AC: AC-REP-003.3 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
Precondiciones: Usar una organización de test cuyo plan tiene gapDetection: false.
await loginAs(page, fixtures.corpAdmin)await page.goto('/corporate/reports')
// La sección de Brecha de competencias muestra bloqueo// data-testid a confirmar en OrgReportsPage: 'skills-gap-section'const gapSection = page.getByText('Disponible en planes superiores')await expect(gapSection).toBeVisible()
// No se hace la llamada al endpoint (no aparece en network)// Verificación alternativa: el contenido de barras de progreso no es visibleconst gapBars = page.getByTestId('skills-gap-bar')await expect(gapBars).toHaveCount(0)TC-REP-006: Exportar CSV de miembros — cabeceras y formato
Section titled “TC-REP-006: Exportar CSV de miembros — cabeceras y formato”AC: AC-REP-004.1 + AC-REP-004.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members/export`, { responseType: 'text', })
expect(res.status).toBe(200)expect(res.headers['content-type']).toContain('text/csv')expect(res.headers['content-disposition']).toMatch( /attachment; filename="equipo-\d{4}-\d{2}-\d{2}\.csv"/)
// Verificar cabeceras del CSVconst lines = (res.data as string).split('\n')expect(lines[0]).toBe('Name,Email,Score,Trend,Progress,Status,LastSeen,PathsAssigned')
// Verificar que hay al menos una fila de datos (además del header)expect(lines.length).toBeGreaterThan(1)TC-REP-007: CSV de miembros — miembro sin actividad muestra “Never”
Section titled “TC-REP-007: CSV de miembros — miembro sin actividad muestra “Never””AC: AC-REP-004.3 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Precondiciones: La organización de test tiene al menos un miembro con lastActiveAt: null.
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members/export`, { responseType: 'text', })
expect(res.status).toBe(200)
// Al menos una fila debe contener "Never" en la columna LastSeenconst csvContent = res.data as stringexpect(csvContent).toContain(',Never,')TC-REP-008: Solicitud de reporte formal devuelve confirmación de encolado
Section titled “TC-REP-008: Solicitud de reporte formal devuelve confirmación de encolado”AC: AC-REP-005.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
const orgId = fixtures.corpAdmin.organizationId
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${orgId}/reports/executive-summary?format=pdf&period=30d`)
expect(res.status).toBe(200)expect(res.data).toHaveProperty('message')expect(res.data.message).toContain('executive-summary')expect(res.data.message).toContain('pdf')expect(res.data.message).toContain('30d')expect(res.data.orgId).toBe(orgId)TC-REP-009: Reportes avanzados bloqueados en UI sin feature (E2E)
Section titled “TC-REP-009: Reportes avanzados bloqueados en UI sin feature (E2E)”AC: AC-REP-005.3 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
Precondiciones: Usar una organización de test cuyo plan tiene advancedReports: false.
await loginAs(page, fixtures.corpAdmin)await page.goto('/corporate/reports')
// Las tarjetas de exportación no son visiblesconst exportCards = page.getByText('Resumen ejecutivo')await expect(exportCards).toHaveCount(0)
// Aparece el mensaje de bloqueoconst lockMessage = page.getByText('Disponible en planes superiores')await expect(lockMessage.first()).toBeVisible()TC-REP-010: churnAlerts incluye miembros inactivos con datos correctos
Section titled “TC-REP-010: churnAlerts incluye miembros inactivos con datos correctos”AC: AC-REP-006.1 + AC-REP-006.3 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get('/corporate/dashboard')
expect(res.status).toBe(200)expect(Array.isArray(res.data.churnAlerts)).toBe(true)// Máximo 5 alertasexpect(res.data.churnAlerts.length).toBeLessThanOrEqual(5)
for (const alert of res.data.churnAlerts) { expect(alert).toHaveProperty('userId') expect(alert).toHaveProperty('name') expect(alert).toHaveProperty('initials') expect(alert).toHaveProperty('reason') expect(alert).toHaveProperty('daysSinceActivity') expect(alert).toHaveProperty('risk') expect(alert.risk).toBe('high') expect(typeof alert.daysSinceActivity).toBe('number') expect(alert.daysSinceActivity).toBeGreaterThanOrEqual(0)}TC-REP-011: Selector de período pasa el parámetro correcto al endpoint
Section titled “TC-REP-011: Selector de período pasa el parámetro correcto al endpoint”AC: AC-REP-007.1 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/reports.spec.ts
await loginAs(page, fixtures.corpAdmin)await page.goto('/corporate/reports')
// Seleccionar período "Trimestral"// data-testid a confirmar en OrgReportsPage: 'period-btn-quarterly'const quarterlyBtn = page.getByText('Trimestral')await quarterlyBtn.click()
// Interceptar la petición al endpoint de reporteconst [request] = await Promise.all([ page.waitForRequest(req => req.url().includes('/reports/') && req.url().includes('quarterly') ), // Hacer clic en un botón de descarga de reporte page.getByText('PDF').first().click(),])
expect(request.url()).toContain('period=quarterly')TC-REP-012: API devuelve 403 para CORPORATE_USER en endpoints de analítica
Section titled “TC-REP-012: API devuelve 403 para CORPORATE_USER en endpoints de analítica”AC: AC-REP-008.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/access-control.spec.ts
const orgId = fixtures.corpAdmin.organizationId
// CORPORATE_USER no puede acceder a skills gapconst gapRes = await apiClient .withToken(fixtures.corpUser.token) .get(`/organizations/${orgId}/analytics/skills-gap`)
expect(gapRes.status).toBe(403)
// CORPORATE_USER no puede exportar CSV de miembrosconst csvRes = await apiClient .withToken(fixtures.corpUser.token) .get(`/organizations/${orgId}/members/export`)
expect(csvRes.status).toBe(403)
// CORPORATE_USER no puede acceder a stats de la organizaciónconst statsRes = await apiClient .withToken(fixtures.corpUser.token) .get(`/organizations/${orgId}/stats`)
expect(statsRes.status).toBe(403)TC-REP-013: CorporateGuard redirige a /dashboard en el frontend
Section titled “TC-REP-013: CorporateGuard redirige a /dashboard en el frontend”AC: AC-REP-008.3 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/access-control.spec.ts
// Usuario FREE no puede acceder a /corporate/reportsawait loginAs(page, fixtures.free)await page.goto('/corporate/reports')await expect(page).toHaveURL(/\/dashboard/)
// CORPORATE_USER (miembro del equipo) tampoco puede acceder al portal corporativoawait loginAs(page, fixtures.corpUser)await page.goto('/corporate/reports')await expect(page).toHaveURL(/\/dashboard/)