Saltar a contenido

Errores y validaciones (sad paths)

Catálogo de lo que el sistema impide hacer. Cada caso fue ejecutado contra el sistema real (suite sad-paths.spec.ts, 13 escenarios automatizados): las capturas son de la interfaz y las respuestas JSON son de la API, tal cual las devolvió.

Autenticación y acceso

Contraseña incorrecta

Login incorrecto

Fuerza bruta

El login tiene límite por IP (60/min — calibrado para que un colegio entero detrás de un NAT no se bloquee a sí mismo). Superado el límite: 429 — Demasiados intentos: espere un minuto e intente de nuevo.

Contraseña temporal sin cambiar

Cualquier operación distinta de cambiar contraseña responde 403 — Debe cambiar su contraseña temporal.

Permisos

Cada endpoint exige rol. Un docente intentando operaciones de coordinación:

POST /api/v1/academic/years    HTTP 403
{ "message": "Rol insuficiente", "error": "Forbidden", "statusCode": 403 }
GET /api/v1/admin/audit    HTTP 403
{ "message": "Rol insuficiente", "error": "Forbidden", "statusCode": 403 }

Año lectivo y períodos

Dos años activos a la vez

Regla: un solo año lectivo activo. Intentar activar 2028-2029 con 2027-2028 aún en curso:

Segundo año activo

Saltarse etapas del ciclo de vida

PUT /api/v1/academic/years/{id}/status  { "status": "EN_CURSO" }    HTTP 409
{ "message": "Transición inválida: PLANIFICACION → EN_CURSO", "statusCode": 409 }

Cerrar parciales o períodos fuera de orden

El "parcial activo" es el primero abierto; se cierra en secuencia:

Cierre fuera de orden


Matrícula

Matrícula duplicada

POST /api/v1/enrollment    HTTP 409
{ "message": "El alumno ya está matriculado en este año", "statusCode": 409 }

Excel con errores: todo-o-nada

Archivo con cédula corta, fecha imposible y grado inexistente → 3 errores, 0 importados (se verificó que el total de alumnos no cambió):

Excel rechazado

La importación valida además, antes de tocar la base (todo verificado por tests automatizados):

Intento Respuesta
Encabezados corridos o renombrados 400 indicando columna, valor esperado y recibido
Archivo que no es un Excel real (.xlsx) 400 — El archivo no es un Excel válido (nunca un error 500)
Archivo sin filas de datos 400 — El archivo no tiene filas de datos
Alumno ya matriculado en el año Error de fila; cero importados
Cédula de representante que pertenece a un alumno o al personal Error de fila explicando el conflicto
Cédula duplicada dentro del archivo Error de fila
Año lectivo que no admite matrículas 400 antes de procesar
Importar el histórico con cédulas inexistentes Todo-o-nada también: se listan las filas y no entra nada

Calificaciones

Nota fuera de rango

La UI limita el campo, y aunque se fuerce el valor, el backend valida 0–10:

Nota fuera de rango

Parcial cerrado: notas congeladas

La grilla queda deshabilitada y la API rechaza el intento — la única vía es la rectificación autorizada:

Parcial cerrado

POST /api/v1/grades    HTTP 409
{ "message": "El parcial está cerrado: use el flujo de rectificación", "statusCode": 409 }

Horarios y aulas

Tres validaciones de choque por bloque — docente, paralelo y aula:

Aula ocupada


Pagos

Pago que excede el saldo

Pago excede saldo

Idempotencia de obligaciones

Generar dos veces la misma pensión: { "created": 0, "skipped": N } — no hay cobros duplicados.


Privacidad (LOPDP)

Observación DECE invisible para el docente

El DECE registró una observación sensible sobre Ana. El docente, en la misma ficha, ve su propia observación académica — la del DECE no existe para él:

DECE invisible

La familia no accede al expediente

GET /api/v1/people/students/{id}  (como representante)    HTTP 403

La familia consulta el progreso por my-students; la ficha administrativa es del personal, y cada acceso queda auditado.


Reproducir estos escenarios

cd backend && ./scripts/reset-db.sh
cd ../frontend && JOURNEY=1 E2E_BASE_URL=http://localhost:8080 \
  npx playwright test journey sad-paths --workers=1