Cómo Desarrollar Plugins de Informes en Moodle: Guía Paso a Paso

Cómo Desarrollar Plugins de Informes en Moodle: Guía Paso a Paso

En el mundo de la educación en línea, Moodle se ha consolidado como una de las plataformas más utilizadas para gestionar cursos y contenidos educativos. Sin embargo, a medida que las instituciones y organizaciones crecen, también aumenta la necesidad de obtener informes personalizados que permitan analizar datos clave, como el número de estudiantes, profesores, fechas de inicio y fin de los cursos, y su visibilidad. ¿Alguna vez te has encontrado en la situación de necesitar un informe específico que Moodle no ofrece por defecto?

Imagina que un cliente te solicita un informe que muestre:

  • El número de estudiantes y profesores en cada curso.
  • La categoría a la que pertenece cada curso.
  • El estado de visibilidad (visible u oculto).
  • La posibilidad de filtrar los datos por un rango de fechas.

Con esta guía, no solo aprenderás a desarrollar este plugin, sino que también entenderás cómo estructurar tu código, gestionar permisos de acceso, generar datos eficientemente y mostrar los resultados en una interfaz interactiva utilizando jQuery DataTables. Además, te mostraré cómo implementar la internacionalización (i18n) para que tu plugin pueda ser utilizado en múltiples idiomas.

Para ello, crearemos un plugin llamado report_edf_course_report, que quedará similar a esta captura de pantalla. En ella observamos el título del informe, las fechas de inicio y de final para realizar la consulta, una tabla dinámica en la que mostrar y consultar la información, así como dos botones para exportar los datos de la tabla a formatos PDF y Excel.

Ya sea que estés desarrollando para una institución educativa, una empresa o como parte de un proyecto personal, este tutorial te proporcionará las herramientas necesarias para crear informes personalizados que agreguen valor a tu plataforma Moodle. ¡Empecemos!


A continuación veremos cada uno de los pasos para conseguir desarrollar este sencillo plugin de informes.

¿Necesitas una solución LMS personalizada o integrada con tus sistemas actuales?

Entornos de Formación #edTech Experts

En Entornos de Formación (www.edf.global) desarrollamos e integramos plataformas de aprendizaje a medida, adaptadas a las necesidades de tu organización. ¡No dudes en contactarnos para una consulta sin compromiso! Estamos aquí para ayudarte a crear el entorno de aprendizaje perfecto para tu equipo o institución.

Paso 1: Crear la Estructura del Plugin

Para comenzar, organizamos nuestro plugin siguiendo la estructura recomendada para los plugins de Moodle. Crearemos nuestra estructura de ficheros dentro del directorio report de nuestra instalación de Moodle. La estructura de ficheros es la siguiente:

report_edf_course_report/
├── classes
│   ├── output
│   │   ├── renderer.php
│   │   └── report.php
│   ├── report_generator.php
├── db
│   ├── access.php
│   ├── install.php
├── index.php
├── lang
│   ├── en
│   │   └── report_edf_course_report.php
│   └── es
│       └── report_edf_course_report.php
├── lib.php
├── settings.php
├── templates
│   └── report.mustache
└── version.php

Esta estructura nos permite separar la lógica del negocio, la presentación de datos y la configuración del plugin.

Fichero version.php

El fichero version.php en un plugin de Moodle define información clave para su instalación y funcionamiento.

¿Para qué sirve?

Este fichero le dice a Moodle:
El nombre del plugin
La versión actual
La versión mínima de Moodle requerida
El estado de desarrollo
El número de versión visible para los administradores

PHP
<?php
defined('MOODLE_INTERNAL') || die();

$plugin->component = 'report_edf_course_report';
$plugin->version = 2025031301;
$plugin->requires = 2022112806;
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '1.0';


Fichero settings.php

El fichero settings.php en nuestro plugin de Moodle se encarga de agregar el enlace al informe en el menú de administración de Moodle.

PHP
<?php
defined('MOODLE_INTERNAL') || die();

$ADMIN->add('reports', new admin_externalpage('report_edf_course_report',
    'EDF Reporte de Cursos',
    new moodle_url('/report/edf_course_report/index.php'),
    'report/edf_course_report:view'
));


Fichero db/install.php

El fichero db/install.php en un plugin de Moodle se ejecuta cuando el plugin se instala por primera vez. Se usa para definir configuraciones iniciales o realizar tareas de instalación.
En nuestro caso, nuestro plugin no incorpora definición de tablas nuevas o modificaciones de existentes, por lo que podemos dejar la función de instalación vacía.

PHP
<?php
defined('MOODLE_INTERNAL') || die();

/**
 * Acciones a ejecutar al instalar el plugin.
 */
function xmldb_report_edf_course_report_install() {
  // En nuestro caso vacío porque no necesitamos definir nada de db
}

¿Por qué es importante?

✔ Controla quién puede acceder al informe.
✔ Permite personalizar permisos en la configuración de Moodle.
✔ Se puede modificar para dar acceso a otros roles (ejemplo: profesores).

Paso 2: Definir los Permisos de Acceso

Antes de mostrar nuestro informe en Moodle, debemos asegurarnos de que solo los usuarios autorizados puedan verlo. Para esto, Moodle utiliza un sistema de permisos basado en capacidades.

Nuestro informe debe estar accesible solo para administradores y gestores, pero podríamos ampliarlo a otros roles si fuera necesario.

Definir permisos en db/access.php

En el directorio db/, creamos el fichero access.php, este define quién puede acceder al informe.

