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
boostnavbarcomo punto de extensión, el costo real es menor al estimado en el análisis: no hay que tocar la plantillacore/navbarni reimplementar la detección de secciones/actividades, que Boost ya resuelve correctamente. - Desventajas: acopla el tema a un método
protectedde 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:
| Familia | Rastro 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
protecteddetheme_boost, no a una API pública. Checklist de upgrade de Moodle (ejecutar en cada actualización de versión mayor):- Releer
theme/boost/classes/boostnavbar.php::prepare_nodes_for_boost()completo y diff contra la copia mental que asumecdigitalnavbar: en particular, que$this->itemssigue 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 contype = TYPE_COURSEykey = $course->id(lib/classes/navigation/ global_navigation.php::add_course()). - Verificar que
boostnavbar::remove_last_item_action(),item_count()yclear_items()siguen siendoprotected(noprivate) y con la misma firma. - Repasar el switch de
global_navigation.php::initialise()— si Moodle deja de activar el nodo del curso en/course/view.php, el fallback a$nativeencdigitalnavbar::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. - 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).
- Releer
- Degradación segura: si
cdigital_course_head_nodes()ocdigital_full_breadcrumb()devuelvennull(página no reconocida) o si el interruptor está desactivado, el código cae exactamente enparent::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 entheme/cdigital/classes/cdigitalnavbar.php, consistente con la regla del proyecto de no modificarlocal/APP158C/public/fuera de plugins y overrides de tema.