Saltar al contenido principal

ADR-005: Miga de pan construida por el tema (Opción B)

Estado: Aceptado | Fecha: 2026-08-13 | Autores: Equipo Campus Virtual


Contexto​

El componente miga_de_pan (Figma 19613-9788, aplicado en 19723-2726 para página de curso y en 19639-4622 para el catálogo de categorías) estaba implementado y estilado, pero requierments/analisis miga de pan.md documentó que en la mayoría de las páginas que el diseño cubre Moodle renderiza <ol class="breadcrumb"></ol> vacío: no hay nodos que estilar. El análisis atribuyó la causa al núcleo (lib/classes/ navigation/navbar.php:88, que corta y devuelve una lista vacía si ningún árbol de navegación tiene un nodo activo) y planteó tres opciones de alcance, dejando como pendiente de investigación el caso específico de /course/view.php (el núcleo sí activa el nodo del curso ahí, y aun así el resultado llegaba vacío).

Al implementar se encontró la causa real, más acotada de lo que el análisis suponía: theme_boost\output\core_renderer::navbar() no usa $PAGE->navbar directamente, lo envuelve en theme_boost\boostnavbar. boostnavbar::prepare_nodes_for_boost() (theme/boost/classes/boostnavbar.php:55-156) poda deliberadamente el rastro: en contexto de curso elimina mycourses, courses, los nodos de categoría y, en una vista de curso simple, el propio nodo del curso; si al final queda un solo item, llama a clear_items(). Eso explica el caso pendiente: el núcleo sí activa el nodo del curso, pero Boost lo borra antes de llegar a la plantilla. theme_cdigital declara $THEME->parents = ['boost'], así que hereda ese mismo renderer — la prueba de control del análisis (cambiar a Boost y observar el mismo vacío) no descartaba al tema, confirmaba que ambos comparten código.

Esta corrección abarata la Opción B: no hace falta reimplementar la navegación de Moodle, existe un punto de extensión concreto (boostnavbar::prepare_nodes_for_boost()) para sustituir solo la cabeza del rastro y reutilizar lo que Boost ya resuelve bien (sección, actividad).

Opciones consideradas​

Opción A: Ajustar el alcance a lo que Moodle genera nativamente​

  • Ventajas: costo de desarrollo cero, riesgo de mantenimiento nulo.
  • Desventajas: el mock de la página de curso (19723-2726) y el del catálogo de categorías (19639-4622) no se cumplen: inicio de curso, área personal y mis cursos quedan sin miga.

Opción B (elegida): El tema construye el rastro​

  • Ventajas: cumple el mock en todas las páginas que diseño priorizó. Con el hallazgo de boostnavbar como punto de extensión, el costo real es menor al estimado en el análisis: no hay que tocar la plantilla core/navbar ni reimplementar la detección de secciones/actividades, que Boost ya resuelve correctamente.
  • Desventajas: acopla el tema a un método protected de un tema del núcleo (theme_boost\boostnavbar::prepare_nodes_for_boost()), no a una API pública documentada.

Opción C: Híbrido (rastro nativo + construcción solo en páginas prioritarias)​

  • Ventajas: costo medio-bajo, riesgo acotado a las páginas intervenidas.
  • Desventajas: una vez identificado el punto de extensión de la Opción B, el costo diferencial frente al híbrido es pequeño y el híbrido deja una experiencia inconsistente (algunas páginas de curso con miga completa, otras no) sin necesidad.

Decisión​

Opción B, con alcance acotado: theme_cdigital\cdigitalnavbar (theme/cdigital/classes/cdigitalnavbar.php) extiende theme_boost\boostnavbar y sobrescribe únicamente prepare_nodes_for_boost(). Para las familias de página fuera de alcance, delega en parent::prepare_nodes_for_boost() sin cambios.

Alcance:

