https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-an-appointment-scheduler-using-node-firebase-and-vonage/appointment-scheduler.png

Crear un programador de citas con Node, Firebase y Vonage

Publicado el December 16, 2021

Tiempo de lectura: 12 minutos

Introducción

En este tutorial, vamos a crear una aplicación web de programación de citas utilizando Node.js, Express, Firebase y la Mensajes API de Vonage. El repositorio repositorio de GitHub para este proyecto también está disponible, no dude en clonarlo aquí.

Configurar Firebase

Para empezar, vamos a crear un nuevo proyecto desde la consola de Firebase.

  • Haga clic en add a new project

  • Dé a su proyecto un nombre significativo, por ejemplo vonage appointment scheduler

  • Comprueba si te gusta el identificador único para tu proyecto (se utiliza en la URL de tu base de datos en tiempo real, subdominios de Firebase Hosting y más. No se puede cambiar después de la creación del proyecto)

  • Pulse el botón para continuar

Console view with a text field to enter project and name and edit the project idConsole view with a text field to enter project and name and edit the project id

  • Seleccione si desea activar el análisis. No lo haremos en este tutorial

  • Haga clic en el botón para crear el proyecto

  • Esperar a que se cree el proyecto

Project being createdProject being created

  • Una vez que el proyecto esté listo, haga clic para continuar. Accederá a la vista de consola de su proyecto

  • Establezca el tipo de facturación haciendo clic en el icono de engranaje, seguido de Uso y facturación, luego en la pestaña Detalles y configuración y modifique el plan para utilizar Blaze. Este plan de pago por uso es necesario cuando se utiliza una API de terceros

Instalar Firebase Tools CLI

Desde tu terminal, instala las herramientas Firebase con NPM si aún no las tienes escribiendo: npm install -g firebase-tools. A continuación, escribe firebase login. Esto abrirá una ventana en tu navegador que te autenticará automáticamente (si ya has iniciado sesión) o te pedirá tus credenciales. Una vez completado esto, ya tienes el Firebase CLI instalado.

Crear y configurar una base de datos en tiempo real

Ahora es el momento de crear la instancia de base de datos NoSQL que contendrá la información de las franjas horarias de las citas. Nuestra aplicación incluirá una vista en la que el usuario podrá concertar o cancelar citas. A medida que la persona que interactúa con la vista elige una fecha y hora de la cita, esa ranura se añadirá o eliminará de la base de datos Firebase RealTime.

  • En el menú de la consola Firebase, haga clic en "Realtime Database" en Build

Button to create the databaseButton to create the database

  • Haga clic en "Crear base de datos".

  • Seleccione la ubicación de la base de datos en tiempo real donde se almacenarán sus datos y haga clic en next

  • Seleccione si va a utilizar la base de datos en modo bloqueado o de prueba. Para este ejemplo, estoy utilizando el modo de prueba

  • Haga clic en enable

    Database createdDatabase created

Importar el archivo JSON de la base de datos

Vamos a importar una base de datos de ejemplo que ya contiene algunas franjas horarias asignadas, y desde la que podrá añadir y eliminar futuras franjas horarias. Puede crear un archivo llamado myAppointments.json que contenga el JSON del siguiente fragmento y, a continuación, importarlo desde la consola.

myAppointments.json
{
  "myAppointments": {
    "0": {
      "date": "2021-06-01T09:00",
      "userId": "1234abcd"
    },
    "new_activity_7kh3a3a3z": {
      "date": "2023-06-01T08:50",
      "userId": "_7kh3a3a3z"
    },
    "new_activity_etxen95x3": {
      "date": "2021-06-01T08:40",
      "userId": "_etxen95x3"
    }
  }
}

Import DatabaseImport Database

Añadir las reglas de la base de datos

Las Reglas de Base de Datos en Tiempo Real de Firebase determinan quién puede acceder a su base de datos, cómo se construyen sus índices y cómo se estructuran sus datos.

  • Desde la consola Firebase en la vista de la base de datos en tiempo real, puedes ver "Reglas", haz clic en esa pestaña. Se le llevará a una pantalla que le permitirá editar sus reglas

  • Copie y pegue las reglas del siguiente fragmento de código en la consola para que la colección myAppointments para que sea indexada por el campo date campo.

  • Haga clic en Publish

{
  "rules": {
    ".read": "now < 1643842800000",  // 2022-2-3
    ".write": "now < 1643842800000",  // 2022-2-3
    "myAppointments": {
      ".indexOn": ["date"]
    }
  }
}

Edit Firebase Database RulesEdit Firebase Database Rules

Crear la estructura del proyecto

