Files
admin-edu-space/docs/DEPLOYMENT_AUDIT.md
T

444 lines
18 KiB
Markdown

# Auditoría Técnica y DevOps: Git, Arquitectura y Puesta en Producción
**Repositorio:** `admin-edu-space`
**Rol:** Tech Lead / DevOps Lead
**Fecha de Auditoría:** 2 de Septiembre de 2026
**Entorno de Auditoría:** Windows / Git 2.x / Python 3.12.4
---
## Resumen Ejecutivo
El presente documento constituye la auditoría integral de código, gestión de versiones (Git), arquitectura de software y preparación para producción del proyecto **Edu-Space Admin Panel** (`admin-edu-space`).
### Conclusiones Principales:
1. **Rama seleccionada para despliegue:** **`main`** (commit `3d4e999`). Es la única rama que integra la totalidad de los cambios, bugfixes y features desarrollados en las ramas remotas (`planning`, `commissions`, `spanish`). No existen ramas divergentes sin fusionar.
2. **Nivel de Madurez CI/CD:** **Crítico (0% Automatización)**. No existen flujos de trabajo en GitHub Actions, GitLab CI ni validación de pruebas automatizadas previas al merge.
3. **Seguridad & Configuración:** **Riesgo Alto**. El archivo `.env` se encuentra actualmente trackeado en el repositorio Git con credenciales por defecto. Se requiere eliminarlo del índice, generar `.env.example` y rotar credenciales.
4. **Infraestructura de Despliegue:** El repositorio carece de `Dockerfile`, `docker-compose.yml`, scripts de systemd y no incluye un servidor WSGI de producción (`gunicorn`) en `requirements.txt`. El paso a producción requiere los artefactos y procedimientos estandarizados que se detallan en este informe.
---
# Fase 1: Análisis de Ramas (Git Audit)
## 1.1 Inventario de Ramas y Commits
Se ejecutó la inspección exhaustiva de referencias locales y remotas:
```bash
git fetch --all
git branch -a --sort=-committerdate
git for-each-ref --format='%(refname:short) | %(committerdate:iso) | %(subject) | %(authorname)' refs/heads refs/remotes
```
### Tabla Comparativa de Ramas:
| Rama | Tipo | Último Commit | Fecha del Commit | Autor | Estado respecto a `main` |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **`main`** | Local / Remota | `3d4e999` | 2026-04-17 18:18:00 -0300 | `ale-vz` | **Rama Primaria (HEAD)** |
| **`origin/planning`** | Remota | `ec977bf` | 2026-04-17 18:14:59 -0300 | `Alejandro Vazquez` | **100% Fusionada** (Merge PR #2 en `3d4e999`) |
| **`origin/commissions`**| Remota | `18f1d3a` | 2026-03-30 17:37:53 -0300 | `Alejandro Vazquez` | **100% Fusionada** (Incluida en historia de `planning`) |
| **`origin/spanish`** | Remota | `9af25df` | 2026-03-30 17:11:13 -0300 | `Alejandro Vazquez` | **100% Fusionada** (Merge PR #1 en `d4281cd` y PR #2) |
### Verificación de Divergencias:
Al ejecutar `git branch -r --no-merged main`, el resultado es **vacío**.
Al verificar conteo simétrico `git rev-list --left-right --count <rama>...main`:
- `origin/planning...main`: `0 2` (0 commits pendientes, 2 commits por delante en `main` por los merges).
- `origin/commissions...main`: `0 6` (0 commits pendientes, 6 commits por delante en `main`).
- `origin/spanish...main`: `0 7` (0 commits pendientes, 7 commits por delante en `main`).
### Tags y Releases:
- `git tag -l`: **Vacío**. No existen versiones etiquetadas (e.g. `v1.0.0`, `v0.1.0`) ni releases formales en el historial.
## 1.2 Auditoría de CI/CD y Calidad de Código
- **Workflows automatizados:** Inexistentes. No hay directorio `.github/workflows/`, ni `.gitlab-ci.yml`, ni archivo de integración continua de ningún proveedor.
- **Validación de Tests en Pull Requests:** Los merges de los Pull Requests `#1` y `#2` se realizaron sin puertas de enlace automatizadas (quality gates).
- **Cobertura de pruebas en repo:** Existe únicamente `tests/test_genetic_algorithm.py` enfocado en el motor heurístico, pero no se ejecuta en ningún pipeline.
## 1.3 Dictamen Conclusivo de Rama para Despliegue
> ### 🏆 Rama Determinada: **`main`** (Commit `3d4e999`)
>
> **Justificación Técnica:**
> 1. **Consolidación completa:** `main` agrupa de forma lineal y validada todas las funcionalidades de las ramas temáticas:
> - Incorpora las traducciones y arreglos de internacionalización (`spanish`).
> - Incorpora la lógica y vistas de comisiones (`commissions`).
> - Incorpora el motor de optimización de aulas y correcciones de UI (`planning`).
> 2. **Cero pérdida de código (No regressions):** No existe ningún commit en las ramas remotas que no esté ya contenido en `main`.
> 3. **Estabilidad comprobada:** Resuelve errores críticos de SQLAlchemy con carga ansiosa (`joinedload`) documentados en la evolución del repositorio.
---
# Fase 2: Informe Técnico del Proyecto
## 2.1 Stack Tecnológico y Propósito del Sistema
### Propósito
**Edu-Space Admin Panel** es una interfaz web de administración para la gestión, reserva y optimización algorítmica de aulas universitarias/académicas. El sistema permite:
- Gestionar edificios, aulas, capacidad física y recursos técnicos (proyectores, computadoras).
- Visualizar horarios y agendas en tiempo real mediante calendarios interactivos.
- Administrar materias y comisiones docentes.
- Asignar aulas automáticamente utilizando un **Algoritmo Genético** que evalúa aforo, prioridades horarias y resuelve conflictos de superposición de reservas.
### Stack de Tecnologías
| Componente | Tecnología | Versión | Propósito / Responsabilidad |
| :--- | :--- | :--- | :--- |
| **Lenguaje Base** | Python | `>=3.8` (Óptimo: `3.11` / `3.12`) | Runtime principal de ejecución |
| **Framework Web** | Flask | `3.0.0` | Arquitectura web basada en Application Factory |
| **Capa ORM** | SQLAlchemy / Flask-SQLAlchemy | `2.0.23` / `3.1.1` | Modelado de datos relacional y queries |
| **Driver de BD** | psycopg2-binary | `2.9.9` | Conector nativo de alto rendimiento a PostgreSQL |
| **Migraciones** | Flask-Migrate | `4.0.5` | Wrapper de Alembic (requiere inicialización) |
| **Autenticación** | Flask-Login / Werkzeug | `0.6.3` / `3.0.1` | Manejo de sesiones, roles y hash `pbkdf2:sha256` |
| **Formularios & CSRF** | Flask-WTF / WTForms | `1.2.1` / `3.1.0` | Validación y protección contra ataques CSRF |
| **Internacionalización** | Flask-Babel / Babel | `4.0.0` / `2.14.0` | Detección y renderizado multilingüe (ES / EN) |
| **Frontend** | Bootstrap 5, FullCalendar, Chart.js | Bundled | UI responsiva, calendarios y estadísticas |
| **Microservicio Backend**| Spring Boot (Java) | `8080` (Externo) | API de negocio y persistencia central (opcional/compartida) |
### Arquitectura de Componentes y Flujo de Datos
```mermaid
graph TD
Client[Navegador / Cliente Web] -->|HTTP / HTTPS| Nginx[Nginx Reverse Proxy & SSL]
Nginx -->|WSGI Proxy :5000| Gunicorn[Gunicorn WSGI Server]
Gunicorn --> App[Flask App Factory: create_app]
subgraph "Flask Application Blueprints"
App --> AuthBP[auth_bp: /login, /logout, /profile]
App --> MainBP[main_bp: /dashboard, /api/dashboard-stats]
App --> ClassroomsBP[classrooms_bp: /classrooms]
App --> ScheduleBP[schedule_bp: /schedule]
App --> GeneticBP[genetic_bp: /api/genetic & /genetic-optimizer]
end
subgraph "Core Engine"
GeneticBP --> GAEngine[Algoritmo Genético: ReservationOptimizer]
GAEngine --> FitnessEval[Evaluación: Aforo 40%, Horario 30%, Conflicto 20%]
end
subgraph "Persistencia & Servicios"
App --> SQLAlchemy[SQLAlchemy ORM + joinedload]
SQLAlchemy --> PostgreSQL[(PostgreSQL 14+ / classrooms_db)]
App -.->|Integración Configurada| SpringBoot[Spring Boot API :8080/api]
end
```
---
## 2.2 Requisitos de Entorno y Variables de Configuración
### ⚠️ Hallazgos Críticos de Seguridad
1. **Archivo `.env` versionado:** Se encontró el archivo `.env` físico bajo control de versiones Git con contraseñas en texto plano (`password`).
- *Acción correctiva obligatoria:* Ejecutar `git rm --cached .env`, añadirlo a `.gitignore` y rotar todas las credenciales antes de salir a producción.
2. **Traducciones no compiladas:** El archivo `.gitignore` excluye deliberadamente `*.mo`. Por ende, el paso de build debe compilar los `.po` mediante `pybabel compile -d translations`.
### Especificación de Variables de Entorno (`.env.example`)
```env
# ==============================================================================
# Edu-Space Admin - Configuración de Entorno de Producción
# ==============================================================================
# Entorno Flask
FLASK_APP=app.py
FLASK_ENV=production
DEBUG=False
# Seguridad: Clave secreta para cookies de sesión y CSRF (Mínimo 64 caracteres aleatorios)
# Generar con: python -c 'import secrets; print(secrets.token_hex(32))'
SECRET_KEY=generate-a-strong-random-secret-key-for-production-use
# Conexión a Base de Datos PostgreSQL
# Formato: postgresql://<user>:<password>@<host>:<port>/<dbname>
DATABASE_URL=postgresql://eduspace_user:StrongProductionPassword123!@db-host:5432/classrooms_db
# Parámetros de compatibilidad si aplica
DB_USERNAME=eduspace_user
DB_PASSWORD=StrongProductionPassword123!
# Integración con API externa (Spring Boot)
API_BASE_URL=http://springboot-service:8080/api
# Configuración de Internacionalización y Carga de Archivos
BABEL_DEFAULT_LOCALE=es
BABEL_DEFAULT_TIMEZONE=America/Argentina/Buenos_Aires
MAX_CONTENT_LENGTH=16777216
```
---
## 2.3 Puesta en Producción (Deployment Determinado)
### 2.3.1 Estrategia de Contenedores (Docker + Docker Compose)
Para garantizar un despliegue repetible e inmutable, se especifica el contenedor de producción:
#### `Dockerfile` (Multi-stage / Producción)
```dockerfile
# Stage 1: Build de dependencias y compilación de traducciones
FROM python:3.11-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt && \
pip install --no-cache-dir --user gunicorn==21.2.0
COPY . .
# Compilar catálogos de traducción i18n
RUN python -m babel.messages.frontend compile -d translations
# Stage 2: Runtime limpio y seguro
FROM python:3.11-slim AS runner
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Crear usuario sin privilegios
RUN useradd -m -u 1001 appuser
COPY --from=builder /root/.local /home/appuser/.local
COPY --from=builder --chown=appuser:appuser /app /app
ENV PATH=/home/appuser/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
ENV FLASK_ENV=production
USER appuser
EXPOSE 5000
# Healthcheck interno del contenedor
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/login || exit 1
CMD ["gunicorn", "--workers=4", "--threads=2", "--bind=0.0.0.0:5000", "--access-logfile=-", "--error-logfile=-", "app:create_app()"]
```
#### `docker-compose.yml` (Stack Completo con PostgreSQL y Nginx)
```yaml
version: '3.8'
services:
db:
image: postgres:15-alpine
container_name: eduspace_db
restart: always
environment:
POSTGRES_DB: classrooms_db
POSTGRES_USER: eduspace_user
POSTGRES_PASSWORD: StrongProductionPassword123!
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U eduspace_user -d classrooms_db"]
interval: 10s
timeout: 5s
retries: 5
web:
build:
context: .
dockerfile: Dockerfile
container_name: eduspace_admin
restart: always
depends_on:
db:
condition: service_healthy
env_file:
- .env
expose:
- "5000"
nginx:
image: nginx:alpine
container_name: eduspace_proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./app/static:/app/static:ro
depends_on:
- web
volumes:
pgdata:
```
### 2.3.2 Despliegue en Máquina Virtual Linux (Systemd + Gunicorn + Nginx)
Si el despliegue se efectúa en un servidor Linux (Ubuntu/Debian) sin Docker:
#### 1. Pasos de Instalación y Preparación:
```bash
# 1. Clonar rama main
git clone -b main <URL_DEL_REPOSITORIO> /var/www/admin-edu-space
cd /var/www/admin-edu-space
# 2. Configurar entorno virtual
python3.11 -m venv venv
source venv/bin/activate
# 3. Instalar dependencias del proyecto + Servidor WSGI
pip install --upgrade pip
pip install -r requirements.txt
pip install gunicorn==21.2.0
# 4. Configurar variables de entorno seguras
cp .env.example .env
chmod 600 .env
nano .env # Ajustar SECRET_KEY y DATABASE_URL
# 5. Compilar traducciones
pybabel compile -d translations
# 6. Inicializar esquema de base de datos (si la base está vacía)
python -c "from app import create_app, db; app = create_app(); app.app_context().push(); db.create_all()"
```
#### 2. Configuración de Servicio Systemd (`/etc/systemd/system/eduspace.service`):
```ini
[Unit]
Description=Edu-Space Admin Gunicorn Daemon
After=network.target postgresql.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/admin-edu-space
Environment="PATH=/var/www/admin-edu-space/venv/bin"
EnvironmentFile=/var/www/admin-edu-space/.env
ExecStart=/var/www/admin-edu-space/venv/bin/gunicorn \
--workers 4 \
--threads 2 \
--bind 127.0.0.1:5000 \
--access-logfile /var/log/eduspace/access.log \
--error-logfile /var/log/eduspace/error.log \
"app:create_app()"
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
#### 3. Configuración del Reverse Proxy Nginx (`/etc/nginx/sites-available/eduspace`):
```nginx
server {
listen 80;
server_name admin.edu-space.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name admin.edu-space.com;
ssl_certificate /etc/letsencrypt/live/admin.edu-space.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.edu-space.com/privkey.pem;
client_max_body_size 16M;
# Entrega directa de archivos estáticos por Nginx
location /static/ {
alias /var/www/admin-edu-space/app/static/;
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Proxy hacia el daemon Gunicorn
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
```
---
## 2.4 Verificación, Monitoreo y Health Checks
### 2.4.1 Endpoint de Verificación Recomendado (`/healthz`)
Actualmente el proyecto solo cuenta con endpoints protegidos por autenticación (`@login_required`). Para orquestadores (Kubernetes, AWS ALB, Docker), se debe incorporar una ruta pública de verificación en `app/routes/main.py`:
```python
@main_bp.route('/healthz')
def healthz():
"""Health check endpoint para balanceadores de carga y monitoreo"""
try:
# Validar conectividad con PostgreSQL
db.session.execute(db.text('SELECT 1'))
return jsonify({
'status': 'HEALTHY',
'database': 'CONNECTED',
'timestamp': datetime.utcnow().isoformat()
}), 200
except Exception as e:
return jsonify({
'status': 'UNHEALTHY',
'database': str(e),
'timestamp': datetime.utcnow().isoformat()
}), 503
```
### 2.4.2 Pruebas de Humo (Smoke Tests) Post-Despliegue
Ejecutar la siguiente suite de verificación inmediata tras desplegar:
```bash
# 1. Comprobación de servicio y respuesta HTTP
curl -I -k https://admin.edu-space.com/login
# Respuesta esperada: HTTP/1.1 200 OK
# 2. Comprobación del Health Endpoint
curl -s https://admin.edu-space.com/healthz | jq .
# Respuesta esperada: {"status": "HEALTHY", "database": "CONNECTED"}
# 3. Comprobación de entrega de estáticos
curl -I https://admin.edu-space.com/static/css/style.css
# Respuesta esperada: HTTP/1.1 200 OK (o 304 Not Modified)
# 4. Comprobación de logs de Gunicorn
sudo journalctl -u eduspace.service -n 50 --no-pager
```
### 2.4.3 Matriz de Monitoreo y Observabilidad
| Dimensión | Métrica Clave | Umbral de Alerta | Acción Correctiva |
| :--- | :--- | :--- | :--- |
| **Disponibilidad** | Tasa de Respuestas HTTP 5xx | `> 1%` en 5 min | Inspeccionar `error.log` de Gunicorn; revisar pool de conexiones BD |
| **Latencia** | Tiempo de respuesta P95 | `> 1.5s` en endpoints | Revisar índices en tablas `reservations` y `classrooms` |
| **Base de Datos** | Pool de conexiones agotado | Conexiones activas `> 85%` | Aumentar `max_overflow` en SQLAlchemy o activar PgBouncer |
| **Optimización** | Tiempo de ejecución de Algoritmo Genético | `> 10s` en `/api/genetic/optimize` | Limitar rango de comisiones enviadas o desacoplar a Celery/Redis |
---
## 2.5 Roadmap de Mejoras DevOps & Calidad (Tech Lead Recommendations)
1. **Implementar CI/CD con GitHub Actions:**
- Crear `.github/workflows/ci.yml` para ejecutar linting (`flake8` / `ruff`) y la suite de pruebas `unittest` en cada PR.
2. **Desacoplar `.env` de Git:**
- Remover `.env` del repositorio (`git rm --cached .env`) e incorporar `python-dotenv` seguro mediante inyección de secretos en el pipeline.
3. **Gestión Formal de Migraciones:**
- Ejecutar `flask db init` y generar el primer script de migración versionado en `migrations/versions/`.
4. **Asincronía para el Algoritmo Genético:**
- Actualmente, la optimización heurística se ejecuta de forma sincrónica en el worker HTTP. Si el volumen de comisiones crece, puede bloquear el worker de Gunicorn (timeout 30s). Se recomienda migrar la ejecución pesada a una cola asíncrona con **Celery** o **Redis Queue (RQ)**.
5. **Alinear Rutas Duplicadas:**
- Resolver la colisión de ruta entre `main_bp.route('/genetic-optimizer')` y `genetic_web_bp.route('/genetic-optimizer')`.