Saltar al contenido principal

Card de curso (Mis cursos / Área personal)

Ficha técnica
  • Impacto — Tema: Override core_course/coursecard.mustache; AMD coursecard_favourite.js, coursecard_teachers.js, repository.js
  • Impacto — Plugin: WS external\get_course_teachers (db/services.php)
  • Requisito: Diseño (Figma) · Figma: 19658-13142 · Estado: Implementado

Propósito

Rediseño completo de la card de curso que renderiza block_myoverview en "Mis cursos" (/my/courses.php) y en cualquier región de bloques donde se añada el bloque nativo myoverview (por ejemplo, el Panel de Actividades del Área personal). Siguiendo el diseño Figma "Card_curso", incluye:

La sección Mis Cursos del Área personal (/my/) usa un diseño de card visualmente equivalente pero no pasa por este override: se genera con una plantilla propia (miscursos_section.mustache) renderizada server-side directamente en layout/mydashboard.php.

  • Imagen de portada, categoría y título del curso (estructura core preservada).
  • Botón de favorito (marcador/bookmark) sobre la imagen, funcional como toggle (no solo desde el menú ⋮).
  • Campo "Profesor:" con los nombres de los docentes del curso.
  • Altura uniforme entre cards de una misma fila, con la barra de progreso y el botón "Ver curso" siempre anclados al final de la card.

Por qué es un override de template y no un bloque/renderer nuevo

/my/courses.php y /my/ renderizan las cards 100% client-side: blocks/myoverview/amd/src/view.js llama al WS core_course_get_enrolled_courses_by_timeline_classification (course_summary_exporter, ambos core) y luego Templates.render('core_course/coursecards' → 'core_course/coursecard', ...).

No existe un hook ni método de renderer que el tema pueda sobreescribir para inyectar datos adicionales (como el profesor) en ese JSON sin modificar core. Por eso:

  • El template (theme_cdigital/templates/core_course/coursecard.mustache) sobreescribe core_course/coursecard y añade los elementos visuales (marcador, "Profesor:") con marcado/data-* siempre presente.
  • Los datos adicionales (favorito ya viaja en el contexto del template; profesor no) se completan después vía un módulo AMD que llama a una función externa nueva del plugin local_pccntr8203403_dashboard.

1. Botón de favorito (marcador)

Problema con el comportamiento core

Core solo permite marcar/desmarcar un curso como favorito desde el menú de acciones (⋮) de la card (core_course_set_favourite_courses). El ícono de marcador del diseño Figma debía ser un toggle directo sobre la imagen.

Solución

  • Iconos nuevos pix/Icon/bookmark.svg y pix/Icon/bookmark-heart.svg (24×24, theme_cdigital).
  • coursecard.mustache añade un botón .cdigital-coursecard__bookmark sobre .cdigital-coursecard__media, con dos <span> (is-favourite / not-favourite) que alternan visibilidad según {{isfavourite}}.
  • amd/src/coursecard_favourite.js:
    • Listener delegado global (document.addEventListener('click', ...)) para que funcione también en cards re-renderizadas (filtros, búsqueda, paginación).
    • Al hacer click, llama a setFavouriteCourses (repository → WS core core_course_set_favourite_courses).
    • Sincroniza el ícono y los items del menú ⋮ (add-favourite / remove-favourite).
    • Publica CourseEvents.favourited / unfavorited por core/pubsub, igual que el toggle nativo del menú, para que otros componentes (block_myoverview) se mantengan sincronizados.

2. Campo "Profesor:"

Requisito

La etiqueta "Profesor:" (azul cobalto #0943b5, negrita) debe mostrarse siempre, aunque el curso no tenga ningún docente configurado (en ese caso queda vacía después del label).

Función externa — local_pccntr8203403_dashboard_get_course_teachers

Nueva función de Web Services del plugin local_pccntr8203403_dashboard (classes/external/get_course_teachers.php, registrada en db/services.php, ajax => true, type => read).

Reutiliza la misma API que usa el core para los "course contacts" (la línea "Teacher: X" en los listados de categorías de curso):

core_course_category::preload_course_contacts($courses); // batch, cacheada por core
(new core_course_list_element($record))->get_course_contacts();

Para cada courseid recibido:

  1. get_course() + can_access_course() — descarta cursos inexistentes o sin acceso del usuario (silenciosamente).
  2. preload_course_contacts() en batch sobre todos los cursos válidos (respeta $CFG->coursecontact y usa la caché core/coursecontacts).
  3. Devuelve [{courseid, teachers}], con teachers = nombres de docentes unidos por , (cadena vacía si el curso no tiene contactos configurados).

Template

coursecard.mustache añade, después del título, un párrafo siempre presente:

<p class="cdigital-coursecard__teachers">
<span class="cdigital-coursecard__teachers-label">{{#str}} coursecard_teacherlabel, theme_cdigital {{/str}}</span>
<span class="cdigital-coursecard__teachers-names" data-region="teacher-names" data-course-id="{{id}}"></span>
</p>

El <span> de nombres se rellena por JS; si el curso no tiene profesor, queda vacío pero la etiqueta "Profesor:" permanece visible.

Módulo AMD — coursecard_teachers.js (batching)

Cada card llama init(courseId) desde su propio {{#js}}. El módulo:

  • Acumula los courseId en un Set a nivel de módulo.
  • Agenda un único setTimeout(flush, 0) (debounce al siguiente tick).
  • En el flush, hace una sola llamada AJAX (getCourseTeachers) con todos los courseId pendientes de la página, y rellena cada .cdigital-coursecard__teachers-names[data-course-id="X"].

Esto evita N llamadas WS para N cards visibles (típicamente 6-12 en /my/).

3. Altura fija y anclaje inferior

  • .cdigital-coursecard__body ya usaba display: flex; flex-direction: column; justify-content: space-between;, que ancla .cdigital-coursecard__actions (progreso + "Ver curso" + menú ⋮) al final de la card.
  • Para que todas las cards de una fila midan igual independientemente de cuántos docentes tenga cada curso, .cdigital-coursecard__teachers reserva siempre el espacio de 2 líneas con -webkit-line-clamp: 2 + min-height: calc(14px * 1.5 * 2) (~46px, según Figma).
  • Se eliminó overflow: hidden de .cdigital-coursecard (regresión de una iteración anterior) para que el menú ⋮ desplegable no quede recortado. Los bordes redondeados se conservan porque .cdigital-coursecard__image ya tiene su propio border-radius: 16px 16px 0 0.

Archivos clave

ArchivoDescripción
theme/cdigital/templates/core_course/coursecard.mustacheOverride del template core: marcador de favorito + campo "Profesor:"
theme/cdigital/amd/src/coursecard_favourite.jsToggle de favorito (marcador), sincroniza ícono + menú ⋮ + pubsub
theme/cdigital/amd/src/coursecard_teachers.jsResuelve nombres de docentes por curso, en una llamada AJAX batcheada
theme/cdigital/amd/src/repository.jssetFavouriteCourses, getCourseTeachers (wrappers de core/ajax)
theme/cdigital/pix/Icon/bookmark.svg, bookmark-heart.svgIconos del marcador (24×24)
theme/cdigital/scss/preset/cdigital.scss.cdigital-coursecard*, .cdigital-coursecard__teachers*, .cdigital-coursecard__bookmark*
theme/cdigital/lang/{en,es}/theme_cdigital.phpStrings coursecard_viewcourse, coursecard_teacherlabel
local/pccntr8203403_dashboard/classes/external/get_course_teachers.phpFunción externa (WS) — nombres de docentes por curso
local/pccntr8203403_dashboard/db/services.phpRegistro de local_pccntr8203403_dashboard_get_course_teachers

Build y despliegue

Esta feature incluye módulos AMD que requieren build previo al despliegue. Para el procedimiento de build, ver Build de assets del tema. Para el despliegue completo (copia de código, upgrade, purga de cachés), ver Procedimiento de despliegue.