Feature/reanudar progreso - #19
Merged
Merged
Conversation
Los botones de saltar 10 segundos dibujaban el «10» con <text> SVG a 8px, que renderizaba tosco y distinto en cada navegador: ahora son trazados vectoriales puros (arco con flecha y dígitos como paths). Al reabrir una actividad, el visor vuelve al último punto del alumno: elemento de la colección, segundo del vídeo o página del PDF (ADR-021). Una tabla learner_progress con una fila por alumno y recurso, un único PUT /progress/:kind/:id con UPSERT cada 15 s y la lectura embebida en el bootstrap del launch. El profesor ni guarda ni restaura; un vídeo visto hasta el final vuelve a empezar de cero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GB3AMiazjyG223U2UBFuTq
…ntenido Truncan tablas antes de cada prueba y, apuntados al Postgres del entorno local, arrasaban las plataformas, carpetas y materiales de prueba manual. Ahora corren siempre contra una base dedicada (moodleshield_test) que el lanzador crea si falta, y src/db/guard.js rechaza en cerrado cualquier conexión de un proceso de test a una base sin sufijo _test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GB3AMiazjyG223U2UBFuTq
…a fuente Una fuente HDR de móvil (HLG/PQ) se aplastaba a 8 bits sin tonemapear — colores lavados — y la salida viajaba sin etiquetas de color, dejando al navegador adivinar la matriz. Además se forzaban 24 fps a ciegas: una fuente a 30 o 60 fps salía con tirones que se perciben como pérdida de calidad o desincronización. - La salida se etiqueta siempre como BT.709 SDR; el HDR se tonemapea con zscale+hable (con setparams delante: zimg aborta ante frames a medio etiquetar) y el SD bt601 se convierte con colorspace. - Los fps de salida siguen a la fuente, redondeados a entero para que el GOP siga siendo exacto (la condición de que A y B corten igual) y limitados a 30 dividiendo entre dos (60→30, 50→25). - Paridad local↔prod: MARK_ALPHA baja de 0.5 (demo) a 0.06 y OUTPUT_CRF de 23 a 21 en infra/local; la calidad que se ve en local ya es la real. Cadenas verificadas contra el ffmpeg 8.1.2 del worker con fuentes sintéticas HDR (bien y mal etiquetadas) y SD bt601. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GB3AMiazjyG223U2UBFuTq
…eline Importar un curso entero exigía subir fichero a fichero por la biblioteca del profesor. Se añade /api/v1 para hacerlo desde un script o Postman sin crear un segundo camino de ingesta: reutiliza la recepción troceada de la UI, sus validaciones, sus tablas de trabajo y el mismo worker. - El router de uploads pasa a ser una factoría parametrizada por su guardia de autorización. La UI sigue entrando con sesión LTI; la API, con CONTENT_API_TOKEN más las cabeceras de plataforma y propietario, que se traducen al mismo contrato de identidad. Un solo pipeline. - Vacío, CONTENT_API_TOKEN deja toda la API en 404; en producción se exigen 32 caracteres. La plataforma debe existir y estar habilitada. - El worker alterna las dos colas por turno (src/queue/scheduler.js) en vez de recorrerlas siempre en el mismo orden: una tanda de vídeos ya no deja los PDF esperando. Sigue reclamando un único trabajo cada vez, y el arranque rechaza TRANSCODE_CONCURRENCY distinto de 1. - Deep Linking acepta material en cola o en proceso, no sólo publicado: el profesor puede insertar la actividad mientras el worker trabaja. El launch de un alumno sigue sin servir bytes hasta que hay revisión activa, así que el UUID que Moodle incrusta no cambia después. Documentado en docs/api-migracion-contenido.md, con script de ejemplo en scripts/upload-content.sh (curl + jq, sin cargar el fichero en memoria). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e la sesión
El visor del alumno gastaba tres franjas antes del material —cabecera,
banda de monitorización y banner legal de cuatro líneas— unos 174 px en
la única pantalla donde se estudia. Dentro de un iframe corto de Moodle
lo que sobra no hace scroll: se corta (ADR-022).
- Una sola fila de cromo: «Atrás», un chip ámbar permanente «⚠ Sesión
monitorizada · identidad · IP · Ver detalles» y el botón que pliega el
panel lateral. El resto —título, lista de la colección, navegación,
descarga y estado— baja al panel.
- El chip abre un <dialog> con el aviso completo y los datos concretos de
la sesión. La referencia que enseña es el jti, el mismo que se escribe
en view_event.session_jti: lo que el alumno lee se puede cotejar con lo
registrado. Convence más un dato verificable que un párrafo más largo.
- Sólo se promete lo que existe: LTI 1.3 no trae claim de documento de
identidad, así que cuando no llega se dice, en vez de dejar un hueco.
- verifySession devuelve issuedAt y el bootstrap de los tres lanzamientos
de alumno lleva session: { issuedAt, expiresAt, reference }.
- El helper que limpia returnValue antes de showModal() se extrae a
src/ui/assets/dialog.js para que el visor no arrastre el catálogo del
profesor. La trampa de Chrome/Edge la sigue vigilando ui-iframe.test.js.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| export const contentApiRouter = Router() | ||
|
|
||
| function sameSecret (received, expected) { | ||
| const digest = (value) => createHash('sha256').update(String(value)).digest() |
| */ | ||
| export const progressRouter = Router() | ||
|
|
||
| progressRouter.put('/:kind/:id', requireSession, async (req, res, next) => { |
Comment on lines
+14
to
+47
| progressRouter.put('/:kind/:id', requireSession, async (req, res, next) => { | ||
| try { | ||
| const kind = req.params.kind | ||
| if (!['video', 'pdf', 'collection'].includes(kind)) { | ||
| return res.status(404).json({ error: 'No encontrado' }) | ||
| } | ||
| const id = assertUuid(req.params.id, 'Identificador de recurso') | ||
|
|
||
| // El profesor abre materiales constantemente al editar: ni guarda ni | ||
| // restaura. 204 y no 403 para que el cliente no distinga ni reintente. | ||
| if (req.session.isInstructor || req.session.mode !== 'launch') { | ||
| return res.status(204).end() | ||
| } | ||
|
|
||
| const scope = kind === 'collection' | ||
| ? await authorizeCollection(req.session, id) | ||
| : await authorizeResource(req.session, kind, id) | ||
| if (!scope.ok) return res.status(404).json({ error: 'Recurso no encontrado' }) | ||
|
|
||
| const payload = normalizeProgressPayload(kind, req.body) | ||
| if (!payload) return res.status(400).json({ error: 'Datos de progreso no válidos' }) | ||
|
|
||
| await saveProgress({ | ||
| platformId: req.session.platformId, | ||
| userSub: req.session.sub, | ||
| resourceKind: kind, | ||
| resourceId: id, | ||
| ...payload | ||
| }) | ||
| res.status(204).end() | ||
| } catch (err) { | ||
| next(err) | ||
| } | ||
| }) |
|
|
||
| // El profesor abre materiales constantemente al editar: ni guarda ni | ||
| // restaura. 204 y no 403 para que el cliente no distinga ni reintente. | ||
| if (req.session.isInstructor || req.session.mode !== 'launch') { |
|
|
||
| // El profesor abre materiales constantemente al editar: ni guarda ni | ||
| // restaura. 204 y no 403 para que el cliente no distinga ni reintente. | ||
| if (req.session.isInstructor || req.session.mode !== 'launch') { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.