Al final de este tutorial, esta será aproximadamente la estructura de tu proyecto. En los siguientes pasos, crearemos los archivos que construirán el contenido, la apariencia, las funcionalidades y manejarán los servicios que utilizaremos.

Project Structure also displayed as a code block below for accessibilityProject Structure

appointment-scheduler | public | |- styles | | | L styles.css | |- favicon.ico | L index.html |- script | |- server.js |- .env |- .firebaserc |- README.md |- firebase.json |- package-lock.json |- package.json |- serviceAccountKey.json

Configurar

  • Cree la carpeta del proyecto y cd en ella: mkdir appointment-scheduler && cd appointment-scheduler

  • Inicializar NPM: npm init. Este comando le pide que añada información sobre el proyecto

  • Instale las dependencias: npm install @vonage/server-sdk dotenv uuid express firebase-admin firebase-functions

  • Tipo firebase init. Como ya hemos creado un proyecto en el cuadro de mandos, puede seleccionar Use an existing project que le pedirá que elija el proyecto deseado. Puede ver mi ejemplo con mi id de proyecto vonage-appointment-scheduler abajo. También elegí utilizar la Realtime Database función

? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance === Project Setup First, let's associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now, we'll just set up a default project. ? Please select an option: Use an existing project ? Select a default Firebase project for this directory: vonage-appointment-scheduler (vonage appointment scheduler) i Using project vonage-appointment-scheduler (vonage appointment scheduler)

Crear el contenido HTML

¿Sabías que el elemento de entrada HTML tiene muchas opciones de tipo para la selección de fecha y hora? Por ejemplo, tenemos: date, datetime-local, time. En este tutorial utilizaremos <input type="datetime-local">. Este enfoque quizás no sea tan robusto como el uso de la librería fecha-hora, ya que puede haber algunas inconsistencias, pero funciona para el propósito de este tutorial. El usuario podrá reservar franjas horarias cada 5 minutos que terminen en 0 o 5. Por ejemplo, las 18:00 se pueden reservar, pero las 18:01 no.

  • Cree el archivo public/index.html que contiene el contenido para que la vista seleccione una nueva cita o las cancele añadiendo el siguiente fragmento de código

  <!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Appointment Scheduler</title>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="styles/styles.css" />
  </head>
  <body>
    <main>
      <h1>Appointment Scheduler</h1>
      <!-- datepicker from html. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local -->
      <form action="/appointment" method="POST">
        <div>
          <label for="slot">Choose your slot: </label>
          <input
            id="slot"
            type="datetime-local"
            name="slotdate"
            min="2021-06-01T08:30"
            max="2023-10-30T16:30"
            step="300"
            required
          />
          <span class="validity"></span>
        </div>
        <div>
          <label for="phonenumber">Your phone number:</label>
          <input type="tel" id="phonenumber" name="phonenumber" required />
          <span class="validity"></span>
        </div>
        <div>
          <input type="submit" value="Book slot!" />
        </div>
      </form>
      <form action="/cancelAppointment" method="POST">
        <div>
          <input type="text" name="code" placeholder="code" />
          <input type="submit" value="Remove slot!" />
        </div>
      </form>
    </main>
  </body>
</html>

Añadir estilo CSS

Para esta aplicación web de demostración, añadiremos algunos estilos para centrar el contenido en la página, y también mostraremos un ✖ rojo en caso de que la entrada no sea válida y un ✓ en caso de que sea válida.

  • Cree el public/styles.css archivo

  • Pegue el siguiente código CSS

body {
    margin: auto;
    width: 50%;
    padding: 10px;
}

div {
    margin-bottom: 10px;
    display: flex;
    align-items: center;
}

label {
  display: inline-block;
  width: 300px;
}

input:invalid+span:after {
    content: '✖';
    color: red;
    padding-left: 5px;
}

input:valid+span:after {
    content: '✓';
    color: green;
    padding-left: 5px;
}

Crear el archivo de variables de entorno

  • Cree el .env y rellénalo con la siguiente información

FIREBASE_DATABASE_URL= VONAGE_API_KEY= VONAGE_API_SECRET= VONAGE_FROM_NUMBER= VONAGE_TO_NUMBER=
  • La dirección FIREBASE_DATABASE_URL puede encontrarse en la consola de Firebase

  • En VONAGE_API_KEYy el VONAGE_API_SECRET se pueden encontrar en el Panel de Vonage

  • La dirección VONAGE_FROM_NUMBER contiene el número, nombre o marca que aparecerá como remitente del mensaje

  • El VONAGE_TO_NUMBER es el número que recibirá los SMS

Crear el archivo JavaScriptserver.js

