Gestión de Equipo
Módulos API: organizations · Páginas web: OrgMembersPage, OrgPathsPage, TeamProgressPage, OrgDashboard
Spec Playwright: apps/e2e/tests/corporate/team.spec.ts — 📋 pendiente
Módulos API cubiertos
Section titled “Módulos API cubiertos”| Endpoint | Método | Descripción |
|---|---|---|
GET /organizations/:orgId/members | GET | Listado de miembros activos con estado y progreso |
GET /organizations/:orgId/members/stats | GET | KPIs de miembros: seats, activos, pendientes |
GET /organizations/:orgId/members/:memberId | GET | Detalle de un miembro individual |
PATCH /organizations/:orgId/members/:memberId | PATCH | Actualizar rol de un miembro |
DELETE /organizations/:orgId/members/:memberId | DELETE | Eliminar miembro (pierde acceso corporativo) |
GET /organizations/:orgId/invitations | GET | Invitaciones pendientes |
POST /organizations/:orgId/invitations | POST | Invitar uno o varios usuarios por email |
POST /organizations/:orgId/invitations/:id/resend | POST | Reenviar invitación pendiente |
GET /organizations/:orgId/paths | GET | Paths asignados a la organización |
POST /organizations/:orgId/paths | POST | Añadir path del catálogo a la organización |
POST /organizations/:orgId/paths/:pathId/assign | POST | Asignar path a miembros específicos |
GET /organizations/:orgId/paths/catalog | GET | Paths del catálogo no añadidos aún |
POST /organizations/:orgId/members/:memberId/nudge | POST | Enviar recordatorio a un miembro |
GET /corporate/dashboard | GET | Dashboard corporativo con métricas y alertas |
GET /organizations/:orgId/members/export | GET | Exportar miembros como CSV |
User Stories
Section titled “User Stories”US-CORP-001: Invitar usuario al equipo corporativo por email
Section titled “US-CORP-001: Invitar usuario al equipo corporativo por email”Como CORPORATE_ADMIN Quiero invitar uno o varios usuarios por email al equipo de mi organización Para que obtengan acceso corporativo a los contenidos de la plataforma
Criterios: AC-CORP-001 · Casos: TC-CORP-001, TC-CORP-002, TC-CORP-003 · Prioridad: Alta · ✅ Implementado
US-CORP-002: Ver estado de las invitaciones enviadas
Section titled “US-CORP-002: Ver estado de las invitaciones enviadas”Como CORPORATE_ADMIN Quiero ver el listado de invitaciones pendientes con su estado y fecha de expiración Para saber qué usuarios aún no han aceptado su invitación
Criterios: AC-CORP-002 · Casos: TC-CORP-004 · Prioridad: Alta · ✅ Implementado
US-CORP-003: Reenviar una invitación pendiente
Section titled “US-CORP-003: Reenviar una invitación pendiente”Como CORPORATE_ADMIN Quiero poder reenviar una invitación que ya emitió sin crear una nueva Para renovar su validez y que el usuario la reciba de nuevo
Criterios: AC-CORP-003 · Casos: TC-CORP-005 · Prioridad: Media · ✅ Implementado
US-CORP-004: Ver listado de miembros activos del equipo
Section titled “US-CORP-004: Ver listado de miembros activos del equipo”Como CORPORATE_ADMIN Quiero ver la lista de miembros activos con su estado, rol y última actividad Para tener visibilidad sobre el equipo y detectar miembros inactivos
Criterios: AC-CORP-004 · Casos: TC-CORP-006, TC-CORP-007 · Prioridad: Alta · ✅ Implementado
US-CORP-005: Eliminar miembro del equipo
Section titled “US-CORP-005: Eliminar miembro del equipo”Como CORPORATE_ADMIN Quiero revocar el acceso de un miembro a la organización Para que deje de consumir un seat y pierda los privilegios corporativos
Criterios: AC-CORP-005 · Casos: TC-CORP-008, TC-CORP-009 · Prioridad: Alta · ✅ Implementado
US-CORP-006: Asignar un path a miembros del equipo
Section titled “US-CORP-006: Asignar un path a miembros del equipo”Como CORPORATE_ADMIN Quiero asignar un path de aprendizaje a uno o varios miembros Para estructurar el plan formativo del equipo y que reciban notificación de la asignación
Criterios: AC-CORP-006 · Casos: TC-CORP-010, TC-CORP-011 · Prioridad: Alta · ✅ Implementado
US-CORP-007: Ver progreso de miembros en asignaciones
Section titled “US-CORP-007: Ver progreso de miembros en asignaciones”Como CORPORATE_ADMIN Quiero ver el progreso individual de cada miembro en los paths asignados Para identificar quiénes están avanzando y quiénes necesitan apoyo
Criterios: AC-CORP-007 · Casos: TC-CORP-012 · Prioridad: Alta · ✅ Implementado
US-CORP-008: Enviar recordatorio (nudge) a un miembro inactivo
Section titled “US-CORP-008: Enviar recordatorio (nudge) a un miembro inactivo”Como CORPORATE_ADMIN Quiero enviar un recordatorio personalizado a un miembro con poca actividad Para re-engancharle en su formación sin necesidad de contactarle por otro canal
Criterios: AC-CORP-008 · Casos: TC-CORP-013 · Prioridad: Media · ✅ Implementado
US-CORP-009: Controlar el límite de seats del plan
Section titled “US-CORP-009: Controlar el límite de seats del plan”Como CORPORATE_ADMIN Quiero recibir una advertencia visual cuando el plan alcance su límite de seats Para no superar la capacidad contratada y saber cuándo ampliar el plan
Criterios: AC-CORP-009 · Casos: TC-CORP-014 · Prioridad: Alta · ✅ Implementado
US-CORP-010: Denegar acceso al portal corporativo a usuarios sin rol adecuado
Section titled “US-CORP-010: Denegar acceso al portal corporativo a usuarios sin rol adecuado”Como sistema
Quiero bloquear el acceso a /corporate/* a usuarios que no sean CORPORATE_ADMIN o SUPER_ADMIN
Para proteger la información del equipo y los datos corporativos
Criterios: AC-CORP-010 · Casos: TC-CORP-015, TC-CORP-016 · Prioridad: Alta · ✅ Implementado
Criterios de Aceptación
Section titled “Criterios de Aceptación”AC-CORP-001: Invitar usuario por email
Section titled “AC-CORP-001: Invitar usuario por email”US: US-CORP-001
AC-CORP-001.1 — Invitación exitosa a un email válido
Dado que soy CORPORATE_ADMIN con seats disponiblesCuando envío una invitación al email "nuevo@empresa.com"Entonces la API devuelve { sent: 1 } con status 201Y aparece una entrada en invitaciones pendientes con status "PENDING"Y la invitación expira en 7 díasAC-CORP-001.2 — Invitación masiva a varios emails
Dado que soy CORPORATE_ADMIN con suficientes seats disponiblesCuando envío invitaciones a ["a@e.com", "b@e.com", "c@e.com"]Entonces la API devuelve { sent: 3 }Y se crean 3 entradas en invitaciones pendientesAC-CORP-001.3 — Error al superar el límite de seats
Dado que el plan tiene 10 seats y ya hay 9 miembros activos + 0 pendientesCuando intento invitar a 2 nuevos emailsEntonces la API devuelve 403 con código "SEATS_LIMIT_REACHED"Y el cuerpo incluye { maxSeats: 10, currentUsed: 9, requested: 2 }Y no se crea ninguna invitaciónAC-CORP-001.4 — Rol del invitado configurable
Dado que envío una invitación con role: "CORPORATE_ADMIN"Cuando el invitado acepta la invitaciónEntonces el usuario obtiene rol CORPORATE_ADMIN en la organizaciónAC-CORP-002: Ver invitaciones pendientes
Section titled “AC-CORP-002: Ver invitaciones pendientes”US: US-CORP-002
AC-CORP-002.1 — Listado de invitaciones
Dado que existen 3 invitaciones con status PENDING en mi organizaciónCuando accedo a "GET /organizations/:orgId/invitations"Entonces la API devuelve exactamente 3 objetosY cada objeto contiene: id, email, role, status, expiresAt, createdAtAC-CORP-002.2 — Solo se muestran invitaciones PENDING
Dado que existen invitaciones con status ACCEPTED y PENDINGCuando consulto las invitacionesEntonces solo recibo las que tienen status "PENDING"AC-CORP-003: Reenviar invitación
Section titled “AC-CORP-003: Reenviar invitación”US: US-CORP-003
AC-CORP-003.1 — Reenvío exitoso
Dado que existe una invitación PENDING con id "inv-123"Cuando llamo a "POST /organizations/:orgId/invitations/inv-123/resend"Entonces la invitación se actualiza con una nueva expiresAt (7 días desde ahora)Y el status permanece en "PENDING"Y la API devuelve el objeto de invitación actualizadoAC-CORP-003.2 — Invitación no encontrada
Dado que el id "inv-999" no existe en la organizaciónCuando intento reenviarlaEntonces la API devuelve 404AC-CORP-004: Ver listado de miembros activos
Section titled “AC-CORP-004: Ver listado de miembros activos”US: US-CORP-004
AC-CORP-004.1 — Listado con estado calculado
Dado que un miembro tiene lastActiveAt hace 3 díasCuando consulto el listado de miembrosEntonces ese miembro aparece con status "active"Y si tiene lastActiveAt hace 15 días, aparece con status "inactive"Y si lleva menos de 7 días en la plataforma, aparece con status "new"AC-CORP-004.2 — Filtro por estado
Dado que el listado tiene miembros con estados activo, inactivo y nuevoCuando llamo con query "?filter=active"Entonces solo se devuelven los miembros con status "active"AC-CORP-004.3 — Búsqueda por nombre o email
Dado que hay un miembro con nombre "Ana García"Cuando llamo con query "?q=ana"Entonces solo se devuelven los miembros cuyo nombre o email contiene "ana" (case-insensitive)AC-CORP-005: Eliminar miembro
Section titled “AC-CORP-005: Eliminar miembro”US: US-CORP-005
AC-CORP-005.1 — Eliminación exitosa
Dado que "miembro-123" pertenece a mi organizaciónCuando llamo a "DELETE /organizations/:orgId/members/miembro-123"Entonces la API devuelve 204Y el usuario queda con organizationId: null y role: "FREE"Y deja de aparecer en el listado de miembros de la organizaciónAC-CORP-005.2 — No puede eliminarse a sí mismo
Dado que soy el CORPORATE_ADMIN con id "admin-001"Cuando llamo a "DELETE /organizations/:orgId/members/admin-001"Entonces la API devuelve 400 con mensaje "Cannot remove yourself"AC-CORP-005.3 — Miembro no pertenece a la organización
Dado que "usuario-ajeno" no pertenece a mi organizaciónCuando intento eliminarloEntonces la API devuelve 404AC-CORP-006: Asignar path a miembros
Section titled “AC-CORP-006: Asignar path a miembros”US: US-CORP-006
AC-CORP-006.1 — Asignación exitosa
Dado que el path "path-abc" existe y el miembro "miembro-001" pertenece a mi organizaciónCuando llamo a "POST /organizations/:orgId/paths/path-abc/assign" con { memberIds: ["miembro-001"] }Entonces la API devuelve { assigned: 1 }Y el path queda añadido a la organización si no estabaY el miembro queda matriculado en el pathAC-CORP-006.2 — Notificación enviada al asignar
Dado que asigno el path "IA en el trabajo" a los miembros ["m1", "m2"]Cuando la asignación se completaEntonces se envía una notificación CORP_PATH_ASSIGNED a cada miembro asignadoY la notificación contiene el título del path y el nombre de la organizaciónAC-CORP-006.3 — Path no existe
Dado que el path "path-inexistente" no existe en la BDCuando intento asignarloEntonces la API devuelve 404AC-CORP-007: Ver progreso en asignaciones
Section titled “AC-CORP-007: Ver progreso en asignaciones”US: US-CORP-007
AC-CORP-007.1 — Progreso visible por path y miembro
Dado que el miembro "miembro-001" tiene 3 paths asignadosCuando accedo al detalle del miembro via "GET /organizations/:orgId/members/:memberId"Entonces la respuesta incluye un array "paths" con cada path asignadoY cada path muestra su título y progresoAC-CORP-007.2 — Dashboard muestra top members con progreso
Dado que mi organización tiene miembros con distintos scores de fluidezCuando accedo al corporate dashboardEntonces la respuesta incluye "topMembers" con los primeros 6 miembrosY cada uno muestra score, progress y lastSeenAtAC-CORP-008: Enviar nudge a miembro inactivo
Section titled “AC-CORP-008: Enviar nudge a miembro inactivo”US: US-CORP-008
AC-CORP-008.1 — Nudge individual exitoso
Dado que el plan tiene la feature "churnPrediction" activadaY el miembro "miembro-001" pertenece a mi organizaciónCuando llamo a "POST /organizations/:orgId/members/miembro-001/nudge"Entonces la API devuelve { message: "Nudge sent to ..." } con status 200AC-CORP-008.2 — Nudge bloqueado si el plan no lo incluye
Dado que el plan de mi organización no tiene la feature "churnPrediction"Cuando intento enviar un nudgeEntonces la API devuelve un error indicando que la feature no está disponible en el planAC-CORP-009: Control del límite de seats
Section titled “AC-CORP-009: Control del límite de seats”US: US-CORP-009
AC-CORP-009.1 — Banner de seats visible con progreso
Dado que mi organización tiene 10 seats y 7 usadosCuando accedo a la página de gestión de miembrosEntonces veo un banner con "7 / 10 usados"Y la barra de progreso muestra un 70% de ocupaciónAC-CORP-009.2 — Botón invitar deshabilitado al 100% de ocupación
Dado que usedSeats + pendingInvites = maxSeatsCuando accedo a la página de gestión de miembrosEntonces el botón "Invitar miembro" está deshabilitadoY al pasar el cursor sobre él se muestra el tooltip "Has alcanzado el límite de asientos del plan"AC-CORP-009.3 — Barra de seats en rojo al superar el 90%
Dado que el ratio usedSeats / maxSeats supera el 90%Cuando visualizo el banner de seatsEntonces la barra de progreso se muestra en rojo en lugar de púrpuraAC-CORP-010: Control de acceso al portal corporativo
Section titled “AC-CORP-010: Control de acceso al portal corporativo”US: US-CORP-010
AC-CORP-010.1 — Acceso denegado a usuario FREE
Dado que soy usuario con rol FREECuando accedo a "/corporate/team"Entonces soy redirigido a "/dashboard"Y no veo ningún dato corporativoAC-CORP-010.2 — Acceso denegado a STARTER y PRO
Dado que soy usuario con rol STARTERCuando accedo directamente a "/corporate/team"Entonces soy redirigido a "/dashboard"Y la API devuelve 403 si intento llamar a "GET /organizations/:orgId/members"AC-CORP-010.3 — CORPORATE_USER no puede acceder al portal de admin corporativo
Dado que soy usuario con rol CORPORATE_USER (miembro del equipo, no admin)Cuando accedo a "/corporate/team"Entonces soy redirigido a "/dashboard"AC-CORP-010.4 — CORPORATE_ADMIN accede correctamente
Dado que soy usuario con rol CORPORATE_ADMIN y tengo organizationId asignadoCuando accedo a "/corporate/team"Entonces veo la página de gestión de miembros sin redirecciónTest 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 ya en el equipo)// fixtures.free → usuario con rol FREE (a invitar o sin acceso corporativo)// fixtures.starter → usuario con rol STARTER (sin acceso corporativo)TC-CORP-001: Invitación exitosa a un email
Section titled “TC-CORP-001: Invitación exitosa a un email”AC: AC-CORP-001.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/invitations.spec.ts
const newEmail = `qa-corp-invite-${Date.now()}@nappai.test`
const res = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`, { emails: [newEmail], role: 'CORPORATE_USER', })
expect(res.status).toBe(201)expect(res.data).toEqual({ sent: 1 })
// Verificar que la invitación aparece en el listadoconst list = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`)
expect(list.data).toContainEqual( expect.objectContaining({ email: newEmail, status: 'PENDING' }))TC-CORP-002: Invitación masiva a varios emails
Section titled “TC-CORP-002: Invitación masiva a varios emails”AC: AC-CORP-001.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const emails = [ `qa-bulk-a-${Date.now()}@nappai.test`, `qa-bulk-b-${Date.now()}@nappai.test`, `qa-bulk-c-${Date.now()}@nappai.test`,]
const res = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`, { emails })
expect(res.status).toBe(201)expect(res.data.sent).toBe(3)TC-CORP-003: Error al superar el límite de seats
Section titled “TC-CORP-003: Error al superar el límite de seats”AC: AC-CORP-001.3 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Precondiciones: La organización de test debe tener el plan con maxSeats conocido y estar al límite.
// Usar una organización de test configurada al límite de seatsconst res = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`, { emails: ['overflow1@nappai.test', 'overflow2@nappai.test'], })
expect(res.status).toBe(403)expect(res.data.code).toBe('SEATS_LIMIT_REACHED')expect(res.data).toHaveProperty('maxSeats')expect(res.data).toHaveProperty('currentUsed')expect(res.data).toHaveProperty('requested')TC-CORP-004: Listado de invitaciones solo muestra PENDING
Section titled “TC-CORP-004: Listado de invitaciones solo muestra PENDING”AC: AC-CORP-002.1 + AC-CORP-002.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`)
expect(res.status).toBe(200)expect(Array.isArray(res.data)).toBe(true)
// Todas las invitaciones devueltas tienen status PENDINGfor (const inv of res.data) { expect(inv.status).toBe('PENDING') expect(inv).toHaveProperty('email') expect(inv).toHaveProperty('role') expect(inv).toHaveProperty('expiresAt') expect(inv).toHaveProperty('createdAt')}TC-CORP-005: Reenviar invitación renueva expiración
Section titled “TC-CORP-005: Reenviar invitación renueva expiración”AC: AC-CORP-003.1 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
// Crear una invitación primeroconst createRes = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`, { emails: [`qa-resend-${Date.now()}@nappai.test`], })expect(createRes.data.sent).toBe(1)
// Obtener el id de la invitación creadaconst listRes = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/invitations`)const inv = listRes.data[0]const originalExpiry = inv.expiresAt
// Reenviarconst resendRes = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/invitations/${inv.id}/resend`)
expect(resendRes.status).toBe(201)expect(resendRes.data.status).toBe('PENDING')
// La nueva expiración es >= a la original (renovada en 7 días)expect(new Date(resendRes.data.expiresAt).getTime()).toBeGreaterThanOrEqual( new Date(originalExpiry).getTime())TC-CORP-006: Listado de miembros con filtro de estado
Section titled “TC-CORP-006: Listado de miembros con filtro de estado”AC: AC-CORP-004.1 + AC-CORP-004.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/members.spec.ts
const allRes = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members`)
expect(allRes.status).toBe(200)expect(Array.isArray(allRes.data)).toBe(true)
// Verificar que cada miembro tiene los campos esperadosfor (const member of allRes.data) { expect(member).toHaveProperty('id') expect(member).toHaveProperty('name') expect(member).toHaveProperty('email') expect(member).toHaveProperty('role') expect(member).toHaveProperty('status') expect(['active', 'risk', 'inactive', 'new']).toContain(member.status)}
// Filtro por activosconst activeRes = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members?filter=active`)
for (const m of activeRes.data) { expect(m.status).toBe('active')}TC-CORP-007: Búsqueda de miembros por nombre o email (E2E)
Section titled “TC-CORP-007: Búsqueda de miembros por nombre o email (E2E)”AC: AC-CORP-004.3 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/members.spec.ts
await loginAs(page, fixtures.corpAdmin)await page.goto('/corporate/team')
// data-testid a confirmar en OrgMembersPage:// 'members-search', 'member-row'const searchInput = page.getByTestId('members-search')await searchInput.fill(fixtures.corpUser.email.split('@')[0])
await expect(page.getByTestId('member-row')).toHaveCount(1)TC-CORP-008: Eliminación de miembro — flujo completo
Section titled “TC-CORP-008: Eliminación de miembro — flujo completo”AC: AC-CORP-005.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
// Verificar que el miembro existe antes de eliminarconst beforeRes = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members/${fixtures.corpUser.id}`)expect(beforeRes.status).toBe(200)
// Eliminarconst deleteRes = await apiClient .withToken(fixtures.corpAdmin.token) .delete(`/organizations/${fixtures.corpAdmin.organizationId}/members/${fixtures.corpUser.id}`)expect(deleteRes.status).toBe(204)
// Verificar que ya no aparece en la organizaciónconst afterRes = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members/${fixtures.corpUser.id}`)expect(afterRes.status).toBe(404)TC-CORP-009: Admin no puede eliminarse a sí mismo
Section titled “TC-CORP-009: Admin no puede eliminarse a sí mismo”AC: AC-CORP-005.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient .withToken(fixtures.corpAdmin.token) .delete(`/organizations/${fixtures.corpAdmin.organizationId}/members/${fixtures.corpAdmin.id}`)
expect(res.status).toBe(400)TC-CORP-010: Asignación de path a miembros
Section titled “TC-CORP-010: Asignación de path a miembros”AC: AC-CORP-006.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/paths.spec.ts
// Primero añadir el path al catálogo de la organización si no estáawait apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/paths`, { pathId: fixtures.testPathId, })
// Asignar a un miembroconst assignRes = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/paths/${fixtures.testPathId}/assign`, { memberIds: [fixtures.corpUser.id], })
expect(assignRes.status).toBe(201)expect(assignRes.data.assigned).toBe(1)TC-CORP-011: Path inexistente devuelve 404
Section titled “TC-CORP-011: Path inexistente devuelve 404”AC: AC-CORP-006.3 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/paths/non-existent-path-id/assign`, { memberIds: [fixtures.corpUser.id], })
expect(res.status).toBe(404)TC-CORP-012: Detalle de miembro incluye paths y actividad reciente
Section titled “TC-CORP-012: Detalle de miembro incluye paths y actividad reciente”AC: AC-CORP-007.1 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
const res = await apiClient .withToken(fixtures.corpAdmin.token) .get(`/organizations/${fixtures.corpAdmin.organizationId}/members/${fixtures.corpUser.id}`)
expect(res.status).toBe(200)expect(Array.isArray(res.data.paths)).toBe(true)expect(Array.isArray(res.data.recentActivity)).toBe(true)
for (const path of res.data.paths) { expect(path).toHaveProperty('id') expect(path).toHaveProperty('title') expect(path).toHaveProperty('progress')}TC-CORP-013: Nudge rechazado si plan no incluye la feature
Section titled “TC-CORP-013: Nudge rechazado si plan no incluye la feature”AC: AC-CORP-008.2 · Prioridad: Media · Tipo: API · Estado: 📋 Pendiente
Precondiciones: La organización de test tiene churnPrediction: false en el plan.
const res = await apiClient .withToken(fixtures.corpAdmin.token) .post(`/organizations/${fixtures.corpAdmin.organizationId}/members/${fixtures.corpUser.id}/nudge`)
// Debe devolver error de feature no disponible (403 o 422 según implementación)expect([403, 422]).toContain(res.status)TC-CORP-014: Banner de seats en rojo al superar 90% (E2E)
Section titled “TC-CORP-014: Banner de seats en rojo al superar 90% (E2E)”AC: AC-CORP-009.3 · Prioridad: Media · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/members.spec.ts
// Requiere organización de test configurada con seats al 91% o másawait loginAs(page, fixtures.corpAdmin)await page.goto('/corporate/team')
// data-testid a confirmar en OrgMembersPage: 'seats-progress-bar'const progressBar = page.getByTestId('seats-progress-bar')const bgColor = await progressBar.evaluate(el => window.getComputedStyle(el).backgroundColor)// La barra debe estar en rojo cuando supera el 90%expect(bgColor).toMatch(/rgb\(220,\s*38,\s*38\)/) // var(--red)TC-CORP-015: Redirección al dashboard para usuario FREE
Section titled “TC-CORP-015: Redirección al dashboard para usuario FREE”AC: AC-CORP-010.1 · Prioridad: Alta · Tipo: E2E · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/access-control.spec.ts
await loginAs(page, fixtures.free)await page.goto('/corporate/team')// CorporateGuard redirige a /dashboardawait expect(page).toHaveURL(/\/dashboard/)TC-CORP-016: API devuelve 403 para STARTER intentando acceder a datos corporativos
Section titled “TC-CORP-016: API devuelve 403 para STARTER intentando acceder a datos corporativos”AC: AC-CORP-010.2 · Prioridad: Alta · Tipo: API · Estado: 📋 Pendiente
Spec: apps/e2e/tests/corporate/access-control.spec.ts
// Usar el token de un usuario STARTER (no CORPORATE_ADMIN)const res = await apiClient .withToken(fixtures.starter.token) .get(`/organizations/any-org-id/members`)
expect(res.status).toBe(403)