workspace/projects/gamilit/orchestration/directivas/DIRECTIVA-POLITICA-CARGA-LIMPIA.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00

22 KiB

DIRECTIVA: POLÍTICA DE CARGA LIMPIA (Clean Load Policy)

Proyecto: GAMILIT - Sistema de Gamificación Educativa Versión: 1.0.0 Fecha: 2025-11-23 Ámbito: Database-Agent y todos los desarrolladores Tipo: Directiva Obligatoria Stack: PostgreSQL 15+ con PostGIS


🎯 PROPÓSITO

Garantizar que la base de datos pueda recrearse completamente desde archivos DDL en cualquier momento, sin dependencia de scripts incrementales, migrations o fixes.

Esta política establece que:

  • Los archivos DDL son la fuente de verdad
  • La base de datos es el resultado de ejecutar esos archivos
  • Todo cambio se valida mediante recreación completa
  • No se usan migrations ni scripts incrementales

📜 REGLAS OBLIGATORIAS

1. DDL-First Approach

Principio: Los archivos DDL se crean/actualizan ANTES de modificar la base de datos.

FLUJO CORRECTO

1. Crear/actualizar archivo DDL
   └─> apps/database/ddl/schemas/{schema}/{tipo}/{archivo}.sql

2. Validar sintaxis del archivo DDL
   └─> Revisar manualmente o con linter SQL

3. Ejecutar recreación completa
   └─> ./apps/database/drop-and-recreate-database.sh

4. Si funciona → Commitear archivo DDL
   └─> git add apps/database/ddl/...
   └─> git commit -m "feat(db): descripción del cambio"

5. Si falla → Corregir DDL y volver a paso 3
   └─> NO ejecutar fixes manuales en BD
   └─> Corregir archivo DDL

FLUJO PROHIBIDO

❌ 1. Ejecutar ALTER TABLE directamente en psql
❌ 2. "Documentar" el cambio creando archivo DDL después
❌ 3. Esperar que funcione en producción
❌ 4. Crear migration incremental para aplicar cambio

Razón de prohibición:

  • La BD y el DDL quedan desincronizados
  • No hay garantía de que el cambio funcione en recreación limpia
  • Otros desarrolladores no pueden recrear la BD localmente
  • Riesgo alto en producción (cambio no validado)

2. Prohibición de Migrations

PROHIBIDO crear o usar:

❌ Carpeta migrations/:
  - apps/database/migrations/
  - Archivos estilo TypeORM/Prisma migrations
  - Archivos versionados tipo: 001-create-users.sql, 002-add-column.sql

❌ Scripts incrementales:
  - migration-*.sql
  - alter-*.sql
  - update-*.sql
  - change-*.sql

❌ Estrategia ALTER TABLE como cambio principal:
  - ALTER TABLE ... ADD COLUMN (sin actualizar DDL base)
  - ALTER TABLE ... DROP COLUMN (sin actualizar DDL base)
  - ALTER TABLE ... MODIFY COLUMN (sin actualizar DDL base)

¿Por qué NO migrations?

Problemas de migrations incrementales:

  1. Orden de ejecución: Difícil de mantener, errores si se ejecuta fuera de orden
  2. Estado de BD desconocido: No sabes si migration ya se aplicó o no
  3. Recreación imposible: No puedes crear BD limpia sin ejecutar todas las migrations en orden
  4. Dependencias complejas: Migrations dependen de migrations anteriores
  5. Debugging difícil: Difícil saber en qué migration está el problema
  6. Rollback riesgoso: Difícil revertir migrations sin perder datos

Ventajas de carga limpia:

  1. Fuente de verdad clara: DDL es el estado actual, siempre
  2. Recreación simple: ./drop-and-recreate-database.sh y listo
  3. Sin estado: No importa el estado anterior de la BD
  4. Debugging fácil: Error en recreación = DDL tiene problema
  5. Onboarding rápido: Nuevos devs crean BD en 1 comando
  6. Testing robusto: Tests siempre empiezan con BD limpia