FamiliaRastro construido
Curso / sección / actividad (contexto CONTEXT_COURSE/CONTEXT_MODULE)Inicio › Mis Cursos (o Categorías + cadena si no está matriculado) › Curso › [lo que Boost ya resuelve: sección, actividad]
Catálogo de categorías (/course/index.php)Inicio › Categorías › [cadena de categorías]
Buscador de cursos (/course/search.php)Inicio › Categorías › Buscar cursos
Área personal (/my/)Inicio › Área personal
Mis cursos (/my/courses.php)Inicio › Mis Cursos
Resto del sitio (admin, informes, perfil, preferencias…)Sin cambios, rastro nativo de Boost

El único método sobrescrito de boostnavbar es prepare_nodes_for_boost(): para el family de curso/sección/actividad, se captura la lista nativa ($PAGE->navbar->get_items(), ya copiada en $this->items por el constructor del padre) antes de que Boost la pode, se deja que Boost pode normalmente, y se busca en el resultado podado (o en el nativo si Boost vació todo) el nodo TYPE_COURSE con key = $course->id como punto de anclaje: todo lo posterior a ese nodo (sección, actividad) se conserva tal cual, y todo lo anterior se reemplaza por los nodos propios del tema. core_renderer::navbar() (theme/cdigital/classes/output/core_renderer.php) solo cambia la clase renderable que pasa a render_from_template('core/navbar', ...); la plantilla core/navbar no se toca, siguiendo la misma decisión ya documentada para el CSS (scss/breadcrumb.scss): esa plantilla cambia entre versiones de Moodle y forzaría a resincronizar en cada upgrade.

Interruptor theme_cdigital/enablethemebreadcrumb (activado por defecto): al desactivarlo, todas las páginas vuelven al comportamiento nativo de Boost sin redesplegar.

Desviación deliberada respecto del Figma (ya documentada en scss/breadcrumb.scss antes de esta implementación, ahora con más peso porque afecta a más niveles por página): el Figma aplicado solo pinta "Inicio" en cobalto subrayado y dejaba el resto en gris matterhorn, incluidos los enlaces navegables. La implementación pinta en cobalto todo item navegable y reserva la negrilla para la página actual, por accesibilidad (el usuario distingue qué es clicable). Confirmado con diseño al plantear esta opción.

Curso sin matrícula: cuando el usuario no está matriculado en el curso que ve (invitado, visitante, administrador explorando), el segundo nivel deja de ser "Mis Cursos" y pasa a ser "Categorías" seguido de la cadena de categorías del curso, porque "Mis Cursos" enlazaría a una lista donde ese curso no aparece.

Consecuencias​

  • Acoplamiento a un método protected de theme_boost, no a una API pública. Checklist de upgrade de Moodle (ejecutar en cada actualización de versión mayor):
    1. Releer theme/boost/classes/boostnavbar.php::prepare_nodes_for_boost() completo y diff contra la copia mental que asume cdigitalnavbar: en particular, que $this->items sigue estando en orden de visualización (raíz primero) tanto al entrar como al salir del método, y que el nodo del curso sigue creándose con type = TYPE_COURSE y key = $course->id (lib/classes/navigation/ global_navigation.php::add_course()).
    2. Verificar que boostnavbar::remove_last_item_action(), item_count() y clear_items() siguen siendo protected (no private) y con la misma firma.
    3. Repasar el switch de global_navigation.php::initialise() — si Moodle deja de activar el nodo del curso en /course/view.php, el fallback a $native en cdigitalnavbar::prepare_nodes_for_boost() deja de tener un ancla y el resultado degrada a mostrar solo Inicio + Mis Cursos/Categorías (sin el curso), no un error.
    4. Purga de cachés + recorrido visual de la lista de verificación de la sección "Verificación" del plan de implementación (curso, sección, actividad, categorías, buscador, área personal, y las páginas de regresión fuera de alcance).
  • Degradación segura: si cdigital_course_head_nodes() o cdigital_full_breadcrumb() devuelven null (página no reconocida) o si el interruptor está desactivado, el código cae exactamente en parent::prepare_nodes_for_boost() — nunca hay una ruta que deje el árbol de navegación de Moodle en un estado inconsistente para otras páginas.
  • No se toca el núcleo ni theme_boost: todo el código nuevo vive en theme/cdigital/classes/cdigitalnavbar.php, consistente con la regla del proyecto de no modificar local/APP158C/public/ fuera de plugins y overrides de tema.