Sistema web completo para gestión de reservas en una barbería, desarrollado con React + TypeScript en el frontend y Node.js + Express + MongoDB en el backend.
- Tema General del Proyecto
- Estructura del Estado Global
- Mapa de Rutas y Flujo de Autenticación
- Tests E2E
- Librería de Estilos
- Requisitos Previos
- Estructura del Proyecto
- Instalación
- Configuración
- Ejecución
- API Endpoints
- Uso
Este proyecto es un Sistema de Gestión de Reservas para Barbería que permite:
- Clientes: Registrarse, ver servicios disponibles, hacer reservas seleccionando barbero, fecha y hora, y gestionar sus reservas (ver, cancelar).
- Barberos: Ver sus reservas confirmadas del día, editar reservas (cambiar fecha/hora) y cancelar reservas asignadas.
- Administradores:
- Crear y gestionar usuarios (clientes, barberos y otros administradores) desde un panel de administración.
- Crear, editar y eliminar servicios de barbería con sus respectivos precios y duraciones.
El sistema maneja tres roles principales (cliente, barbero, admin) con diferentes permisos y vistas según el rol del usuario autenticado.
El proyecto utiliza Zustand (v5.0.8) como librería de manejo de estado global. Se implementaron tres stores principales:
Gestiona el estado de autenticación del usuario:
- Estado:
user,isAuthenticated,csrfToken,isLoading,error - Acciones:
login(): Inicia sesión con credencialeslogout(): Cierra sesión y limpia el estadorestoreSession(): Restaura la sesión desde localStoragesetUser(): Actualiza el usuario actualclearError(): Limpia errores
- Persistencia: Usa
zustand/middleware/persistpara guardar enlocalStorage
Gestiona las reservas tanto para clientes como para barberos:
- Estado para Clientes:
clientReservations: Lista de reservas del clienteclientLoading,clientError
- Estado para Barberos:
barberReservations: Lista de reservas del barbero por fechabarberLoading,barberError,selectedDate
- Acciones para Clientes:
fetchClientReservations(): Obtiene todas las reservas del clienteupdateClientReservation(): Actualiza una reserva del clientedeleteClientReservation(): Cancela una reserva del cliente
- Acciones para Barberos:
fetchBarberReservations(date): Obtiene reservas del barbero para una fechaupdateBarberReservation(): Actualiza una reserva (fecha/hora/estado)cancelBarberReservation(): Cancela una reserva asignada al barbero
Gestiona los usuarios en el panel de administración:
- Estado:
users,loading,error,successMessage - Acciones:
fetchUsers(): Obtiene todos los usuarioscreateUser(): Crea un nuevo usuario (solo admin)clearError(),clearSuccess(): Limpia mensajes
Gestiona los servicios de barbería en el panel de administración:
- Estado:
services,loading,error,successMessage - Acciones:
fetchServices(): Obtiene todos los servicioscreateService(): Crea un nuevo servicio (solo admin)updateService(): Actualiza un servicio existente (solo admin)deleteService(): Elimina un servicio (solo admin)clearError(),clearSuccess(): Limpia mensajes
Todos los stores se exportan desde frontend/src/stores/index.ts para facilitar su importación.
/- Página principal (lista de servicios)/login- Inicio de sesión/register- Registro de nuevos usuarios (solo clientes)
/reservas- Crear una nueva reserva- Protegida por
ProtectedRoute - Requiere usuario autenticado (cualquier rol)
- Protegida por
/mis-reservas- Ver y gestionar reservas propias- Protegida por
ProtectedRoute - Vista diferente según el rol:
- Cliente: Lista todas sus reservas con opción de cancelar
- Barbero: Vista de calendario con reservas del día seleccionado, permite editar y cancelar
- Protegida por
/admin/usuarios- Panel de administración de usuarios- Protegida por
AdminRoute - Permite crear usuarios con cualquier rol (cliente, barbero, admin)
- Lista todos los usuarios del sistema
- Protegida por
/admin/servicios- Panel de administración de servicios- Protegida por
AdminRoute - Permite crear, editar y eliminar servicios de barbería
- Gestiona precios, duraciones y tipos de servicios
- Protegida por
-
Inicio de Sesión:
- Usuario ingresa credenciales en
/login - Backend valida y retorna JWT token + CSRF token
authStore.login()guarda tokens y datos del usuario- Redirección a página principal o ruta protegida solicitada
- Usuario ingresa credenciales en
-
Protección de Rutas:
ProtectedRouteverificaisAuthenticateddelauthStore- Si no está autenticado, redirige a
/login AdminRouteademás verifica queuser.role === 'admin'
-
Restauración de Sesión:
- Al cargar la app,
AppWithAuthllama arestoreSession() - Verifica tokens en
localStoragey valida con el backend - Si la sesión es válida, restaura el estado de autenticación
- Al cargar la app,
-
Cierre de Sesión:
authStore.logout()limpia tokens y estado- Elimina datos de
localStorage - Redirige a página principal
ProtectedRoute: Verifica autenticación, redirige a/loginsi no está autenticadoAdminRoute: Verifica autenticación Y rol admin, redirige a/si no es admin
El proyecto utiliza Playwright para tests end-to-end. Los tests se encuentran en la carpeta e2etests/ y se ejecutan de forma independiente.
- Base URL:
http://localhost:5173(frontend) - Web Server: Configurado para iniciar automáticamente backend (puerto 3001) y frontend (puerto 5173)
- Navegadores: Chromium, Firefox, WebKit (configurables)
- ✅ Redirección a login: Verifica que al acceder a rutas protegidas sin autenticación, se redirige a
/login - ✅ Credenciales inválidas: Prueba el manejo de errores con credenciales incorrectas
- ✅ Login exitoso: Valida el flujo completo de login y redirección a página principal
- ✅ Acceso a rutas protegidas: Verifica que después del login se puede acceder a rutas protegidas
- ✅ Persistencia de sesión: Comprueba que la sesión se mantiene al recargar la página
- ✅ CREATE: Crea una nueva reserva desde la UI (selecciona servicio, barbero, fecha y hora)
- ✅ READ: Lista las reservas del usuario autenticado
- ✅ UPDATE: Actualiza una reserva existente (cambia hora y estado)
- ✅ DELETE: Elimina una reserva existente
- ✅ Flujo completo: Ejecuta un flujo completo de CRUD en la UI
cd e2etests
npm install
npm test # Ejecuta todos los tests en modo headless
npm run test:ui # Abre la UI de Playwright
npm run test:headed # Ejecuta con navegador visible
npm run test:debug # Modo debug con inspectorLos tests crean usuarios de prueba automáticamente y esperan a que los servidores estén listos antes de ejecutarse.
El proyecto utiliza Tailwind CSS v4.1.17 como framework de estilos utilitarios.
El tema se define en frontend/src/style.css y frontend/tailwind.config.js:
@theme {
--color-primary: #1e3a8a; /* Azul principal */
--color-primary-600: #1f4aa6; /* Azul más oscuro para hover */
--color-muted: #6b7280; /* Gris para texto secundario */
--color-bg: #f5f6fa; /* Fondo gris claro */
--radius-card: 14px; /* Radio de borde para tarjetas */
--shadow-card: 0 6px 18px rgba(16, 24, 40, 0.08); /* Sombra suave */
}-
Sistema de Colores:
- Primario: Azul (
#1e3a8a) para botones principales y elementos destacados - Muted: Gris para texto secundario y elementos deshabilitados
- Estados: Verde para confirmado, rojo para cancelado, amarillo para pendiente
- Primario: Azul (
-
Componentes Reutilizables:
- Tarjetas con
rounded-card(14px) yshadow-cardpara profundidad - Botones con estados hover usando
primary-600 - Inputs con focus states usando
border-indigo-300y sombras
- Tarjetas con
-
Layout:
- Grid system de Tailwind para layouts responsivos
- Espaciado consistente usando las utilidades de Tailwind
- Diseño mobile-first
-
Tipografía:
- Tamaños de fuente escalables usando clases de Tailwind
- Pesos de fuente:
font-boldpara títulos,font-semiboldpara subtítulos
-
Interactividad:
- Transiciones suaves en botones y elementos interactivos
- Estados hover claramente definidos
- Feedback visual inmediato en acciones del usuario
- Node.js (v18 o superior)
- npm o yarn
- MongoDB (v6 o superior)
.
├── backend/ # API REST con Express y TypeScript
│ ├── src/
│ │ ├── controllers/
│ │ ├── models/
│ │ ├── types/
│ │ └── utils/
│ └── package.json
│
└── frontend/ # Aplicación React con TypeScript
├── src/
│ ├── api/
│ ├── components/
│ ├── pages/
│ └── types/
└── package.json
cd backend
npm installcd frontend
npm installCrea un archivo .env en la carpeta backend/ con las siguientes variables:
HOST=localhost
PORT=3001
MONGODB_URI=mongodb://localhost:27017/myapp
MONGODB_DBNAME=mybarbershop
JWT_SECRET=tu-secreto-super-seguro-cambia-en-produccionEl frontend está configurado para conectarse al backend en http://localhost:3001 por defecto. Si necesitas cambiar esto, modifica las configuraciones en los archivos de la carpeta src/api/.
Asegúrate de que MongoDB esté ejecutándose:
# Linux/Mac
sudo systemctl start mongod
# O si usas Docker
docker run -d -p 27017:27017 --name mongodb mongo:latestcd backend
npm run devEl servidor estará disponible en http://localhost:3001
En otra terminal:
cd frontend
npm run devEl frontend estará disponible en http://localhost:5173 (o el puerto que indique Vite)
cd backend
npm run build:ui
npm run build
npm startEste endpoint solo permite crear usuarios con rol cliente. Para crear barberos o admins, usar el endpoint /api/users/admin.
curl -X POST http://localhost:3001/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "juanperez",
"name": "Juan Pérez",
"email": "juan@mail.com",
"password": "password123",
"phone": "123456789"
}'Nota: El rol siempre será cliente para este endpoint, incluso si se intenta especificar otro rol.
Este endpoint solo funciona si NO existe ningún admin en el sistema. Úsalo para crear el primer administrador.
curl -X POST http://localhost:3001/api/users/first-admin \
-H "Content-Type: application/json" \
-d '{
"username": "admin1",
"name": "Administrador Principal",
"email": "admin@mail.com",
"password": "password123",
"phone": "123456789"
}'Nota: Este endpoint solo funciona una vez. Después de crear el primer admin, debes usar /api/users/admin con autenticación.
Este endpoint permite a los administradores crear usuarios con cualquier rol (cliente, barbero, admin).
curl -X POST http://localhost:3001/api/users/admin \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TU_TOKEN_JWT" \
-H "X-CSRF-Token: TU_CSRF_TOKEN" \
-d '{
"username": "barbero1",
"name": "Barbero Ejemplo",
"email": "barbero@mail.com",
"password": "password123",
"phone": "123456789",
"role": "barbero"
}'Roles disponibles:
cliente- Usuario regular que puede hacer reservasbarbero- Barbero que atiende reservas (solo puede ser creado por admin)admin- Administrador del sistema (solo puede ser creado por otro admin)
Requisitos:
- Debes estar autenticado como
admin - El token JWT debe estar en las cookies
- El CSRF token debe estar en el header
X-CSRF-Token
curl -X POST http://localhost:3001/api/login \
-H "Content-Type: application/json" \
-d '{
"username": "juanperez",
"password": "password123"
}'Respuesta de ejemplo:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"username": "juanperez",
"name": "Juan Pérez",
"role": "cliente"
}curl http://localhost:3001/api/servicescurl http://localhost:3001/api/services/SERVICE_IDcurl -X POST http://localhost:3001/api/services \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: TU_CSRF_TOKEN" \
-b "token=TU_TOKEN_JWT" \
-d '{
"name": "Corte de cabello premium",
"type": "hair",
"description": "Corte de cabello con acabado profesional",
"durationMin": 45,
"price": 15.00
}'Tipos de servicio disponibles:
hair- Corte de Cabellobeardeyebrow- Corte de Barba y Cejashairbeard- Corte de Cabello y Barbafull_service- Servicio Completo
Requisitos:
- Debes estar autenticado como
admin - El token JWT debe estar en las cookies
- El CSRF token debe estar en el header
X-CSRF-Token
curl -X PUT http://localhost:3001/api/services/SERVICE_ID \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: TU_CSRF_TOKEN" \
-b "token=TU_TOKEN_JWT" \
-d '{
"name": "Corte de cabello premium actualizado",
"price": 18.00,
"durationMin": 50
}'Nota: Solo necesitas enviar los campos que deseas actualizar.
curl -X DELETE http://localhost:3001/api/services/SERVICE_ID \
-H "X-CSRF-Token: TU_CSRF_TOKEN" \
-b "token=TU_TOKEN_JWT"curl -X POST http://localhost:3001/api/reservations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TU_TOKEN_JWT" \
-d '{
"serviceId": "ID_DEL_SERVICIO",
"barberId": "ID_DEL_BARBERO",
"date": "2025-10-25",
"time": "14:00"
}'curl http://localhost:3001/api/reservations \
-H "Authorization: Bearer TU_TOKEN_JWT"curl http://localhost:3001/api/hours?barberId=ID_DEL_BARBERO&date=2025-10-25- Registro: Crear una cuenta con rol "cliente"
- Login: Iniciar sesión para obtener el token JWT
- Ver Servicios: Explorar los servicios disponibles
- Hacer Reserva: Seleccionar servicio, barbero, fecha y hora
- Ver Mis Reservas: Consultar las reservas realizadas
- Login: Iniciar sesión con credenciales de administrador
- Gestión de Usuarios: Acceder a
/admin/usuariospara crear y gestionar usuarios del sistema - Gestión de Servicios: Acceder a
/admin/serviciospara:- Crear nuevos servicios de barbería con nombre, tipo, descripción, duración y precio
- Editar servicios existentes (actualizar precios, duraciones, descripciones)
- Eliminar servicios que ya no se ofrecen
- Verificación: Los servicios creados estarán disponibles para que los clientes los vean y reserven
# 1. Crear usuario
curl -X POST http://localhost:3001/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "maria",
"name": "María García",
"email": "maria@mail.com",
"password": "segura123",
"phone": "987654321",
"role": "cliente"
}'
# 2. Hacer login y guardar el token
TOKEN=$(curl -s -X POST http://localhost:3001/api/login \
-H "Content-Type: application/json" \
-d '{
"username": "maria",
"password": "segura123"
}' | grep -o '"token":"[^"]*' | cut -d'"' -f4)
# 3. Ver servicios disponibles
curl http://localhost:3001/api/services
# 4. Crear una reserva (usando el token obtenido)
curl -X POST http://localhost:3001/api/reservations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"serviceId": "SERVICE_ID_AQUI",
"barberId": "BARBER_ID_AQUI",
"date": "2025-10-25",
"time": "15:00"
}'
# 5. Ver mis reservas
curl http://localhost:3001/api/reservations \
-H "Authorization: Bearer $TOKEN"npm run dev- Inicia el servidor en modo desarrollo con hot-reloadnpm run build- Compila el código TypeScript a JavaScriptnpm start- Inicia el servidor en modo producciónnpm test- Ejecuta los testsnpm run build:ui- Buildea el frontend y lo deja endist
npm run dev- Inicia el servidor de desarrollo de Vitenpm run build- Construye la aplicación para producciónnpm run preview- Previsualiza la build de producciónnpm run lint- Ejecuta el linter
El proyecto incluye tests end-to-end (E2E) implementados con Playwright que validan el funcionamiento completo de la aplicación.
Los tests se encuentran en la carpeta e2etests/ y cubren los siguientes casos mínimos:
-
Login + Acceso Protegido (
e2etests/tests/login.spec.ts)- Redirección a login cuando se accede a ruta protegida sin autenticación
- Manejo de credenciales inválidas
- Login exitoso y acceso a rutas protegidas
- Persistencia de sesión
-
CRUD sobre Reservas (
e2etests/tests/reservations-crud.spec.ts)- CREATE: Crear una nueva reserva desde la UI
- READ: Listar las reservas del usuario
- UPDATE: Actualizar una reserva existente
- DELETE: Eliminar una reserva existente
- Flujo completo de CRUD en la UI
cd e2etests
npm install
npm testnpm test- Ejecuta todos los tests en modo headlessnpm run test:ui- Abre la interfaz gráfica de Playwright (recomendado para desarrollo)npm run test:headed- Ejecuta con el navegador visiblenpm run test:debug- Modo debug con Playwright Inspectornpm run test:report- Ver el reporte HTML de la última ejecución
Los tests están configurados para:
- Iniciar automáticamente el backend (puerto 3001) y frontend (puerto 5173)
- Ejecutarse en Chromium por defecto
- Generar reportes HTML con screenshots en caso de fallos
Para información detallada sobre los tests E2E, configuración avanzada, debugging y solución de problemas, consulta el README de e2etests.
- Las contraseñas se almacenan hasheadas usando bcrypt
- Las rutas protegidas requieren un token JWT válido
- El token se envía en el header
Authorization: Bearer <token> - Cambia el
JWT_SECRETen producción por un valor seguro
- El backend incluye un sistema de seed para servicios iniciales
- Los tokens JWT expiran según la configuración del servidor
- Asegúrate de tener MongoDB corriendo antes de iniciar el backend
- En desarrollo, CORS está configurado para aceptar peticiones del frontend
- Haz fork del proyecto
- Crea una rama para tu feature (
git checkout -b feature/AmazingFeature) - Commit tus cambios (
git commit -m 'Add some AmazingFeature') - Push a la rama (
git push origin feature/AmazingFeature) - Abre un Pull Request
fullstack.dcc.uchile.cl:7130
ISC