3. Prohibición de Fixes y Patches

PROHIBIDO crear:

❌ Archivos de corrección única (one-time scripts):
  - fix-*.sql
  - patch-*.sql
  - hotfix-*.sql
  - repair-*.sql
  - cleanup-*.sql

❌ Scripts "temporales" que se vuelven permanentes:
  - temp-fix-users.sql
  - manual-update-points.sql
  - data-correction-2025-11-23.sql

¿Qué hacer en lugar de fix/patch?

Escenario 1: Error en DDL

❌ INCORRECTO:
1. Crear fix-missing-column.sql con ALTER TABLE
2. Ejecutar fix en BD
3. "Ya funciona, no tocar"

✅ CORRECTO:
1. Identificar archivo DDL original con error
   └─> apps/database/ddl/schemas/auth_management/tables/01-users.sql
2. Corregir DDL (agregar columna faltante en CREATE TABLE)
3. Validar con recreación completa: ./drop-and-recreate-database.sh
4. Commitear DDL corregido

Escenario 2: Datos incorrectos en seeds

❌ INCORRECTO:
1. Crear fix-seed-data.sql con UPDATE/INSERT
2. Ejecutar fix en BD
3. Dejar seed original con datos incorrectos

✅ CORRECTO:
1. Identificar archivo seed con error
   └─> apps/database/seeds/dev/auth_management/01-users.sql
2. Corregir seed (arreglar datos)
3. Validar con recreación completa: ./drop-and-recreate-database.sh
4. Commitear seed corregido

Escenario 3: Error en producción

❌ INCORRECTO:
1. Ejecutar fix directo en BD de producción
2. "Olvidar" actualizar DDL
3. Siguiente deploy rompe porque DDL está desactualizado

✅ CORRECTO:
1. Corregir DDL en desarrollo
2. Validar con recreación completa local
3. Crear ADR documentando el cambio
4. Aplicar cambio en producción usando DDL corregido
5. Commitear DDL + ADR

4. Cambios en Tablas Existentes

PROCESO CORRECTO para Cambios

Ejemplo: Agregar columna a tabla existente

# 1. Actualizar DDL base (NO crear migration)
vim apps/database/ddl/schemas/auth_management/tables/01-users.sql

# Cambiar de:
CREATE TABLE auth_management.users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) NOT NULL,
    email VARCHAR(255) NOT NULL
);

# A:
CREATE TABLE auth_management.users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) NOT NULL,
    email VARCHAR(255) NOT NULL,
    phone_number VARCHAR(20),  -- ← Nueva columna agregada
    phone_verified BOOLEAN DEFAULT false
);

# 2. Validar con recreación completa
cd apps/database
./drop-and-recreate-database.sh

# 3. Si funciona, commitear
git add apps/database/ddl/schemas/auth_management/tables/01-users.sql
git commit -m "feat(db): add phone_number and phone_verified to users table"

# 4. Documentar en TRAZA-TAREAS-DATABASE.md
echo "## [DB-042] Agregar verificación de teléfono
**Fecha:** 2025-11-23
**Estado:** ✅ Completado
**Cambios:**
- Agregadas columnas phone_number, phone_verified a auth_management.users
**Archivos modificados:**
- apps/database/ddl/schemas/auth_management/tables/01-users.sql
" >> orchestration/trazas/TRAZA-TAREAS-DATABASE.md

PROCESO PROHIBIDO

# ❌ NO hacer esto:
psql -d gamilit_db -c "ALTER TABLE auth_management.users ADD COLUMN phone_number VARCHAR(20);"

# ❌ NO crear migration:
echo "ALTER TABLE auth_management.users ADD COLUMN phone_number VARCHAR(20);" \
  > apps/database/migrations/002-add-phone-to-users.sql

# ❌ NO crear fix:
echo "ALTER TABLE auth_management.users ADD COLUMN phone_number VARCHAR(20);" \
  > apps/database/fix-add-phone.sql

