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

18 KiB

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:

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

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)

# ==============================================================================
# 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)

# 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)

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:

# 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):

[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):

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:

@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:

# 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').