PHP
<?php
defined('MOODLE_INTERNAL') || die();

$capabilities = [
    'report/edf_course_report:view' => [
        'captype' => 'read',
        'contextlevel' => CONTEXT_SYSTEM,
        'archetypes' => [
            'manager' => CAP_ALLOW,
            'admin' => CAP_ALLOW,
        ],
    ],
];

Define el permiso report/edf_course_report:view

  • 'captype' => 'read' → Indica que el permiso solo permite ver el informe, sin modificar datos.
  • 'contextlevel' => CONTEXT_SYSTEM → Aplica el permiso a todo Moodle, no solo a cursos específicos.
  • 'archetypes' => [...] → Indica qué roles tienen acceso:
    • 'manager' => CAP_ALLOW' → Los gestores pueden ver el informe.
    • 'admin' => CAP_ALLOW' → Los administradores también tienen acceso.

¿Por qué es importante?

✔ Controla quién puede acceder al informe.
✔ Permite personalizar permisos en la configuración de Moodle.
✔ Se puede modificar para dar acceso a otros roles (ejemplo: profesores).

3. Generación de Datos del Informe

Una de las partes más críticas de cualquier plugin de informes es la generación de datos. Los informes deben extraer información relevante de la base de datos de Moodle, como cursos, usuarios, calificaciones, fechas de inicio y fin de los cursos, etc.

La fuente de datos, consulta SQL

Nuestra fuente de datos será la propia base de datos de nuestra instalación de Moodle, para ello escribiremos una consulta SQL que enlazará cursos con categorías de cursos, con usuarios, matriculaciones en cursos y roles de usuarios.

SQL
SELECT
    c.id AS course_id,
    c.fullname AS course_name,
    c.summary AS course_summary,
    c.startdate AS course_start_date,
    c.enddate AS course_end_date,
    CASE
        WHEN c.visible = 1 THEN 'visible'
        ELSE 'hidden'
    END 
      AS course_visibility,
    cat.id AS category_id,
    cat.name AS category_name,
    COUNT(DISTINCT CASE WHEN ra.roleid = 5 THEN ue.userid END) AS qty_students,
    COUNT(DISTINCT CASE WHEN ra.roleid IN (3, 4) THEN ue.userid END) AS
    qty_teachers
    
    FROM mdl_course c
    LEFT JOIN mdl_enrol e ON (e.courseid = c.id)
    LEFT JOIN mdl_user_enrolments ue ON (ue.enrolid = e.id)
    LEFT JOIN mdl_context ctx ON (
        ctx.instanceid = c.id 
        AND ctx.contextlevel = 50
    )
    LEFT JOIN mdl_role_assignments ra ON (
        ra.userid = ue.userid 
        AND ra.contextid = ctx.id
    )
    LEFT JOIN mdl_course_categories cat ON (c.category = cat.id)

    WHERE
          c.startdate >= :p_start_date AND
          c.startdate < :p_end_date
    GROUP BY
          c.id, c.fullname, c.startdate, c.enddate, c.visible, cat.id, cat.name
    ORDER BY
       c.startdate DESC

Esta consulta SQL está diseñada para extraer información detallada sobre cursos en una base de datos de Moodle, filtrando aquellos que comienzan dentro de un rango de fechas específico. A continuación, se describe cada parte de la consulta:

Selección de columnas

  • c.id AS course_id: ID del curso.
  • c.fullname AS course_name: Nombre completo del curso.
  • c.summary AS course_summary: Resumen del curso.
  • c.startdate AS course_start_date: Fecha de inicio del curso.
  • c.enddate AS course_end_date: Fecha de finalización del curso.
  • CASE WHEN c.visible = 1 THEN 'visible' ELSE 'hidden' END AS course_visibility: Indica si el curso está visible o oculto.
  • cat.id AS category_id: ID de la categoría del curso.
  • cat.name AS category_name: Nombre de la categoría del curso.
  • COUNT(DISTINCT CASE WHEN ra.roleid = 5 THEN ue.userid END) AS qty_students: Cuenta el número de estudiantes en el curso (rol con ID 5).
  • COUNT(DISTINCT CASE WHEN ra.roleid IN (3, 4) THEN ue.userid END) AS qty_teachers: Cuenta el número de profesores en el curso (roles con ID 3 y 4).

Tablas involucradas

  • mdl_course c: Tabla principal de cursos.
  • mdl_enrol e: Tabla de métodos de inscripción.
  • mdl_user_enrolments ue: Tabla de inscripciones de usuarios.
  • mdl_context ctx: Tabla de contextos (define el ámbito de los roles).
  • mdl_role_assignments ra: Tabla de asignaciones de roles.
  • mdl_course_categories cat: Tabla de categorías de cursos.

Relaciones (JOINs)

  • LEFT JOIN mdl_enrol e ON e.courseid = c.id: Relaciona los cursos con los métodos de inscripción.
  • LEFT JOIN mdl_user_enrolments ue ON ue.enrolid = e.id: Relaciona las inscripciones con los usuarios.
  • LEFT JOIN mdl_context ctx ON ctx.instanceid = c.id AND ctx.contextlevel = 50: Relaciona los cursos con su contexto (nivel 50 corresponde a cursos).
  • LEFT JOIN mdl_role_assignments ra ON ra.userid = ue.userid AND ra.contextid = ctx.id: Relaciona las asignaciones de roles con los usuarios y el contexto.
  • LEFT JOIN mdl_course_categories cat ON c.category = cat.id: Relaciona los cursos con sus categorías.