5. Validación de Carga Limpia

Regla: TODO cambio en DDL DEBE validarse con recreación completa.

Comandos de Validación

# Validación básica (desarrollo)
cd apps/database
./drop-and-recreate-database.sh

# Validación completa (pre-commit)
cd apps/database
./drop-and-recreate-database.sh && \
  psql -d gamilit_platform -f scripts/validate-integrity.sql && \
  echo "✅ Carga limpia validada"

# Si falla la recreación
# → El DDL tiene un problema
# → NO ejecutar fixes manuales
# → Corregir el archivo DDL y volver a intentar

Frecuencia de Validación

Desarrollo local:
  - Después de cada cambio en DDL
  - Antes de cada commit que toca apps/database/

CI/CD:
  - En cada pull request
  - Antes de merge a main/master
  - En cada deploy a staging/producción

Mantenimiento:
  - Recreación semanal de BD de desarrollo
  - Validación mensual de que DDL + seeds = BD completa

Checklist de Validación

- [ ] Recreación completa ejecuta sin errores
- [ ] Todas las tablas se crean correctamente
- [ ] Todos los índices se crean
- [ ] Todas las funciones y triggers se crean
- [ ] Todas las RLS policies se aplican
- [ ] Seeds se cargan sin errores
- [ ] Integridad referencial validada (FKs)
- [ ] No hay warnings en el log de create-database.sh

6. Homologación BD ↔ Archivos DDL

Principio: Los archivos DDL son la fuente de verdad, la BD es derivada.

Fuente de verdad:
  ✅ apps/database/ddl/schemas/**/*.sql
  ✅ apps/database/seeds/**/*.sql

Derivado (resultado de ejecutar DDL):
  ✅ Base de datos PostgreSQL real

Flujo de sincronización:
  DDL actualizado → Recreación BD → BD actualizada

Flujo PROHIBIDO:
  ❌ BD actualizada manualmente → "Documentar" en DDL después

Validación de Homologación

# ¿Cómo saber si DDL y BD están sincronizados?
# → Recrear BD completa y comparar

# 1. Backup de BD actual (por seguridad)
pg_dump gamilit_platform > /tmp/backup-before-recreation.sql

# 2. Recrear desde DDL
./drop-and-recreate-database.sh

# 3. Si recreación falla:
# → DDL y BD NO están sincronizados
# → Actualizar DDL con cambios faltantes
# → Volver a intentar recreación

# 4. Si recreación funciona:
# → DDL y BD están sincronizados ✅

🔧 HERRAMIENTAS Y SCRIPTS

Scripts de Recreación

apps/database/
├── create-database.sh              # Crear BD completa desde DDL
├── drop-and-recreate-database.sh   # Drop + Create (carga limpia total)
├── reset-database.sh               # Reset a estado inicial
└── recreate-database.sh            # Alias de drop-and-recreate

Uso principal:

# Desarrollo diario
cd apps/database
./drop-and-recreate-database.sh

# Pre-commit
git add apps/database/ddl/...
./drop-and-recreate-database.sh && git commit -m "feat(db): ..."

# Validar que DDL está completo
./drop-and-recreate-database.sh && echo "✅ DDL completo"

Script de Validación (Opcional)

# apps/database/scripts/validate-clean-load-policy.sh
# Validar cumplimiento de Política de Carga Limpia

#!/bin/bash
echo "🔍 Validando Política de Carga Limpia..."

# 1. Verificar que no existe carpeta migrations/
if [ -d "apps/database/migrations" ]; then
  echo "❌ ERROR: Carpeta migrations/ detectada (PROHIBIDA)"
  exit 1
fi

# 2. Verificar que no hay archivos fix-*.sql o patch-*.sql
FIXES=$(find apps/database -name "fix-*.sql" -o -name "patch-*.sql" -o -name "hotfix-*.sql")
if [ -n "$FIXES" ]; then
  echo "❌ ERROR: Archivos fix/patch detectados (PROHIBIDOS):"
  echo "$FIXES"
  exit 1
