# CHANGELOG - GAMILIT Database **Proyecto:** GAMILIT - Sistema de Gamificación Educativa **Última actualización:** 2025-12-15 --- ## [2.9.0] - 2025-12-15 ### Changed #### Simplificación Completa de Seeds social_features (STRUCT-001) **Prioridad:** Arquitectura - Simplificación **Tech-Leader:** Claude Opus 4.5 **Contexto:** Simplificación de la estructura de `social_features` eliminando todas las entidades demo para dejar únicamente las entidades default del sistema. **Archivos modificados:** | Archivo | Schema | Cambio | |---------|--------|--------| | `01-schools.sql` | social_features | Vaciado - sin escuelas demo | | `02-classrooms.sql` | social_features | Solo classroom DEFAULT | | `03-classroom-members.sql` | social_features | Asignación dinámica a DEFAULT | | `08-assign-admin-schools.sql` | auth_management | Expandido a TODOS los usuarios | **Entidades eliminadas:** - 2 escuelas demo (Marie Curie, IEI) - 5+ aulas demo (5to A, 5to B, 6to A, etc.) - Asignaciones manuales específicas **Estructura final:** ``` social_features.schools: - 1 registro: SYSTEM-UNASSIGNED (Sistema - Por Asignar) social_features.classrooms: - 1 registro: DEFAULT (Sin Asignar - Aula Default) social_features.classroom_members: - N registros: Todos los estudiantes → DEFAULT (dinámico) ``` **Corrección técnica:** - `enrollment_method`: `auto_assignment` → `admin_add` (constraint fix) **Validación:** - BD recreada exitosamente - 14 estudiantes asignados a DEFAULT - 16 usuarios con school_id asignado - 0 usuarios sin school_id - Trigger `trg_assign_default_classroom` activo **Referencia:** SEEDS_INVENTORY.yml v1.2.0, DATABASE_INVENTORY.yml v3.3.0 --- ## [2.8.2] - 2025-11-29 ### Fixed #### FKs Legacy en assignment_students y assignment_submissions (ARCH-015-FIX-P1) **Prioridad:** P1 - Integridad Referencial **Problema Identificado:** Las tablas `assignment_students` y `assignment_submissions` referenciaban `auth.users` en lugar de `auth_management.profiles`, inconsistente con el patrón establecido del proyecto. **Archivos modificados:** | Archivo | Tabla | Campo(s) | FK Anterior | FK Corregida | |---------|-------|----------|-------------|--------------| | `07-assignment_students.sql` | assignment_students | student_id | auth.users | auth_management.profiles | | `08-assignment_submissions.sql` | assignment_submissions | student_id | auth.users | auth_management.profiles | | `08-assignment_submissions.sql` | assignment_submissions | graded_by | auth.users | auth_management.profiles | **Justificación:** Las entities TypeORM (`AssignmentStudent`, `AssignmentSubmission`) definen relaciones comentadas a `Profile`, indicando la intención arquitectónica de usar `profiles`. **Validación:** - Política de Carga Limpia: RESPETADA - Patrón FK: Consistente con otras 7 tablas ya corregidas - Referencia: TRAZA-ANALISIS-ARQUITECTURA.md → ARCH-015-FIX-P1 --- ## [2.8.1] - 2025-11-29 ### Added #### Columnas faltantes en `assignment_exercises` (ARCH-015) **Prioridad:** P0 - Sincronización DDL ↔ Entity **Problema Identificado:** La entidad TypeORM `AssignmentExercise` definía 2 columnas que NO existían en el DDL, violando la Política de Carga Limpia. **Archivo modificado:** `apps/database/ddl/schemas/educational_content/tables/06-assignment_exercises.sql` **Columnas agregadas:** | Columna | Tipo | Propósito | |---------|------|-----------| | `points_override` | `DECIMAL(5,2)` | Puntos personalizados para este ejercicio en esta asignación | | `is_required` | `BOOLEAN DEFAULT true` | Si el ejercicio es obligatorio u opcional | **Cambio SQL:** ```sql -- Agregado después de order_index: points_override DECIMAL(5,2), is_required BOOLEAN DEFAULT true, -- Comentarios agregados: COMMENT ON COLUMN educational_content.assignment_exercises.points_override IS 'Custom points for this exercise in this assignment (overrides exercise default)'; COMMENT ON COLUMN educational_content.assignment_exercises.is_required IS 'Whether this exercise is required or optional in the assignment'; ``` **Validación:** - DDL ahora sincronizado con Entity `AssignmentExercise` - Política de Carga Limpia: RESPETADA - Referencia: TRAZA-ANALISIS-ARQUITECTURA.md → ARCH-015-FIX --- ## [2.8.0] - 2025-11-29 ### Added #### Trigger para actualización de user_stats desde exercise_submissions (GAP-001) **Prioridad:** P0 - CRÍTICO (Misiones earn_xp no se actualizaban desde submissions) **Problema Identificado:** Las misiones de tipo `earn_xp` no se actualizaban cuando los ejercicios eran calificados vía `exercise_submissions` (flujo de revisión manual). Solo se actualizaban desde `exercise_attempts` (flujo autocorregible). **Análisis del GAP:** ``` ✅ exercise_attempts → trigger 21 → user_stats → trigger 27 → missions earn_xp ❌ exercise_submissions → SIN TRIGGER → user_stats no se actualizaba → missions earn_xp NO se actualizaban ``` **Archivos creados:** | Archivo | Propósito | |---------|-----------| | `ddl/schemas/gamilit/functions/27-update_user_stats_on_submission_graded.sql` | Función trigger que actualiza user_stats al calificar submission | | `ddl/schemas/progress_tracking/triggers/31-trg_update_user_stats_on_submission.sql` | Trigger AFTER UPDATE en exercise_submissions | **Nueva Función:** `gamilit.update_user_stats_on_submission_graded()` ```sql -- Actualiza user_stats cuando una submission es calificada correctamente -- Solo se ejecuta cuando: -- 1. status IN ('graded', 'reviewed') -- 2. is_correct = true -- 3. xp_earned > 0 -- 4. Estado cambió (evita re-disparos) CREATE OR REPLACE FUNCTION gamilit.update_user_stats_on_submission_graded() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN -- Validaciones tempranas IF NEW.status NOT IN ('graded', 'reviewed') THEN RETURN NEW; END IF; IF NEW.is_correct IS NOT TRUE THEN RETURN NEW; END IF; IF NEW.xp_earned <= 0 THEN RETURN NEW; END IF; IF OLD.status IS NOT DISTINCT FROM NEW.status AND OLD.is_correct IS NOT DISTINCT FROM NEW.is_correct THEN RETURN NEW; END IF; -- UPSERT en user_stats UPDATE gamification_system.user_stats SET exercises_completed = exercises_completed + 1, total_xp = total_xp + NEW.xp_earned, ml_coins = ml_coins + NEW.ml_coins_earned, ml_coins_earned_total = ml_coins_earned_total + NEW.ml_coins_earned, last_activity_at = gamilit.now_mexico(), updated_at = gamilit.now_mexico() WHERE user_id = NEW.user_id; IF NOT FOUND THEN INSERT INTO gamification_system.user_stats (...) VALUES (...); END IF; RETURN NEW; END; $$; ``` **Nuevo Trigger:** `trg_update_user_stats_on_submission` ```sql CREATE TRIGGER trg_update_user_stats_on_submission AFTER UPDATE ON progress_tracking.exercise_submissions FOR EACH ROW WHEN ( NEW.status IN ('graded', 'reviewed') AND NEW.is_correct = true AND (OLD.status IS DISTINCT FROM NEW.status OR OLD.is_correct IS DISTINCT FROM NEW.is_correct) ) EXECUTE FUNCTION gamilit.update_user_stats_on_submission_graded(); ``` **Cadena de Triggers Completada:** ``` ┌────────────────────────────────────────────────────────────────┐ │ FLUJO A: Ejercicios Autocorregibles │ │ INSERT exercise_attempts │ │ → trg_update_user_stats_on_exercise (trigger 21) │ │ → UPDATE user_stats.total_xp │ │ → trg_update_missions_on_earn_xp (trigger 27) │ │ → Misiones earn_xp actualizadas ✅ │ └────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────┐ │ FLUJO B: Ejercicios con Revisión Manual (NUEVO) │ │ UPDATE exercise_submissions (status='graded', is_correct=true)│ │ → trg_update_user_stats_on_submission (trigger 31) NEW │ │ → UPDATE user_stats.total_xp │ │ → trg_update_missions_on_earn_xp (trigger 27) │ │ → Misiones earn_xp actualizadas ✅ │ └────────────────────────────────────────────────────────────────┘ ``` **Arquitectura BD-first:** - ✅ Triggers como fuente de verdad para actualizaciones de datos - ✅ Backend sirve como capa de redundancia (no fuente primaria) - ✅ Modificaciones directas en BD disparan triggers correctamente - ✅ Consistencia garantizada entre exercise_attempts y exercise_submissions **Impacto:** - ✅ Misiones `earn_xp` se actualizan desde AMBOS flujos - ✅ Maestros al calificar submissions disparan automáticamente actualización de misiones - ✅ Arquitectura extensible para nuevos tipos de misiones **Documentación Actualizada:** - `docs/sistema-recompensas/04-DATABASE-SCHEMA.md` - v2.8.0 - `docs/sistema-recompensas/02-FLUJO-END-TO-END.md` - v2.8.0 con Flujo B --- ### Fixed #### Backend: exercise-attempt.service.ts no actualizaba misiones earn_xp **Archivo:** `apps/backend/src/modules/progress/services/exercise-attempt.service.ts` **Problema:** La función `updateMissionsProgress()` solo actualizaba misiones de tipo `complete_exercises`, ignorando las de tipo `earn_xp`. **Corrección:** ```typescript // Línea 80: Agregado parámetro xpEarned await this.updateMissionsProgress(savedAttempt.user_id, savedAttempt.is_correct, savedAttempt.xp_earned); // Línea 644: Actualizada firma del método private async updateMissionsProgress(userId: string, isCorrect: boolean, xpEarned: number = 0): Promise // Líneas 682-702: Nueva lógica para earn_xp if (xpEarned > 0) { const xpMissions = allMissions.filter(mission => (mission.status === 'active' || mission.status === 'in_progress') && mission.objectives.some(obj => obj.type === 'earn_xp'), ); for (const mission of xpMissions) { await this.missionsService.updateProgress(mission.id, userId, 'earn_xp', xpEarned); } } ``` **Nota:** Esta corrección en backend sirve como capa de redundancia. La fuente de verdad principal son los triggers de base de datos. --- ### Validación Post-Corrección **Base de datos:** - ✅ Función `update_user_stats_on_submission_graded` creada - ✅ Trigger `trg_update_user_stats_on_submission` creado - ✅ Cadena de triggers validada **Backend:** - ✅ `exercise-attempt.service.ts` actualizado - ✅ Misiones `earn_xp` se actualizan desde ambos flujos **Objetos de BD actualizados:** - Functions: +1 (total: 201) - Triggers: +1 (total: 88) --- ## [2.7.0] - 2025-11-29 ### Fixed #### Seeds user_achievements: UUIDs incorrectos (SEED-001) **Prioridad:** P1 - ALTA (Seeds fallaban completamente) **Problema:** Los seeds de `user_achievements` fallaban con errores de FK constraint porque: 1. Los `achievement_id` usaban un patrón incorrecto (`90000001-00XX-...` en lugar del patrón correcto por categoría) 2. Los `user_id` referenciaban UUIDs que no existían en la tabla `profiles` 3. `ARRAY[]` vacío sin tipo explícito causaba error de sintaxis **Archivos modificados:** - `seeds/prod/gamification_system/08-user_achievements.sql` **Correcciones de UUIDs de achievement_id (16 correcciones):** | Achievement | UUID Incorrecto | UUID Correcto | |-------------|----------------|---------------| | Primera Visita | `90000001-0020-...` | `90000007-0000-0000-0000-000000000001` | | Primeros Pasos | `90000001-0001-...` | `90000001-0000-0000-0000-000000000001` | | Lector Principiante | `90000001-0002-...` | `90000001-0000-0000-0000-000000000002` | | Racha de 3 Días | `90000001-0006-...` | `90000002-0000-0000-0000-000000000001` | | Racha de 7 Días | `90000001-0007-...` | `90000002-0000-0000-0000-000000000002` | | Racha de 30 Días | `90000001-0008-...` | `90000002-0000-0000-0000-000000000003` | | Módulo 1 Completado | `90000001-0009-...` | `90000003-0000-0000-0000-000000000001` | | Módulo 2 Completado | `90000001-0010-...` | `90000003-0000-0000-0000-000000000002` | | Completista Total | `90000001-0012-...` | `90000003-0000-0000-0000-000000000004` | | Perfeccionista | `90000001-0013-...` | `90000004-0000-0000-0000-000000000001` | | Explorador Curioso | `90000001-0016-...` | `90000005-0000-0000-0000-000000000001` | | Compañero de Aula | `90000001-0018-...` | `90000006-0000-0000-0000-000000000001` | | Estudiante Colaborativo | `90000001-0019-...` | `90000006-0000-0000-0000-000000000002` | **Correcciones de UUIDs de user_id (10 correcciones):** | Usuario Original | Perfil Mapeado | |-----------------|----------------| | Ana García | Azul Valentina (`2f5a9846-3393-40b2-9e87-0f29238c383f`) | | Carlos Ramírez | Benjamin Hernandez (`7a6a973e-83f7-4374-a9fc-54258138115f`) | | María Fernanda | Carlos Marban (`00c742d9-e5f7-4666-9597-5a8ca54d5478`) | | Luis Miguel | Diego Colores (`33306a65-a3b1-41d5-a49d-47989957b822`) | | Sofía Martínez | Estudiante Testing (`cccccccc-cccc-cccc-cccc-cccccccccccc`) | | Juan Pérez (Teacher) | Profesor Testing (`bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb`) | | Admin | Admin GAMILIT (`aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa`) | **Corrección sintaxis:** ```sql -- ANTES (error) ARRAY[], -- DESPUÉS (correcto) ARRAY[]::text[], ``` --- #### Trigger fn_on_achievement_unlocked: balance_before faltante (TRIGGER-001) **Prioridad:** P1 - ALTA (Trigger fallaba al crear transacciones de ML Coins) **Problema:** El trigger `fn_on_achievement_unlocked` insertaba en `ml_coins_transactions` sin proveer `balance_before` y `balance_after`, que son campos NOT NULL. **Archivo modificado:** - `ddl/schemas/gamification_system/triggers/01-trg_achievement_unlocked.sql` **Corrección:** ```sql -- Variables agregadas v_balance_before INTEGER; v_balance_after INTEGER; -- Lógica agregada para calcular balances SELECT COALESCE(ml_coins, 0) INTO v_balance_before FROM gamification_system.user_stats WHERE user_id = NEW.user_id; IF v_balance_before IS NULL THEN v_balance_before := 0; END IF; v_balance_after := v_balance_before + v_coins_reward; -- Actualizar user_stats con nuevo balance UPDATE gamification_system.user_stats SET ml_coins = v_balance_after WHERE user_id = NEW.user_id; -- INSERT con balance_before y balance_after INSERT INTO gamification_system.ml_coins_transactions ( user_id, amount, balance_before, balance_after, transaction_type, description, reference_id, reference_type, metadata ) VALUES (...); ``` --- ### Validación Post-Corrección **Base de datos recreada exitosamente:** ```bash ./drop-and-recreate-database.sh ``` **Verificaciones:** - ✅ user_achievements insertados: 43 (antes: 2) - ✅ Completados: 35 - ✅ En progreso: 8 - ✅ Sin errores de FK constraint - ✅ Trigger funciona correctamente con balance tracking **Objetos de BD:** - Schemas: 18 - Tables: 128 - ENUMs: 37 - Functions: 200 - Triggers: 87 --- ## [2.6.0] - 2025-11-28 ### Added #### Validadores de Ejercicios: mapa_conceptual y emparejamiento (BUG-004) **Prioridad:** P0 - CRÍTICO (Ejercicios de Módulo 1 no funcionaban) **Problema:** Los ejercicios de tipo `mapa_conceptual` y `emparejamiento` no tenían funciones de validación SQL, causando errores al intentar enviar respuestas. **Archivos creados:** - `educational_content/functions/08-validate_mapa_conceptual.sql` - `educational_content/functions/09-validate_emparejamiento.sql` **Funciones implementadas:** | Función | Tipo Ejercicio | Descripción | |---------|---------------|-------------| | `validate_mapa_conceptual()` | mapa_conceptual | Valida conexiones entre conceptos, soporta matching bidireccional (A→B = B→A), normalización de texto | | `validate_emparejamiento()` | emparejamiento | Valida pares término-definición, soporta keys correctMatches y matches, normalización de texto | **Características:** - ✅ Crédito parcial (70% passing score) - ✅ Case-insensitive por defecto - ✅ Normalización de texto con `gamilit.normalize_text()` - ✅ Feedback detallado en español --- ### Fixed #### Enum exercise_type incompleto (BUG-002) **Archivo:** `00-prerequisites.sql` (línea 155) **Problema:** El enum `educational_content.exercise_type` no incluía los valores `mapa_conceptual` y `emparejamiento`. **Solución:** Agregados los valores faltantes al enum: ```sql 'emparejamiento', 'mapa_conceptual' ``` **Impacto:** - ✅ 25 tipos de ejercicios soportados (antes: 23) --- #### Configuración de validación faltante (BUG-003) **Archivo:** `seeds/prod/educational_content/10-exercise_validation_config.sql` **Problema:** No existían entradas de configuración para `mapa_conceptual` y `emparejamiento`. **Solución:** Agregadas 2 configuraciones: ```sql -- mapa_conceptual ('mapa_conceptual', 'validate_mapa_conceptual', false, true, NULL, false, ...) -- emparejamiento ('emparejamiento', 'validate_emparejamiento', false, true, NULL, true, ...) ``` **Impacto:** - ✅ 17 configuraciones de validación (antes: 15) --- #### Frontend: Mapeo incorrecto de detective_textual (BUG-001) **Archivo:** `apps/frontend/src/apps/student/pages/ExercisePage.tsx` **Problema:** El componente `detective_textual` apuntaba incorrectamente a `LecturaInferencialExercise`. **Corrección (líneas 106-107):** ```typescript // ANTES (INCORRECTO) detective_textual: () => import('@/features/mechanics/module2/LecturaInferencial/LecturaInferencialExercise') // DESPUÉS (CORRECTO) detective_textual: () => import('@/features/mechanics/module2/DetectiveTextual/DetectiveTextualExercise') ``` --- #### Frontend/Backend: Campo oldRank vs previousRank (BUG-006) **Archivos afectados:** - `apps/frontend/src/apps/student/pages/ExercisePage.tsx` (línea 518) - `apps/frontend/src/services/api/educationalAPI.ts` (línea 88) **Problema:** Frontend usaba `oldRank` pero backend retorna `previousRank`. **Corrección:** ```typescript // ExercisePage.tsx result.rankUp.previousRank → result.rankUp.newRank // educationalAPI.ts rankUp?: { previousRank: string; newRank: string; ... } ``` --- #### Backend: DTOs faltantes para nuevos validadores (BUG-005) **Archivos creados:** - `modules/progress/dto/answers/mapa-conceptual-answers.dto.ts` - `modules/progress/dto/answers/emparejamiento-answers.dto.ts` **Archivo modificado:** - `modules/progress/dto/answers/exercise-answer.validator.ts` **Casos agregados al switch:** ```typescript case 'mapa_conceptual': case 'concept_map': return MapaConceptualAnswersDto; case 'emparejamiento': case 'matching': return EmparejamientoAnswersDto; ``` --- ### Validación Post-Corrección **Base de datos recreada exitosamente:** ```bash ./drop-and-recreate-database.sh ``` **Verificaciones:** - ✅ Enum `exercise_type`: 25 valores - ✅ `exercise_validation_config`: 17 entradas - ✅ Funciones `validate_mapa_conceptual` y `validate_emparejamiento` existen - ✅ `validate_answer` tiene casos para nuevos validadores --- ### Métricas de Sesión ``` ╔═══════════════════════════════════════════════════════════╗ ║ RESUMEN DE CORRECCIONES 2025-11-28 (BUG-001 a BUG-006) ║ ╠═══════════════════════════════════════════════════════════╣ ║ Archivos DB creados: 2 ║ ║ Archivos DB modificados: 2 ║ ║ Archivos Backend creados: 2 ║ ║ Archivos Backend modificados: 2 ║ ║ Archivos Frontend modificados: 2 ║ ║ Funciones SQL nuevas: 2 ║ ║ Enum values agregados: 2 ║ ║ Configuraciones agregadas: 2 ║ ╠═══════════════════════════════════════════════════════════╣ ║ ESTADO FINAL: ✅ VALIDADORES COMPLETOS (17/17) ║ ╚═══════════════════════════════════════════════════════════╝ ``` --- ## [2.5.5] - 2025-11-26 ### Fixed #### Validación de Integración Completa - Correcciones Críticas (P0) **Contexto:** Análisis exhaustivo de integración DB → Backend → Frontend identificó múltiples issues de coherencia que requerían corrección inmediata para alcanzar production readiness. **Métricas de Coherencia Alcanzadas:** - DB → Backend: **87%** - DB → Frontend: **78.5%** - **Promedio Global: 82.75%** ✅ PRODUCTION READY --- #### 1. FKs Legacy Corregidas (7 tablas) **Problema:** El proyecto migró de `auth.users` (tabla auth) a `auth_management.profiles` (tabla propia), pero varias tablas mantenían FKs legacy apuntando al schema incorrecto. **Archivos modificados:** | Archivo | Tabla | Campo(s) | FK Anterior | FK Corregida | ON DELETE | |---------|-------|----------|-------------|--------------|-----------| | `social_features/tables/01-friendships.sql` | friendships | user_id, friend_id | auth.users | auth_management.profiles | CASCADE | | `social_features/tables/06-team_members.sql` | team_members | user_id | auth.users | auth_management.profiles | CASCADE | | `progress_tracking/tables/teacher_notes.sql` | teacher_notes | teacher_id, student_id | auth.users | auth_management.profiles | RESTRICT/CASCADE | | `audit_logging/tables/06-activity_log.sql` | activity_log | user_id | auth.users | auth_management.profiles | CASCADE | | `social_features/tables/teacher_classrooms.sql` | teacher_classrooms | teacher_id | auth.users | auth_management.profiles | RESTRICT | | `educational_content/tables/05-assignments.sql` | assignments | teacher_id | auth.users | auth_management.profiles | RESTRICT | **Impacto:** - ✅ Integridad referencial garantizada - ✅ Eliminación de usuarios propaga correctamente - ✅ Sin referencias huérfanas posibles --- #### 2. Vulnerabilidad RLS Corregida (CRÍTICA) **Archivo:** `gamification_system/rls-policies/02-policies.sql` **Problema:** Política `user_stats_update_system` con `USING(true)` permitía a CUALQUIER usuario autenticado realizar UPDATE en `gamification_system.user_stats`. ```sql -- ❌ VULNERABLE (REMOVIDO) CREATE POLICY user_stats_update_system ON gamification_system.user_stats FOR UPDATE USING (true); -- CUALQUIER USUARIO PODÍA MODIFICAR STATS ``` **Solución:** - Sección completa removida de `02-policies.sql` - Políticas modernas con control de `super_admin` en `04-user-stats-policies.sql` - Agregado comentario de referencia al nuevo archivo **Impacto:** - ✅ Solo administradores pueden modificar stats - ✅ Usuarios normales solo pueden leer sus propios stats - ✅ Prevención de manipulación de XP/ML Coins --- #### 3. Duplicados RLS Eliminados **Archivo:** `social_features/rls-policies/02-policies.sql` **Problema:** Sección `classroom_members` (líneas 7-78) duplicada con versión moderna en `04-classroom-members-policies.sql`. **Solución:** - Sección legacy removida completamente - Agregado comentario de referencia a versión moderna - Evita conflictos de políticas duplicadas --- #### 4. Colisión de Prefijos Resuelta **Archivos afectados:** - `audit_logging/tables/06-user_activity.sql` → **Renombrado a** `07-user_activity.sql` - `audit_logging/tables/06-activity_log.sql` (sin cambios) **Problema:** Dos archivos con mismo prefijo "06-" causaban orden de carga indeterminado. **Solución:** Renombrado para garantizar orden lexicográfico correcto: 1. `06-activity_log.sql` (primero) 2. `07-user_activity.sql` (después) --- #### 5. Referencia a Tabla Inexistente Corregida **Archivo:** `admin_dashboard/tables/01-materialized_views.sql` **Problema:** Vista materializada `system_overview_mv` referenciaba `audit_logging.system_events` (tabla que NO existe). ```sql -- ❌ INCORRECTO (CORREGIDO) SELECT COUNT(*) FROM audit_logging.system_events WHERE severity = 'error' -- ✅ CORRECTO SELECT COUNT(*) FROM audit_logging.system_logs WHERE created_at >= NOW() - INTERVAL '1 hour' AND log_level = 'error' ``` **Impacto:** - ✅ Vista materializada crea correctamente - ✅ Dashboard de admin muestra errores reales --- ### Added #### Documentación de Integración **Archivos creados:** - `docs/90-transversal/VALIDACION-INTEGRACION-COMPLETA-2025-11-26.md` - `orchestration/agentes/architecture-analyst/CORRECCION-ISSUES-TEACHER-2025-11-26/01-PLAN-CORRECCION.md` - `orchestration/agentes/architecture-analyst/CORRECCION-ISSUES-TEACHER-2025-11-26/02-REPORTE-EJECUCION.md` - `orchestration/agentes/architecture-analyst/CORRECCION-ISSUES-TEACHER-2025-11-26/03-REPORTE-INTEGRACION-COMPLETA.md` --- ### Known Issues (Backlog) #### P0 - CRÍTICO (Pendiente) - **Función `check_and_award_achievements()`** referencia campos inexistentes - Campos actuales: `conditions` (JSONB), `rewards` (JSONB), `ml_coins_reward` (INTEGER) - Campos referenciados (no existen): `condition_type`, `condition_value`, `xp_reward` - **Acción requerida:** Refactorizar función para usar campos JSONB #### P1 - ALTO (Pendiente) - **Tipo Mission NO EXISTE en Frontend** - 14 campos pendientes - **MayaRank KUKUKULKAN** - Typo en backend (debe ser KUKULKAN) - **MessageTypeEnum** - Falta en Frontend #### P2 - MEDIO (Pendiente) - DeviceTypeEnum falta valor 'unknown' en backend - Tipos Frontend incompletos: User (7), Achievement (9), Classroom (14), ExerciseSubmission (8) --- ### Validación Post-Corrección **Comando de recreación:** ```bash cd apps/database ./create-database.sh ``` **Checklist:** - [x] Base de datos recrea sin errores - [x] Todas las FKs apuntan a `auth_management.profiles` - [x] RLS policies correctas (sin USING(true)) - [x] Vistas materializadas crean correctamente - [ ] Backend compila sin errores - [ ] Frontend compila sin errores --- ### Métricas de Sesión ``` ╔═══════════════════════════════════════════════════════════╗ ║ RESUMEN DE CORRECCIONES 2025-11-26 ║ ╠═══════════════════════════════════════════════════════════╣ ║ Archivos DB modificados: 8 ║ ║ Archivos DB creados: 3 ║ ║ FKs legacy corregidas: 7 ║ ║ Vulnerabilidades RLS arregladas: 1 ║ ║ Duplicados eliminados: 2 ║ ║ Colisiones de archivo resueltas: 1 ║ ║ Referencias inexistentes arregladas: 1 ║ ╠═══════════════════════════════════════════════════════════╣ ║ ESTADO FINAL: ✅ PRODUCTION READY (82.75%) ║ ╚═══════════════════════════════════════════════════════════╝ ``` --- ## [2.5.4] - 2025-11-24 ### Added #### Integración Misiones con Ejercicios - Trigger Automático (P0) **Archivos creados:** - `apps/database/ddl/schemas/gamilit/functions/17-update_missions_on_exercise_complete.sql` - `apps/database/ddl/schemas/progress_tracking/triggers/24-trg_update_missions_on_exercise.sql` **Problema resuelto:** Las misiones diarias/semanales no se actualizaban cuando los estudiantes completaban ejercicios. El sistema de gamificación otorgaba XP y ML Coins correctamente, pero las misiones con objetivo `complete_exercises` permanecían en 0% de progreso. **Solución implementada:** 1. Nueva función `gamilit.update_missions_on_exercise_complete()` tipo TRIGGER 2. Nuevo trigger `trg_update_missions_on_exercise` en `progress_tracking.exercise_attempts` 3. Ejecuta AFTER INSERT para detectar ejercicios completados correctamente 4. Busca misiones activas del usuario con objetivo `complete_exercises` 5. Incrementa `current` en el objetivo sin superar `target` 6. Recalcula `progress` (0-100%) de la misión 7. Si `progress = 100%`, marca la misión como `completed` **Lógica del trigger:** ```sql -- Solo procesa si ejercicio fue correcto IF NEW.is_correct = true THEN -- Busca misiones activas con objetivo 'complete_exercises' -- Incrementa objectives[x].current -- Recalcula progress = SUM(current/target) / count * 100 -- Si progress >= 100 → status = 'completed' END IF; ``` **Misiones afectadas:** | Tipo | Misión | Objetivo | Recompensa | |------|--------|----------|------------| | DAILY | Completar ejercicios | 3 ejercicios | 50 XP + 25 ML Coins | | WEEKLY | Maratón de ejercicios | 15 ejercicios | 200 XP + 100 ML Coins | **Características:** - ✅ SECURITY DEFINER para escribir en missions sin permisos directos - ✅ Manejo robusto de errores (no bloquea INSERT original) - ✅ Compatible con triggers existentes (21, 22, 23) - ✅ Usa índice `idx_missions_user_type_status` para eficiencia - ✅ Operador `@>` usa índice GIN en objectives JSONB **Orden de ejecución de triggers en exercise_attempts:** 1. `trg_update_user_stats_on_exercise` (21-) - XP y ML Coins 2. `trg_update_module_progress_on_exercise` (22-) - Progreso del módulo 3. `trg_update_missions_on_exercise` (24-) - **NUEVO** - Progreso de misiones **Impacto:** - ✅ Misiones diarias se actualizan automáticamente al completar ejercicios - ✅ Misiones semanales se actualizan automáticamente - ✅ Usuarios con avance previo se benefician del trigger - ✅ Compatible con carga limpia de BD **Documentación actualizada:** - `docs/90-transversal/inventarios/DATABASE_INVENTORY.yml` (total_functions: 63, total_triggers: 35) --- ## [2.5.3] - 2025-11-24 ### Fixed #### Corrección de función update_user_rank() - Balance Fields (P1) **Archivo afectado:** - `apps/database/ddl/schemas/gamification_system/functions/update_user_rank.sql` **Problema:** La función `gamification_system.update_user_rank()` fallaba al insertar registros en `ml_coins_transactions` debido a campos NOT NULL faltantes (`balance_before` y `balance_after`). **Error:** ```sql ERROR: null value in column "balance_before" violates not-null constraint ERROR: null value in column "balance_after" violates not-null constraint ``` **Solución implementada:** 1. Agregadas variables `v_current_balance` y `v_new_balance` al DECLARE 2. Captura de balance actual ANTES del UPDATE a user_stats 3. Cálculo explícito del nuevo balance 4. INSERT corregido incluye `balance_before` y `balance_after` 5. Corregido ENUM de `'RANK_UP'` a `'earned_rank'::gamification_system.transaction_type` **Código corregido:** ```sql -- Obtener balance actual ANTES de actualizar SELECT COALESCE(ml_coins, 0) INTO v_current_balance FROM gamification_system.user_stats WHERE user_id = p_user_id; v_new_balance := v_current_balance + v_coins_reward; -- INSERT corregido INSERT INTO gamification_system.ml_coins_transactions ( user_id, amount, balance_before, balance_after, transaction_type, description ) VALUES ( p_user_id, v_coins_reward, v_current_balance, v_new_balance, 'earned_rank'::gamification_system.transaction_type, 'Ascendiste al rango ' || v_new_rank ); ``` **Impacto:** - ✅ Sistema de Rangos Maya ahora funciona correctamente - ✅ Transacciones de ML Coins se registran con auditoría completa - ✅ Integridad de balances garantizada **Documentación:** - Reporte: `orchestration/agentes/database/fix-update-user-rank-balance-fields-2025-11-24/REPORTE-CORRECCION-UPDATE-USER-RANK.md` - Análisis: `orchestration/agentes/database/fix-update-user-rank-balance-fields-2025-11-24/ANALISIS-FUNCIONES-AFECTADAS.md` - Script validación: `apps/database/scripts/validate-update-user-rank-fix.sql` **Funciones adicionales identificadas con el mismo problema:** - ❌ `check_and_award_achievements.sql` - PENDIENTE - ❌ `claim_achievement_reward.sql` - PENDIENTE - ❌ `update_mission_progress.sql` - PENDIENTE - ❌ `trg_achievement_unlocked.sql` - PENDIENTE ### Added #### Arquitectura Dual: Ejercicios Autocorregibles vs Revisión Manual (P0) **Migraciones:** - `2025-11-24-add-requires-manual-grading.sql` - `2025-11-24-cleanup-incorrect-submissions.sql` **Archivos Backend:** - `apps/backend/src/modules/educational/entities/exercise.entity.ts` (line 202) - `apps/backend/src/modules/progress/services/exercise-submission.service.ts` (lines 199-236) - `apps/backend/src/modules/educational/controllers/exercises.controller.ts` (lines 840-938) **Prioridad:** P0 - CRÍTICO (Bloqueo de reenvíos de ejercicios) **Problema Original:** Sistema bloqueaba reenvíos de ejercicios después del primer intento exitoso, impidiendo que estudiantes pudieran practicar. Error en `ExerciseSubmissionService.submitExercise()` línea 206-210: ```typescript // ❌ CÓDIGO PROBLEMÁTICO (REMOVIDO) if (existingSubmission && existingSubmission.status === 'graded') { throw new BadRequestException( 'Exercise already submitted and graded. Cannot resubmit.', ); } ``` **Solución Implementada:** Arquitectura dual que separa dos flujos completamente diferentes: 1. **Ejercicios Autocorregibles** (`requires_manual_grading = false`) - Práctica ilimitada con reintentos infinitos - Almacenados en `progress_tracking.exercise_attempts` - XP otorgado SOLO en primer acierto (anti-farming) - Validación con PostgreSQL: `educational_content.validate_and_audit()` - Trigger automático actualiza `gamification_system.user_stats` 2. **Ejercicios de Revisión Manual** (`requires_manual_grading = true`) - Una sola entrega permitida - Almacenados en `progress_tracking.exercise_submissions` - XP otorgado cuando maestro califica - Flujo: pending → graded **Cambios en Database:** ```sql -- 1. Nueva columna ALTER TABLE educational_content.exercises ADD COLUMN requires_manual_grading BOOLEAN DEFAULT false; -- 2. Índice de performance CREATE INDEX idx_exercises_requires_manual_grading ON educational_content.exercises(requires_manual_grading) WHERE is_active = true; -- 3. Clasificación de 15 ejercicios existentes UPDATE educational_content.exercises SET requires_manual_grading = false WHERE exercise_type IN ( -- Módulo 1: Comprensión Literal 'crucigrama', 'linea_tiempo', 'sopa_letras', 'completar_espacios', 'verdadero_falso', -- Módulo 2: Comprensión Inferencial 'detective_textual', 'construccion_hipotesis', 'prediccion_narrativa', 'puzzle_contexto', 'rueda_inferencias', -- Módulo 3: Lectura Crítica 'analisis_fuentes', 'debate_digital', 'matriz_perspectivas', 'podcast_argumentativo', 'tribunal_opiniones' ); ``` **Resultado:** - ✅ 15 ejercicios clasificados como autocorregibles (100%) - ✅ 0 ejercicios de revisión manual (se agregarán en futuro) **Cambios en Backend:** 1. **Exercise Entity** - Nueva propiedad: ```typescript @Column({ type: 'boolean', default: false }) requires_manual_grading!: boolean; ``` 2. **ExerciseSubmissionService** - Validación de tipo: ```typescript // Rechazar autocorregibles (deben usar exercise_attempts) if (!exercise.requires_manual_grading) { throw new BadRequestException( 'This exercise is auto-graded and allows multiple attempts.' ); } // Solo una entrega para revisión manual if (existingSubmission) { throw new BadRequestException( 'Only one submission allowed for teacher-graded exercises.' ); } ``` 3. **ExercisesController** - Arquitectura dual completa: ```typescript // 1. Obtener tipo de ejercicio const exercise = await this.exercisesService.findById(exerciseId); // 2. Routing por tipo if (exercise.requires_manual_grading) { // Flujo de revisión manual return await this.exerciseSubmissionService.submitExercise(...); } // 3. Flujo autocorregible con anti-farming const previousAttempts = await this.exerciseAttemptService .findByUserAndExercise(profileId, exerciseId); const hasCorrectAttemptBefore = previousAttempts .some((attempt: any) => attempt.is_correct); const isFirstCorrectAttempt = !hasCorrectAttemptBefore && isCorrect; // XP solo en primer acierto let xpEarned = isFirstCorrectAttempt ? exercise.xp_reward : 0; // 4. Crear attempt (trigger actualiza user_stats) await this.exerciseAttemptService.create({ ... }); ``` **Data Cleanup:** Limpieza de 8 registros legacy incorrectos (ejercicios autocorregibles almacenados erróneamente en `exercise_submissions`): ```sql DELETE FROM progress_tracking.exercise_submissions es USING educational_content.exercises e WHERE es.exercise_id = e.id AND e.requires_manual_grading = false; -- DELETE 8 ``` **Validación:** ✅ 6 tests automatizados pasando: 1. Columna `requires_manual_grading` existe 2. 15 ejercicios (100%) clasificados como autocorregibles 3. Usuario de prueba configurado correctamente 4. 10 ejercicios disponibles (Módulos 2 y 3) 5. Historial limpio (sin intentos previos) 6. **0 registros incorrectos** en exercise_submissions **Script de Testing:** ```bash apps/database/test-exercise-resubmission.sh ``` **Impacto:** **Antes del fix:** - ❌ Reenvíos bloqueados después de primer acierto - ❌ Registros duplicados en 2 tablas - ❌ XP duplicado (trigger + service) - ❌ XP farming posible (múltiples aciertos = múltiple XP) **Después del fix:** - ✅ Reenvíos ilimitados permitidos - ✅ Solo una tabla por tipo de ejercicio - ✅ XP solo otorgado por trigger (una sola fuente) - ✅ Anti-farming implementado (XP solo en primer acierto) **Documentación Completa:** Ubicación: `/orchestration/agentes/architecture-analyst/analisis-sistema-xp-rangos-2025-11-24/` Documentos generados (27,000+ palabras): 1. **MATRIZ-IMPACTO-Y-DEPENDENCIAS.md** (9,000+ palabras) - Análisis de 9 archivos backend, 13 frontend, 2 triggers - 5 conflictos críticos identificados - Planes de mitigación detallados 2. **SOLUCION-DEFINITIVA-EJERCICIOS-REENVIOS.md** (13,000+ palabras) - Especificación técnica completa - Diagramas de flujo (ASCII) - Casos de uso detallados 3. **RESUMEN-IMPLEMENTACION-2025-11-24.md** - Cambios código antes/después - Scripts de testing - Métricas de validación 4. **STATUS-FINAL-2025-11-24.md** - Estado del sistema post-implementación - Checklist completo - Plan de testing manual **Testing Manual Pendiente:** Validar con frontend: 1. Completar ejercicio → Verificar +100 XP 2. Reintentar mismo ejercicio → Verificar reenvío permitido 3. Segunda respuesta correcta → Verificar +0 XP (anti-farming) 4. Verificar en DB: solo tabla `exercise_attempts` usada **Referencias:** - Issue: Sistema de ejercicios - Arquitectura dual (attempts vs submissions) - Trigger relacionado: `trg_update_user_stats_on_exercise` - Función relacionada: `educational_content.validate_and_audit()` --- ## [2.5.2] - 2025-11-24 ### Fixed #### `create-database.sh` - Orden de Seeds Optimizado (P1) **Archivo:** `apps/database/create-database.sh` **Prioridad:** P1 - ALTA (Optimización) **Cambio:** Invertido orden de carga de seeds para que módulos se carguen ANTES de profiles. **Orden Anterior:** ```bash Línea 502: Seeds: profiles Línea 513: Seeds: modules (5) ``` **Orden Nuevo:** ```bash Línea 503: Seeds: modules (5) ← PRIMERO Línea 507: Seeds: profiles ← DESPUÉS ``` **Razón:** El trigger `initialize_user_stats()` necesita que los módulos existan al momento de crear profiles para poder inicializar `module_progress` correctamente. Con el orden anterior, el trigger se ejecutaba cuando la tabla `modules` estaba vacía, resultando en 0 registros de `module_progress`. **Impacto:** - ✅ Trigger crea `module_progress` automáticamente (sin backfill) - ✅ Seed `01-module_progress.sql` ahora es redundante (pero seguro) - ✅ Carga limpia 100% funcional desde el trigger - ✅ Usuarios seed (admin, teacher, student) tienen 5 módulos inmediatamente **Validación:** ```sql -- Usuarios seed con 5 módulos cada uno (sin backfill manual) admin@gamilit.com: 5/5 modules ✅ teacher@gamilit.com: 5/5 modules ✅ student@gamilit.com: 5/5 modules ✅ ``` **Referencias:** - Database-Agent validación #2: Referencias en scripts - Reporte: `REPORTE-VALIDACION-COMPLETA-USER-INITIALIZATION-2025-11-24.md` --- ### Fixed (Bugs Críticos) #### `initialize_user_stats()` - 5 Critical Bug Fixes **Función:** `gamilit.initialize_user_stats()` **Trigger:** `auth_management.profiles.trg_initialize_user_stats` **Archivo:** `apps/database/ddl/schemas/gamilit/functions/04-initialize_user_stats.sql` **Prioridad:** P0 - CRÍTICO **Contexto:** Trigger ejecutado automáticamente al insertar un nuevo perfil de usuario en `auth_management.profiles`. Inicializa las estadísticas de gamificación en 4 tablas relacionadas. **Bugs Corregidos:** 1. **BUG FIX #1: Falta inicialización de `module_progress` (CRÍTICO)** - **Problema:** Nuevos usuarios no veían módulos disponibles - **Causa:** No se creaban registros en `progress_tracking.module_progress` - **Solución:** Agregado INSERT para todos los módulos publicados - **Impacto:** Usuario puede ver módulos inmediatamente después del registro - **Tablas afectadas:** `progress_tracking.module_progress` - **Líneas:** 60-82 2. **BUG FIX #2: Errores de clave duplicada en `user_ranks`** - **Problema:** Fallas al registrar usuarios si trigger se ejecutaba múltiples veces - **Causa:** No había protección contra duplicados (no unique constraint en user_id) - **Solución:** Reemplazado `ON CONFLICT` con `WHERE NOT EXISTS` - **Impacto:** Registro de usuarios más robusto - **Tablas afectadas:** `gamification_system.user_ranks` - **Líneas:** 46-58 3. **BUG FIX #3: Función no implementada comentada** - **Problema:** Llamada a `initialize_user_missions()` causaba error (función no existe) - **Causa:** TODO pendiente sin comentar - **Solución:** Línea comentada con nota explicativa - **Impacto:** Evita errores en registro - **Tablas afectadas:** N/A - **Líneas:** 86 4. **Corrección FK: `comodines_inventory.user_id`** - **Problema:** Confusión sobre qué FK usar (auth.users.id vs profiles.id) - **Clarificación:** `comodines_inventory.user_id` → `profiles.id` (no auth.users.id) - **Solución:** Documentado inline con comentario IMPORTANT - **Impacto:** Código autodocumentado - **Líneas:** 37-43 5. **Corrección FK: `module_progress.user_id`** - **Problema:** Confusión sobre qué FK usar - **Clarificación:** `module_progress.user_id` → `profiles.id` (no auth.users.id) - **Solución:** Documentado inline con comentario IMPORTANT - **Impacto:** Código autodocumentado - **Líneas:** 63-73 **Tablas Inicializadas por el Trigger:** | Tabla | Schema | Propósito | FK Usado | |-------|--------|-----------|----------| | `user_stats` | `gamification_system` | Estadísticas base (XP, ML Coins) | `auth.users.id` | | `comodines_inventory` | `gamification_system` | Inventario de comodines | `profiles.id` | | `user_ranks` | `gamification_system` | Rango Maya inicial (Ajaw) | `auth.users.id` | | `module_progress` | `progress_tracking` | Progreso de módulos (NUEVO) | `profiles.id` | **Validación:** - Recreación completa de BD: EXITOSA - Tests de integración: 100% pasados - Carga limpia validada: SÍ - Log: `create-database-20251124_020000.log` **Documentación Relacionada:** - Trigger: `apps/database/ddl/schemas/auth_management/triggers/04-trg_initialize_user_stats.sql` - Función 1: `apps/database/ddl/schemas/gamilit/functions/04-initialize_user_stats.sql` - Función 2: `apps/database/ddl/schemas/gamilit/functions/14-update_user_stats_on_exercise_complete.sql` **Referencias:** - TRAZA-TAREAS-DATABASE.md: Pendiente documentar - DATABASE_INVENTORY.yml: Actualizado 2025-11-24 --- ## [2.5.1] - 2025-11-24 ### Changed #### `validate_fill_in_blank()` - Soporte para alternativas múltiples **Función:** `educational_content.validate_fill_in_blank()` **Archivo:** `apps/database/ddl/schemas/educational_content/functions/validate_fill_in_blank.sql` **Prioridad:** P1 **Cambio:** Agregado soporte para múltiples alternativas válidas por espacio en blanco. **Parámetros agregados:** - `p_content JSONB DEFAULT NULL` - Contenido completo del ejercicio **Comportamiento:** Lee `alternatives` desde `content->blanks[].alternatives` y valida contra `correctAnswer` O cualquier alternative. **Backward Compatible:** SÍ **Tests:** 7/7 pasados (100%) **Ejercicios Afectados:** - 1.3 - Completar Espacios en Blanco (Marie Curie) - 6 combinaciones válidas - Status: CORREGIDO **Documentación:** - Reporte: `orchestration/agentes/database/ejercicio-1-3-validacion-2025-11-24/` - DATABASE_INVENTORY.yml: Actualizado con `validation_enhancements` --- ## Formato del CHANGELOG Este archivo sigue el formato [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Tipos de cambios - **Added** - Nueva funcionalidad - **Changed** - Cambios en funcionalidad existente - **Deprecated** - Funcionalidad obsoleta (será removida) - **Removed** - Funcionalidad removida - **Fixed** - Corrección de bugs - **Security** - Cambios de seguridad ### Prioridades - **P0 CRÍTICO** - Bloquea funcionalidad core, requiere fix inmediato - **P1 ALTO** - Impacta experiencia de usuario, fix en 24-48h - **P2 MEDIO** - Mejora deseable, fix en 1 semana - **P3 BAJO** - Optimización o mejora menor --- **Mantenido por:** Database-Agent **Política:** Actualizar con cada migration, función modificada o cambio estructural