
Compartir:
Karl is a writer, speaker, and technology team lead. He’s currently the Chief Technology Officer at The Graide Network and runs CFP Land in his spare time.
Crear una herramienta de recordatorio por SMS para profesores con Google Classroom
Tiempo de lectura: 8 minutos
Llevo varios años trabajando en tecnología educativa, y un problema que oigo a menudo a los profesores es que los alumnos no comprueban sus cuentas de correo electrónico. Hay empresas enteras, como Recordaren torno a este problema de comunicación.
En este tutorial, vamos a crear una aplicación de recordatorio por SMS que permita a los profesores recordar a sus alumnos las próximas tareas en Google Classroom. Utilizaremos la API de Google Classroom para aplicar la autenticación y obtener datos del curso y de las tareas, y la API de Messages de Vonage para alimentar los mensajes de texto que los profesores envían a sus alumnos.
Planificación de la solicitud
Antes de empezar, vamos a entender la funcionalidad básica y la arquitectura de nuestra aplicación. En este tutorial, abordaremos tres historias de usuario:
Los profesores pueden acceder a nuestra aplicación utilizando su cuenta de Google.
Los profesores pueden ver una lista de sus tareas más recientes y seleccionar la que quieren recordar a los alumnos.
Los profesores pueden recordar a cada alumno por SMS la próxima tarea.
Veamos el flujo de datos entre nuestra aplicación y las dos API de soporte (Google Classroom y Vonage Messages):