fi

# 3. Validar que recreación completa funciona
echo "🔄 Validando recreación completa..."
./apps/database/drop-and-recreate-database.sh > /tmp/clean-load-test.log 2>&1
if [ $? -ne 0 ]; then
  echo "❌ ERROR: Recreación completa falló"
  echo "Ver log: /tmp/clean-load-test.log"
  exit 1
fi

echo "✅ Política de Carga Limpia: CUMPLIDA"
exit 0

📊 EJEMPLOS PRÁCTICOS

Ejemplo 1: Crear Nueva Tabla

-- ✅ CORRECTO: Crear archivo DDL primero
-- File: apps/database/ddl/schemas/gamification_system/tables/05-challenges.sql

-- ============================================================================
-- Tabla: challenges
-- Schema: gamification_system
-- Descripción: Desafíos y retos del sistema de gamificación
-- Autor: Database-Agent
-- Fecha: 2025-11-23
-- Dependencias: gamification_system.levels
-- ============================================================================

DROP TABLE IF EXISTS gamification_system.challenges CASCADE;

CREATE TABLE gamification_system.challenges (
    -- Identificador
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Datos del desafío
    code VARCHAR(50) NOT NULL UNIQUE,
    name VARCHAR(200) NOT NULL,
    description TEXT,
    difficulty VARCHAR(20) NOT NULL,
    points_reward INTEGER NOT NULL DEFAULT 0,

    -- Requisitos
    required_level_id UUID,

    -- Auditoría
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

    -- Constraints
    CONSTRAINT fk_challenges_to_levels
        FOREIGN KEY (required_level_id)
        REFERENCES gamification_system.levels(id)
        ON DELETE SET NULL,

    CONSTRAINT chk_challenges_difficulty_valid
        CHECK (difficulty IN ('easy', 'medium', 'hard', 'expert')),

    CONSTRAINT chk_challenges_points_positive
        CHECK (points_reward >= 0)
);

-- Índices
CREATE INDEX idx_challenges_difficulty ON gamification_system.challenges(difficulty);
CREATE INDEX idx_challenges_required_level_id ON gamification_system.challenges(required_level_id);

-- Comentarios
COMMENT ON TABLE gamification_system.challenges IS
'Desafíos del sistema de gamificación que los estudiantes pueden completar para ganar puntos adicionales';

COMMENT ON COLUMN gamification_system.challenges.difficulty IS
'Dificultad del desafío: easy, medium, hard, expert';

Luego validar:

cd apps/database
./drop-and-recreate-database.sh
# Si funciona → commit
git add apps/database/ddl/schemas/gamification_system/tables/05-challenges.sql
git commit -m "feat(db): add challenges table to gamification_system"

Ejemplo 2: Modificar Tabla Existente

-- ✅ CORRECTO: Actualizar DDL existente (NO crear migration)
-- File: apps/database/ddl/schemas/auth_management/tables/01-users.sql

-- Cambio: Agregar columnas para autenticación de dos factores

DROP TABLE IF EXISTS auth_management.users CASCADE;

CREATE TABLE auth_management.users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,

    -- ← NUEVAS COLUMNAS AGREGADAS
    two_factor_enabled BOOLEAN DEFAULT false,
    two_factor_secret VARCHAR(100),
    backup_codes TEXT[],
    -- ← FIN NUEVAS COLUMNAS

    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

Validar:

./drop-and-recreate-database.sh
# ✅ Si funciona → DDL correcto, commitear
# ❌ Si falla → Corregir DDL, no ejecutar ALTER TABLE manual

Ejemplo 3: Corregir Error en Seed

-- ❌ INCORRECTO: Crear fix-seed-data.sql
-- apps/database/fix-seed-users.sql
UPDATE auth_management.users SET role = 'admin' WHERE username = 'admin';

