Skip to content

Feature/reanudar progreso - #19

Merged
jamataran merged 5 commits into
mainfrom
feature/reanudar-progreso
Aug 9, 2026
Merged

Feature/reanudar progreso#19
jamataran merged 5 commits into
mainfrom
feature/reanudar-progreso

Conversation

@jamataran

Copy link
Copy Markdown
Owner

No description provided.

jamataran and others added 5 commits August 9, 2026 19:28
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>
@jamataran
jamataran merged commit 2325a7e into main Aug 9, 2026
2 checks passed
@jamataran
jamataran deleted the feature/reanudar-progreso branch August 9, 2026 22:45
Comment thread src/routes/content-api.js
export const contentApiRouter = Router()

function sameSecret (received, expected) {
const digest = (value) => createHash('sha256').update(String(value)).digest()
Comment thread src/routes/progress.js
*/
export const progressRouter = Router()

progressRouter.put('/:kind/:id', requireSession, async (req, res, next) => {
Comment thread src/routes/progress.js
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)
}
})
Comment thread src/routes/progress.js

// 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') {
Comment thread src/routes/progress.js

// 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') {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants