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:
- Rama seleccionada para despliegue:
main(commit3d4e999). 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. - 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.
- Seguridad & Configuración: Riesgo Alto. El archivo
.envse encuentra actualmente trackeado en el repositorio Git con credenciales por defecto. Se requiere eliminarlo del índice, generar.env.exampley rotar credenciales. - Infraestructura de Despliegue: El repositorio carece de
Dockerfile,docker-compose.yml, scripts de systemd y no incluye un servidor WSGI de producción (gunicorn) enrequirements.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 enmainpor los merges).origin/commissions...main:0 6(0 commits pendientes, 6 commits por delante enmain).origin/spanish...main:0 7(0 commits pendientes, 7 commits por delante enmain).
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
#1y#2se realizaron sin puertas de enlace automatizadas (quality gates). - Cobertura de pruebas en repo: Existe únicamente
tests/test_genetic_algorithm.pyenfocado en el motor heurístico, pero no se ejecuta en ningún pipeline.
1.3 Dictamen Conclusivo de Rama para Despliegue
🏆 Rama Determinada:
main(Commit3d4e999)Justificación Técnica:
- Consolidación completa:
mainagrupa 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).- Cero pérdida de código (No regressions): No existe ningún commit en las ramas remotas que no esté ya contenido en
main.- 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
- Archivo
.envversionado: Se encontró el archivo.envfí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.gitignorey rotar todas las credenciales antes de salir a producción.
- Acción correctiva obligatoria: Ejecutar
- Traducciones no compiladas: El archivo
.gitignoreexcluye deliberadamente*.mo. Por ende, el paso de build debe compilar los.pomediantepybabel 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)
- Implementar CI/CD con GitHub Actions:
- Crear
.github/workflows/ci.ymlpara ejecutar linting (flake8/ruff) y la suite de pruebasunittesten cada PR.
- Crear
- Desacoplar
.envde Git:- Remover
.envdel repositorio (git rm --cached .env) e incorporarpython-dotenvseguro mediante inyección de secretos en el pipeline.
- Remover
- Gestión Formal de Migraciones:
- Ejecutar
flask db inity generar el primer script de migración versionado enmigrations/versions/.
- Ejecutar
- 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).
- Alinear Rutas Duplicadas:
- Resolver la colisión de ruta entre
main_bp.route('/genetic-optimizer')ygenetic_web_bp.route('/genetic-optimizer').
- Resolver la colisión de ruta entre