-- ✅ CORRECTO: Corregir archivo seed original
-- apps/database/seeds/dev/auth_management/01-users.sql

INSERT INTO auth_management.users (username, email, password_hash, role)
VALUES
    ('admin', 'admin@gamilit.com', '$2b$10$...', 'admin'),  -- ← Corregir role aquí
    ('teacher1', 'teacher1@gamilit.com', '$2b$10$...', 'teacher'),
    ('student1', 'student1@gamilit.com', '$2b$10$...', 'student')
ON CONFLICT (username) DO NOTHING;

Validar:

./drop-and-recreate-database.sh
# Verificar que admin tiene role correcto desde el inicio
psql -d gamilit_platform -c "SELECT username, role FROM auth_management.users WHERE username = 'admin';"

🚨 CASOS ESPECIALES

Caso 1: Migración desde Otro Sistema

Escenario: Importar datos desde sistema legacy.

❌ INCORRECTO:
1. Crear migration-import-legacy.sql con INSERT masivo
2. Ejecutar una vez y "olvidar"

✅ CORRECTO:
1. Crear seed en apps/database/seeds/prod/migration/01-import-legacy.sql
2. Documentar que es importación única con comentarios
3. Incluir en create-database.sh con flag condicional
4. Mantener seed para recreaciones futuras (testing)

Caso 2: Datos de Producción

Escenario: Necesito actualizar datos en producción.

❌ INCORRECTO:
1. Ejecutar UPDATE directo en producción
2. No actualizar seeds

✅ CORRECTO:
1. Si es dato de configuración:
   └─> Actualizar seed en apps/database/seeds/prod/
   └─> Validar con recreación local
   └─> Aplicar seed en producción

2. Si es dato de usuario (transaccional):
   └─> NO va en seeds (datos dinámicos)
   └─> Crear script de actualización documentado
   └─> Ejecutar en producción
   └─> Archivar script como documentación (NO en seeds)

Caso 3: Hotfix en Producción

Escenario: Bug crítico en producción que requiere cambio urgente en BD.

❌ INCORRECTO:
1. Ejecutar ALTER TABLE directo en producción
2. "Ya funcionó, documentar después"

✅ CORRECTO (incluso en emergencia):
1. Corregir DDL localmente
2. Validar con recreación local (rápido: 2-3 min)
3. Crear ADR documentando el hotfix
4. Aplicar DDL en producción
5. Commitear DDL + ADR inmediatamente
6. Post-mortem: ¿Por qué no se detectó en testing?

CHECKLIST DE CUMPLIMIENTO

Para Database-Agent

Antes de completar una tarea, verificar:

- [ ] Todos los cambios están en archivos DDL (no en BD directamente)
- [ ] NO se crearon archivos en migrations/
- [ ] NO se crearon archivos fix-*.sql o patch-*.sql
- [ ] Recreación completa funciona: ./drop-and-recreate-database.sh
- [ ] MASTER_INVENTORY.yml actualizado
- [ ] TRAZA-TAREAS-DATABASE.md actualizado
- [ ] Commits incluyen archivos DDL, no scripts temporales

Para Code Reviewers

Al revisar PRs que tocan base de datos:

- [ ] Cambios están en apps/database/ddl/, no en migrations/
- [ ] NO hay archivos fix-*.sql, patch-*.sql, hotfix-*.sql
- [ ] DDL sigue estándares (ver DIRECTIVA-DISENO-BASE-DATOS.md)
- [ ] CI/CD ejecutó recreación completa exitosamente
- [ ] TRAZA-TAREAS-DATABASE.md documenta el cambio

Para Desarrolladores

Al trabajar con base de datos:

- [ ] Leí DIRECTIVA-POLITICA-CARGA-LIMPIA.md (este documento)
- [ ] Entiendo que DDL es fuente de verdad, BD es derivada
- [ ] Sé que NO debo crear migrations/
- [ ] Sé que debo actualizar DDL antes de modificar BD
- [ ] Sé validar con ./drop-and-recreate-database.sh

