Sistema monolítico moderno para la gestión inteligente de inventario y ventas farmacéuticas
Optimizando la trazabilidad de lotes, sincronización entre sucursales y control de stock en tiempo real.
- Descripción General
- Arquitectura del Sistema
- Concurrencia y Procesamiento
- Endpoints API Relevantes
- Gestión de Roles y Permisos
- Tecnologías Principales
- Instalación y Ejecución
Pyllren es una plataforma integral para la gestión farmacéutica, construida con una arquitectura monolítica moderna que unifica un backend ágil con FastAPI y un frontend interactivo con React + TypeScript.
- 📦 Gestión de inventario multi-sucursal con trazabilidad completa de lotes
- 🔐 Control de acceso basado en roles (RBAC) con 4 niveles de permisos
- 🏢 Bodegas inteligentes con validación de capacidad y sugerencias de distribución
- ⚡ Caché Redis para optimización de queries frecuentes
- 🔄 Procesamiento concurrente para tareas de background
- 📊 Dashboard en tiempo real con estadísticas por sucursal
- 🔍 Auditoría completa de operaciones críticas
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (React) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Dashboard │ │ Lotes │ │ Bodegas │ │
│ │ Productos │ │ Usuarios │ │ Reportes │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ HTTP/REST
┌─────────────────────────────────────────────────────────────┐
│ BACKEND (FastAPI) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ API Routes (REST) │ │
│ │ /users /lotes /bodegas /productos /stats │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Auth │ │ Cache │ │ Background │ │
│ │ JWT + RBAC │ │ Redis │ │ Threads │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ CAPA DE DATOS │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PostgreSQL │ │ Redis │ │ MongoDB │ │
│ │ (Principal) │ │ (Cache) │ │ (Logs) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
El sistema utiliza concurrencia basada en hilos y locks de PostgreSQL para garantizar la integridad de datos en operaciones críticas:
def calcular_ocupacion_bodega(session: Session, bodega_id: int) -> int:
"""
⚠️ CONCURRENCIA: SELECT FOR UPDATE
- Usa lock pesimista en PostgreSQL
- Bloquea fila de bodega para evitar race conditions
- Previene sobreventa/sobrecarga concurrente
"""
bodega_lock_stmt = select(Bodega).where(
Bodega.id_bodega == bodega_id
).with_for_update() # 🔒 LOCK de fila
session.exec(bodega_lock_stmt).one() # Bloquea hasta commit/rollback
# ... cálculo de ocupaciónEscenario protegido:
- ✅ Usuario A intenta recepcionar 500 productos
- ✅ Usuario B intenta recepcionar 600 productos simultáneamente
- 🔒 El lock garantiza que solo uno proceda a la vez
- ✅ El segundo usuario recibe error 409 con sugerencias
@router.post("/lotes/recepcion-distribuida")
def recepcion_lote_distribuida(payload: RecepcionDistribuidaPayload):
"""
🔄 CONCURRENCIA: Transacción atómica con locks múltiples
- Valida capacidad de N bodegas simultáneamente
- Crea sub-lotes en múltiples bodegas
- Rollback automático si falla cualquier operación
"""
# Para cada bodega en la distribución:
# 1. Lock de la bodega (SELECT FOR UPDATE)
# 2. Validación de capacidad
# 3. Creación de sub-lote
# 4. Commit o rollback globaldef invalidate_entity_cache(entity: str):
"""
⚡ CONCURRENCIA: Invalidación atómica en Redis
- Usa SCAN para patrones en lugar de KEYS (no bloqueante)
- Invalidación por entidad: users, lotes, bodegas, productos
- TTL automático para prevenir crecimiento infinito
"""
pattern = f"{entity}:*"
cursor = 0
while True:
cursor, keys = redis_client.scan(
cursor, match=pattern, count=100
)
# Invalidación en lotes de 100 keys# 🔄 TAREAS RECURRENTES PLANIFICADAS:
# Vigilancia de lotes próximos a vencer (cada 6 horas)
@background_task(interval=21600)
def verificar_lotes_proximos_vencer():
"""
🔍 RECURRENCIA: Escaneo programado
- Identifica lotes que vencen en <30 días
- Genera alertas automáticas
- Notifica a farmacéuticos por sucursal
"""
# Sincronización entre sucursales (cada 1 hora)
@background_task(interval=3600)
def sincronizar_inventario_sucursales():
"""
🔄 CONCURRENCIA: Sincronización paralela
- Usa ThreadPoolExecutor para procesar N sucursales
- Actualiza stock en paralelo
- Manejo de conflictos con last-write-wins
"""
# Backup automático de auditoría (cada 24 horas)
@background_task(interval=86400)
def backup_auditoria_mongodb():
"""
💾 PERSISTENCIA: Backup incremental
- Exporta logs de auditoría a MongoDB
- Limpia registros antiguos de PostgreSQL
- Compresión y archivado automático
"""
# Limpieza de caché obsoleto (cada 12 horas)
@background_task(interval=43200)
def limpiar_cache_obsoleto():
"""
🧹 MANTENIMIENTO: Limpieza automática
- Elimina keys expiradas de Redis
- Libera memoria de consultas antiguas
- Recalcula estadísticas populares
"""
# Precarga de productos populares (cada 4 horas)
@background_task(interval=14400)
def precargar_productos_populares():
"""
⚡ OPTIMIZACIÓN: Warm-up de caché
- Identifica productos más consultados
- Precarga en Redis antes de horas pico
- Reduce latencia en consultas frecuentes
"""| Técnica | Ubicación | Propósito |
|---|---|---|
| SELECT FOR UPDATE | lotes.py, bodegas.py |
Lock pesimista en operaciones críticas |
| Transacciones ACID | Todas las operaciones de escritura | Atomicidad y consistencia |
| Redis Cache TTL | cache.py |
Prevención de datos obsoletos |
| Query Debouncing | Frontend (TanStack Query) | Reducción de requests concurrentes |
| Invalidación por patrón | cache.py |
Sincronización cache-DB |
| Método | Endpoint | Descripción | Permisos |
|---|---|---|---|
POST |
/api/v1/login/access-token |
Login con JWT | Público |
POST |
/api/v1/login/test-token |
Verificar token | Autenticado |
POST |
/api/v1/password-recovery/{email} |
Recuperar contraseña | Público |
POST |
/api/v1/reset-password/ |
Resetear contraseña | Público |
GET |
/api/v1/users/me |
Usuario actual | Autenticado |
PATCH |
/api/v1/users/me |
Actualizar perfil | Autenticado |
GET |
/api/v1/users/ |
Listar usuarios | Admin |
POST |
/api/v1/users/ |
Crear usuario | Admin |
PATCH |
/api/v1/users/{id} |
Actualizar usuario | Admin |
DELETE |
/api/v1/users/{id} |
Eliminar usuario | Admin |
| Método | Endpoint | Descripción | Concurrencia |
|---|---|---|---|
GET |
/api/v1/lotes/ |
Listar lotes | Filtrado por sucursal |
GET |
/api/v1/lotes/stats |
Estadísticas de lotes | Cache Redis (TTL 60s) |
POST |
/api/v1/lotes/recepcion |
SELECT FOR UPDATE | |
POST |
/api/v1/lotes/recepcion-distribuida |
Locks múltiples | |
GET |
/api/v1/lotes/{id} |
Detalle de lote | - |
PUT |
/api/v1/lotes/{id} |
Actualizar lote | Validación de scope |
DELETE |
/api/v1/lotes/{id} |
Eliminar lote | Soft delete |
Algoritmos Especiales:
- Validación de capacidad: Lock de bodega + cálculo de ocupación
- Sugerencias automáticas: Algoritmo greedy para distribuir productos
- Manejo de conflictos: Error 409 con alternativas viables
| Método | Endpoint | Descripción | Filtrado |
|---|---|---|---|
GET |
/api/v1/bodegas/ |
Listar bodegas | Por sucursal (scope) |
GET |
/api/v1/bodegas/stats |
⚡ Estadísticas globales | Cache + filtro sucursal |
GET |
/api/v1/bodegas/{id} |
Detalle extendido | Incluye lotes y productos |
POST |
/api/v1/bodegas/ |
Crear bodega | Solo admin |
PUT |
/api/v1/bodegas/{id} |
Actualizar bodega | Scope validation |
DELETE |
/api/v1/bodegas/{id} |
Eliminar bodega | Soft delete |
Campos Clave:
capacidad: Límite máximo de productostipo: Principal, Secundaria, De tránsitotemperatura_min/max: Control ambientalestado: Operativa o inactiva
| Método | Endpoint | Descripción | Filtrado |
|---|---|---|---|
GET |
/api/v1/productos/ |
Listar productos | Por sucursal + búsqueda |
GET |
/api/v1/productos/stats |
Estadísticas de productos | Cache + sucursal |
GET |
/api/v1/productos/{id} |
Detalle de producto | Validación de scope |
PUT |
/api/v1/productos/{id} |
Actualizar stock | Auditoría automática |
| Método | Endpoint | Descripción | Permisos |
|---|---|---|---|
GET |
/api/v1/sucursales/ |
Listar sucursales | Autenticado |
POST |
/api/v1/sucursales/ |
Crear sucursal | Admin |
GET |
/api/v1/roles/ |
Listar roles | Autenticado |
GET |
/api/v1/auditorias/ |
Consultar auditoría | Admin/Auditor |
GET |
/api/v1/movimientos/ |
Historial movimientos | Filtrado por sucursal |
| Método | Endpoint | Descripción | Datos |
|---|---|---|---|
GET |
/api/v1/lotes/stats |
Stats de lotes | Activos, vencidos, próximos |
GET |
/api/v1/bodegas/stats |
Stats de bodegas | Total, operativas, capacidad |
GET |
/api/v1/productos/stats |
Stats de productos | Total, bajo stock, sin stock |
Optimizaciones:
- ⚡ Caché Redis con TTL de 60 segundos
- 🔍 Filtrado automático por sucursal (no-admin)
- 📊 Precarga de datos populares
| Rol | ID | Permisos | Alcance |
|---|---|---|---|
| ADMINISTRADOR | 1 | ✅ Acceso total | Todas las sucursales |
| FARMACÉUTICO | 2 | ✅ Gestión de inventario ✅ Recepciones ✅ Consultas |
Solo su sucursal |
| AUXILIAR | 3 | ✅ Consultas ✅ Movimientos básicos |
Solo su sucursal |
| AUDITOR | 4 | ✅ Solo lectura ✅ Auditorías |
Solo su sucursal |
# backend/app/api/deps.py
def get_user_scope(user: User) -> dict | None:
"""
🔒 FILTRADO AUTOMÁTICO POR SUCURSAL
- Admin: None (ve todo)
- Otros: {"id_sucursal": user.id_sucursal}
"""
if user.id_rol == 1: # ADMINISTRADOR
return None
return {"id_sucursal": user.id_sucursal}| Módulo | Admin | Farmacéutico | Auxiliar | Auditor |
|---|---|---|---|---|
| Dashboard | ✅ Todas las sucursales | ✅ Su sucursal | ✅ Su sucursal | ✅ Su sucursal |
| Usuarios | ✅ CRUD completo | ❌ | ❌ | ❌ Solo lectura |
| Sucursales | ✅ CRUD completo | ❌ Solo lectura | ❌ Solo lectura | ❌ Solo lectura |
| Bodegas | ✅ CRUD completo | ✅ Ver/Editar su sucursal | ✅ Ver su sucursal | ❌ Solo lectura |
| Lotes | ✅ CRUD completo | ✅ CRUD su sucursal | ✅ Ver su sucursal | ❌ Solo lectura |
| Productos | ✅ CRUD completo | ✅ CRUD su sucursal | ✅ Ver/Editar básico | ❌ Solo lectura |
| Recepciones | ✅ Todas | ✅ Su sucursal | ✅ Su sucursal | ❌ |
| Proveedores | ✅ CRUD completo | ✅ Ver todos | ✅ Ver todos | ❌ Solo lectura |
| Auditorías | ✅ Ver todas | ❌ | ❌ | ✅ Ver su sucursal |
- FastAPI 0.115+ - Framework web asíncrono
- SQLModel - ORM con validación Pydantic
- PostgreSQL 16+ - Base de datos principal
- Redis 7+ - Cache y sesiones
- Alembic - Migraciones de BD
- PyJWT - Autenticación JWT
- Pytest - Testing
- React 18+ - UI Library
- TypeScript 5+ - Type safety
- TanStack Router - Routing tipado
- TanStack Query - Data fetching y caché
- Chakra UI v3 - Component library
- Vite - Build tool
- Docker - Containerización
- Docker Compose - Orquestación local
- Traefik - Reverse proxy
- GitHub Actions - CI/CD
- Python 3.12+
- Node.js 20+
- PostgreSQL 16+
- Redis 7+
- Docker (opcional)
# Clonar repositorio
git clone https://github.com/Nick0oo/pyllren.git
cd pyllren/backend
# Crear entorno virtual
python -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
# Instalar dependencias
pip install -r requirements.txt
# Configurar variables de entorno
cp .env.example .env
# Editar .env con tus credenciales
# Ejecutar migraciones
alembic upgrade head
# Iniciar servidor
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000cd ../frontend
# Instalar dependencias
npm install
# Configurar variables de entorno
cp .env.example .env
# Editar .env con la URL del backend
# Iniciar servidor de desarrollo
npm run dev# Desde la raíz del proyecto
docker-compose up -d
# Acceder a:
# Frontend: http://localhost:5173
# Backend: http://localhost:8000
# Docs: http://localhost:8000/docs- Líneas de código: ~15,000+
- Endpoints API: 60+
- Componentes React: 50+
- Modelos de BD: 12
- Tests: 30+
- Cobertura: 75%+
Las contribuciones son bienvenidas. Por favor:
- Fork el proyecto
- Crea una rama feature (
git checkout -b feature/AmazingFeature) - Commit tus cambios (
git commit -m 'Add: AmazingFeature') - Push a la rama (
git push origin feature/AmazingFeature) - Abre un Pull Request
Este proyecto está bajo la Licencia MIT. Ver LICENSE para más detalles.
- Nick0oo - Desarrollo inicial - GitHub
- FastAPI por su excelente documentación
- Chakra UI v3 por los componentes modernos
- TanStack por las herramientas de React
- La comunidad open source
Hecho con ❤️ y ☕ por el equipo de Pyllren