Planeación y especificación técnica: API Gateway con autenticación JWT y tres microservicios (Usuarios, Calificaciones, Cursos) que consumen la REST API de Moodle.
| Estado | Borrador para revisión del equipo |
| Stack | Python 3.12 · FastAPI · MySQL 8 · Docker / Kubernetes |
| Autenticación | JWT propio (HS256 en dev, RS256 en prod), emitido por el Gateway |
| Sistema origen | Moodle ≥ 4.1 con Web Services REST habilitados |
| Audiencia | Equipo de desarrollo backend / DevOps |
Este documento define la arquitectura, los contratos REST, los modelos de datos, los flujos de autenticación, la estrategia de despliegue y el plan de trabajo para un sistema distribuido que expone la información académica de Moodle a servicios externos a través de una fachada REST propia.
Objetivos:
wsfunction, límites de tasa).Fuera de alcance (v1): escritura de calificaciones hacia Moodle, mensajería/foros, SSO hacia la interfaz web de Moodle, multi-tenancy.
/api/v1/; cambios incompatibles implican /api/v2/.| Decisión | Justificación |
|---|---|
| FastAPI + Uvicorn en los 4 servicios | Tipado con Pydantic para contratos, OpenAPI generado automáticamente, soporte async para llamadas a Moodle sin bloquear. |
| MySQL 8 en cada servicio | Alineado con la infraestructura existente de Moodle; una instancia lógica por servicio (esquemas o servidores separados). |
| JWT propio emitido por el Gateway | Los clientes nunca ven el wstoken de Moodle; permite claims de rol propios y revocación mediante expiración corta + refresh. |
| SQLAlchemy 2 + Alembic | ORM async y migraciones versionadas por servicio. |
| httpx (async) como cliente Moodle | Timeouts y reintentos configurables; se envuelve en un paquete interno moodle_client compartido. |
| Docker Compose (dev) / Kubernetes (prod) | Paridad de entornos; escalado horizontal independiente por servicio. |
Servicio FastAPI en el puerto :8000, único componente expuesto a Internet. Responsabilidades:
/auth/* son propios del Gateway.X-User-Id, X-User-Role y X-Request-Id ya verificadas.429.| Prefijo público | Destino interno | Autenticación |
|---|---|---|
POST /auth/login | Gateway (local) | Credenciales |
POST /auth/refresh | Gateway (local) | Refresh token |
/api/v1/usuarios/** | http://svc-usuarios:8001 | JWT requerido |
/api/v1/calificaciones/** | http://svc-calificaciones:8002 | JWT requerido |
/api/v1/cursos/** | http://svc-cursos:8003 | JWT requerido |
GET /health | Agregado de los 3 servicios | Pública |
Los microservicios no validan JWT: confían en las cabeceras X-User-* porque solo son alcanzables desde la red interna (NetworkPolicy en K8s / red privada en Compose).
Dominio: identidad y perfil de usuarios (alumnos, docentes, administradores). Fuente de verdad: Moodle (core_user_*).
| Endpoint | Descripción | Función Moodle |
|---|---|---|
GET /api/v1/usuarios | Lista paginada; filtros ?q=, ?rol=, ?page= | — (BD local) |
GET /api/v1/usuarios/{id} | Detalle de usuario | — (BD local) |
POST /api/v1/usuarios | Alta; crea primero en Moodle, luego local | core_user_create_users |
PUT /api/v1/usuarios/{id} | Actualización de perfil | core_user_update_users |
DELETE /api/v1/usuarios/{id} | Baja lógica (suspende en Moodle) | core_user_update_users |
POST /api/v1/usuarios/sync | Sincronización total/incremental (solo rol admin) | core_user_get_users |
db_usuarios| Columna | Tipo | Notas |
|---|---|---|
id | BIGINT PK AI | Identificador propio |
moodle_id | BIGINT UNIQUE | Id del usuario en Moodle |
username | VARCHAR(100) UNIQUE | Igual al de Moodle |
nombre, apellidos | VARCHAR(120) | |
email | VARCHAR(255) UNIQUE | |
rol | ENUM('alumno','docente','admin') | Mapeado desde roles de Moodle |
activo | TINYINT(1) | Baja lógica |
sincronizado_en | DATETIME | Última sync con Moodle |
creado_en, actualizado_en | DATETIME |
POST /api/v1/usuarios HTTP 201 Created
{ {
"username": "ana.perez", "id": 412,
"nombre": "Ana", "moodle_id": 5872,
"apellidos": "Pérez", "username": "ana.perez",
"email": "[email protected]", "rol": "alumno",
"rol": "alumno" "activo": true,
} "creado_en": "2026-07-10T09:14:02Z"
}
Dominio: ítems de calificación y notas por curso/usuario. Solo lectura hacia los clientes en v1; la escritura de notas permanece en Moodle.
| Endpoint | Descripción | Función Moodle |
|---|---|---|
GET /api/v1/calificaciones/usuario/{uid} | Todas las notas del usuario, agrupadas por curso | — (BD local) |
GET /api/v1/calificaciones/curso/{cid} | Notas del curso (solo docente/admin) | — (BD local) |
GET /api/v1/calificaciones/curso/{cid}/usuario/{uid} | Desglose por ítem de un alumno en un curso | — (BD local) |
POST /api/v1/calificaciones/sync | Sincroniza por curso o global (admin) | gradereport_user_get_grade_items |
Si una lectura local tiene sincronizado_en más antiguo que el TTL configurado (default 15 min), el servicio dispara una re-sincronización del curso en segundo plano y responde con los datos locales más la cabecera X-Data-Stale: true.
db_calif| Tabla / columna | Tipo | Notas |
|---|---|---|
item_calificacion | Ítems evaluables por curso | |
· id, moodle_item_id | BIGINT | moodle_item_id único |
· curso_moodle_id | BIGINT INDEX | Referencia lógica (sin FK cruzada) |
· nombre, tipo | VARCHAR | tarea, examen, total… |
· nota_maxima, peso | DECIMAL(10,2) | |
calificacion | Nota de un usuario en un ítem | |
· id | BIGINT PK AI | |
· item_id | BIGINT FK | → item_calificacion.id |
· usuario_moodle_id | BIGINT INDEX | Referencia lógica al MS Usuarios |
· nota | DECIMAL(10,2) NULL | NULL = sin calificar |
· retroalimentacion | TEXT | |
· sincronizado_en | DATETIME |
Dominio: catálogo de cursos, estructura de contenidos e inscripciones.
| Endpoint | Descripción | Función Moodle |
|---|---|---|
GET /api/v1/cursos | Catálogo paginado; filtros ?categoria=, ?visible= | — (BD local) |
GET /api/v1/cursos/{id} | Detalle del curso | — (BD local) |
GET /api/v1/cursos/{id}/contenidos | Secciones y módulos del curso | core_course_get_contents* |
POST /api/v1/cursos | Alta de curso (admin) | core_course_create_courses |
PUT /api/v1/cursos/{id} | Actualización (admin/docente) | core_course_update_courses |
POST /api/v1/cursos/{id}/inscripciones | Inscribe un usuario con un rol | enrol_manual_enrol_users |
GET /api/v1/cursos/{id}/inscripciones | Usuarios inscritos | core_enrol_get_enrolled_users |
POST /api/v1/cursos/sync | Sincronización del catálogo (admin) | core_course_get_courses |
* Los contenidos se cachean localmente 15 min; pasado el TTL se consulta Moodle en línea.
db_cursos| Tabla / columna | Tipo | Notas |
|---|---|---|
curso | ||
· id, moodle_id | BIGINT | moodle_id único |
· nombre_corto, nombre_completo | VARCHAR | |
· categoria | VARCHAR(120) | |
· fecha_inicio, fecha_fin | DATE NULL | |
· visible | TINYINT(1) | |
· sincronizado_en | DATETIME | |
inscripcion | ||
· id | BIGINT PK AI | |
· curso_id | BIGINT FK | → curso.id |
· usuario_moodle_id | BIGINT INDEX | Referencia lógica al MS Usuarios |
· rol | ENUM('alumno','docente') | |
· inscrito_en | DATETIME |
El Gateway valida credenciales contra el MS Usuarios (hash bcrypt local; opcionalmente delega a login/token.php de Moodle para validar la contraseña) y emite dos tokens:
| Token | Vigencia | Uso |
|---|---|---|
access_token | 15 minutos | Cabecera Authorization: Bearer … en cada request |
refresh_token | 7 días | Solo válido en POST /auth/refresh; rotación en cada uso |
{
"sub": "412", // id interno del usuario
"mid": 5872, // moodle_id
"rol": "alumno", // alumno | docente | admin
"iss": "gw.moodle-ms",
"iat": 1783075200,
"exp": 1783076100,
"jti": "b4f1…" // id único, para auditoría
}
alumno: solo lectura de sus propios recursos (sub debe coincidir con el {uid} solicitado).docente: lectura de calificaciones e inscripciones de sus cursos; actualización de sus cursos.admin: todos los endpoints, incluidos POST …/sync y altas/bajas.El wstoken de Moodle vive únicamente como secreto de cada microservicio (variable de entorno / K8s Secret) con una cuenta de servicio dedicada y permisos mínimos por servicio.
moodle_client (httpx async): construye POST /webservice/rest/server.php con wstoken, wsfunction y moodlewsrestformat=json; detecta el campo exception de Moodle (que responde 200 aun en error) y lo convierte en excepciones tipadas.X-Data-Stale: true.502 y no se persiste nada.{"error": {"codigo", "mensaje", "detalle", "request_id"}} con códigos 400/401/403/404/409/422/429/502/503.| Servicio | Imagen | Puerto | Notas |
|---|---|---|---|
gateway | build local | 8000:8000 | Único puerto publicado al host |
svc-usuarios | build local | interno 8001 | Red privada backend |
svc-calificaciones | build local | interno 8002 | |
svc-cursos | build local | interno 8003 | |
mysql-usuarios / -calif / -cursos | mysql:8 | internos 3306 | Un contenedor por servicio + volumen |
moodle (opcional) | bitnami/moodle | 8080 | Entorno de pruebas local |
HorizontalPodAutoscaler (CPU 70 %); probes /health/live y /health/ready (readiness verifica conexión a su MySQL).NetworkPolicy impide tráfico directo a los microservicios desde fuera del namespace.wstoken por servicio y credenciales MySQL en K8s Secrets (o external-secrets + Vault).initContainer.starlette-exporter); trazas OpenTelemetry con X-Request-Id propagado de extremo a extremo.| Fase | Entregable | Duración | Criterio de salida |
|---|---|---|---|
| F0 | Setup: repos, Compose base, Moodle de pruebas con WS habilitados, CI | 1 semana | docker compose up levanta el esqueleto de los 4 servicios |
| F1 | Gateway: auth JWT, routing, rate limiting | 2 semanas | Login + proxy a un servicio stub con tests e2e |
| F2 | MS Usuarios: CRUD + sync con core_user_* | 2 semanas | Alta de usuario visible en Moodle; sync incremental probada |
| F3 | MS Cursos: catálogo, contenidos, inscripciones | 2 semanas | Inscripción vía API reflejada en Moodle |
| F4 | MS Calificaciones: lectura + sync perezosa + circuit breaker | 2 semanas | Notas consistentes con el gradebook de Moodle |
| F5 | Integración: pruebas e2e, carga (k6), hardening de seguridad | 2 semanas | P95 < 300 ms en lecturas locales; auditoría de auth |
| F6 | Despliegue a producción: manifiestos K8s, observabilidad, runbook | 1 semana | Sistema en prod con alertas y documentación operativa |
Total estimado: 12 semanas con un equipo de 2–3 desarrolladores backend + 1 DevOps parcial.
| Riesgo | Impacto | Mitigación |
|---|---|---|
| Límites de tasa / rendimiento de la WS API de Moodle | Alto | Réplica local + sync perezosa; sync masiva en horario valle |
| Cambios de contrato entre versiones de Moodle | Medio | Aislar todo en moodle_client; tests de contrato contra el Moodle de staging |
| Inconsistencia local ↔ Moodle en escrituras | Alto | Write-through estricto + job de reconciliación nocturno |
Filtración del wstoken | Alto | Cuentas de servicio con permisos mínimos por microservicio; rotación trimestral |
| Deriva de esquemas entre servicios | Bajo | Contratos OpenAPI versionados y validados en CI |