Sistema de gestión de citas médicas
Aplicacion web para agendar citas medicas en línea, portal por rol
(administración, recepción, doctor y paciente), historial clínico, resultados de
exámenes, pagos con comprobante y notificaciones en tiempo real.
🔗 Ver demo en vivo · 📡 API en producción · 📖 Documentación Swagger · 🐛 Reportar bug
⚠️ El backend está en Render (plan gratuito): se suspende tras inactividad, así que la primera petición puede tardar ~30 segundos mientras el servidor despierta.Acceso de prueba: admin@medical.com / Admin1234
- Sobre el proyecto
- Funcionalidades
- Stack tecnológico
- Arquitectura
- Estructura del proyecto
- Primeros pasos
- Variables de entorno
- Versionado de la API
- Endpoints de la API
- Roles y permisos
- Tests
- Despliegue
- Roadmap
- Autor
- Licencia
Medical es un sistema de gestión de citas médicas desarrollado como proyecto de portafolio. El objetivo fue construir una aplicación realista de extremo a extremo: una API REST segura con control de acceso por rol, modelado relacional con MongoDB, autenticación con tokens de acceso y refresco, notificaciones en tiempo real y una interfaz reactiva por perfil de usuario.
Cubre los conceptos que aparecen en cualquier sistema profesional: autenticación y autorización granular, CRUD completo de varios recursos relacionados, subida de archivos a CDN, generación de documentos (recetas, comprobantes y reportes en PDF/Excel), caché, envío de correos y trabajo programado (recordatorios automáticos).
El repositorio es un monorepo con dos paquetes: medical-server (la API) y
medical-client (la interfaz web).
- Inicio de sesión con tokens de acceso y refresco (rotación de refresh token)
- Refresh tokens hasheados en base de datos (SHA-256) y revocables al cerrar sesión
- Contraseñas encriptadas con bcrypt
- Control de acceso por rol y acción (RBAC) sobre cada módulo
- Aislamiento a nivel de objeto: un paciente solo accede a sus propios datos (anti-IDOR)
- Sanitización contra XSS e inyección NoSQL, Helmet, CORS y rate limiting
- Monitoreo de errores con Sentry y logs con Winston
- Agenda de citas con verificación de disponibilidad real del doctor
- Prevención de solapamiento de horarios
- Estados de cita (pendiente, confirmada, completada, cancelada, no asistió)
- Edición y cancelación de citas
- Correo automático al agendar y recordatorio el día previo (cron)
- Registro de pacientes (RUT chileno con formato automático, RUT como contraseña inicial)
- Gestión de doctores con matrícula automática y editable, especialidad y horarios
- Foto de perfil para cualquier usuario (subida a Cloudinary con redimensionado en cliente)
- Historial clínico por atención (al registrarlo, la cita se marca como completada)
- Recetas médicas descargables en PDF con formato de receta chilena
- Resultados de exámenes con archivo adjunto, descargables por el paciente
- Registro de pagos por cita (una cita = un pago) con varios métodos
- Total recaudado y filtros por estado y fecha
- Comprobante de pago descargable como respaldo
- Reporte estadístico por doctor y reporte general de administración
- Exportación a PDF y Excel
- Dashboard con gráficos por rol (recaudación, citas por estado y por mes)
- Notificaciones en tiempo real con Socket.io (autenticadas, por sala/rol)
- Toasts de feedback, estados de carga y estados vacíos
- Portal adaptado al rol: cada usuario ve solo lo que le corresponde
- Interfaz responsiva con Tailwind CSS
| Tecnología | Versión | Uso |
|---|---|---|
| Node.js | 20.x | Entorno de ejecución |
| Express | 5.x | Framework HTTP |
| MongoDB + Mongoose | 9.x | Base de datos NoSQL y ODM |
| Redis (ioredis) | 5.x | Caché con degradación graceful |
| JSON Web Token | 9.x | Autenticación (access + refresh) |
| bcryptjs | 3.x | Hash de contraseñas |
| Socket.io | 4.x | Notificaciones en tiempo real |
| Multer + Cloudinary | 2.x | Subida de archivos a CDN |
| Nodemailer | 8.x | Envío de correos |
| node-cron | 4.x | Recordatorios programados |
| PDFKit + ExcelJS | — | Generación de reportes PDF/Excel |
| express-validator | 7.x | Validación de entrada |
| Helmet · CORS · rate-limit · sanitize | — | Seguridad |
| Sentry · Winston · Morgan | — | Observabilidad y logs |
| Swagger (swagger-jsdoc) | — | Documentación de la API |
| Jest + Supertest | 30.x | Tests |
| Tecnología | Versión | Uso |
|---|---|---|
| React | 18.x | Librería de interfaz |
| Vite | 6.x | Bundler y servidor de desarrollo |
| Tailwind CSS | 4.x | Estilos utilitarios (plugin de Vite) |
| React Router DOM | 6.x | Navegación SPA |
| Recharts | 2.x | Gráficos del dashboard |
| lucide-react | — | Iconos |
| socket.io-client | 4.x | Notificaciones en tiempo real |
| react-phone-number-input | 3.x | Campo de teléfono internacional |
| Servicio | Uso |
|---|---|
| MongoDB | Base de datos (local o Atlas) |
| Redis | Caché (opcional; si no está, degrada sin romper) |
| Cloudinary | Almacenamiento de imágenes y archivos |
| Docker Compose | Backend + MongoDB + Redis en contenedores |
┌─────────────────────┐ ┌──────────────────────┐
│ │ HTTP │ │
│ Frontend (React) │────────▶│ Backend (Express) │
│ medical.criveradev │◀────────│ api.medical.crivera │
│ .cl │ JSON │ dev.cl │
│ │◀═══════▶│ WebSocket (Socket.io)│
└─────────────────────┘ WS └──────────┬───────────┘
│
┌────────────┬───────────┼────────────┬────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ MongoDB │ │ Redis │ │Cloudinary│ │ Resend │ │ Sentry │
│ (datos) │ │ (caché) │ │(archivos)│ │ (email) │ │ (errores)│
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
Ambos dominios (
medical.criveradev.clyapi.medical.criveradev.cl) son subdominios propios gestionados en Cloudflare DNS, que apuntan respectivamente a Vercel y Render mediante registros CNAME.
Petición HTTP
│
▼
┌──────────────┐
│ ROUTES │ Define URLs y conecta con controladores
├──────────────┤
│ MIDDLEWARE │ JWT auth · RBAC · scope paciente · validación · uploads · errores
├──────────────┤
│ CONTROLLERS │ Lógica de negocio por recurso
├──────────────┤
│ SERVICES │ Caché · notificaciones · correos · recordatorios · exportar
├──────────────┤
│ MODELS │ Esquemas Mongoose (User, Role, Paciente, Doctor, Cita…)
├──────────────┤
│ MONGODB │ Persistencia
└──────────────┘
Medical/
│
├── medical-server/ # API REST (Express)
│ ├── src/
│ │ ├── config/ # env, db, redis, cloudinary, multer, logger, swagger, sentry, email, api (prefijos/versión)
│ │ ├── models/ # User, Role, Paciente, Doctor, Cita, Historial, Pago, Resultado, Departamento, Especialidad
│ │ ├── controllers/
│ │ │ └── v1/ # Lógica por recurso (auth, citas, pacientes, doctores, pagos, reportes…)
│ │ ├── routes/
│ │ │ └── v1/ # Definición de endpoints + validaciones (Swagger en JSDoc)
│ │ ├── middleware/ # auth (JWT/RBAC/scope), validar, sanitizar, xss, errores, morgan
│ │ ├── services/ # cache, notificaciones (Socket.io), email, recordatorios (cron), exportar (PDF/Excel)
│ │ ├── seed/ # roles.seed.js · admin.seed.js
│ │ └── app.js # App Express (middlewares, rutas versionadas, Swagger)
│ ├── tests/ # Jest + Supertest
│ ├── Dockerfile
│ ├── docker-compose.yml # backend + mongo + redis
│ ├── .env.example
│ └── package.json
│
├── medical-client/ # Interfaz web (React + Vite)
│ ├── src/
│ │ ├── lib/ # api (fetch + refresh token), format, roles, receta, voucher
│ │ ├── hooks/ # useFetch, useMiFicha, useMiDoctor
│ │ ├── context/ # AuthContext, NotificationsContext (Socket.io), ToastContext
│ │ ├── components/ # Navbar, Footer, ui (primitivos), portal/ (Layout, ProtectedRoute, NotificationBell)
│ │ ├── pages/ # Home, Login y portal/ por rol (admin, doctor, paciente)
│ │ ├── data/ # Contenido de marketing de la landing
│ │ ├── App.jsx # Rutas (público + portal protegido)
│ │ └── main.jsx
│ ├── vite.config.js # Proxy /api y /socket.io → :3000
│ └── package.json
│
├── LICENSE
└── README.md
Cada paquete tiene además su propio README con detalle específico. El backend incluye documentación interactiva completa en Swagger (
/api/v1/docs).
- Node.js v20 o superior — Descargar
- npm v10 o superior (incluido con Node)
- MongoDB en local, o una cuenta en MongoDB Atlas
- Cuenta en Cloudinary (gratuita) para subir archivos
- (Opcional) Redis para caché · Docker para levantar todo en contenedores
git clone https://github.com/criveradev/Medical.git
cd Medical# Backend
cd medical-server && npm install
# Frontend
cd ../medical-client && npm install# Backend
cp medical-server/.env.example medical-server/.envEdita medical-server/.env con tus credenciales (ver sección
Variables de entorno).
cd medical-server
npm run seed:roles # Crea los roles del sistema
npm run seed:admin # Crea el usuario administrador inicialCredenciales por defecto del administrador:
Email: admin@medical.com
Password: Admin1234
🔒 Cambia esta contraseña tras el primer inicio de sesión.
Abre dos terminales:
# Terminal 1 — Backend (http://localhost:3000)
cd medical-server && npm run dev
# Terminal 2 — Frontend (http://localhost:4200)
cd medical-client && npm run devEl frontend hace proxy de /api y /socket.io al backend, así que no hay
problemas de CORS en desarrollo.
Abre http://localhost:4200, inicia sesión con el admin y prueba el portal.
La documentación de la API queda en http://localhost:3000/api/v1/docs.
Levanta backend + MongoDB + Redis con un solo comando:
cd medical-server
docker compose up -d # backend :3000 · mongo :27017 · redis :6379Luego ejecuta los seeds dentro del contenedor y levanta el frontend aparte con
npm run dev.
# Servidor
NODE_ENV=development
PORT=3000
API_URL=http://localhost:3000
CLIENT_URL=http://localhost:4200
# Base de datos — MongoDB
MONGO_URI=mongodb://localhost:27017/medical_db
# Redis — caché opcional (si no está, la app degrada a MongoDB sin errores).
# Opción A: una sola URL (proveedores gestionados; rediss:// activa TLS solo).
REDIS_URL=
# Opción B: host/puerto por separado (Redis local).
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# JWT — genera secretos con:
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
JWT_SECRET=cadena_aleatoria_minimo_32_caracteres
JWT_REFRESH_SECRET=otra_cadena_aleatoria_distinta
JWT_EXPIRES=8h
# Email (SMTP) — para recordatorios y avisos. En desarrollo local sirve iCloud/Gmail.
EMAIL_HOST=smtp.mail.me.com
EMAIL_PORT=587
EMAIL_USER=tu_correo
EMAIL_PASS=tu_password_de_aplicacion
EMAIL_FROM= # remitente verificado (opcional; si no, usa EMAIL_USER)
# Email vía API HTTP (puerto 443) — OBLIGATORIO en hosts que bloquean SMTP
# (Render free, Railway). Si se define, el envío usa la API de Resend en vez de SMTP.
RESEND_API_KEY=
# Cloudinary — cloudinary.com → Dashboard → API Keys
CLOUDINARY_CLOUD_NAME=tu_cloud_name
CLOUDINARY_API_KEY=tu_api_key
CLOUDINARY_API_SECRET=tu_api_secret
# Sentry — monitoreo de errores (opcional)
SENTRY_DSN=tu_dsn_de_sentry
⚠️ Nunca subas archivos.enva GitHub. Están incluidos en.gitignore. En modo test el envío de correos se desactiva automáticamente.
La API está versionada en /api/v1 desde src/config/api.js (API_VERSION,
API_PREFIX). /api (sin versión) se mantiene montado como alias de v1 por
compatibilidad hacia atrás, pero es el mismo alias, no una versión distinta.
En el código, cada versión es un árbol independiente de rutas y controladores
(controllers/v1, routes/v1), que comparte models/, services/,
middleware/ y config/ con el resto de versiones. app.js monta cada
recurso de v1 en /api/v1/* y /api/* (legado) desde el mismo router, y
deja documentado en un bloque comentado cómo se agregaría controllers/v2 /
routes/v2 el día que un endpoint necesite un cambio incompatible (nuevo
shape de respuesta, validación distinta, etc.) sin romper a los clientes que
siguen en v1.
El frontend (medical-client/src/lib/api.js) normaliza toda petición a
/api/v1/* en un único punto (normalizeUrl), así que consumir una v2 futura
sería cambiar ese archivo, no cada componente.
URL base en producción: https://api.medical.criveradev.cl/api/v1 · Documentación interactiva: /api/v1/docs
En desarrollo local:
http://localhost:3000/api/v1/api/...(sin/v1) sigue funcionando como alias de la misma versión.
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
POST |
/auth/login |
Iniciar sesión | — |
POST |
/auth/refresh |
Renovar access token | — |
POST |
/auth/logout |
Cerrar sesión (revoca refresh) | ✅ |
GET |
/auth/perfil |
Mi perfil | ✅ |
PUT |
/auth/perfil/foto |
Subir foto de perfil | ✅ |
PUT |
/auth/cambiar-password |
Cambiar contraseña | ✅ |
POST |
/auth/usuarios |
Crear usuario | 🛡 |
GET |
/auth/usuarios |
Listar usuarios | 🛡 |
PUT |
/auth/usuarios/:id |
Editar usuario | 🛡 |
DELETE |
/auth/usuarios/:id |
Eliminar usuario | 🛡 |
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
GET |
/citas |
Listar citas (con filtros) | 🛡 |
GET |
/citas/:id |
Ver una cita | 🛡 |
GET |
/citas/disponibilidad/:doctorId |
Horarios disponibles | 🛡 |
POST |
/citas |
Agendar cita | 🛡 |
PUT |
/citas/:id |
Editar cita | 🛡 |
PUT |
/citas/:id/estado |
Cambiar estado | 🛡 |
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
GET |
/pacientes/mi-ficha |
Mi ficha (paciente) | ✅ |
GET |
/pacientes |
Listar pacientes | 🛡 |
GET |
/pacientes/:id |
Ver paciente | 🛡 |
POST |
/pacientes |
Crear paciente | 🛡 |
PUT |
/pacientes/:id |
Editar paciente | 🛡 |
PUT |
/pacientes/:id/foto |
Subir foto | 🛡 |
DELETE |
/pacientes/:id |
Eliminar paciente | 🛡 |
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
GET |
/doctores/mi-perfil |
Mi perfil (doctor) | ✅ |
GET |
/doctores/siguiente-matricula |
Matrícula sugerida | 🛡 |
GET |
/doctores |
Listar doctores | 🛡 |
GET |
/doctores/:id |
Ver doctor | 🛡 |
GET |
/doctores/disponibilidad/:doctorId |
Disponibilidad | 🛡 |
POST |
/doctores |
Crear doctor | 🛡 |
PUT |
/doctores/:id |
Editar doctor | 🛡 |
PUT |
/doctores/:id/horarios |
Editar horarios | 🛡 |
PUT |
/doctores/:id/foto |
Subir foto | 🛡 |
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
GET |
/historial/paciente/:pacienteId |
Historial de un paciente | 🛡 |
GET |
/historial/:id |
Ver registro | 🛡 |
POST |
/historial |
Registrar atención (receta) | 🛡 |
PUT |
/historial/:id |
Editar registro | 🛡 |
GET |
/resultados/paciente/:pacienteId |
Resultados de un paciente | 🛡 |
GET |
/resultados/:id |
Ver resultado | 🛡 |
POST |
/resultados |
Subir resultado (archivo) | 🛡 |
PUT |
/resultados/:id |
Editar resultado | 🛡 |
DELETE |
/resultados/:id |
Eliminar resultado | 🛡 |
| Método | Endpoint | Descripción | Auth |
|---|---|---|---|
GET |
/pagos |
Listar pagos (filtros + total) | 🛡 |
GET |
/pagos/:id |
Ver pago | 🛡 |
POST |
/pagos |
Registrar pago | 🛡 |
PUT |
/pagos/:id |
Editar pago | 🛡 |
PUT |
/pagos/:id/anular |
Anular pago | 🛡 |
GET |
/reportes/doctor/:doctorId |
Reporte por doctor | 🛡 |
GET |
/reportes/doctor/:doctorId/pdf |
Reporte PDF | 🛡 |
GET |
/reportes/doctor/:doctorId/excel |
Reporte Excel | 🛡 |
GET |
/reportes/admin |
Reporte general | 🛡 |
GET |
/departamentos · /especialidades |
Catálogos (+ CRUD) | 🛡 |
Auth:
—público ·✅requiere sesión ·🛡requiere sesión + permiso de rol (RBAC).
Authorization: Bearer <tu_access_token>| Código | Significado |
|---|---|
200 |
OK |
201 |
Creado |
400 |
Datos inválidos |
401 |
Sin token o token inválido/expirado |
403 |
Sin permiso sobre el recurso |
404 |
Recurso no encontrado |
409 |
Conflicto (duplicado / solapamiento) |
429 |
Demasiadas peticiones (rate limit) |
500 |
Error interno del servidor |
El acceso se controla por rol y acción sobre cada módulo (RBAC), y además con aislamiento a nivel de objeto para el paciente.
| Rol | Acceso principal |
|---|---|
| Administrador | Acceso total: usuarios, doctores, catálogos y reportes |
| Recepcionista | Agenda, pacientes y pagos |
| Enfermero | Apoyo clínico según permisos asignados |
| Doctor | Su agenda, registrar atenciones/recetas y subir resultados |
| Paciente | Solo sus propias citas, historial, resultados y pagos |
El backend incluye una suite de tests de integración con Jest + Supertest.
cd medical-server
npm test # Ejecuta toda la suite
npm run test:watch # Modo watch
npm run test:coverage # Con reporte de coberturaEstado actual: 13 suites · 170 tests en verde. En entorno de test el envío de correos se desactiva automáticamente para que la suite sea rápida y aislada.
El proyecto está desplegado con servicios de plan gratuito, sobre un dominio propio gestionado en Cloudflare:
| Pieza | Servicio | Notas |
|---|---|---|
| Frontend | Vercel | sitio estático (Vite) · medical.criveradev.cl |
| Backend / API | Render | Web Service Node · api.medical.criveradev.cl |
| DNS / Dominio | Cloudflare | registros CNAME hacia Vercel y Render |
| Base de datos | MongoDB Atlas | clúster M0 |
| Caché | Upstash (Redis) | vía REDIS_URL (TLS) |
| Archivos | Cloudinary | imágenes y documentos |
| Resend (API HTTP) | dominio propio autenticado (SPF + DKIM + DMARC) | |
| Errores | Sentry | opcional |
El backend ya usa process.env.PORT y limita el CORS a CLIENT_URL, así que el
despliegue es básicamente configurar variables de entorno.
El frontend usa
VITE_API_URLpara apuntar al backend: déjala vacía en desarrollo (proxy de Vite) y ponla con la URL del backend en producción.
git init && git add . && git commit -m "deploy inicial"
git branch -M main
git remote add origin https://github.com/criveradev/Medical.git
git push -u origin mainVerifica que los
.envno se suban (deben estar en.gitignore).
- Crea un clúster gratuito (M0) en mongodb.com/atlas.
- En Network Access permite el acceso desde cualquier IP (
0.0.0.0/0), porque las IP de Render gratis son dinámicas. - En Database Access crea un usuario y copia la cadena de conexión
(
mongodb+srv://...); será tuMONGO_URI.
Crea un Web Service en render.com apuntando al repo:
| Campo | Valor |
|---|---|
| Root Directory | medical-server |
| Build Command | npm install |
| Start Command | npm start |
Agrega las variables de entorno (ver Variables de entorno),
con MONGO_URI de Atlas y CLIENT_URL = https://medical.criveradev.cl (paso 4 y 5).
SENTRY_DSN y REDIS_* son opcionales; si defines SENTRY_DSN, comprueba que el DSN
corresponda al proyecto de Sentry que estás mirando (cada proyecto tiene su propio DSN)
y que el servidor arranque sin el aviso ⚠️ SENTRY_DSN no está definido. Tras el primer deploy, siembra los datos
(roles y admin). El plan gratuito de Render no incluye Shell, así que usa una de
estas dos vías:
- Local: desde tu máquina, apuntando a la base de Atlas de producción:
cd medical-server MONGO_URI="<tu cadena de Atlas>" npm run seed:roles MONGO_URI="<tu cadena de Atlas>" npm run seed:admin
- Desde el propio Render: cambia temporalmente el Start Command a
npm run seed:roles && npm run seed:admin && npm start, deja que redespliegue una vez, y luego devuélvelo anpm start. Los seeds son idempotentes.
Importa el repo en vercel.com:
| Campo | Valor |
|---|---|
| Root Directory | medical-client |
| Framework Preset | Vite |
| Build Command | npm run build |
| Output Directory | dist |
Agrega la variable de entorno VITE_API_URL con la URL pública del backend
(sin barra final), por ejemplo https://api.medical.criveradev.cl.
El vercel.json incluido se encarga del fallback de rutas de React Router.
El proyecto usa subdominios propios en vez de las URLs .vercel.app / .onrender.com:
- En Vercel → proyecto → Settings → Domains → agrega
medical.criveradev.cl. Si tu zona DNS ya está en Cloudflare, Vercel puede autorizar y crear el registro CNAME automáticamente (integración oficial); si no, agrégalo a mano:CNAME medical cname.vercel-dns.com (Proxy: Solo DNS / DNS only) - En Render → servicio backend → Settings → Custom Domains → agrega
api.medical.criveradev.cly copia el valor CNAME que entrega Render. Agrégalo en Cloudflare:CNAME api.medical <valor entregado por Render> (Proxy: Solo DNS / DNS only) - Deja el proxy de Cloudflare en "Solo DNS" (nube gris) hasta que ambos dominios queden verificados y con SSL emitido en sus respectivos paneles.
- No toques los registros existentes de la raíz (
@,www) ni los de email transaccional (_domainkey, TXT de verificación,_dmarc) — son independientes y no se ven afectados por agregar estos subdominios.
En Render, asegúrate de que CLIENT_URL quede exactamente como
https://medical.criveradev.cl (con https://, sin slash final) → esto resuelve
el CORS tanto de Express como de Socket.io para el nuevo dominio.
⚠️ Error común: siCLIENT_URLqueda sin el prefijohttps://(por ejemplomedical.criveradev.cla secas), el backend rechaza el origen con un error403/ CORS no exitoso, porque la comparación de origen es por texto exacto.
Crea una base Redis gratuita en upstash.com, copia la URL
de la pestaña TCP (empieza con rediss://, con TLS) y agrégala en Render:
REDIS_URL=rediss://default:TU_PASSWORD@xxxx.upstash.io:6379
Es opcional: sin REDIS_URL, la app funciona consultando MongoDB directamente.
⚠️ Render (plan gratuito) bloquea la salida SMTP (puertos 25/465/587), así que iCloud/Gmail/SMTP tradicional dan Connection timeout. La solución es enviar por la API HTTP de Resend (puerto 443), que el código usa automáticamente cuando existeRESEND_API_KEY.
- Crea cuenta en resend.com y autentica tu dominio
(SPF + DKIM) o verifica un remitente; define ese correo en
EMAIL_FROM. - En Resend → API Keys → genera una clave (
re_...). - En Render agrega:
RESEND_API_KEY=re_... EMAIL_FROM=noreply@tudominio.com
⚠️ Plan gratuito de Render: el servicio se suspende tras un rato de inactividad (la primera petición puede tardar ~30 s) y, al estar dormido, el recordatorio por cron de las 09:00 puede no dispararse. Para cron confiable usa un plan de pago o un host sin suspensión (Fly.io, VPS).
El medical-server incluye Dockerfile y docker-compose.yml (backend +
MongoDB + Redis):
cd medical-server
docker compose up -d --build- Autenticación con access + refresh token (hasheado y revocable)
- RBAC por módulo/acción + aislamiento de datos del paciente
- CRUD de citas con disponibilidad y anti-solapamiento
- Pacientes y doctores (matrícula automática/editable, horarios, fotos)
- Historial clínico + recetas en PDF
- Resultados de exámenes con archivos en Cloudinary
- Pagos con comprobante descargable
- Reportes con gráficos y exportación PDF/Excel
- Notificaciones en tiempo real (Socket.io) y correos
- Recordatorios automáticos (cron) y caché con Redis
- Seguridad: Helmet, CORS, rate limiting, sanitización, Sentry
- Tests (Jest + Supertest), Swagger y Docker
- Versionado real de la API (
controllers/v1,routes/v1+ alias legado)
- Pago en línea integrado (pasarela)
- Búsqueda y filtros avanzados en listados
- Modo oscuro
- Tests end-to-end del frontend
Distribuido bajo la licencia MIT. Ver LICENSE para más información.