La gestión de reservas en aplicaciones web es un componente esencial para muchos negocios modernos. Implementar un sistema robusto que permita crear, consultar, actualizar y eliminar citas requiere una estructura organizada y un manejo adecuado de posibles conflictos. En este contenido, exploraremos cómo desarrollar un sistema de reservas utilizando Prisma como ORM y siguiendo buenas prácticas de arquitectura de software.
¿Cómo estructurar un controlador de reservas eficiente?
El controlador de reservas es el componente que maneja las solicitudes HTTP relacionadas con las citas o reservaciones. Para mantener un código limpio y mantenible, es recomendable seguir un patrón consistente para cada operación CRUD (Create, Read, Update, Delete).
Un controlador bien estructurado debe:
Recibir y validar los parámetros de la solicitud
Delegar la lógica de negocio a los servicios
Manejar adecuadamente los errores
Retornar respuestas con códigos HTTP apropiados
Para la actualización de reservas, el controlador debería verificar la existencia del recurso antes de intentar modificarlo:
La separación de responsabilidades es un principio fundamental en el desarrollo de software. Al mover la lógica de negocio a servicios dedicados, logramos:
Mayor modularidad y reutilización de código
Facilidad para realizar pruebas unitarias
Mejor mantenibilidad a largo plazo
Código más limpio y comprensible
Para implementar esta arquitectura, creamos un archivo reservationService.js dentro de la carpeta de servicios:
const prisma =require('../path/to/prisma');exports.createReservation=async(data)=>{// Verificar si el horario ya está ocupadoconst conflict =await prisma.appointment.findFirst({where:{date: data.date,timeBlockId: data.timeBlockId}});if(conflict){thrownewError("El horario ya está ocupado");}return prisma.appointment.create({ data
});};
¿Cómo implementar la validación de conflictos en reservas?
Una de las partes más importantes de un sistema de reservas es evitar conflictos de horarios. No queremos que dos usuarios puedan reservar el mismo horario, lo que generaría problemas operativos.
Para implementar esta validación, debemos verificar si ya existe una reserva para la fecha y bloque de tiempo solicitados:
exports.updateReservation=async(id, data)=>{// Verificar si el nuevo horario ya está ocupado por otra reservaconst conflict =await prisma.appointment.findFirst({where:{date: data.date,timeBlockId: data.timeBlockId,id:{not:parseInt(id,10)}}});if(conflict){thrownewError("El horario solicitado ya está ocupado");}return prisma.appointment.update({where:{id:parseInt(id,10)}, data
});};
Observa cómo en la validación para actualizar, excluimos la reserva actual del chequeo de conflictos usando id: { not: parseInt(id, 10) }. Esto permite que una reserva pueda actualizarse sin cambiar su horario sin generar un falso conflicto.
¿Cómo implementar operaciones de consulta y eliminación?
Las operaciones de consulta y eliminación son más sencillas, ya que no requieren validaciones complejas de conflictos:
Una vez implementado el sistema, es crucial probarlo para asegurar su correcto funcionamiento. Herramientas como Postman son ideales para probar APIs REST:
Importar la colección de endpoints en Postman
Configurar los headers de autenticación (usando tokens JWT)
Probar cada endpoint con diferentes escenarios
Es importante verificar:
Que las validaciones de conflictos funcionen correctamente
Que los códigos de estado HTTP sean apropiados
Que la autenticación y autorización funcionen según lo esperado
Al probar el endpoint GET para obtener una reserva, debemos asegurarnos de incluir el token de autenticación en los headers:
Authorization:Bearer[tu_token_jwt]
Si olvidamos incluir el token, recibiremos un error de acceso denegado: "Access denied: No token provided".
La estructura de nuestro sistema nos permite detectar fácilmente errores. Por ejemplo, si olvidamos importar el servicio en el controlador, veremos un error como "reservation service is not defined", lo que nos indica exactamente qué necesitamos corregir.
El desarrollo de un sistema de reservas robusto requiere atención a los detalles y una arquitectura bien pensada. La separación de responsabilidades entre controladores y servicios nos ayuda a mantener un código limpio y mantenible a largo plazo. ¿Qué enfoque prefieres para estructurar tus servicios? ¿Utilizas exports individuales o prefieres un objeto con múltiples métodos? Comparte tu experiencia en los comentarios.