📚 REFERENCIAS

Documentos Relacionados

Scripts Importantes

apps/database/
├── create-database.sh              # Crear BD desde DDL
├── drop-and-recreate-database.sh   # Recreación completa (carga limpia)
└── reset-database.sh               # Reset a estado inicial

Estructura de Archivos DDL

apps/database/ddl/
├── 00-prerequisites.sql            # Extensions, configuración
└── schemas/
    ├── auth_management/
    │   ├── 00-schema.sql
    │   ├── tables/
    │   │   ├── 01-users.sql
    │   │   └── 02-roles.sql
    │   ├── functions/
    │   └── policies/
    ├── gamification_system/
    │   └── ...
    └── educational_content/
        └── ...

🎓 BENEFICIOS DE ESTA POLÍTICA

Técnicos

  1. Reproducibilidad: BD puede recrearse en cualquier momento, en cualquier ambiente
  2. Simplicidad: Un script (drop-and-recreate), un resultado (BD completa)
  3. Debugging: Error en recreación = DDL tiene problema (fácil de localizar)
  4. Testing: Tests siempre empiezan con BD limpia predecible
  5. Onboarding: Nuevos devs tienen BD funcional en minutos

Operacionales

  1. Disaster Recovery: Recrear BD desde DDL es rápido y confiable
  2. Ambientes: Dev, Staging, Prod tienen misma estructura (desde mismo DDL)
  3. Auditoría: Git history de DDL = historia completa de cambios en BD
  4. Rollback: Revertir commit de DDL = revertir cambio en BD
  5. Compliance: Cambios en BD rastreables y versionados

De Equipo

  1. Claridad: Todo el equipo sabe dónde están los cambios (DDL)
  2. Colaboración: Conflictos en DDL son fáciles de resolver (Git)
  3. Documentación: DDL es documentación ejecutable y siempre actualizada
  4. Confianza: Cambios validados con recreación = alta confianza
  5. Velocidad: No hay "estado misterioso" de BD, siempre es predecible

⚖️ CONTRASTE: Carga Limpia vs Migrations

Aspecto Migrations Incrementales Carga Limpia (Este Proyecto)
Fuente de verdad Estado de BD + Migrations históricas Archivos DDL actuales
Recreación Ejecutar todas las migrations en orden Un script: drop-and-recreate
Debugging Difícil (¿cuál migration falló?) Fácil (DDL tiene el problema)
Onboarding Complejo (ejecutar N migrations) Simple (1 comando, BD lista)
Estado de BD Incierto (¿migrations aplicadas?) Siempre conocido (DDL)
Rollback Requiere migration de rollback Revertir commit de DDL
Testing Difícil (estado previo incierto) Simple (siempre BD limpia)
Producción Riesgoso (migration puede fallar) Confiable (validado en recreación)

Cuándo usar migrations: Proyectos con datos de producción que NO pueden perderse (ej: e-commerce, banking).

Cuándo usar carga limpia: Proyectos educativos, startups, MVPs, sistemas donde BD puede recrearse (como GAMILIT).


Versión: 1.0.0 Fecha: 2025-11-23 Próxima revisión: Trimestral o al identificar necesidad Responsable: Database-Agent + Tech Lead Aprobado por: Tech Lead


NOTA IMPORTANTE:

Esta política formaliza la práctica que ya se viene aplicando exitosamente en GAMILIT. El objetivo es documentarla para prevenir desviaciones futuras y facilitar el onboarding de nuevos desarrolladores.

Evidencia de cumplimiento histórico:

  • 45+ menciones a "Política de Carga Limpia" en TRAZA-TAREAS-DATABASE.md
  • 0 migrations creadas en historial del proyecto
  • Scripts de recreación completa (drop-and-recreate-database.sh) funcionando desde inicio
  • 100% de cambios en BD documentados en archivos DDL