Filtros (WHERE)

  • c.startdate >= :p_start_date AND c.startdate < :p_end_date: Filtra los cursos que comienzan dentro del rango de fechas especificado (:p_start_date y :p_end_date son parámetros).

Agrupación (GROUP BY)

  • GROUP BY c.id, c.fullname, c.startdate, c.enddate, c.visible, cat.id, cat.name: Agrupa los resultados por curso y categoría para calcular correctamente las cantidades de estudiantes y profesores.

Ordenación (ORDER BY)

  • ORDER BY c.startdate DESC: Ordena los resultados por fecha de inicio del curso en orden descendente (los más recientes primero).

En resumen, esta consulta obtiene información detallada de los cursos que comienzan en un rango de fechas específico, incluyendo:

  • Datos básicos del curso (nombre, resumen, fechas, visibilidad).
  • Categoría a la que pertenece el curso.
  • Cantidad de estudiantes y profesores inscritos.

Fichero classes/report_generator.php

Este fichero es donde se define la lógica para generar los datos del informe. En este ejemplo, vamos a crear un informe de cursos que contiene datos sobre el nombre del curso, la fecha de inicio, la fecha de finalización y la cantidad de estudiantes y profesores asignados.

PHP
<?php

namespace report_edf_course_report\classes;

defined('MOODLE_INTERNAL') || die();

class report_generator {

    public static function get_report_data($startDate, $endDate) {
        global $DB, $CFG;

        // Si $startdate está vacío, usar el inicio del año actual
        if (empty($startDate)) {
            $startDate = strtotime(date('Y-01-01')); // Primer día del año actual
        }
        else {
            $startDate = strtotime($startDate);
        }

        // Si $enddate está vacío, usar el fin del año actual
        if (empty($endDate)) {
            $endDate = strtotime(date('Y-12-31')); // Último día del año actual
        }
        else {
            $endDate = strtotime($endDate);
        }

        // Validar las fechas
        if (!$startDate || !$endDate) {
            throw new moodle_exception('Fechas inválidas.');
        }

        // Consulta SQL
        $sql = "SELECT
            c.id AS course_id,
            c.fullname AS course_name,
            c.summary AS course_summary,
            c.startdate AS course_start_date,
            c.enddate AS course_end_date,
            CASE
                WHEN c.visible = 1 THEN 'visible'
                ELSE 'hidden'
            END AS course_visibility,
            cat.id AS category_id,
            cat.name AS category_name,
            COUNT(DISTINCT CASE WHEN ra.roleid = 5 THEN ue.userid END) AS qty_students,
            COUNT(DISTINCT CASE WHEN ra.roleid IN (3, 4) THEN ue.userid END) AS qty_teachers
        FROM {course} c
        LEFT JOIN {enrol} e ON e.courseid = c.id
        LEFT JOIN {user_enrolments} ue ON ue.enrolid = e.id
        LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = 50
        LEFT JOIN {role_assignments} ra ON ra.userid = ue.userid AND ra.contextid = ctx.id
        LEFT JOIN {course_categories} cat ON c.category = cat.id
        WHERE
            c.startdate >= :p_start_date AND
            c.startdate < :p_end_date
        GROUP BY
            c.id, c.fullname, c.startdate, c.enddate, c.visible, cat.id, cat.name
        ORDER BY
            c.startdate DESC";


        // Parámetros de la consulta
        $params = [
            'p_start_date' => (int) $startDate,
            'p_end_date'   => (int) $endDate,
        ];


        // Ejecutar la consulta
        try {
            $data = $DB->get_records_sql($sql, $params);
            if ($data !== null && count($data) > 0) {

                foreach ($data as $key=>$courseInfo) {
                    $courseInfo->course_name    = self::mlangFilter($courseInfo->course_name);
                    $courseInfo->course_summary = self::mlangFilter($courseInfo->course_summary);
                    $courseInfo->category_name  = self::mlangFilter($courseInfo->category_name);;
                }
            }
            return $data;
        }
        catch (Exception $e) {
            // Capturar cualquier error de SQL y mostrarlo
            throw new moodle_exception('Error al ejecutar la consulta SQL: ' . $e->getMessage());
        }
    }

    private function mlangFilter($text) {
        // Filtrar los contenidos que utilizan Multilang2 y extraer el contenido en español
        $pattern = '/{mlang\s+es}([^}]*){mlang}/';
        preg_match($pattern, $text, $matches);
        return isset($matches[1]) ? $matches[1] : $text;
    }
}


¿Qué hace el fichero?

Este fichero define una clase PHP llamada report_generator dentro del espacio de nombres report_edf_course_report\classes. Su propósito es generar un informe personalizado en Moodle que obtiene y procesa datos de cursos basados en un rango de fechas.

  1. Obtiene datos de cursos:
    • Usa una consulta SQL para extraer información de cursos, como nombre, resumen, fechas de inicio y fin, visibilidad, categoría, cantidad de estudiantes y profesores.
  2. Filtra por fechas:
    • Permite filtrar los cursos que comienzan dentro de un rango de fechas especificado. Si no se proporcionan fechas, usa el año actual por defecto.
  3. Procesa textos multilenguaje:
    • Filtra los textos que usan el formato de multilenguaje ({mlang}) en Moodle para extraer solo el contenido en español.
  4. Maneja errores:
    • Captura excepciones durante la ejecución de la consulta SQL y lanza mensajes de error claros.
  5. Devuelve los datos :
    • Finalmente, devuelve los datos en formato array de PHP.

