Deuda técnica, parte 4: creía que había terminado… y quedaba el último que recargaba la página entera

Deuda técnica, parte 4: creía que había terminado… y quedaba el último que recargaba la página entera
ContenidoContents

Le he cogido el vicio y no podía parar. Así que, aprovechando que es domingo, me senté a rematar otro fleco del plugin. Y salió esto.

En la tercera parte cerré diciendo que el gran refactor estaba hecho: external.php, lib.php, la videollamada y view.php. Y lo estaba… casi. Porque al refactorizar view.php dejé, a propósito, un fleco para más tarde: la tabla de grabaciones. Era el último rincón del plugin que seguía haciendo las cosas como en 2015 —recargar la página entera cada vez que borras, ocultas o añades una grabación— y con todo el HTML de cada fila cosido a mano.

Spoiler de la moraleja de hoy: “terminar” un refactor es un horizonte que se mueve. Cada vez que crees que llegas, aparece el siguiente fleco. Este es el que tocaba. Todo en la rama dev, ya desplegado y validado en mi Moodle real (aula.sergiocomeron.com).

El último que recargaba la página entera

El CRUD de grabaciones (borrar, ocultar, mostrar, añadir, editar) seguía siendo un PRG clásico: envías un formulario, el servidor procesa, te redirige y recarga la página completa. Y, de paso, te deja arriba del todo, lejos de la grabación que acababas de tocar.

Debajo, la otra mitad del problema: el HTML de cada fila se construía a mano en view_table.php::col_id()475 líneas de strings concatenados—, con ramas para GCS, Dropbox, YouTube, 8x8 y Jibri, más el dropdown de IA, el accordion de resumen, la barra de segmentos y el heatmap. Todo mezclado: decidir, consultar y pintar en la misma función.

El plan

Tres objetivos para esta tanda:

  1. CRUD a Web Services + AMD AJAX, refrescando solo la pestaña con un re-fetch del fragmento —sin recargar la página—.
  2. Sacar el HTML de col_id() a plantillas Mustache: el PHP decide y construye el contexto, la plantilla pinta.
  3. Mantener el PRG y el parsing de $state intactos como red de compatibilidad con la app móvil de Moodle.

Cuatro Web Services y un evento que se repetía

Los endpoints viven ahora en classes/external/, siguiendo el mismo patrón que ya tenía para la IA (cmid → context_module::instance → validate_context → require_capability → {success, message}):

  • delete_recording — capability mod/jitsi:deleterecord.
  • set_recording_visibility — capability mod/jitsi:hide.
  • add_recording_link y update_recording_link — capability mod/jitsi:record.

Registrados en db/services.php con ajax => true. Lo importante: la lógica de negocio ya vivía en classes/local/recording.php (de tandas anteriores), así que los WS no reimplementan nada; solo añaden validación de contexto, capability y el evento. Y ahí cacé una duplicación: el evento jitsi_delete_record se disparaba en dos sitios. Lo centralicé en recording::log_deletion(...), que usan tanto el WS nuevo como el PRG de fallback. Una sola fuente de verdad para el log de borrado.

La tabla, a plantillas

col_id() se reescribió para construir un context en PHP y delegar todo el marcado a cinco plantillas: vídeo embebible, enlace externo, iframe de YouTube, el partial de botones de acción y el formulario de alta/edición.

¿Y los bloques muy dinámicos —dropdown de IA, accordion, barra de segmentos, heatmap—? Esos no los reescribí en Mustache: los pre-renderizo en PHP con los renderers que ya existían y los inyecto como HTML crudo ({{{...}}}). Reutilizar en vez de reescribir, conservando todos los id/clases que consumen los otros módulos JS. La regla que me marqué: la plantilla no piensa, solo pinta.

La joya: refrescar sin saltar al principio

Aquí está el detalle del que estoy más orgulloso, y no es el más vistoso. Tras cada cambio, el módulo recordings_lazyload.js hace un re-fetch del fragmento y lo inyecta. Fácil. Lo difícil es que no se note.

Un AJAX ingenuo borra una fila, recarga el contenedor… y la página pega un salto al principio. Para evitarlo: fijo la altura del contenedor mientras carga (que el spinner no colapse el layout) y restauro el desplazamiento exacto al inyectar el contenido nuevo. Resultado: ocultas o borras una grabación y te quedas justo donde estabas. Eso es lo que separa un “AJAX que funciona” de uno que se siente bien.

Las batallitas del entorno

Como en cada tanda, el terreno muerde:

  • ESLint otra vez de portero. El hook de pre-push rechaza promise/no-nesting. El manejador de borrado, que encadenaba la confirmación con la llamada, lo reescribí con async/await para aplanar las promesas. (La misma lección de la parte 3: reproduce el lint estricto del CI en local antes de pushear. ¿Me lo aprenderé de una vez?)
  • Mustache tiene sus manías. No se puede anidar el helper {{#str}} dentro de {{#pix}}: no se procesa de forma fiable. El texto accesible del icono acabó en el atributo title del botón. Pequeño, pero te hace perder media hora si no lo sabes.

El respeto por la cápsula del tiempo

¿Recuerdas el $state fantasma de la parte 3 —ese parámetro que construye la app móvil de Moodle, herencia de un commit de “ionic 5 compatibility”—? El PRG y ese parsing se quedan intactos como red de seguridad. La web ya no los usa, pero romperlos rompería a los usuarios de la app.

Eso sí, me dejé apuntado el siguiente paso: antes de eliminar el PRG algún día, hay que instrumentar cuándo llega $state != null para confirmar que la app ya no lo necesita. Hasta tener ese dato, la cápsula se respeta. Borrar a ciegas algo que no entiendes del todo es justo el error que esta serie intenta no cometer.

Lo que cierra esto

En métricas: las 475 líneas de col_id() desaparecen a plantillas, cuatro Web Services nuevos, el CRUD entero sin una sola recarga, y la suite en 144 tests (5 nuevos de los WS, 4 de render de plantillas), con ESLint sin warnings y phpcs limpio.

Y la tesis de toda la serie, llevada a su conclusión: el frontend deja de recibir HTML cosido desde PHP y pasa a consumir datos por servicio y pintar con plantillas. Que es —una vez más— el cimiento exacto que la interfaz reactiva de Moodle, la que va hacia React, va a necesitar.

¿El refactor “terminado”? Esta vez no me atrevo a decirlo. Lo que me llevo de estas cuatro entregas es que el código siempre tiene un fleco más —y que está bien—. Refactorizar no es un proyecto con final: es una forma de cuidar lo que mantienes.

  1. “Terminar” es un horizonte móvil. Cada refactor destapa el siguiente. La pregunta no es si quedan flecos, sino cuál toca ahora.
  2. Un AJAX que se siente bien es, sobre todo, detalles. Preservar el scroll no sale en ninguna métrica, pero es la diferencia entre usable y agradable.
  3. Respeta las cápsulas del tiempo aunque ya no las uses. Déjalas como fallback e instrumenta antes de quitarlas. No borres a ciegas lo que otro puso por un motivo que tú ya no ves.
CompartirShare