{"id":51049646,"url":"https://github.com/oscar503sv/medicore","last_synced_at":"2026-06-22T16:03:14.242Z","repository":{"id":361760495,"uuid":"1255516649","full_name":"oscar503sv/medicore","owner":"oscar503sv","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-11T01:03:23.000Z","size":1205,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T01:14:06.037Z","etag":null,"topics":["alembic-migration","clean-architecture","fastapi","postgresql","pytest","ruff-python","sqlalchemy-orm","tanstack-react-query","zustand"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oscar503sv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T23:21:38.000Z","updated_at":"2026-06-11T01:03:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/oscar503sv/medicore","commit_stats":null,"previous_names":["oscar503sv/medicore"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oscar503sv/medicore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscar503sv%2Fmedicore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscar503sv%2Fmedicore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscar503sv%2Fmedicore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscar503sv%2Fmedicore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oscar503sv","download_url":"https://codeload.github.com/oscar503sv/medicore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oscar503sv%2Fmedicore/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34655728,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alembic-migration","clean-architecture","fastapi","postgresql","pytest","ruff-python","sqlalchemy-orm","tanstack-react-query","zustand"],"created_at":"2026-06-22T16:03:12.283Z","updated_at":"2026-06-22T16:03:14.229Z","avatar_url":"https://github.com/oscar503sv.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Medicore\n\nSistema de gestión clínica **multi-tenant** construido con **Clean Architecture**.\n\nCubre el ciclo completo de atención: autenticación por organización, dashboard, gestión de pacientes, agenda y citas, consulta en curso (bitácora SOAP) con **catálogo de diagnósticos CIE-10/CIE-11**, historiales clínicos firmados e inmutables, archivos médicos, disponibilidad por doctor, aseguradoras, gestión de usuarios, **permisos granulares por rol con personalización por clínica** y ajustes de organización.\n\nIncluye además una **consola de plataforma (superadmin)** por encima del límite multi-tenant: alta y administración de clínicas, estadísticas y auditoría globales, soporte de cuentas (reseteo de contraseña, desbloqueo, suspensión) e **impersonación auditada** de una clínica para dar soporte.\n\n---\n\n## Stack\n\n| Capa | Tecnología |\n|---|---|\n| Lenguaje | Python ≥ 3.12 |\n| API | FastAPI ≥ 0.115 + Uvicorn |\n| ORM | SQLAlchemy 2.0 (sync) |\n| Migraciones | Alembic ≥ 1.13 |\n| Base de datos | PostgreSQL (psycopg3) |\n| Auth | PyJWT ≥ 2.8 + bcrypt ≥ 4 |\n| Validación config | pydantic-settings |\n| Tests / Lint | pytest ≥ 8 + ruff (adaptadores en memoria) |\n| Frontend | React 18 + TypeScript 5 + Vite 6 |\n| Estado / datos (FE) | Zustand + TanStack Query + Axios |\n| Estilos (FE) | Tailwind CSS 3 + design tokens |\n| Enrutado / i18n (FE) | React Router 6 + i18n propio ES/EN |\n\n---\n\n## Arquitectura\n\nEl backend sigue **Clean Architecture** estricta con cuatro capas. La regla de dependencia apunta siempre hacia adentro — `domain` no importa nada externo:\n\n```\nsrc/medicore/\n├── domain/           # Núcleo de negocio — sin dependencias de framework\n│   ├── enums.py\n│   ├── shared/         # Errores de dominio + identificadores UUID tipados\n│   ├── value_objects/  # 10 VOs: TimeRange, DateRange, Vitals, IcdCode, Slug,\n│   │                   # SoapNote, ContactInfo, BloodType, Money, UserPreferences\n│   ├── entities/       # 16 agregados: Appointment (FSM), Consultation, MedicalRecord,\n│   │                   # Prescription, Patient, User, Tenant, Insurer, MedicalDocument,\n│   │                   # Availability, Notification, AuditLog, CatalogDiagnosis,\n│   │                   # RolePermissionOverride, PlatformAdmin, PlatformAuditLog\n│   ├── services/       # slot_resolver — lógica de disponibilidad pura\n│   └── repositories/   # Interfaces (puertos): 16 Protocols de repositorio\n│\n├── application/      # Casos de uso (interactors) + puertos de infra\n│   ├── ports/          # 5 puertos: Clock, CodeGenerator, PasswordHasher,\n│   │                   # TokenIssuer, UnitOfWork\n│   ├── common/         # ActorContext, errores de app, catálogo de permisos\n│   │                   # (permissions.py), timezone, audit_entry\n│   └── use_cases/      # 13 grupos: auth, patients, appointments, consultations,\n│                       # records, availability, users, organization, insurers,\n│                       # diagnoses, audit, role_permissions, platform\n│\n├── infrastructure/   # Implementaciones concretas\n│   ├── config.py       # Settings desde .env (pydantic-settings) — dev-safe por defecto\n│   ├── database/       # Engine SQLAlchemy + session factory\n│   ├── persistence/\n│   │   ├── models/     # ORM models (20 tablas; VOs complejos como JSONB)\n│   │   ├── mappers/    # ORM ↔ domain entity (sin contaminar el dominio)\n│   │   └── repositories/  # Implementaciones SQLAlchemy de los puertos\n│   └── auth/           # JwtTokenIssuer, BcryptPasswordHasher, DbSequentialCodeGenerator\n│\n└── presentation/\n    ├── app.py           # Factory: crea FastAPI + CORS + error handlers + routers\n    ├── main.py          # Entry point (uvicorn medicore.presentation.main:app)\n    ├── dependencies.py  # DI: get_actor / get_platform_actor (JWT→Context),\n    │                    # get_uow (yield, cierra sesión), get_codes, get_clock\n    ├── serializers.py   # domain entity → dict (sin tocar el dominio)\n    ├── error_handlers.py# DomainError/AppError → HTTP 4xx/5xx\n    ├── schemas/         # Pydantic request + response por recurso\n    └── routers/         # 13 routers, 80 endpoints bajo /api/v1/\n```\n\n### Multi-tenant\nToda entidad de negocio lleva `tenant_id`. El filtro se aplica **en cada repositorio**, no en el caller — el aislamiento se cumple estructuralmente. El `UnitOfWork` está scoped por tenant: `factory.for_tenant(tenant_id)`. Las operaciones globales (resolución de slug, superadmin) usan un `UnitOfWork` no scopeado o helpers de solo lectura.\n\n### Plataforma (superadmin)\nSesión separada (token de scope `platform`) que opera por encima del límite multi-tenant: alta/edición de clínicas, cambio de estado (activa / suspendida / archivada), estadísticas y auditoría globales, soporte de cuentas (reseteo de contraseña, desbloqueo, suspensión de usuarios) e **impersonación**. La impersonación emite un token de tenant marcado con `impersonated_by` y con TTL más corta, de modo que toda escritura durante el soporte queda auditada.\n\n### Reglas de negocio críticas\n- Citas solo dentro de la disponibilidad del doctor, sin solapes, respetando las reglas de reserva.\n- `MedicalRecord` firmado es **inmutable**; las correcciones son **enmiendas versionadas** (`status=amended` que referencia al original).\n- `SignConsultation` es **atómico**: record + recetas + cita `completed` en una sola transacción.\n- Diagnósticos en consulta limitados al catálogo de la **versión CIE configurada por cada clínica** (CIE-10 / CIE-11), con autocompletado.\n- **Permisos granulares** validados en la capa de aplicación (no solo en UI) — ver sección siguiente.\n- Contraseña temporal con cambio forzado en el primer ingreso.\n- Auditoría de accesos y cambios sensibles (HIPAA/GDPR), por tenant y global.\n\n---\n\n## Permisos\n\nEl control de acceso es **granular y personalizable por clínica** (`application/common/permissions.py`):\n\n- **Catálogo de 23 permisos** (`Permission`, `StrEnum`) agrupados por recurso: `patients.*`, `appointments.*`, `availability.manage`, `consultations.*`, `records.*` (incluye `sign` y `amend`), `prescriptions.manage`, `diagnoses.view`, `insurers.*`, `users.*`, `organization.*`, `audit.view`, `permissions.manage`.\n- **4 roles** — `admin`, `doctor`, `nurse`, `receptionist` — con un conjunto de permisos por defecto en `ROLE_PERMISSIONS`.\n- **Overrides por tenant**: cada clínica puede personalizar el set de un rol mediante una fila `RolePermissionOverride`. `effective_permissions(role, stored)` resuelve defaults vs. override (e intersecta con el catálogo vigente para tolerar permisos retirados).\n- En cada request, la capa de presentación coloca los permisos efectivos en `ActorContext.permissions`, de modo que toda comprobación `ensure_permission(...)` honra la personalización de la clínica.\n- El frontend consume el mismo modelo: la UI se condiciona por los permisos efectivos del actor y la pantalla **Roles \u0026 Permisos** edita los overrides (router `role_permissions`: `GET`/`PUT`/`DELETE`).\n\n---\n\n## Estructura del monorepo\n\n```\nmedicore/\n├── README.md                 # Este archivo\n├── LICENSE\n├── backend/\n│   ├── .env.example          # Plantilla de variables de entorno (sin secretos)\n│   ├── .env                  # Variables reales — gitignoreado, nunca commitear\n│   ├── Dockerfile            # Imagen de producción (usuario no-root, uvicorn :8000)\n│   ├── alembic.ini           # Config de Alembic (URL inyectada desde .env)\n│   ├── migrations/           # 10 migraciones Alembic (autogenerate)\n│   ├── scripts/\n│   │   ├── seed_demo.py            # Datos demo: clínicas, staff por rol, pacientes\n│   │   ├── import_icd.py           # Carga del catálogo CIE-10/CIE-11\n│   │   └── create_platform_admin.py # Alta de superadmin de plataforma\n│   ├── pyproject.toml        # Metadata + deps + config pytest/ruff\n│   ├── src/medicore/         # Código fuente (ver Arquitectura arriba)\n│   └── tests/\n│       ├── domain/           # Tests de dominio (VOs, FSM, slot_resolver…)\n│       ├── application/      # Tests de casos de uso con repos en memoria\n│       ├── presentation/     # Tests de endpoints HTTP (TestClient)\n│       ├── infrastructure/   # Tests del repositorio SQLAlchemy real\n│       └── support/          # InMemoryStore, repos, UoW, fakes, builders\n└── frontend/                 # SPA React + TS + Vite\n    └── src/\n        ├── api/              # Cliente Axios + 14 módulos por recurso\n        ├── stores/           # Zustand: auth + platformAuth + ui (tema/idioma/sidebar)\n        ├── lib/              # i18n ES/EN, formato, QueryClient, cn(), timezones\n        ├── types/            # Tipos TS espejo del dominio backend\n        ├── components/       # ui/ + shell/ + appointments/ + audit/ + patients/\n        │                     # + permissions/ + records/ + guards (RequireAuth…)\n        └── pages/            # 16 pantallas de clínica (Login, Dashboard, Patients,\n                              # PatientDetail, Appointments, Schedule, Consultation,\n                              # Records, Availability, Insurers, Users, Settings,\n                              # Audit, Permissions, ChangePassword, ComingSoon)\n                              # + platform/ (5 pantallas de superadmin)\n```\n\n---\n\n## Estado del proyecto\n\n| Fase | Contenido | Estado |\n|---|---|---|\n| **1** | Dominio completo | ✅ Completada |\n| **2** | Casos de uso (interactors) + repos en memoria | ✅ Completada |\n| **3** | SQLAlchemy, Alembic, JWT, bcrypt, multi-tenant | ✅ Completada |\n| **4** | API FastAPI bajo `/api/v1/` | ✅ Completada |\n| **5** | Frontend React — todas las pantallas de clínica | ✅ Completada |\n| **6** | Plataforma superadmin, catálogo CIE y aseguradoras | ✅ Completada |\n| **7** | Permisos granulares con overrides por tenant + UI por permisos | ✅ Completada |\n| **8** | Hardening de producción (dev-safe defaults) + Dockerfile | ✅ Completada |\n\n---\n\n## Configuración y desarrollo\n\n### Requisitos\n- Python 3.12+\n- PostgreSQL (base de datos `medicore` creada)\n- Node.js (para el frontend)\n\n### Backend\n\n```bash\ncd backend\n\n# 1. Entorno e instalación (incluye dependencias de desarrollo)\npython -m venv .venv\n.venv/bin/pip install -e \".[dev]\"\n\n# 2. Configurar variables de entorno\ncp .env.example .env\n#   DATABASE_URL=postgresql+psycopg://user:password@localhost:5432/medicore\n#   JWT_SECRET=$(openssl rand -hex 32)\n\n# 3. Crear tablas\n.venv/bin/alembic upgrade head\n\n# 4. (Opcional) Catálogo CIE-10/CIE-11 y datos demo\n.venv/bin/python scripts/import_icd.py\n.venv/bin/python scripts/seed_demo.py        # idempotente (--reset recrea)\n\n# 5. Levantar la API\n.venv/bin/uvicorn medicore.presentation.main:app --reload\n# API en http://localhost:8000/api/v1/ · docs en /api/v1/docs · health en /health\n```\n\nTras el seed, todos los usuarios demo usan la contraseña **`demo1234`**. Ver detalles en\n[`backend/README.md`](backend/README.md).\n\n### Frontend\n\n```bash\ncd frontend\nnpm install            # primera vez\nnpm run dev            # http://localhost:3000 (proxy /api → :8000)\nnpm run build          # type-check (tsc -b) + build de producción → dist/\n```\n\nDetalles de variables `VITE_*` y despliegue en [`frontend/README.md`](frontend/README.md).\n\n### Comandos habituales\n\n```bash\n# Backend\n.venv/bin/pytest                          # suite completa (271 tests)\n.venv/bin/pytest tests/domain/            # solo tests de dominio\n.venv/bin/ruff check src tests            # lint (--fix para corregir)\n\n# Migraciones\n.venv/bin/alembic upgrade head            # aplicar migraciones pendientes\n.venv/bin/alembic revision --autogenerate -m \"descripción\"  # nueva migración\n```\n\n---\n\n## Base de datos\n\n**20 tablas**, creadas por la migración inicial (`a622afdfaa21`) y ampliadas por las migraciones posteriores (aseguradoras, estado de clínica + versión CIE, overrides de permisos por rol, superadmin de plataforma, catálogo de diagnósticos):\n\n| Tabla | Descripción |\n|---|---|\n| `tenants` + `locations` | Organización / clínica y sus sedes |\n| `users` + `doctor_profiles` | Cuentas de acceso y perfil clínico |\n| `patients` | Pacientes (datos demográficos, etiquetas, alergias) |\n| `appointments` | Citas con FSM de estado |\n| `consultations` | Consulta en curso (borrador mutable) |\n| `medical_records` | Historiales firmados e inmutables |\n| `prescriptions` | Recetas emitidas |\n| `medical_documents` | Archivos médicos |\n| `insurers` | Aseguradoras de la clínica |\n| `doctor_availability` + `availability_exceptions` | Horario + excepciones |\n| `notifications` | Notificaciones in-app |\n| `audit_logs` | Trazabilidad HIPAA/GDPR por tenant |\n| `tenant_counters` | Contadores para códigos legibles (`P-00142`, `A-2401`…) |\n| `role_permission_overrides` | Personalización de permisos por rol y tenant |\n| `diagnosis_codes` | Catálogo CIE-10/CIE-11 (global, para autocompletado) |\n| `platform_admins` | Superadmins de la plataforma |\n| `platform_audit_logs` | Auditoría global de operaciones de plataforma |\n\n---\n\n## Tests\n\nCasi toda la suite es **independiente de la base de datos** — usa adaptadores en memoria que ejercen el mismo contrato multi-tenant que los repos SQLAlchemy; un grupo pequeño de tests de infraestructura ejerce además el repositorio SQLAlchemy real.\n\n```\n271 tests en verde\n    78  — dominio (VOs, FSM, slot_resolver, Consultation.sign, MedicalRecord.amend)\n   119  — aplicación (auth, citas, consultas, records, diagnósticos, plataforma,\n            permisos granulares, aislamiento multi-tenant)\n    71  — presentación (endpoints HTTP: auth guards, booking, lifecycle, 403 por permiso, plataforma)\n     3  — infraestructura (repositorio SQLAlchemy real)\n```\n\nLos tests verifican, entre otras cosas:\n- Atomicidad de `SignConsultation`: un fallo revierte record, recetas y estado de cita.\n- Aislamiento multi-tenant: los repos de un tenant no filtran datos de otro.\n- Permisos efectivos por rol y override por tenant, y de scope de plataforma.\n- Login de todos los roles y cierre correcto de las sesiones de BD (sin fuga de conexiones).\n\n---\n\n## Variables de entorno (backend)\n\n| Variable | Descripción | Default |\n|---|---|---|\n| `ENVIRONMENT` | `development` o `production`. En producción se activan validaciones estrictas. | `development` |\n| `DATABASE_URL` | URL de conexión PostgreSQL (psycopg3) | `postgresql+psycopg://localhost/medicore` |\n| `JWT_SECRET` | Clave HMAC para tokens. **Obligatoria y fuerte en producción** (`openssl rand -hex 32`) | `change-me` (solo dev) |\n| `JWT_ALGORITHM` | Algoritmo JWT | `HS256` |\n| `JWT_EXPIRE_MINUTES` | Expiración del token en minutos | `1440` (24 h) |\n| `JWT_SUPPORT_EXPIRE_MINUTES` | Expiración (min) de sesiones de impersonación/soporte — más corta por seguridad | `60` (1 h) |\n| `CORS_ORIGINS` | Orígenes permitidos (coma-separados) o `*` para cualquiera | `*` |\n| `ENABLE_DOCS` | Exponer Swagger/OpenAPI en `/api/v1/docs` | `true` |\n\n\u003e En **producción** (`ENVIRONMENT=production`) la app **falla al arrancar** si `JWT_SECRET` sigue siendo el default inseguro o es demasiado corto. En desarrollo no se valida y los defaults funcionan tal cual.\n\n---\n\n## Despliegue\n\nEl backend incluye un `Dockerfile` listo para producción (usuario no-root, uvicorn en el puerto 8000). El frontend se sirve como estático (`dist/`) en el mismo origen tras un reverse-proxy, o en un host separado con `VITE_API_BASE_URL` definido al hacer el build. Pasos detallados en [`backend/README.md`](backend/README.md) y [`frontend/README.md`](frontend/README.md).\n\n---\n\n## Backlog\n\nEl trabajo diferido (funcionalidad a medio implementar y hallazgos de seguridad pendientes) está inventariado en [`docs/BACKLOG.md`](docs/BACKLOG.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foscar503sv%2Fmedicore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foscar503sv%2Fmedicore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foscar503sv%2Fmedicore/lists"}