4. Mostrar el Informe en la Interfaz de Usuario

Una vez que hayas generado los datos, el siguiente paso es mostrarlos en una página de Moodle. Moodle utiliza Mustache como motor de plantillas, lo que facilita la integración de datos en HTML.
En este paso veremos cómo crear la plantilla para mostrar el informe visualmente y añadir las clases que enlazan los datos del informe con la plantilla Mustache, a través del motor de plantillas.

Plantilla de Mustache (fichero templates/report.mustache)

Mustache es un sistema de plantillas sencillo y potente que se utiliza para generar HTML (u otros formatos de texto) a partir de datos estructurados. Es «logic-less», lo que significa que no permite lógica compleja en las plantillas (como bucles o condicionales avanzados), lo que la hace fácil de usar y mantener


¿Qué hace este componente?

  1. Muestra el informe en una tabla interactiva:
    • Utiliza jQuery DataTables para convertir una tabla HTML básica en una tabla interactiva con paginación, búsqueda y ordenación.
    • Permite al usuario filtrar los datos por un rango de fechas.
  2. Exporta los datos:
    • Incluye botones para exportar los datos a PDF y Excel, usando las librerías pdfmake y las funcionalidades de DataTables.
  3. Integra características de Moodle:
    • Usa cadenas traducibles para adaptar el informe a diferentes idiomas.
    • Incluye enlaces dinámicos a cursos y categorías, utilizando la URL base de Moodle.

Disclaimer: Para no hacer más largo este ejemplo, se ha utilizado el JavaScript básico e incluido en la plantilla. Se recomienda encarecidamente hacer uso de módulos AMD de JavaScript para modularizar el frontend y separar la lógica del frontend de los datos y de la presentación.

Handlebars
<!-- Cargar los CSS de jQuery DataTable desde CDN -->
<link href="https://cdn.datatables.net/v/bs5/jszip-3.10.1/dt-2.2.2/b-3.2.2/b-colvis-3.2.2/b-html5-3.2.2/b-print-3.2.2/cr-2.0.4/date-1.5.5/fh-4.0.1/sb-1.8.2/datatables.css" rel="stylesheet" integrity="sha384-8SyYmFf6YfPL4cPTIo0CSzlHDRj0lMFnufGQ+JKvfrvOQs2UnaP2iI4Y8WvY3Ajo" crossorigin="anonymous">