Crearemos el archivo server.js para decirle a Express cómo manejar las peticiones enviadas por la UI. Te mostraré paso a paso cómo lo construiremos. Puedes encontrar el archivo completo del servidor aquí.

Nuestra aplicación web utilizará express y leerá los archivos estáticos que creamos previamente desde la carpeta public carpeta.

  • Para añadir las dependencias y los archivos de importación, añada el siguiente fragmento de código a su archivo script/server.js

// script/server.js
require('dotenv').config();
const express = require('express');
const app = require('express')();
const port = 3000; //setting the port to listen to as 3000
const admin = require('firebase-admin');
const Vonage = require('@vonage/server-sdk');
const SMS = require('@vonage/server-sdk/lib/Messages/SMS';
const { v4: uuidv4 } = require('uuid');

app.use(express.static('public'));

app.use(express.json());

app.use(express.urlencoded({ extended: true }));

Añadir la cuenta de servicio

Una Account de servicio Firebase puede ser usada para autenticar varias características de Firebase; para nuestro proyecto, usaremos el SDK Firebase Admin para acceder a la URL de nuestra Base de Datos.

  • En la consola Firebase, haga clic en el engranaje y seleccione la pestaña Service Account.

  • Pulse el botón para generate key

  • Añade el archivo generado a la raíz de tu proyecto y renómbralo a serviceAccountKey.json

  • Copia y pega el fragmento de configuración de Admin SDK en tu proyecto, como puedes ver en el siguiente paso de este tutorial, para inicializar Firebase. Estamos usando ${process.env.FIREBASE_DATABASE_URL para leer la URL del archivo .env pero es la misma URL de la base de datos que se encuentra en la configuración de Firebase Admin SDK.

Admin SDK configurationAdmin SDK configuration

Inicializar Firebase

Usamos initializeApp para crear e inicializar una instancia de aplicación Firebase que utilizará la instancia de base de datos /myAppointments Firebase que hemos creado y rellenado previamente desde la Consola Firebase.

  • Añada el siguiente fragmento de código a su server.js para inicializar Firebase.

const serviceAccount = require('../serviceAccountKey.json');

// Initializes firebase
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: `${process.env.FIREBASE_DATABASE_URL}`,
});

// A Reference represents a specific location in your Database and can be 
// used for reading or writing data to that Database location.
ref = admin.database().ref('/myAppointments');

Inicializar el objeto API de Vonage

Creamos la instancia de la clase cliente de Vonage, inicializándola con la clave y el secreto de la API de Vonage que agregaste previamente a tu archivo .env archivo.

  • Agrega el siguiente fragmento de código a tu server.js para añadir Vonage.

const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY,
  apiSecret: process.env.VONAGE_API_SECRET,
});

Crear la función getDateTime()

El tipo de entrada HTML datetime-local tiene el formato AAAA-MM-DDThh:mm. Así que escribiremos una función para separar la fecha de la hora dividiéndola en el carácter T. Por ejemplo, en el ejemplo 2018-06-12T19:30tendríamos 2018-06-12 para la fecha y 19:30 para la hora.

  • Añada este fragmento de código a su server.js para añadir la función getDateTime() función

  const getDateTime = (slot) => {
    return slot.split('T');
  };

Crear el/appointment punto final

Es hora de crear el endpoint /appointment para manejar las solicitudes POST para crear una cita. Este punto final verificará si el espacio está disponible, agregará el espacio a la base de datos de Firebase y, por último, enviará un SMS de confirmación al teléfono del usuario mediante la API de Messages API de Vonage.

  • Añada el siguiente fragmento de código a su server.js para crear el endpoint /appointment punto final.

app.post('/appointment', async (request, response) => {
  let phonenumber = request.body.phonenumber;
  let slot = request.body.slotdate;
  let [date, time] = getDateTime(slot);

  // Checks if a slot is available
  checkIfAvailable = async (slot) => {};
  
  // Adds to Database
  addToDatabase = () =>  {};
  
  // Sends an SMS back to the user's phone using the Vonage Messages API
  sendSMStoUser = async (code) => {};
});

Te habrás dado cuenta de que gran parte de la funcionalidad dentro del manejador de peticiones aún no se ha implementado, así que ahora vamos a ampliar los stubs para las funcionalidades requeridas.

Comprobar disponibilidad de franjas horarias

