Escribir a los estudiantes inactivos
Un programa para un profesor que busca a los estudiantes que llevan un número de días sin abrir un curso, envía a cada uno un mensaje privado y después imprime un resumen. Por defecto solo los enumera: envía cuando se ejecuta con --send.
Usa: participantes, mensajes.
El programa
import { createMoodle, MessageError } from "@falkenslab/moodle-driver";
const COURSE_ID = 3; // course/view.php?id=3, o listCourses()
const STUDENT_ROLE_ID = 5; // Estudiante en un sitio por defecto: el programa lo comprueba contra los roles del curso
const DAYS = 14;
// Un ensayo salvo que se pida: los mensajes no se pueden retirar.
const send = process.argv.includes("--send");
await using moodle = createMoodle({ site: "https://campus.example.org/", profileDir: "/home/teacher/.moodle-profile", headless: true, downloadBrowser: true });
await moodle.logIn();
// Los nombres de los roles están en el idioma del sitio; lo que filtra es el id, así que comprueba que es un rol de este curso.
const { roles } = await moodle.participantFilters(COURSE_ID);
const role = roles.find((r) => r.id === STUDENT_ROLE_ID);
if (!role) throw new Error(`No hay rol ${STUDENT_ROLE_ID} en el curso ${COURSE_ID}; sus roles: ${roles.map((r) => `${r.id} ${r.name}`).join(", ")}`);
// El filtro "Inactivo durante más de" de la página de participantes, combinado con el rol.
const since = new Date(Date.now() - DAYS * 24 * 60 * 60 * 1000);
const inactive = await moodle.participants(COURSE_ID, { role: role.id, inactiveSince: since });
console.log(`${inactive.length} participantes con rol ${role.name} no han entrado al curso ${COURSE_ID} en ${DAYS} días.`);
const courseUrl = new URL(`course/view.php?id=${COURSE_ID}`, moodle.site).href;
const sent: string[] = [];
const failed: string[] = [];
for (const student of inactive) {
if (!send) {
console.log(` se escribiría a ${student.fullName} (${student.userId})`);
continue;
}
const text = `<p>Hola, ${student.fullName}:</p><p>Hace tiempo que no te vemos por el curso. Los últimos materiales están en <a href="${courseUrl}">${courseUrl}</a>. Escríbeme si algo te lo está poniendo difícil.</p>`;
try {
// Un mensaje privado, como lo envía el panel de mensajería: aparece en la conversación al instante.
await moodle.sendMessage(student.userId, text);
sent.push(student.fullName);
} catch (error) {
// Moodle no lo entregó (el estudiante bloquea los mensajes, por ejemplo): se anota y se sigue.
if (error instanceof MessageError) failed.push(`${student.fullName}: ${error.message}`);
else throw error;
}
}
if (send) {
console.log(`Enviados ${sent.length}, fallidos ${failed.length}.`);
for (const line of failed) console.log(` ${line}`);
} else {
console.log("Ensayo: no se ha enviado nada. Ejecútalo con --send para enviar los mensajes.");
}
Paso a paso
COURSE_IDes el id del curso (su URL, olistCourses()).STUDENT_ROLE_IDes el id del rol Estudiante: 5 en un sitio por defecto, pero los sitios pueden variar, así que el programa lo comprueba contra los roles queparticipantFilterslee de la página de participantes del curso. Los nombres de los roles llegan en el idioma del sitio ("Student", "Estudiante"), y por eso el programa filtra por id y solo imprime el nombre.logIn()vuelve al instante con una sesión guardada, o abre una ventana para que el profesor inicie sesión la primera vez.participantsconroleeinactiveSincepide a la tabla de participantes de Moodle los que tienen ese rol y no han entrado al curso desde esa fecha, como hace el filtro "Inactivo durante más de" de la página.- Sin
--send, el programa imprime a quién se escribiría y se detiene ahí: ejecútalo así primero y lee la lista. - Con
--send,sendMessageenvía a cada estudiante un mensaje privado del profesor. Aparece al instante en el panel de mensajes del estudiante, y Moodle le avisa según sus preferencias de notificación (una ventana emergente, un correo). Cada mensaje es una conversación aparte con ese estudiante. - Un mensaje que Moodle no entrega lanza
MessageErrorcon el motivo de Moodle; el programa los reúne y sigue con el resto, y después imprime el resumen.
Lo que imprime
Un ensayo de ejemplo, y después el envío real:
3 participantes con rol Estudiante no han entrado al curso 3 en 14 días.
se escribiría a Alan Turing (9)
se escribiría a Grace Hopper (12)
se escribiría a Edsger Dijkstra (15)
Ensayo: no se ha enviado nada. Ejecútalo con --send para enviar los mensajes.
3 participantes con rol Estudiante no han entrado al curso 3 en 14 días.
Enviados 2, fallidos 1.
Edsger Dijkstra: Moodle didn't send the message to user 15: El usuario al que intenta enviar un mensaje le ha bloqueado.
Variaciones
- Solo un grupo: añade
groupsacado departicipantFilters(COURSE_ID).groupso degroups(COURSE_ID):participants(COURSE_ID, { role: role.id, group: 21, inactiveSince: since }). - Los estudiantes que no han entrado nunca están en la misma lista; para distinguirlos el programa necesitaría las fechas del último acceso, que el driver deja fuera de los participantes a propósito.
- Usar el nombre de pila: Moodle solo da el nombre completo; un programa que quiera el nombre de pila lo separa él mismo, sabiendo que puede equivocarse con algunos nombres.
- Dejar que el profesor elija el rol: imprime
rolesy toma el id de la línea de órdenes en vez de una constante.
Cuando algo sale mal
NoParticipantsError: el profesor no puede ver los participantes del curso, o el curso no existe. CompruebaCOURSE_ID.MessageError: Moodle no entregó un mensaje, con su motivo (el estudiante bloquea los mensajes de quien no es contacto, o la mensajería está desactivada en el sitio). También antes de enviar, cuando un mensaje está vacío.NotLoggedInError: la sesión caducó; vuelve a ejecutarlo para iniciar sesión. Consulta Errores.
Datos que toca
participants es others-personal y participantFilters, course-content; sendMessage es communication: el programa escribe a otras personas en nombre del profesor, y por eso hace un ensayo salvo que se le diga lo contrario. Consulta Categorías de datos.