Utilizaremos Nodo y Express para esta demostración, pero tanto Google como Vonage ofrecen clientes de API en la mayoría de los lenguajes de programación principales. Si quieres avanzar, puedes descargar el código en Github y seguir la sección "Inicio rápido" del archivo Léame para poner en marcha la aplicación.
Requisitos previos
A Account Google APIs y un proyecto con Credenciales de cliente OAuth 2.0
A cuenta de Google Classroom con una clase en la que haya varios alumnos y al menos una tarea
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Creación de la aplicación
Paso 1: Creación de una nueva aplicación Express
En primer lugar, vamos a crear una nueva aplicación Express que utilice Handlebars para las plantillas y express-session para el almacenamiento de sesiones. Utilizaremos el generador de aplicaciones Generador de aplicaciones express para hacer esto más fácil:
npx express-generator --hbs --git classroom-remindersEste comando creará un nuevo directorio llamado classroom-reminders con una aplicación Express dentro. Naveguemos dentro de ese directorio e instalemos el paquete de almacenamiento de sesión Express así como nuestros otros paquetes proporcionados por Express:
npm i --save express-session && npm i
Para poder utilizar el paquete de sesión, tendremos que añadirlo a nuestro archivo app.js archivo. Añade las siguientes líneas donde se indica en el comentario:
// Add this line to the top of your app.js file
var session = require("express-session");
...
var app = express();
...
// And this block after the app has been created
app.use(session({
secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: true,
}));
...Si quieres asegurarte de que todo funciona hasta ahora, ejecuta SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> npm start y visita localhost:3000 en su navegador. Debería ver la página de bienvenida predeterminada de Express.
Paso 2: Añadir autenticación de Google
Ahora que tenemos una nueva aplicación Express vamos a añadir autenticación usando el cliente OAuth de Google.
Si aún no lo ha hecho, cree un nuevo proyecto en el portal de la API de Google y añádele las credenciales OAuth 2.0. Asegúrate de establecer la URL de devolución de llamada de OAuth en localhost:3000 para este tutorial, pero si implementas esta aplicación en un entorno de producción, deberás cambiar la URL de devolución de llamada.
Creating a Client ID in the Google Developer Console
Google generará un ID de cliente y un secreto de cliente que utilizaremos a lo largo de este tutorial.
A continuación, instalaremos el paquete paquete npm de las API de Google:
npm i --save googleapisPara mantener nuestro código organizado, crearemos un nuevo archivo en nuestro proyecto classroom-reminders para el código de la API de Google. Crea una nueva carpeta llamada helpers/ y un archivo llamado google-api.js. Añade lo siguiente a este archivo:
const google = require("googleapis").google;
const googleConfig = {
clientId: process.env.GOOGLE_OAUTH_ID,
clientSecret: process.env.GOOGLE_OAUTH_SECRET,
redirect: process.env.GOOGLE_OAUTH_REDIRECT,
};
const createConnection = () => {
return new google.auth.OAuth2(
googleConfig.clientId,
googleConfig.clientSecret,
googleConfig.redirect
);
};
const getConnectionUrl = (auth) => {
return auth.generateAuthUrl({
access_type: "offline",
prompt: "consent",
scope: [
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/classroom.courses.readonly",
"https://www.googleapis.com/auth/classroom.rosters.readonly",
"https://www.googleapis.com/auth/classroom.coursework.students.readonly",
],
});
};
/**
* Exported functions
*/
module.exports.loginUrl = () => {
const auth = createConnection();
return getConnectionUrl(auth);
};
module.exports.getToken = async (code) => {
const auth = createConnection();
const data = await auth.getToken(code);
return data.tokens;
};
module.exports.getCurrentUser = async (tokens) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.oauth2({
auth,
version: "v2",
})
.userinfo.get();
return { ...res.data };
};
No voy a entrar en todos los detalles sobre cómo funciona funciona OAuthpero las tres funciones exportadas forman parte de un flujo de trabajo OAuth estándar del lado del servidor. El loginUrl genera una URL de acceso única que los usuarios verán antes de autenticarse. La función getToken intercambia un código de un solo uso generado por los servidores de Google por un token de acceso de larga duración. La función getCurrentUser utiliza ese token de acceso para obtener la información del usuario autenticado actualmente desde la API de Google.
Otra consideración que merece la pena scopes que solicitamos:
scope: [
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/classroom.courses.readonly",
"https://www.googleapis.com/auth/classroom.rosters.readonly",
"https://www.googleapis.com/auth/classroom.coursework.students.readonly",
],Los ámbitos limitan los datos a los que puede acceder nuestra aplicación. En general, debes pedir el menor acceso posible para crear tu aplicación, así que solo pediremos información sobre el perfil del usuario y acceso de lectura a Google Classroom.
A continuación, actualizaremos el archivo routes/index.js de nuestra aplicación Express. Esta ruta buscará un code en la cadena de consulta y, si lo encuentra, utilizará el archivo de ayuda google-api que hemos creado para cambiar ese código por un token de autorización. A continuación, guardará ese token (junto con un token de actualización y una fecha de caducidad) en el almacenamiento de sesión. Por último, redirigirá a los usuarios a la página /assignments una vez que hayan iniciado sesión:
const express = require("express");
const router = express.Router();
const googleApi = require("../helpers/google-api");
router.get("/", function (req, res, next) {
if (req.query.code) {
googleApi.getToken(req.query.code).then((tokens) => {
req.session.tokens = tokens;
req.session.save(() => {
res.redirect("/assignments");
});
});
} else {
res.render("index", {
loginUrl: googleApi.loginUrl(),
});
}
});
module.exports = router;
También tendremos que modificar el archivo views/index.hbs para mostrar este enlace de inicio de sesión:
<h1>Google Classroom Reminders</h1>
<p>Log in with Google to remind your students about their upcoming assignments.</p>
<p>
<a href="{{ loginUrl }}">Login</a>
</p>Si queremos probar nuestra aplicación hasta ahora, tendremos que iniciarla con nuestro ID de Google OAuth, el secreto y la URL de redirección:
GOOGLE_OAUTH_ID=<YOUR GOOGLE OAUTH ID> \
GOOGLE_OAUTH_SECRET=<YOUR GOOGLE OAUTH SECRET> \
GOOGLE_OAUTH_REDIRECT=http://localhost:3000/ \
SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> \
npm startEsta vez, cuando naveguemos a localhost:3000veremos un enlace de inicio de sesión:
Login screen for Google Classroom Reminders application
Después de hacer clic en iniciar sesión, Google nos llevará a través del proceso de aprobación de nuestra nueva aplicación:
Permissions approval for Google Classroom Reminders application
Después de aprobar nuestra solicitud, se le llevará a localhost:3000/assignmentspero esa URL aún no existe. La crearemos en la siguiente sección.
Paso 3: Visualización de las tareas y los cursos de un profesor desde Google Classroom
Ahora que hemos creado un proceso de inicio de sesión para nuestra aplicación, debemos utilizar el token de acceso del usuario para obtener sus cursos y tareas de la API de Google Classroom.
En primer lugar, tendremos que añadir dos nuevas funciones al archivo helpers/google-api.js archivo:
...
module.exports.getCourses = async (tokens) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.list({ teacherId: "me", courseStates: "ACTIVE" });
return res.data.courses ? [...res.data.courses] : [];
};
module.exports.getCourseWorks = async (tokens, courseId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.courseWork.list({ courseId: courseId, orderBy: "dueDate desc" });
return res.data.courseWork ? [...res.data.courseWork] : [];
};
Estos nos permitirán solicitar una lista de Cursos y CourseWork (el nombre de Google Classroom para las tareas) de la API de Google Classroom en nombre del usuario actual. A continuación, cree un nuevo archivo de ruta en routes/assignments.js:
const express = require("express");
const router = express.Router();
const googleApi = require("../helpers/google-api");
router.get("/", function (req, res, next) {
if (!req.session.tokens) {
res.redirect("/");
}
googleApi.getCourses(req.session.tokens).then(async (courses) => {
Promise.all(
courses.map(async (course) => {
course.assignments = await googleApi.getCourseWorks(
req.session.tokens,
course.id
);
return course;
})
).then((courses) => {
res.render("assignments", { courses });
});
});
});
module.exports = router;
Esto recorrerá todos los cursos del usuario y obtendrá las últimas tareas de cada uno de ellos. También tendremos que añadir esta ruta al archivo app.js archivo:
var assignmentsRouter = require('./routes/assignments');
...
app.use('/', indexRouter);
app.use('/assignments', assignmentsRouter);
...Por último, crearemos un nuevo archivo de vista (views/assignments.hbs) para mostrar todos los cursos y tareas del usuario actual:
<h1>Google Classroom Reminders</h1>
<p>Select an Assignment to remind your students about.</p>
{{#each courses}}
<h2>{{ this.name }}</h2>
{{#if assignments}}
<ul>
{{#each assignments}}
<li>
<a href="/assignments/{{ ../id }}:{{ this.id }}">{{ this.title }}</a><br/>
Due on {{ this.dueDate.month }}/{{ this.dueDate.day }}/{{ this.dueDate.year }}
</li>
{{/each}}
</ul>
{{else}}
<p>No assignments found</p>
{{/if}}
{{/each}}
Si inicias la aplicación como lo hiciste en el paso anterior y vuelves a acceder a ella, deberías ver una lista de tus cursos y tareas de Google Classroom:
Viewing Google Classroom courses and assignments
En este punto, los usuarios pueden acceder a nuestra aplicación utilizando su cuenta de Google y ver una lista de las tareas más recientes de sus cursos de Google Classroom. A continuación, permitiremos a los usuarios desglosar y ver los estudiantes de cada curso y si han entregado o no una tarea concreta.
Paso 4: Visualización de la lista de profesores y del trabajo de los alumnos para una tarea concreta de Google Classroom
Para mostrar una lista de los alumnos matriculados en un curso y comprobar si han entregado esa tarea concreta, tendremos que acceder a algunos puntos finales nuevos de la API de Google Classroom.
Añadamos estas nuevas funciones al google-api.js archivo de ayuda:
...
module.exports.getCourse = async (tokens, courseId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.get({ id: courseId });
return { ...res.data };
};
module.exports.getCourseRoster = async (tokens, courseId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.students.list({ courseId: courseId });
return res.data.students ? [...res.data.students] : [];
};
module.exports.getCourseWork = async (tokens, courseId, assignmentId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.courseWork.get({ courseId: courseId, id: assignmentId });
return { ...res.data };
};
module.exports.getStudentSubmissions = async (
tokens,
courseId,
assignmentId
) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.courseWork.studentSubmissions.list({
courseId: courseId,
courseWorkId: assignmentId,
});
return res.data.studentSubmissions ? [...res.data.studentSubmissions] : [];
};
A continuación, vamos a crear una nueva ruta en el archivo routes/assignments.js para obtener lo siguiente:
Un solo curso
La lista de alumnos de ese Curso
Un único objeto Trabajo de curso
Propuestas de los estudiantes para ese curso
Curiosamente, la API de Google Classroom no nos permite obtener un único trabajo de curso sin un ID de curso y un ID de trabajo de curso. Para poder pasar ambos ID como un único parámetro de ruta, los concatenamos con un signo : en el paso anterior. De ahí esta línea en el archivo views/assignments.hbs archivo:
...
<a href="/assignments/{{ ../id }}:{{ this.id }}">{{ this.title }}</a>
...Ahora necesitamos parsear estos dos IDs en nuestra nueva ruta y luego pasarlos a las funciones apropiadas que creamos en el archivo google-api.js archivo. Añade las siguientes líneas a tu archivo routes/assignments.js archivo:
...
router.get("/:id", function (req, res, next) {
if (!req.session.tokens) {
res.redirect("/");
}
const ids = req.params.id.split(":");
const courseId = ids[0];
const assignmentId = ids[1];
Promise.all([
googleApi.getCourse(req.session.tokens, courseId),
googleApi.getCourseRoster(req.session.tokens, courseId),
googleApi.getCourseWork(req.session.tokens, courseId, assignmentId),
googleApi.getStudentSubmissions(req.session.tokens, courseId, assignmentId),
]).then(([course, students, courseWork, submissions]) => {
// Match submissions to students
if (
students &&
students.length > 0 &&
submissions &&
submissions.length > 0
) {
students.map((student) => {
student.submission = submissions.find(
(submission) => submission.userId === student.userId
);
if (student.submission && student.submission.state === "TURNED_IN") {
student.turnedIn = true;
}
return student;
});
}
res.render("assignment", {
course,
students,
courseWork,
submissions,
});
});
});
...
Por último, necesitaremos una nueva vista para ver todos los estudiantes y su estado de envío para una tarea concreta. Cree un archivo en views/assignment.hbs y añade lo siguiente:
<h1>Google Classroom Reminders</h1>
<p>
Send your students reminders about <a href="{{ courseWork.alternateLink }}">{{ courseWork.title }}</a>
in <a href="{{ course.alternateLink }}">{{ course.name }}</a>.
</p>
{{#if students}}
<div>
{{#each students}}
<p>
{{#if this.turnedIn}}
<a href="{{ this.alternateLink }}" title="Assignment turned in">✅</a>
{{else}}
<span title="Assignment not turned in">❗️</span>
{{/if}}
<strong>{{ this.profile.name.fullName }}</strong>
</p>
{{/each}}
</div>
{{else}}
<p>No students found</p>
{{/if}}
<p><a href="/assignments">↩️ Back to all assignments</a></p>Ahora, si iniciamos la aplicación y nos conectamos de nuevo, podemos desglosar una tarea concreta y ver el estado de envío de cada estudiante (indicado mediante o ❗️).
En el último paso, permitiremos que los usuarios envíen mensajes SMS a los estudiantes mediante la API de Messages API de Vonage.
Paso 5: Cómo agregar recordatorios de mensajes de texto mediante Messages API de Vonage
Messages API de Vonage puede enviar y recibir mensajes a través de varios canales, pero para esta aplicación, sólo usaremos mensajes de texto SMS.
Suponiendo que ya has creado una aplicación API de Vonageel próximo paso es instalar el cliente JavaScript. Además de este cliente, también agregaremos el archivo google-libphonenumber para ayudar a formatear los números de teléfono:
npm i --save nexmo@beta google-libphonenumberA continuación, vamos a crear otro archivo de ayuda para nuestro código que formatea números de teléfono y envía mensajes SMS a través de la biblioteca Nexmo. Cree un nuevo archivo en helpers/nexmo-api.js:
const Nexmo = require("nexmo");
const PNF = require("google-libphonenumber").PhoneNumberFormat;
const phoneUtil = require("google-libphonenumber").PhoneNumberUtil.getInstance();
const nexmo = new Nexmo({
apiKey: process.env.NEXMO_API_KEY,
apiSecret: process.env.NEXMO_API_SECRET,
applicationId: process.env.NEXMO_APP_ID,
privateKey: process.env.NEXMO_PRIVATE_KEY_PATH,
});
module.exports.sendSms = (telephone, message, callback) => {
const formattedPhoneNumber = phoneUtil.format(
phoneUtil.parseAndKeepRawInput(telephone, "US"),
PNF.E164
);
nexmo.channel.send(
{ type: "sms", number: formattedPhoneNumber },
{ type: "sms", number: process.env.NEXMO_PHONE_NUMBER },
{
content: {
type: "text",
text: message,
},
},
callback,
{ useBasicAuth: true }
);
};
Para procesar las entradas de un usuario y llamar a la función sendSms que acabamos de crear, vamos a crear un nuevo archivo de ruta en routes/messages.js:
const express = require("express");
const router = express.Router();
const nexmoApi = require("../helpers/nexmo-api");
router.post("/", function (req, res, next) {
if (!req.session.tokens) {
res.redirect("/");
}
const { telephones, messages } = req.body;
Promise.all(
telephones.map((telephone, key) => {
if (telephone) {
return nexmoApi.sendSms(telephone, messages[key]);
}
})
).then((results) => {
res.redirect("/assignments");
});
});
module.exports = router;
Este fichero itera sobre un array de números de teléfono y mensajes y llama al método sendSms para cada uno que tenga un número de teléfono. También tendremos que actualizar nuestro archivo app.js para utilizar esta nueva ruta:
var messagesRouter = require('./routes/messages');
...
app.use('/', indexRouter);
app.use('/assignments', assignmentsRouter);
app.use('/messages', messagesRouter);
...Dado que la API de Google no nos da acceso a los números de teléfono de los alumnos, tendremos que hacer que los profesores introduzcan estos números en nuestra interfaz junto con un mensaje para cada alumno. Vamos a editar el archivo views/assignment.hbs para incluir estos dos campos de formulario para cada alumno y un botón de envío:
<h1>Google Classroom Reminders</h1>
<p>
Send your students reminders about <a href="{{ courseWork.alternateLink }}">{{ courseWork.title }}</a>
in <a href="{{ course.alternateLink }}">{{ course.name }}</a>.
</p>
{{#if students}}
<form action="/messages" method="post">
{{#each students}}
<p>
{{#if this.turnedIn}}
<a href="{{ this.alternateLink }}" title="Assignment turned in">✅</a>
{{else}}
<span title="Assignment not turned in">❗️</span>
{{/if}}
<strong>{{ this.profile.name.fullName }}</strong>
</p>
<div>
<label for="message-{{ this.userId }}" style="display: block;">Reminder message</label>
<textarea id="message-{{ this.userId }}" name="messages" maxlength="140" minlength="3" rows="5" cols="30">Hey {{ this.profile.name.givenName }}, don't forget about your assignment for {{ ../course.name }}. It's due on {{ ../courseWork.dueDate.month }}/{{ ../courseWork.dueDate.day }}/{{ ../courseWork.dueDate.year }}</textarea>
</div>
<div>
<label for="telephone-{{ this.userId }}" style="display: block;">Telephone</label>
<input type="tel" id="telephone-{{ this.userId }}" autocomplete="off" name="telephones" />
</div>
{{/each}}
<div style="margin-top: 10px;">
<input type="submit" value="Send Reminders">
</div>
</form>
{{else}}
<p>No students found</p>
{{/if}}
<p><a href="/assignments">↩️ Back to all assignments</a></p>
Nuestra aplicación está básicamente terminada, pero necesitamos tener todas nuestras credenciales de Vonage en orden antes de poder usarla. Primero, descarga tu clave privada de la API de Vonage y guárdala en un nuevo archivo llamado .private_key. Ahora, inicia tu aplicación con todas las variables de entorno de Google y de la API de Vonage:
GOOGLE_OAUTH_ID=<YOUR GOOGLE OAUTH ID> \
GOOGLE_OAUTH_SECRET=<YOUR GOOGLE OAUTH SECRET> \
GOOGLE_OAUTH_REDIRECT=http://localhost:3000/ \
SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> \
NEXMO_API_KEY=<YOUR VONAGE MESSAGES API KEY> \
NEXMO_API_SECRET=<YOUR VONAGE MESSAGES API SECRET> \
NEXMO_PHONE_NUMBER=<YOUR VONAGE MESSAGES PHONE NUMBER> \
NEXMO_APP_ID=<YOUR VONAGE MESSAGES APP ID> \
NEXMO_PRIVATE_KEY_PATH=./.private_key \
npm startEsta vez, cuando inicies sesión y veas una tarea individual, podrás introducir un número de teléfono para cada uno de tus alumnos y personalizar un mensaje para enviárselo. Cuando estés listo, haz clic en "Enviar recordatorios" para probarlo todo.
Sending SMS Reminders using Google Classroom and the Vonage Messages API
Próximos pasos
Si bien esta aplicación de demostración cubre un caso de uso relativamente simple, es fácil ver lo poderosa que puede ser la API Messages API de Vonage cuando se combina con un LMS como Google Classroom. Hay varias formas en las que podrías seguir mejorando la experiencia del usuario con una aplicación como esta:
Almacenar los números de teléfono en una base de datos para que los profesores no tengan que introducirlos cada vez.
Permitir a los usuarios cambiar el mensaje recordatorio por defecto
Añadir compatibilidad con otros canales de mensajería como Facebook, What's App o Viber.
Almacenamiento en caché de los datos de Google Classroom para mejorar el rendimiento y evitar los límites de velocidad
Mensajes de error y éxito fáciles de usar
Diseño y estilo personalizados
Si tienes preguntas u otras ideas para implantar un sistema de recordatorios mediante Vonage y Google Classroom, ponte en contacto con nosotros en Twitter o en Slack de la comunidad de desarrolladores de Vonage¡!