Esta función valida si una ranura está disponible comprobando si la ranura ya existe en la base de datos. Estamos consultando ref.orderByChild('date'). Las consultas pueden ordenar una clave cada vez. Hemos definido previamente nuestro índice a través de la función .indexOn en las reglas de Firebase para un mejor rendimiento. Y luego hacemos uso .once('value') para escuchar exactamente un evento del valor, y luego deja de escuchar.

  • Añada el siguiente fragmento de código a su server.js para crear la función checkIfAvailable() función

  // Checks if a slot is available
  checkIfAvailable = async (slot) => {
    let snapshot = await ref.orderByChild('date').once('value');

    let available = true;
    
    snapshot.forEach((data) => {
      let dataval = data.val();
      for (let key in dataval) {
        let datapoint = dataval[key];
        if (slot === datapoint) {
          available = false;
        }
      }
    });
    return available;
  };

Añadir la ranura a la base de datos

La siguiente función addToDatabase() añade la franja horaria y un código a la base de datos Firebase. Este código es necesario para cancelar la cita.

  // Adds the slot to the database
  addToDatabase = () => {
    let code = uuidv4();

    ref.child(code).set({
      date: slot,
      userId: code,
    });

    return code;
  };

Enviar un SMS con la información de la cita

Por último, una vez reservada la plaza, se envía un SMS de confirmación al usuario con el mensaje Meeting booked at ${time} on date: ${date}. Please save this code: ${code} in case you'd like to cancel your appointment. como puede verse en la función sendSMStoUser().

  • Añada el siguiente fragmento de código a su server.js para crear la función sendSMStoUser() función

// Sends an SMS back to the user's phone using the Vonage Messages API
sendSMStoUser = async (code) => {
  const to = phonenumber;
  const text = `Meeting booked at ${time} on date: ${date}. Please save this code: ${code} in case you'd like to cancel your appointment.`;
  const result = await new Promise((resolve, reject) => {
    vonage.messages.send(
      new SMS(text, process.env.VONAGE_TO_NUMBER, "Vonage"),
      (err, data) => {
        if (err) {
          console.error(err);
        } else {
          console.log(data.message_uuid);
        }
      }
    );
  });
};

Finalizar la lógica empresarial

El siguiente fragmento de código se encarga de llamar a las funciones de ayuda creadas anteriormente. Si la franja horaria está disponible, se añadirá a la base de datos y se enviará el SMS al usuario. En caso contrario, se le pedirá que elija otra franja horaria.

let available = await checkIfAvailable(slot);

if (available) {
	let code = addToDatabase();
	await sendSMStoUser(code);
	response.send(`This slot is available, booking it for you now: ${slot}`);
} else {
	// Sends user error
	response.send(
		`Sorry, you'll need to choose a different slot.${slot} is already busy.`
	);
}

Anular la cita/cancelAppointment

Vamos a crear el endpoint /cancelAppointment que gestiona las peticiones POST para cancelar una cita desde la base de datos utilizando un código proporcionado por el usuario al concertar su cita.

app.post('/cancelAppointment', async (request, response) => {
  let code = request.body.code;

  // Removes slot from the database
  removeSlotFromDB = (code) => {
    ref.child(code).remove();
  };
  removeSlotFromDB(code);

  response.send(`This slot has been removed.`);
});

Escuchar el puerto

Por último, la aplicación estará a la escucha en el puerto especificado; si se ejecuta localmente, será accesible en https://localhost:${port}. En esta URL, puede interactuar con la interfaz de usuario de esta aplicación de demostración y comprobar las ranuras que se añaden/eliminan en la página web de la consola de Firebase.

app.listen(port, () => {
  console.log(`I run on port ${port}`);
});

Pruébelo

  • En su package.json añada el script de inicio "start": "node script/server.js" justo debajo de "test": "echo \"Error: no test specified\" && exit 1",. Debería tener este aspecto:

"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node script/server.js" },
  • Instalar todas las dependencias npm install

  • Ejecute el comando NPM para ejecutar el proyecto npm run start

  • Vaya a http://localhost:3000

  • Añadir y eliminar franjas horarias de citas y ver cómo se añaden y eliminan de la base de datos en tiempo real de Firebase.

Example adding a slot and it is shown on the Firebase Realtime databaseExample adding a slot and it being shown on the Firebase Realtime database

Conclusión y próximos pasos

Hoy has visto cómo construir una aplicación web de demostración de un programador de citas. Ahora puedes seguir adelante y añadir un estilo más elegante y otras funcionalidades. Puedes tomar lo que has aprendido aquí para crear muchos programadores de citas, ya sea para un gimnasio o para un espacio de vacunación - ¡deja que fluya la creatividad!

Contáctanos en Twitter y únete a nuestra comunidad en Slack.

Compartir:

https://a.storyblok.com/f/270183/400x400/3f6b0c045f/amanda-cavallaro.png
Amanda CavallaroDefensor del Desarrollador