diff --git a/CLAUDE.md b/CLAUDE.md index a70e116..9febdb0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -298,6 +298,13 @@ npm run test:integration:local dañado (#97). `checkStructure` sólo acepta los avisos de una lista corta, porque qpdf también «repara» un fichero truncado y lo cuenta como avisos. Un aviso nuevo se rechaza con su texto en el log: léelo antes de ampliar la lista. +- **El visor del alumno arranca en navegadores de 2020** (ADR-036): ES2021 sin + `await` de nivel superior, campos de clase ni lo que lista `SUELO_DEL_VISOR` + en `eslint.config.js` (asignación lógica, lookbehind, grupos con nombre…); lo + vigila `test/ui-compat.test.js`. `assets/compat.js` es ES5 y, si el visor no + deja `window.__visorArrancado`, explica qué hacer y lo cuenta a + `POST /telemetry/compat`: una línea `Visor sin arrancar` en el log. Si arrancó + y falló PDF.js o el vídeo, la línea es `Visor sin poder con el PDF o el vídeo`. - **PDF.js va en su build legacy, con el worker envuelto y sin `import` estático** (ADR-035, #110). La moderna revienta al importarse en Chrome < 122, Firefox < 131 e iOS < 18.4, y un `import` estático tumbaba la colección entera. diff --git a/docs/README.md b/docs/README.md index 619491a..a049484 100644 --- a/docs/README.md +++ b/docs/README.md @@ -208,6 +208,7 @@ proponérselo a nadie. El detalle, con su motivo, está en | **Compartir** | Sólo entre profesores de la **misma** instancia Moodle, y por carpeta, colección o curso completo. No hay compartición con un profesor concreto ni entre instancias | | **Transcodificación** | Un ffmpeg por software a la vez. La aceleración por hardware está documentada pero **no probada** | | **Escalado** | Una réplica de app y un worker. Los límites de reproducción son por proceso | +| **Navegadores** | El visor arranca en navegadores de 2020 (Chrome 80, Firefox 74, iOS 13.4) y el de PDF desde Chrome 94, Firefox 93 o iOS 16.4; por debajo de eso se ofrece la copia sellada. Por debajo de todo, un aviso de qué hacer y una línea en el log, nunca la pantalla en blanco (ADR-035, ADR-036) | | **Ciclo de vida** | Moodle **nunca avisa** cuando se borra una actividad. No existe callback | --- diff --git a/docs/arquitectura.md b/docs/arquitectura.md index 2a16082..c924e82 100644 --- a/docs/arquitectura.md +++ b/docs/arquitectura.md @@ -358,6 +358,7 @@ Detalle y motivos en las fichas de cierre archivadas como issues: | PUT | `/progress/:kind/:id` | sesión con alcance | Marcador de reanudación del alumno (la lectura viaja en el bootstrap del launch, ADR-021) | | POST | `/telemetry/video/:id` | sesión con alcance | Beat de visionado: tramos vistos y tiempo (**fail-open**: cualquier fallo responde 204, ADR-030) | | POST | `/telemetry/pdf/:id` | sesión con alcance | Beat de lectura: páginas distintas y tiempo, con el mismo contrato | +| POST | `/telemetry/compat` | sesión | Un visor que no pudo con este navegador (no arrancó, PDF.js no cargó, vídeo sin HLS o sin códec): una línea `warn` en el log con user-agent y motivo —«Visor sin arrancar» o, si arrancó, «Visor sin poder con el PDF o el vídeo»—, 204 siempre (ADR-036) | | GET | `/reports/course` | catálogo + curso | **Informe de seguimiento** del curso de la sesión: actividades, alumnos y avance | | GET | `/reports/course/students/:sub` | catálogo + curso | Detalle de un alumno de ESE curso (404 si no aparece en él) | | GET | `/api/v1/reports/courses` | `REPORTS_API_TOKEN` | Cursos conocidos de una plataforma | @@ -603,6 +604,21 @@ endpoint que él pueda pedir, y por eso no aparecen. El título del curso sí se guarda desde la migración 018, pero para el informe del profesor: no viaja al visor. +### En qué navegadores arranca el visor + +El visor del alumno está escrito para arrancar en navegadores de 2020 (Chrome 80, +Firefox 74, Safari/iOS 13.4): ES2021 sin `await` de nivel superior ni campos de +clase, y lo vigila `test/ui-compat.test.js` (ADR-036). El visor de PDF exige algo +más —PDF.js necesita Chrome 94, Firefox 93 o Safari 16.4— y, por debajo, ofrece +en su sitio la copia sellada (ADR-035). + +Por debajo de todo eso no hay pantalla en blanco. Cada visor carga primero +`assets/compat.js`, un script clásico en ES5 que se ejecuta en cualquier +navegador: pone `replaceChildren` si falta y, si al terminar de cargar la página el +visor no ha dejado su marca (`window.__visorArrancado`), explica qué hacer, +ofrece la copia sellada de los PDF y lo cuenta a `POST /telemetry/compat`. Esa +línea del log es la forma de saber, tras desplegar, qué navegadores siguen fuera. + ## Modelo de seguridad Qué protege qué, y contra quién: diff --git a/docs/decisiones.md b/docs/decisiones.md index 126579e..6258bef 100644 --- a/docs/decisiones.md +++ b/docs/decisiones.md @@ -1623,3 +1623,75 @@ producción y dentro de un iframe de otro origen. estático de `pdf-component.js`. Revertirlo devuelve el fallo de #110 en Windows 7, en las ESR de Firefox y en los iPhone sin la última actualización. Lo vigilan `test/pdf-legacy.test.js` y `test/integration/vendor-pdfjs.integration.js`. + +## ADR-036 · El visor arranca en navegadores de 2020 y, por debajo, nunca deja la pantalla en blanco + +**Estado**: aceptada · **Fecha**: 2026-10 · Issue #110 · Complementa a ADR-035 + +**Contexto.** El visor del alumno son módulos ES servidos tal cual, sin +compilar. Hasta octubre de 2026 nadie fijaba qué navegadores tenían que +entenderlos, y el código fue usando lo último a mano: `await` en el nivel +superior de `pdf.js` y `collection.js` (Safari 15, Chrome 89), +`replaceChildren` al arrancar (Safari 14), `` sin respaldo (Safari +15.4). Un navegador que no entendía una línea no ejecutaba **ninguna**: la +pantalla quedaba en blanco, sin explicación, y el servidor no se enteraba. Del +10 % que no podía estudiar sólo se sabía lo que contaban los alumnos. + +**Decisión.** + +1. **Suelo del visor: navegadores de 2020** (Chrome 80, Firefox 74, Safari/iOS + 13.4). ES2021 sin `await` de nivel superior ni campos de clase, sin + asignación lógica (Safari 14) ni *lookbehind* en expresiones regulares (Safari + 16.4: no compila el módulo entero), y sin lo que no entiende Firefox 74–79 + (grupos con nombre, `\p{…}`, flag `s`, `export * as`) ni Safari 13 (BigInt). + La lista es `SUELO_DEL_VISOR` en `eslint.config.js`, que lo marca en el + editor; `test/ui-compat.test.js` la aplica a todo lo que piden las entradas + y comprueba que caza cada caso. +2. **Guardia de arranque** (`src/ui/assets/compat.js`): ES5 y script clásico, + antes que `hls.min.js` y que el módulo. Pone `replaceChildren` si falta; anota + el primer fallo de un script; y en `load`, si el visor no dejó su marca + (`window.__visorArrancado`, que cada entrada pone al terminar su arranque + síncrono), explica qué hacer, ofrece la copia sellada de los PDF y lo cuenta + al servidor. Sin JavaScript, un `