API REST para el sistema de gestión de turnos médicos. Permite administrar médicos, pacientes, especialidades, obras sociales y turnos, con autenticación JWT y autorización por roles.
| Área | Tecnología |
|---|---|
| Runtime | Node.js |
| Framework | Express 5 |
| Base de datos | MySQL |
| Autenticación | JWT (jsonwebtoken) |
| Hash contraseñas | SHA2-256 (MySQL) |
| Documentación | Swagger (swagger-jsdoc + swagger-ui-express) |
| Validaciones | express-validator |
| Logs | Morgan |
| Upload de archivos | Multer |
| PDFKit | |
| CORS | cors |
| Variables de entorno | dotenv |
| Dev server | Nodemon |
- Node.js >= 18
- MySQL >= 8 o MariaDB >= 10.4
- npm
git clone https://github.com/marianodevel/TP_FINAL_Prog_III
cd TP_FINAL_Prog_IIInpm installCrear un archivo .env en la raíz del proyecto:
PORT=3000
NODE_ENV=development
FRONTEND_URL=http://localhost:5173
DB_HOST=localhost
DB_PORT=3306
DB_NAME=prog3_turnos
DB_USER=root
DB_PASSWORD=
JWT_SECRET=tu_clave_secreta_muy_larga_y_segura
JWT_EXPIRES_IN=8hImportar el script principal en phpMyAdmin o desde la terminal:
mysql -u root -p < prog3_turnos.sqlLuego importar los stored procedures adicionales:
mysql -u root -p prog3_turnos < scripts/estadisticas_sp.sql# Desarrollo (con nodemon)
npm run dev
# Producción
npm startEl servidor estará disponible en http://localhost:3000.
La documentación Swagger estará disponible en http://localhost:3000/api/v1/docs.
Todos los usuarios del seed utilizan como contraseña la parte del email que precede al @.
| Contraseña | Rol | |
|---|---|---|
| ferben@correo.com | ferben | Administrador |
| gomsil@correo.com | gomsil | Administrador |
| lopmar@correo.com | lopmar | Médico |
| diajua@correo.com | diajua | Médico |
| benhor@correo.com | benhor | Médico |
| perlui@correo.com | perlui | Médico |
| lopjac@correo.com | lopjac | Paciente |
| hunlor@correo.com | hunlor | Paciente |
| agubri@correo.com | agubri | Paciente |
src/
├── app.js # Entrada principal
├── config/
│ ├── cors.config.js # Configuración CORS
│ ├── db.config.js # Configuración MySQL
│ ├── multer.config.js # Configuración Multer
│ └── swagger.config.js # Configuración Swagger
├── controllers/
│ ├── auth.controller.js
│ ├── especialidades.controller.js
│ ├── estadisticas.controller.js
│ ├── medicos.controller.js
│ ├── obras_sociales.controller.js
│ ├── pacientes.controller.js
│ ├── pdf.controller.js
│ ├── turnos.controller.js
│ └── usuarios.controller.js
├── db/
│ ├── connection.js # Pool de conexión MySQL
│ └── repositories/
│ ├── especialidades.repository.js
│ ├── estadisticas.repository.js
│ ├── medicos.repository.js
│ ├── obras_sociales.repository.js
│ ├── pacientes.repository.js
│ ├── turnos.repository.js
│ └── usuarios.repository.js
├── docs/ # Anotaciones Swagger
│ ├── auth.docs.js
│ ├── especialidades.docs.js
│ └── turnos.docs.js
├── dtos/ # Data Transfer Objects
│ ├── medico.dto.js
│ ├── paciente.dto.js
│ ├── turno.dto.js
│ └── usuario.dto.js
├── middlewares/
│ ├── auth.middleware.js # verificarToken, verificarRol
│ ├── validarCampos.js # express-validator handler
│ └── validateContentType.js # Valida Content-Type
├── routes/
│ └── v1/
│ ├── index.js
│ ├── auth.js
│ ├── especialidades.js
│ ├── estadisticas.js
│ ├── medicos.js
│ ├── obras_sociales.js
│ ├── pacientes.js
│ ├── pdf.js
│ ├── status.js
│ ├── turnos.js
│ └── usuarios.js
├── services/
│ ├── auth.service.js
│ ├── especialidades.service.js
│ ├── estadisticas.service.js
│ ├── medicos.service.js
│ ├── obras_sociales.service.js
│ ├── pacientes.service.js
│ ├── pdf.service.js
│ ├── turnos.service.js
│ └── usuarios.service.js
├── uploads/ # Fotos de perfil (gitignored)
│ └── .gitkeep
└── validators/
├── auth.js
├── medicos.js
├── obras_sociales.js
├── pacientes.js
├── turnos.js
└── usuarios.js
scripts/
├── queries.sql
└── estadisticas_sp.sql # Stored procedures adicionales
tests/
├── setup.js # Variables de entorno para Jest
├── helpers/
│ ├── token.helper.js # Generadores de JWT por rol
│ └── db.helper.js # Limpieza de datos y cierre de conexión
├── unit/
│ ├── dtos/
│ │ ├── turno.dto.test.js
│ │ ├── medico.dto.test.js
│ │ ├── paciente.dto.test.js
│ │ └── usuario.dto.test.js
│ └── services/
│ └── turnos.service.test.js
├── integration/
│ ├── auth.test.js
│ ├── especialidades.test.js
│ └── turnos.test.js
└── bruno/ # Colección Bruno para pruebas manuales
├── environments/
│ └── local.bru
├── auth/
├── especialidades/
├── obras-sociales/
├── medicos/
├── pacientes/
├── turnos/
├── usuarios/
├── estadisticas/
└── reportes/
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| POST | /api/v1/auth/login |
Iniciar sesión | Público |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/especialidades |
Listar especialidades | Todos |
| GET | /api/v1/especialidades/:id |
Obtener especialidad | Todos |
| POST | /api/v1/especialidades |
Crear especialidad | Admin |
| PUT | /api/v1/especialidades/:id |
Actualizar especialidad | Admin |
| DELETE | /api/v1/especialidades/:id |
Eliminar (soft delete) | Admin |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/obras-sociales |
Listar obras sociales | Todos |
| GET | /api/v1/obras-sociales/:id_obra_social |
Obtener obra social | Todos |
| POST | /api/v1/obras-sociales |
Crear obra social | Admin |
| PUT | /api/v1/obras-sociales/:id_obra_social |
Actualizar obra social | Admin |
| DELETE | /api/v1/obras-sociales/:id_obra_social |
Eliminar (soft delete) | Admin |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/medicos |
Listar médicos | Paciente, Admin |
| GET | /api/v1/medicos?especialidad=1 |
Filtrar por especialidad | Paciente, Admin |
| GET | /api/v1/medicos/:id_medico |
Obtener médico | Paciente, Admin |
| PUT | /api/v1/medicos/:id_medico |
Actualizar médico | Admin |
| GET | /api/v1/medicos/:id_medico/obras-sociales |
Obras sociales del médico | Admin |
| POST | /api/v1/medicos/:id_medico/obras-sociales |
Asociar obra social | Admin |
| DELETE | /api/v1/medicos/:id_medico/obras-sociales/:id_obra_social |
Desasociar obra social | Admin |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/pacientes/me |
Ver perfil propio | Paciente |
| GET | /api/v1/pacientes |
Listar pacientes | Admin |
| GET | /api/v1/pacientes/:id_paciente |
Obtener paciente | Admin |
| PATCH | /api/v1/pacientes/:id_paciente/obra-social |
Actualizar obra social | Admin |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/turnos/mis-turnos |
Listar turnos propios | Médico |
| PATCH | /api/v1/turnos/:id_turno/atendido |
Marcar como atendido | Médico |
| GET | /api/v1/turnos/mis-reservas |
Listar reservas propias | Paciente |
| POST | /api/v1/turnos/reserva |
Crear reserva | Paciente |
| GET | /api/v1/turnos |
Listar todos los turnos | Admin |
| GET | /api/v1/turnos/:id_turno |
Obtener turno | Admin |
| POST | /api/v1/turnos |
Registrar turno | Admin |
| DELETE | /api/v1/turnos/:id_turno |
Eliminar (soft delete) | Admin |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| POST | /api/v1/usuarios/pacientes |
Registrar paciente | Admin |
| POST | /api/v1/usuarios/medicos |
Registrar médico | Admin |
| POST | /api/v1/usuarios/admins |
Registrar administrador | Admin |
| PATCH | /api/v1/usuarios/me/foto |
Subir foto de perfil | Todos |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/estadisticas/especialidades |
Turnos por especialidad | Admin |
| GET | /api/v1/estadisticas/medicos |
Médicos con más turnos | Admin |
| GET | /api/v1/estadisticas/obras-sociales |
Turnos por obra social | Admin |
| GET | /api/v1/estadisticas/atendidos |
Atendidos vs pendientes | Admin |
| GET | /api/v1/estadisticas/rango?fecha_desde=...&fecha_hasta=... |
Turnos por rango de fechas | Admin |
| Método | Ruta | Descripción | Roles |
|---|---|---|---|
| GET | /api/v1/reportes/turnos |
Descargar PDF de turnos | Admin |
| GET | /api/v1/reportes/turnos?fecha_desde=...&fecha_hasta=... |
PDF filtrado por fechas | Admin |
obra social NO particular (es_particular = 0):
valor_total = valor_consulta - (porcentaje_descuento * valor_consulta / 100)
obra social particular (es_particular = 1):
valor_total = valor_consulta
Ningún registro se elimina físicamente. Se utiliza el campo activo:
activo = 1→ registro visible y operativoactivo = 0→ registro eliminado lógicamente
Todas las consultas filtran por activo = 1.
Las estadísticas se generan exclusivamente mediante stored procedures almacenados en MySQL, llamados desde el backend con CALL nombre_procedure().
| Rol | Valor | Permisos |
|---|---|---|
| Médico | 1 | Ver y gestionar sus propios turnos |
| Paciente | 2 | Crear y ver sus reservas, listar médicos y especialidades |
| Administrador | 3 | Acceso completo al sistema |
- Arquitectura en capas:
routes → controllers → services → repositories - No se escribe SQL en controllers ni services
- Consultas parametrizadas con
?para prevenir SQL injection - Transacciones MySQL en operaciones que afectan múltiples tablas
- Variables de entorno para datos sensibles
- Soft delete en todas las entidades
- Versionado de API:
/api/v1/ - Manejo centralizado de errores con códigos HTTP apropiados
- Validaciones en todas las entradas con express-validator
- CORS configurable por variable de entorno
Repository Pattern
Cada entidad tiene su propio repository que encapsula todo el acceso a datos. El resto de las capas nunca toca SQL directamente. Implementado en especialidades, medicos, pacientes, turnos, obras_sociales, usuarios y estadisticas.
Service Layer
Los services concentran la lógica de negocio: calcular valor_total, verificar conflictos de horario, orquestar transacciones entre múltiples repositories. Los controllers no toman decisiones de negocio, solo traducen HTTP a llamadas al service y devuelven la respuesta.
DTO — Data Transfer Object
Implementado en src/dtos/ para las entidades de mayor riesgo de exposición. Los DTOs transforman los datos crudos de la base de datos antes de enviarlos al cliente, evitando filtrar campos internos y desacoplando la estructura de la BD de la respuesta de la API.
| DTO | Responsabilidad |
|---|---|
usuario.dto.js |
Oculta contraseña y campo activo; aplica en login y subida de foto |
medico.dto.js |
Agrupa campos planos del JOIN en subobjetos especialidad y usuario; convierte valor_consulta a float |
paciente.dto.js |
Agrupa obra social en subobjeto; convierte es_particular a boolean |
turno.dto.js |
Corrige el typo atentido → atendido; convierte a boolean; agrupa médico y paciente en subobjetos |
Facade
Los services actúan como fachada para los controllers. Por ejemplo turnosService.createTurno() encapsula internamente la consulta de médicos y pacientes, el cálculo del valor total, la apertura de una transacción y la escritura en la base de datos. El controller invoca únicamente una función.
Singleton
El pool de conexiones en connection.js se crea una sola vez y se exporta. Todos los repositories importan la misma instancia, garantizando que no se abran conexiones innecesarias.
Dependency Injection (manual) Los repositories se instancian dentro de los services y los services dentro de los controllers. Las dependencias se reciben, no se crean en el lugar de uso.
Registro de usuarios — El administrador puede registrar nuevos pacientes, médicos y administradores directamente desde la API. Cada registro utiliza una transacción MySQL para garantizar consistencia entre las tablas usuarios y pacientes / medicos.
Adicionalmente, cualquier usuario autenticado puede subir o actualizar su foto de perfil mediante PATCH /api/v1/usuarios/me/foto con multipart/form-data. Las imágenes se almacenan en src/uploads/ y se sirven como archivos estáticos en /uploads/.
# Todos los tests
npm test
# Solo unitarios
npm run test:unit
# Solo integración
npm run test:integration
# Con reporte de cobertura
npm run test:coverage
# Modo watch
npm run test:watchLos tests unitarios no requieren base de datos (utilizan mocks). Los tests de integración requieren la base de datos activa con el seed cargado.
| Tipo | Qué cubre |
|---|---|
| Unitario — DTOs | Transformaciones, tipos, campos expuestos/ocultos |
| Unitario — Services | Lógica de negocio: cálculo valor_total, conflicto de horario |
| Integración — Auth | Login por rol, credenciales inválidas, validaciones |
| Integración — Especialidades | CRUD completo, control de roles, duplicados |
| Integración — Turnos | Crear, listar, marcar atendido, validación DTO en respuesta |
El proyecto incluye una colección Bruno en la carpeta tests/bruno/ con todos los endpoints organizados por entidad.
Para utilizarla:
- Abrir Bruno
- Abrir la carpeta
tests/bruno/como colección - Seleccionar el entorno
local - Ejecutar primero el login correspondiente al rol requerido — el token se guarda automáticamente en la variable de entorno
- Los requests subsiguientes utilizarán el token de forma automática