<!-- Título del informe -->
<div class="d-flex mb-3">
    <h2>{{#str}} reporttitle, report_edf_course_report {{/str}}</h2>
</div>

<div class="d-flex justify-content-between align-items-center mb-3">
    <!-- Filtro de fechas -->
    <form method="get" class="d-flex align-items-center" id="filterForm">
        <div class="d-flex align-items-center mr-3">
            <label for="startdate" class="mr-2">{{#str}} startdate, report_edf_course_report {{/str}}:</label>
            <input type="date" id="startdate" name="startdate" value="{{startDate}}" class="form-control mr-2">
        </div>
        <div class="d-flex align-items-center mr-3">
            <label for="enddate" class="mr-2">{{#str}} enddate, report_edf_course_report {{/str}}:</label>
            <input type="date" id="enddate" name="enddate" value="{{endDate}}" class="form-control mr-2">
        </div>
        <button type="submit" class="btn btn-primary">{{#str}} filter, report_edf_course_report {{/str}}</button>
    </form>
    
    <!-- Botones de exportación ( los crea la tabla ) -->
    <div class="dt-container dt-bootstrap5 dt-empty-footer bt-toolbar">
    </div>
</div>

<!-- Tabla del informe -->
<table class="generaltable" id="courseReportTable">
    <thead>
    <tr>
        <th>{{#str}} courseid, report_edf_course_report {{/str}}</th>
        <th>{{#str}} coursename, report_edf_course_report {{/str}}</th>
        <th>{{#str}} startdate, report_edf_course_report {{/str}}</th>
        <th>{{#str}} enddate, report_edf_course_report {{/str}}</th>
        <th>{{#str}} visibility, report_edf_course_report {{/str}}</th>
        <th>{{#str}} category, report_edf_course_report {{/str}}</th>
        <th>{{#str}} students, report_edf_course_report {{/str}}</th>
        <th>{{#str}} teachers, report_edf_course_report {{/str}}</th>
    </tr>
    </thead>
    <tbody>
        {{#reportData}}
        <!-- Para cada fila del informe -->
        <tr>
            <td>{{course_id}}</td>
            <td><a href="{{baseUrl}}/course/view.php?id={{course_id}}" target="_blank">{{course_name}}</a></td>
            <td>{{#userdate}} {{course_start_date}}, %d/%m/%Y {{/userdate}}</td>
            {{#course_end_date}}
                <td>{{#userdate}} {{course_end_date}}, %d/%m/%Y {{/userdate}}</td>
            {{/course_end_date}}
            {{^course_end_date}}
                <td></td>
            {{/course_end_date}}
            <td>{{course_visibility}}</td>
            <td><a href="{{baseUrl}}/course/category.php?id={{category_id}}" target="_blank">{{category_name}}</a></td>
            <td>{{qty_students}}</td>
            <td>{{qty_teachers}}</td>
        </tr>
        {{/reportData}}
    </tbody>
</table>

<!-- Cargar los scripts de jQuery DataTable desde CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.js" integrity="sha384-P2rohseTZr3+/y/u+6xaOAE3CIkcmmC0e7ZjhdkTilUMHfNHCerfVR9KICPeFMOP" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js" integrity="sha384-/RlQG9uf0M2vcTw3CX7fbqgbj/h8wKxw7C3zu9/GxcBPRKOEcESxaxufwRXqzq6n" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/v/bs5/jszip-3.10.1/dt-2.2.2/b-3.2.2/b-colvis-3.2.2/b-html5-3.2.2/b-print-3.2.2/cr-2.0.4/date-1.5.5/fh-4.0.1/sb-1.8.2/datatables.js" integrity="sha384-4NbBefR1wWBc8x32BpW9nPSwozOcPWKN4vlas43aZkC/WgKWpuFHwP079Be2wqLA" crossorigin="anonymous"></script>

<script type="text/javascript">
    // Lógica de jQuery Datatables
    $(document).ready(function() {
        var table = $("#courseReportTable").DataTable({
            layout: {
                topStart: ['pageLength','buttons'],
                topEnd: 'search',
                bottomStart: 'info',
                bottomEnd: 'paging'
            },
            buttons: [
                {
                    extend: 'pdf',
                    className: 'pdf-ext-all mr-2',
                    text: '<i class="fas fa-file-pdf"></i> {{#str}} exporttopdf, report_edf_course_report {{/str}}',
                    orientation: 'portrait',
                    pageSize: 'A4',
                    title: '{{#str}} reporttitle, report_edf_course_report {{/str}}',
                    customize: function(doc) {
                        if (doc.styles) {
                            if (doc.styles.tableHeader) {
                                doc.styles.tableHeader.fillColor = '#007bff';
                            }
                            if (doc.styles.tableBodyEven) {
                                doc.styles.tableBodyEven.fillColor = '#f0f0f0';
                            }
                        }
                    }
                },
                {
                    extend: 'excel',
                    className: 'excel-ext-all mr-2',
                    text: '<i class="fas fa-file-excel"></i> {{#str}} exporttoexcel, report_edf_course_report {{/str}}',
                    title: '{{#str}} reporttitle, report_edf_course_report {{/str}}',
                    exportOptions: {
                        columns: ':visible'
                    }
                },
            ],
            "language": {
                url: '//cdn.datatables.net/plug-ins/2.2.2/i18n/es-ES.json',
            }
        });
        table.on('draw', function(e) {
            // Mover botones de exportación alineados con el formulario
            $(".bt-toolbar").append($(".buttons-html5"));
        });

    });
</script>

A continuación mostraremos visualmente todos los elementos que se dibujan con la plantilla.

1. Formulario de filtrado y botones de exportación
Se permite al usuario introducir la fecha de inicio y de final de los datos que quiere obtener en el informe, por defecto es un periodo de un año. Además, se le añaden al usuario botones para poderse exportar los datos del informe a ficheros en formatos PDF y Excel, lo que lo hace ideal para tratar los datos externamente a Moodle o integrarlos en otros procesos de negocio.

2. Tabla dinámica de datos
Haciendo uso de la librería jQuery Datatables y con los poderes del anticuado jQuery se le añade una capa de interactividad a la tabla estática creda en la plantilla y rellenada por el procesador de plantillas de Mustache. Con esta librería hacemos que el usuario sea capaz de ordenar los datos por cada columna, buscar textos en global o por columa – no implementado aquí – visualizar los datos paginados y mostrar más o menos registros por página.



Fichero classes/output/renderer.php

El fichero classes/output/renderer.php es un renderizador personalizado para nuestro informe en Moodle. Su función principal es tomar los datos del informe, preparados previamente en un objeto report, y convertirlos en HTML para mostrarlos en la interfaz de usuario. Para ello, utiliza el sistema de plantillas de Moodle, específicamente una plantilla llamada report_edf_course_report/report. El método render_report recibe el objeto report, llama a su método export_for_template para formatear los datos, y luego usa render_from_template para generar el HTML final. Este enfoque separa claramente la lógica de negocio (generación de datos) de la presentación (renderizado en HTML), siguiendo las mejores prácticas de Moodle.

El renderizador extiende la clase plugin_renderer_base, que es una clase base de Moodle para renderizar componentes de plugins. Esto permite integrar el informe en la interfaz de Moodle de manera consistente con el resto del sistema. La plantilla utilizada (report_edf_course_report/report) debe estar definida en la carpeta templates/ del plugin y es responsable de definir cómo se estructuran y estilizan los datos en la página. En resumen, este fichero es el puente entre los datos del informe y su visualización en Moodle, asegurando que el contenido se muestre de manera clara y accesible para los usuarios autorizados.

PHP
<?php
namespace report_edf_course_report\output;

defined('MOODLE_INTERNAL') || die();

use plugin_renderer_base;

class renderer extends plugin_renderer_base {

    /**
     * Renderiza el informe de cursos.
     *
     * @param report $report Objeto del informe.
     * @return string HTML renderizado.
     */
    protected function render_report(report $report) {
        return $this->render_from_template('report_edf_course_report/report', $report->export_for_template($this));
    }
}

Fichero classes/output/report.php

PHP
<?php
namespace report_edf_course_report\output;

defined('MOODLE_INTERNAL') || die();

use renderable;
use renderer_base;
use templatable;

class report implements renderable, templatable {

    protected $reportdata;
    protected $startdate;
    protected $enddate;

    public function __construct($reportdata, $startdate, $enddate) {
        $this->reportdata = $reportdata;
        $this->startdate = $startdate;
        $this->enddate = $enddate;
    }

    public function export_for_template(renderer_base $output) {

        global $CFG;

        return [
            'baseUrl' => $CFG->wwwroot,
            'reportData' => array_values($this->reportdata),
            'startDate' => $this->startdate,
            'endDate' => $this->enddate,
        ];
    }
}

El fichero classes/output/report.php es una clase que actúa como modelo de datos para el informe en Moodle. Su función principal es encapsular los datos del informe y prepararlos para ser renderizados en la interfaz de usuario. La clase implementa dos interfaces clave de Moodle: renderable (que indica que el objeto puede ser renderizado) y templatable (que permite que los datos se formateen para su uso en plantillas). En el constructor, recibe tres parámetros: los datos del informe ($reportdata), la fecha de inicio ($startdate) y la fecha de fin ($enddate). Estos datos se almacenan como propiedades de la clase para su posterior uso.

El método export_for_template es el núcleo de esta clase. Su objetivo es transformar los datos del informe en un formato compatible con el sistema de plantillas de Moodle. En este caso, devuelve un array que incluye la URL base de Moodle ($CFG->wwwroot), los datos del informe (reportData), y las fechas de inicio y fin (startDate y endDate). Este array se pasa a la plantilla (report_edf_course_report/report) para generar el HTML final. En resumen, esta clase actúa como un intermediario entre la lógica de negocio (que genera los datos) y la presentación (que los muestra), asegurando que los datos estén bien estructurados y listos para ser renderizados.

El controllador, fichero index.php

A continuación crearemos el fichero index.php que será el punto de entrada a nuestro informe.
Este fichero se encargará de comprobar si tenemos login y capacidades para acceder al informe, después configurará la página, se encargará de recibir los parámetros enviados por el formulario y finalmente ejecutará el informe, generando los resultados en la plantilla de Mustache y enviando los mismos al usuario.

PHP
<?php
require_once('../../config.php');
require_once($CFG->dirroot . '/report/edf_course_report/classes/report_generator.php');

global $PAGE, $OUTPUT;

require_login();
$context = context_system::instance();
require_capability('report/edf_course_report:view', $context);

$PAGE->set_url(new moodle_url('/report/edf_course_report/index.php'));
$PAGE->set_context($context);
$PAGE->set_title(get_string('reporttitle', 'report_edf_course_report'));
$PAGE->set_heading(get_string('reporttitle', 'report_edf_course_report'));
$PAGE->set_pagelayout('report');

// Fechas predeterminadas (último año)
$defaultstart   = date('Y-m-d', strtotime('-1 year'));
$defaultend     = date('Y-m-d');

// Obtener fechas desde el formulario
$startdate  = optional_param('startdate', $defaultstart, PARAM_RAW);
$enddate    = optional_param('enddate', $defaultend, PARAM_RAW);

// Validar fechas
if (strtotime($startdate) > strtotime($enddate)) {
    throw new moodle_exception('La fecha de inicio no puede ser mayor que la fecha de fin.');
}

// Obtener datos filtrados
$reportdata = \report_edf_course_report\classes\report_generator::get_report_data($startdate, $enddate);

// Asegura que jQuery esté disponible
$PAGE->requires->jquery(); 

// Crear instancia de la clase de salida
$output = $PAGE->get_renderer('report_edf_course_report');
$report = new \report_edf_course_report\output\report($reportdata, $startdate, $enddate);

echo $OUTPUT->header();
echo $output->render($report);
echo $OUTPUT->footer();
?>

Funcionamiento de index.php

1. Inclusión de dependencias y configuración inicial

El fichero comienza incluyendo archivos necesarios y configurando el entorno:

  • require_once('../../config.php'): Incluye el archivo de configuración de Moodle, que es esencial para acceder a las funciones globales y la base de datos.
  • require_once($CFG->dirroot . '/report/edf_course_report/classes/report_generator.php'): Incluye la clase report_generator, que se encarga de obtener los datos del informe.
  • global $PAGE, $OUTPUT: Hace disponibles las variables globales $PAGE y $OUTPUT, que se usan para gestionar la página y su salida.

2. Autenticación y permisos

Antes de mostrar el informe, se verifica que el usuario esté autenticado y tenga los permisos necesarios:

  • require_login(): Asegura que el usuario haya iniciado sesión.
  • $context = context_system::instance(): Obtiene el contexto del sistema, que es necesario para verificar permisos.
  • require_capability('report/edf_course_report:view', $context): Verifica que el usuario tenga el permiso report/edf_course_report:view para acceder al informe. Si no lo tiene, se le deniega el acceso.

3. Configuración de la página

Se configura la página de Moodle donde se mostrará el informe:

  • $PAGE->set_url(...): Define la URL de la página.
  • $PAGE->set_context($context): Establece el contexto de la página.
  • $PAGE->set_title(...) y $PAGE->set_heading(...): Configuran el título y el encabezado de la página, usando una cadena de texto traducible (reporttitle).
  • $PAGE->set_pagelayout('report'): Define el diseño de la página como «report», que es un layout común para informes en Moodle.

4. Manejo de fechas

El informe permite filtrar los datos por un rango de fechas:

  • Fechas predeterminadas: Si no se proporcionan fechas, se usa el último año como rango predeterminado.
    • $defaultstart = date('Y-m-d', strtotime('-1 year')): Fecha de inicio predeterminada (hace un año).
    • $defaultend = date('Y-m-d'): Fecha de fin predeterminada (hoy).
  • Obtener fechas desde el formulario: Usa optional_param para obtener las fechas enviadas por el usuario a través de un formulario.
    • $startdate = optional_param('startdate', $defaultstart, PARAM_RAW): Obtiene la fecha de inicio.
    • $enddate = optional_param('enddate', $defaultend, PARAM_RAW): Obtiene la fecha de fin.
  • Validación de fechas: Verifica que la fecha de inicio no sea mayor que la fecha de fin. Si lo es, lanza una excepción.

5. Obtención de datos del informe

Se obtienen los datos del informe llamando al método get_report_data de la clase report_generator:

  • $reportdata = \report_edf_course_report\classes\report_generator::get_report_data($startdate, $enddate): Obtiene los datos filtrados por el rango de fechas.

6. Configuración de JavaScript

Se asegura que jQuery esté disponible en la página:

  • $PAGE->requires->jquery(): Carga jQuery, que puede ser necesario para funcionalidades adicionales en el informe.

7. Renderización del informe

Se prepara y renderiza el informe:

echo $OUTPUT->footer(): Muestra el pie de página.

$output = $PAGE->get_renderer('report_edf_course_report'): Obtiene el renderizador personalizado del plugin.

$report = new \report_edf_course_report\output\report($reportdata, $startdate, $enddate): Crea una instancia de la clase report, pasando los datos y las fechas.

echo $OUTPUT->header(): Muestra el encabezado de la página.

echo $output->render($report): Renderiza el informe utilizando el renderizador personalizado.

5. Último paso, internacionalización 18n

La internacionalización (i18n) es un paso crucial para asegurar que tu informe personalizado en Moodle pueda ser utilizado por usuarios de diferentes idiomas. Moodle cuenta con un sistema robusto de traducción que permite adaptar las cadenas de texto a múltiples idiomas. En este paso, nos aseguraremos de que todas las etiquetas, mensajes y textos del informe estén preparados para ser traducidos.

¿Qué es la internacionalización (i18n)?

La internacionalización es el proceso de diseñar y desarrollar un software para que pueda adaptarse a diferentes idiomas y regiones sin necesidad de cambios en el código. En Moodle, esto se logra mediante el uso de archivos de idioma que contienen las traducciones de las cadenas de texto.

¿Cómo implementar internacionalización en nuestro informe?

Para añadir traducción a nuestro informe de Moodle, debemos crear el directorio lang y dentro de él un subdirectorio para cada uno de los idiomas a los que queremos traducir nuestro informe, por ejemplo en y es para inglés y castellano respectivamente. Finalmente, para cada idioma añadiremos un fichero report_edf_course_report.php que tiene que llevar el mismo nombre nuestro informe, con extensión php.
En cada fichero de idioma las cadenas se traducen de la siguiente forma ( clave, valor ):

PHP
<?php
$string['cadena'] = 'Valor de traducción';

Traducción en castellano ( fichero lang/es/report_edf_course_report.php )

PHP
<?php
defined('MOODLE_INTERNAL') || die();

$string['pluginname'] = 'Informe de Cursos EDF';
$string['reportname'] = 'Informe de Cursos';
$string['viewreport'] = 'Ver Informe de Cursos EDF';
$string['courseid'] = 'ID del Curso';
$string['coursename'] = 'Nombre del Curso';
$string['courselink'] = 'Enlace al Curso';
$string['startdate'] = 'Fecha de Inicio';
$string['enddate'] = 'Fecha de Fin';
$string['visibility'] = 'Visibilidad';
$string['category'] = 'Categoría';
$string['studentscount'] = 'Número de Estudiantes';
$string['teacherscount'] = 'Número de Profesores';
$string['filterdates'] = 'Filtrar por Fecha';
$string['submit'] = 'Filtrar';
$string['privacy:metadata'] = 'Este complemento no almacena datos personales.';
$string['reporttitle'] = 'Informe de Cursos EDF';
$string['viewcourse'] = 'Ver Curso';
$string['exporttopdf'] = 'Exportar a PDF';
$string['exporttoexcel'] = 'Exportar a Excel';
$string['filter'] = 'Filtrar';
$string['export'] = 'Exportar';
$string['exportpdf'] = 'Exportar a PDF';
$string['exportexcel'] = 'Exportar a excel';
$string['students'] = 'Estudiantes';
$string['teachers'] = 'Profesores';

Traducción al inglés ( fichero lang/en/report_edf_course_report.php )

PHP
<?php
defined('MOODLE_INTERNAL') || die();

$string['pluginname'] = 'EDF Course Report';
$string['reportname'] = 'Course Report';
$string['viewreport'] = 'View EDF Course Report';
$string['courseid'] = 'Course ID';
$string['coursename'] = 'Course Name';
$string['courselink'] = 'Course Link';
$string['startdate'] = 'Start Date';
$string['enddate'] = 'End Date';
$string['visibility'] = 'Visibility';
$string['category'] = 'Category';
$string['studentscount'] = 'Number of Students';
$string['teacherscount'] = 'Number of Teachers';
$string['filterdates'] = 'Filter by Date';
$string['submit'] = 'Filter';
$string['privacy:metadata'] = 'This plugin does not store personal data.';
$string['reporttitle'] = 'EDF Course Report';
$string['viewcourse'] = 'View Course';
$string['exporttopdf'] = 'Export to PDF';
$string['exporttoexcel'] = 'Export to Excel';
$string['filter'] = 'Filter';
$string['export'] = 'Export';
$string['exportpdf'] = 'Export to PDF';
$string['exportexcel'] = 'Export to excel';
$string['students'] = 'Qty students';
$string['teachers'] = 'Qty teachers';


Conclusiones

Desarrollar plugins de informes personalizados en Moodle es una tarea que combina lógica de negocio, presentación de datos y consideraciones de usabilidad. A lo largo de este tutorial, hemos explorado cómo crear un plugin robusto y escalable, desde la definición de la estructura del proyecto hasta la generación de informes dinámicos y su visualización en la interfaz de usuario.


Aspectos clave del desarrollo:

  1. Aspectos clave del desarrollo
  2. En primer lugar, la estructura modular y organizada ha sido fundamental en este proyecto. Hemos seguido las mejores prácticas de Moodle, separando la lógica de negocio (en report_generator.php) de la presentación (usando plantillas Mustache y un renderizador personalizado). Como resultado, esto no solo facilita el mantenimiento del código, sino que también permite una mayor flexibilidad para futuras actualizaciones.
  3. En cuanto a los permisos y seguridad, hemos implementado un sistema que garantiza que solo los usuarios autorizados (como administradores y gestores) puedan acceder al informe. Esto es especialmente importante porque protege la privacidad de los datos y asegura el cumplimiento de las políticas de seguridad de Moodle.
  4. Por otro lado, la generación eficiente de datos ha sido uno de los pilares de este proyecto. Mediante consultas SQL optimizadas, hemos logrado extraer información detallada de los cursos, incluyendo datos como el número de estudiantes, profesores, fechas de inicio y fin, y visibilidad. Además, hemos añadido la capacidad de filtrar los resultados por un rango de fechas, lo que hace que el informe sea más útil y adaptable a las necesidades del usuario.
  5. En relación con la interfaz de usuario, gracias a la integración de jQuery DataTables, hemos creado una tabla dinámica que permite a los usuarios ordenar, filtrar y paginar los datos de manera intuitiva. Asimismo, hemos incluido botones para exportar los informes a PDF y Excel, lo que facilita el análisis externo de los datos.
  6. sFinalmente, la internacionalización (i18n) ha sido otro aspecto clave. Hemos preparado el plugin para ser utilizado en múltiples idiomas, creando archivos de traducción en inglés y español. De esta manera, no solo mejoramos la accesibilidad del informe, sino que también seguimos las mejores prácticas de desarrollo en Moodle, asegurando que el plugin pueda ser utilizado en entornos globales.

Lecciones aprendidas:

  • En primer lugar, la separación de preocupaciones ha demostrado ser fundamental. Mantener la lógica de negocio separada de la presentación es esencial para crear plugins mantenibles y escalables.
  • En segundo lugar, la optimización de consultas es crucial, especialmente al trabajar con grandes volúmenes de datos. Optimizar las consultas SQL garantiza un rendimiento óptimo y evita cuellos de botella en la generación de informes.
  • Por último, la experiencia del usuario es un factor clave. Herramientas como DataTables y la capacidad de exportar datos en diferentes formatos no solo mejoran la usabilidad, sino que también aumentan la satisfacción del usuario final.

Próximos pasos:

En cuanto a las mejoras futuras, este plugin puede ser extendido y mejorado de varias maneras:

  • Integración con otras herramientas: Podrías añadir la capacidad de integrar el informe con herramientas de análisis de datos como Google Analytics o Power BI. De esta forma, los usuarios podrían obtener insights aún más profundos.
  • Pruebas automatizadas: Implementar pruebas unitarias y de integración es esencial para asegurar que el plugin funcione correctamente en diferentes versiones de Moodle. Esto garantizaría la estabilidad y confiabilidad del plugin a largo plazo.
  • Módulos adicionalesOtra opción interesante sería crear módulos complementarios que permitan a los usuarios personalizar aún más los informes, como la inclusión de gráficos o métricas adicionales.

Reflexión final:

En resumen, crear informes personalizados en Moodle no solo es una excelente manera de agregar valor a la plataforma, sino que también es una oportunidad para mejorar la toma de decisiones basada en datos. Con las herramientas y técnicas que hemos explorado en este tutorial, estás bien equipado para desarrollar soluciones que no solo cumplan con los requisitos técnicos, sino que también mejoren la experiencia de los usuarios finales.

Ahora que has adquirido estos conocimientos, estás listo para llevar tus habilidades de desarrollo de Moodle al siguiente nivel y crear informes personalizados que marquen la diferencia. 
¡El siguiente paso es ponerlo en práctica!

Listo para transformar tu plataforma de aprendizaje?

Si este tutorial te ha inspirado y estás buscando una solución personalizada para tu plataforma Moodle, no dudes en contactarme. En Entornos de Formación, somos especialistas en tecnología educativa (#EdTech) y estamos aquí para ayudarte a crear entornos de aprendizaje innovadores, eficientes y adaptados a tus necesidades.

Entornos de Formación #edTech Experts

📩 Contáctanos hoy mismo a través de nuestro formulario de contacto o escríbeme directamente a mi formulario de contacto.

💡 ¿Tienes preguntas o necesitas asesoramiento? ¡Déjame un comentario abajo! Estaré encantado de ayudarte y seguir conversando sobre cómo podemos mejorar tu plataforma educativa.

🚀 Juntos, podemos llevar tu proyecto al siguiente nivel. ¡Espero saber de ti pronto!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *