diff --git a/core/orchestration/directivas/DIRECTIVA-DOCUMENTACION-DEFINITIVA.md b/core/orchestration/directivas/DIRECTIVA-DOCUMENTACION-DEFINITIVA.md new file mode 100644 index 0000000..335e948 --- /dev/null +++ b/core/orchestration/directivas/DIRECTIVA-DOCUMENTACION-DEFINITIVA.md @@ -0,0 +1,313 @@ +# DIRECTIVA: DOCUMENTACION DEFINITIVA + +**Version:** 1.0.0 +**Fecha:** 2025-12-18 +**Nivel:** CORE +**Tipo:** Directiva Fundamental - OBLIGATORIA +**Alias:** @DOC-DEFINITIVA +**Aplica a:** TODOS los agentes y subagentes en TODOS los niveles + +--- + +## DECLARACION DE LA DIRECTIVA + +``` ++==============================================================================+ +| | +| LA DOCUMENTACION ES EL ESTADO FINAL DEL SISTEMA | +| | +| "docs/ refleja SIEMPRE el estado actual, nunca el historico." | +| "Si algo cambio, la documentacion refleja el RESULTADO, no el cambio." | +| "Los cambios, correcciones e historicos van en orchestration/." | +| | ++==============================================================================+ +``` + +--- + +## PRINCIPIO FUNDAMENTAL + +### La Documentacion como Espejo del Sistema + +```yaml +Principio: + docs/: + - Contiene SOLO estado actual y definitivo + - Es un "espejo" de lo que existe implementado + - NO contiene historico de cambios + - NO contiene correcciones pendientes + - NO contiene discrepancias o gaps + + orchestration/: + - Contiene planes, analisis, correcciones + - Contiene historico de cambios + - Contiene reportes de implementacion + - Contiene trazas de tareas + - Contiene documentacion de proceso + +Razon: + - Onboarding rapido (docs/ = estado actual) + - Sin confusion entre "lo que es" vs "lo que fue" + - SSOT (Single Source of Truth) para el estado del sistema +``` + +--- + +## ESTRUCTURA DE DOCUMENTACION (Patron Universal) + +### docs/ - Estado Definitivo + +``` +docs/ +├── 00-vision-general/ # Vision y proposito (definitivo) +│ └── directivas/ # Directivas especificas del proyecto +├── 01-fase-*/ # Especificaciones por fase (definitivo) +├── 90-transversal/ # Documentacion cross-cutting (definitivo) +│ ├── arquitectura/ # Arquitectura actual +│ ├── features/ # Features implementadas +│ └── correcciones/ # SOLO backlog activo +├── 95-guias-desarrollo/ # Guias de desarrollo (definitivo) +├── 96-quick-reference/ # Referencias rapidas (definitivo) +└── 97-adr/ # Decisiones arquitectonicas (definitivo) +``` + +### orchestration/ - Proceso y Cambios + +``` +orchestration/ +├── 00-guidelines/ # Contexto del proyecto +│ ├── CONTEXTO-PROYECTO.md +│ ├── HERENCIA-SIMCO.md +│ └── HERENCIA-DIRECTIVAS.md +├── inventarios/ # SSOT de componentes +├── reportes/ # Historico de cambios +│ ├── historicos/ # Reportes por fecha +│ ├── correcciones/ # Correcciones aplicadas +│ ├── implementacion/ # Reportes de desarrollo +│ └── gaps/ # Gaps identificados/cerrados +├── trazas/ # Trazas de tareas +├── analisis/ # Analisis y planes +└── directivas/ # Directivas de proceso (opcionales) +``` + +--- + +## REGLAS DE ACTUALIZACION + +### Al Completar una Tarea (Fase D de CAPVED) + +```yaml +Actualizacion_Obligatoria: + + Durante_Ejecucion: + # Cuando implementas un cambio: + - Actualizar documentacion en docs/ como estado FINAL + - NO escribir "se corrigio X" o "se cambio de Y a Z" + - SI escribir el estado actual resultante + + Al_Finalizar: + # Cuando terminas la tarea: + - Verificar que docs/ refleja estado actual + - Generar reporte en orchestration/reportes/ con historico + - Actualizar inventarios con metricas actuales +``` + +### Ejemplo de Actualizacion Correcta + +```yaml +Situacion: "Se corrigio el numero de schemas de 14 a 16" + +INCORRECTO_en_docs/: + texto: "Actualizado: Antes habia 14 schemas, ahora hay 16" + razon: "Esto es historico, no estado actual" + +CORRECTO_en_docs/: + texto: "Total schemas: 16" + razon: "Solo estado actual, sin referencia a lo anterior" + +CORRECTO_en_orchestration/: + archivo: "orchestration/reportes/correcciones/CORRECCION-{fecha}.md" + contenido: | + ## Correccion de Metricas + - Antes: 14 schemas + - Despues: 16 schemas + - Archivos actualizados: [lista] +``` + +--- + +## SEPARACION DE CONTENIDO + +### Que VA en docs/ + +```yaml +Contenido_Definitivo: + - Especificaciones tecnicas actuales + - Arquitectura actual del sistema + - Guias de desarrollo vigentes + - Referencias rapidas actualizadas + - ADRs (decisiones arquitectonicas) + - Features implementadas (estado actual) + - Backlog activo (issues pendientes) + - Directivas especificas del proyecto + +Formato: + - Siempre en presente o futuro + - Sin referencias a "antes" o "se cambio" + - Metricas siempre actuales + - Fechas de ultima actualizacion (no de creacion) +``` + +### Que VA en orchestration/ + +```yaml +Contenido_de_Proceso: + - Planes de implementacion + - Reportes de analisis + - Historico de correcciones + - Trazas de tareas completadas + - Reportes por fecha + - Gaps identificados y cerrados + - Lecciones aprendidas + - Contexto y herencia del proyecto + +Formato: + - Puede incluir "antes/despues" + - Incluye fechas de ejecucion + - Incluye contexto de cambios + - Incluye metricas historicas +``` + +--- + +## SSOT (Single Source of Truth) + +### Inventarios como SSOT + +```yaml +SSOT_para_Metricas: + archivo: "orchestration/inventarios/MASTER_INVENTORY.yml" + contiene: + - Metricas de database (schemas, tablas, etc.) + - Metricas de backend (modulos, endpoints, etc.) + - Metricas de frontend (componentes, hooks, etc.) + +Regla: + - TODA documentacion que cite metricas debe referenciar este archivo + - O debe ser actualizada cuando este archivo cambie + - NO duplicar valores, referenciar SSOT +``` + +### Actualizacion de Referencias + +```yaml +Cuando_cambia_SSOT: + 1. Actualizar MASTER_INVENTORY.yml + 2. Actualizar docs/ que citan las metricas + 3. Generar reporte en orchestration/reportes/ + +Archivos_tipicos_que_citan_metricas: + - docs/README.md + - docs/95-guias-desarrollo/README.md + - docs/96-quick-reference/*.md + - orchestration/00-guidelines/CONTEXTO-PROYECTO.md +``` + +--- + +## TRAZABILIDAD DE DOCUMENTACION DEPRECADA + +### Cuando se Mueve Documentacion + +```yaml +Proceso: + 1. Mover archivo de docs/ a orchestration/reportes/ + 2. Registrar en TRAZA-DOCUMENTACION-DEPRECADA.md + 3. Actualizar referencias en documentos vigentes + 4. Actualizar _MAP.md correspondiente + +Archivo_de_Traza: + ubicacion: "orchestration/trazas/TRAZA-DOCUMENTACION-DEPRECADA.md" + contenido: + - Ubicacion original + - Nueva ubicacion + - Fecha de movimiento + - Razon del movimiento + - Referencias actualizadas +``` + +--- + +## CHECKLIST DE DOCUMENTACION + +### Al Finalizar Cada Tarea + +``` +DOCUMENTACION DEFINITIVA (docs/) +[ ] docs/ refleja estado actual (no historico) +[ ] Metricas estan actualizadas +[ ] No hay referencias a "antes" o "se cambio" +[ ] Fechas de ultima actualizacion correctas + +DOCUMENTACION DE PROCESO (orchestration/) +[ ] Reporte de tarea en orchestration/reportes/ +[ ] Traza actualizada si aplica +[ ] Inventarios actualizados +[ ] Historico documentado + +TRAZABILIDAD +[ ] MASTER_INVENTORY.yml actualizado +[ ] Referencias cruzadas actualizadas +[ ] Sin enlaces rotos +``` + +--- + +## INTEGRACION CON CAPVED + +Esta directiva se ejecuta principalmente en la **Fase D (Documentacion)** del ciclo CAPVED: + +```yaml +Fase_D_Documentacion: + durante: + - Actualizar docs/ como estado FINAL + - NO escribir historico de cambios + + despues: + - Generar reporte en orchestration/reportes/ + - Actualizar inventarios + - Registrar traza si aplica +``` + +--- + +## APLICACION POR NIVEL + +Esta directiva aplica a TODOS los niveles del workspace: + +| Nivel | Ubicacion docs/ | Ubicacion orchestration/ | +|-------|-----------------|--------------------------| +| WORKSPACE | N/A | workspace/orchestration/ | +| CORE | N/A | core/orchestration/ | +| STANDALONE | projects/{p}/docs/ | projects/{p}/orchestration/ | +| SUITE | projects/{s}/docs/ | projects/{s}/orchestration/ | +| VERTICAL | .../verticales/{v}/docs/ | .../verticales/{v}/orchestration/ | + +--- + +## REFERENCIAS + +| Documento | Alias | Proposito | +|-----------|-------|-----------| +| PRINCIPIO-DOC-PRIMERO.md | @DOC-PRIMERO | Documentar antes de implementar | +| PRINCIPIO-CAPVED.md | @CAPVED | Ciclo de vida de tareas | +| SIMCO-DOCUMENTAR.md | @DOCUMENTAR | Proceso detallado de documentacion | +| INDICE-DIRECTIVAS-WORKSPACE.yml | @INDICE | Indice maestro de directivas | + +--- + +**Esta directiva es OBLIGATORIA y define como documentar el sistema en TODOS los niveles.** + +--- + +**Version:** 1.0.0 | **Nivel:** CORE | **Sistema:** SIMCO v2.3.0 diff --git a/orchestration/INDICE-DIRECTIVAS-WORKSPACE.yml b/orchestration/INDICE-DIRECTIVAS-WORKSPACE.yml new file mode 100644 index 0000000..7a3e207 --- /dev/null +++ b/orchestration/INDICE-DIRECTIVAS-WORKSPACE.yml @@ -0,0 +1,336 @@ +# INDICE MAESTRO DE DIRECTIVAS DEL WORKSPACE +# ============================================ +# Este archivo es el SSOT para todas las directivas disponibles +# Consultar antes de cargar directivas en cualquier nivel + +version: "1.0.0" +fecha_actualizacion: "2025-12-18" +descripcion: "Indice maestro de todas las directivas del workspace" + +# ============================================ +# NIVELES Y SUS DIRECTIVAS +# ============================================ + +niveles: + + # ------------------------------------------ + # NIVEL 0: WORKSPACE + # ------------------------------------------ + WORKSPACE: + ruta_base: "/home/isem/workspace" + descripcion: "Coordinacion global de todos los proyectos" + directivas: + - archivo: "orchestration/directivas/DIRECTIVA-CARGA-CONTEXTO.md" + alias: "@CARGA-CONTEXTO" + tipo: "proceso" + obligatoria: true + descripcion: "Como cargar contexto segun nivel de trabajo" + + # ------------------------------------------ + # NIVEL 1: CORE + # ------------------------------------------ + CORE: + ruta_base: "/home/isem/workspace/core" + descripcion: "Infraestructura compartida - directivas SIMCO" + + principios: + - archivo: "orchestration/directivas/principios/PRINCIPIO-CAPVED.md" + alias: "@CAPVED" + obligatoria: true + descripcion: "Ciclo de vida de tareas (6 fases)" + + - archivo: "orchestration/directivas/principios/PRINCIPIO-DOC-PRIMERO.md" + alias: "@DOC-PRIMERO" + obligatoria: true + descripcion: "Documentar antes de implementar" + + - archivo: "orchestration/directivas/principios/PRINCIPIO-ANTI-DUPLICACION.md" + alias: "@ANTI-DUP" + obligatoria: true + descripcion: "Verificar catalogo antes de crear" + + - archivo: "orchestration/directivas/principios/PRINCIPIO-VALIDACION-OBLIGATORIA.md" + alias: "@VALIDAR" + obligatoria: true + descripcion: "Build/lint obligatorios" + + - archivo: "orchestration/directivas/principios/PRINCIPIO-ECONOMIA-TOKENS.md" + alias: "@ECONOMIA" + obligatoria: false + descripcion: "Optimizar uso de tokens" + + - archivo: "orchestration/directivas/principios/PRINCIPIO-NO-ASUMIR.md" + alias: "@NO-ASUMIR" + obligatoria: false + descripcion: "Verificar antes de asumir" + + operaciones_simco: + - archivo: "orchestration/directivas/simco/SIMCO-TAREA.md" + alias: "@TAREA" + obligatoria: true + descripcion: "Proceso completo para HU/tareas" + + - archivo: "orchestration/directivas/simco/SIMCO-INICIALIZACION.md" + alias: "@INIT" + obligatoria: false + descripcion: "Protocolo CCA de inicializacion" + + - archivo: "orchestration/directivas/simco/SIMCO-CREAR.md" + alias: "@CREAR" + obligatoria: false + descripcion: "Crear nuevos componentes" + + - archivo: "orchestration/directivas/simco/SIMCO-MODIFICAR.md" + alias: "@MODIFICAR" + obligatoria: false + descripcion: "Modificar componentes existentes" + + - archivo: "orchestration/directivas/simco/SIMCO-VALIDAR.md" + alias: "@VALIDAR-OP" + obligatoria: false + descripcion: "Validar implementaciones" + + - archivo: "orchestration/directivas/simco/SIMCO-DOCUMENTAR.md" + alias: "@DOCUMENTAR" + obligatoria: false + descripcion: "Documentar cambios" + + - archivo: "orchestration/directivas/simco/SIMCO-BUSCAR.md" + alias: "@BUSCAR" + obligatoria: false + descripcion: "Buscar en codebase" + + - archivo: "orchestration/directivas/simco/SIMCO-DELEGACION.md" + alias: "@DELEGAR" + obligatoria: false + descripcion: "Delegar a subagentes" + + operaciones_dominio: + - archivo: "orchestration/directivas/simco/SIMCO-DDL.md" + alias: "@OP_DDL" + dominio: "database" + descripcion: "Operaciones PostgreSQL" + + - archivo: "orchestration/directivas/simco/SIMCO-BACKEND.md" + alias: "@OP_BACKEND" + dominio: "backend" + descripcion: "Operaciones NestJS/Express" + + - archivo: "orchestration/directivas/simco/SIMCO-FRONTEND.md" + alias: "@OP_FRONTEND" + dominio: "frontend" + descripcion: "Operaciones React" + + - archivo: "orchestration/directivas/simco/SIMCO-MOBILE.md" + alias: "@OP_MOBILE" + dominio: "mobile" + descripcion: "Operaciones React Native" + + - archivo: "orchestration/directivas/simco/SIMCO-ML.md" + alias: "@OP_ML" + dominio: "ml" + descripcion: "Operaciones ML/AI" + + documentacion: + - archivo: "orchestration/directivas/DIRECTIVA-DOCUMENTACION-DEFINITIVA.md" + alias: "@DOC-DEFINITIVA" + obligatoria: true + descripcion: "Docs como estado final del sistema" + + catalogo: + - archivo: "catalog/CATALOG-INDEX.yml" + alias: "@CATALOG" + descripcion: "Indice de funcionalidades reutilizables" + + # ------------------------------------------ + # NIVEL 2A: PROYECTOS STANDALONE + # ------------------------------------------ + STANDALONE: + patron_ruta: "/home/isem/workspace/projects/{proyecto}" + descripcion: "Proyectos autonomos sin subproyectos" + + directivas_obligatorias: + - archivo: "orchestration/00-guidelines/CONTEXTO-PROYECTO.md" + descripcion: "Variables de contexto del proyecto" + + - archivo: "orchestration/00-guidelines/HERENCIA-SIMCO.md" + descripcion: "Define herencia de directivas SIMCO" + + directivas_opcionales: + - patron: "docs/00-vision-general/directivas/*.md" + descripcion: "Directivas especificas del proyecto" + + - patron: "orchestration/directivas/*.md" + descripcion: "Directivas de proceso del proyecto" + + proyectos: + - nombre: "gamilit" + descripcion: "Plataforma educativa gamificada" + tiene_docs: true + tiene_orchestration: true + + - nombre: "trading-platform" + descripcion: "Plataforma de trading" + tiene_docs: true + tiene_orchestration: true + + - nombre: "betting-analytics" + descripcion: "Analytics de apuestas" + tiene_docs: true + tiene_orchestration: true + + - nombre: "inmobiliaria-analytics" + descripcion: "Analytics inmobiliaria" + tiene_docs: true + tiene_orchestration: true + + - nombre: "platform_marketing_content" + descripcion: "Contenido de marketing" + tiene_docs: true + tiene_orchestration: true + + # ------------------------------------------ + # NIVEL 2B: SUITE (erp-suite) + # ------------------------------------------ + SUITE: + ruta_base: "/home/isem/workspace/projects/erp-suite" + descripcion: "Suite multi-vertical con codigo compartido" + + directivas_obligatorias: + - archivo: "orchestration/00-guidelines/CONTEXTO-PROYECTO.md" + - archivo: "orchestration/00-guidelines/HERENCIA-SIMCO.md" + + subniveles: + + SUITE-CORE: + ruta: "apps/erp-core" + descripcion: "Base de codigo compartida (60-70%)" + hereda_de: ["SUITE"] + directivas_adicionales: + - archivo: "orchestration/00-guidelines/HERENCIA-SPECS-CORE.md" + + VERTICALES: + patron_ruta: "apps/verticales/{vertical}" + descripcion: "Verticales especializadas" + hereda_de: ["SUITE", "SUITE-CORE"] + directivas_adicionales: + - archivo: "orchestration/00-guidelines/HERENCIA-ERP-CORE.md" + - archivo: "orchestration/00-guidelines/HERENCIA-SPECS-ERP-CORE.md" + + verticales: + - nombre: "clinicas" + descripcion: "ERP para clinicas" + - nombre: "construccion" + descripcion: "ERP para construccion" + - nombre: "mecanicas-diesel" + descripcion: "ERP para mecanicas diesel" + - nombre: "retail" + descripcion: "ERP para retail" + - nombre: "vidrio-templado" + descripcion: "ERP para vidrio templado" + + SUITE-SERVICE: + ruta: "apps/saas" + descripcion: "Servicios transversales" + hereda_de: ["SUITE"] + + SUITE-PRODUCT: + patron_ruta: "apps/products/{producto}" + descripcion: "Productos derivados" + hereda_de: ["SUITE", "SUITE-CORE"] + productos: + - nombre: "erp-basico" + - nombre: "pos-micro" + +# ============================================ +# CADENAS DE HERENCIA +# ============================================ + +cadenas_herencia: + STANDALONE: + orden: + - nivel: "WORKSPACE" + directivas: ["@CARGA-CONTEXTO"] + - nivel: "CORE" + directivas: ["@CAPVED", "@DOC-PRIMERO", "@TAREA", "@DOC-DEFINITIVA"] + - nivel: "PROYECTO" + directivas: ["CONTEXTO-PROYECTO.md", "HERENCIA-SIMCO.md"] + + SUITE: + orden: + - nivel: "WORKSPACE" + directivas: ["@CARGA-CONTEXTO"] + - nivel: "CORE" + directivas: ["@CAPVED", "@DOC-PRIMERO", "@TAREA", "@DOC-DEFINITIVA"] + - nivel: "SUITE" + directivas: ["CONTEXTO-PROYECTO.md", "HERENCIA-SIMCO.md"] + + SUITE-CORE: + orden: + - nivel: "WORKSPACE" + directivas: ["@CARGA-CONTEXTO"] + - nivel: "CORE" + directivas: ["@CAPVED", "@DOC-PRIMERO", "@TAREA", "@DOC-DEFINITIVA"] + - nivel: "SUITE" + directivas: ["CONTEXTO-PROYECTO.md"] + - nivel: "SUITE-CORE" + directivas: ["HERENCIA-SIMCO.md", "HERENCIA-SPECS-CORE.md"] + + VERTICAL: + orden: + - nivel: "WORKSPACE" + directivas: ["@CARGA-CONTEXTO"] + - nivel: "CORE" + directivas: ["@CAPVED", "@DOC-PRIMERO", "@TAREA", "@DOC-DEFINITIVA"] + - nivel: "SUITE" + directivas: ["CONTEXTO-PROYECTO.md"] + - nivel: "SUITE-CORE" + directivas: ["HERENCIA-SPECS-CORE.md"] + - nivel: "VERTICAL" + directivas: ["HERENCIA-SIMCO.md", "HERENCIA-ERP-CORE.md"] + +# ============================================ +# ALIASES GLOBALES +# ============================================ + +aliases: + # Principios + "@CAPVED": "core/orchestration/directivas/principios/PRINCIPIO-CAPVED.md" + "@DOC-PRIMERO": "core/orchestration/directivas/principios/PRINCIPIO-DOC-PRIMERO.md" + "@ANTI-DUP": "core/orchestration/directivas/principios/PRINCIPIO-ANTI-DUPLICACION.md" + "@VALIDAR": "core/orchestration/directivas/principios/PRINCIPIO-VALIDACION-OBLIGATORIA.md" + + # Operaciones + "@TAREA": "core/orchestration/directivas/simco/SIMCO-TAREA.md" + "@CREAR": "core/orchestration/directivas/simco/SIMCO-CREAR.md" + "@MODIFICAR": "core/orchestration/directivas/simco/SIMCO-MODIFICAR.md" + "@BUSCAR": "core/orchestration/directivas/simco/SIMCO-BUSCAR.md" + "@DELEGAR": "core/orchestration/directivas/simco/SIMCO-DELEGACION.md" + + # Dominios + "@OP_DDL": "core/orchestration/directivas/simco/SIMCO-DDL.md" + "@OP_BACKEND": "core/orchestration/directivas/simco/SIMCO-BACKEND.md" + "@OP_FRONTEND": "core/orchestration/directivas/simco/SIMCO-FRONTEND.md" + + # Documentacion + "@DOC-DEFINITIVA": "core/orchestration/directivas/DIRECTIVA-DOCUMENTACION-DEFINITIVA.md" + + # Workspace + "@CARGA-CONTEXTO": "orchestration/directivas/DIRECTIVA-CARGA-CONTEXTO.md" + "@INDICE": "orchestration/INDICE-DIRECTIVAS-WORKSPACE.yml" + + # Catalogo + "@CATALOG": "core/catalog/CATALOG-INDEX.yml" + +# ============================================ +# METADATA +# ============================================ + +metadata: + total_niveles: 7 + total_proyectos_standalone: 5 + total_verticales: 5 + total_directivas_core: 28 + sistema_simco_version: "2.3.0" + ultima_actualizacion: "2025-12-18" + mantenido_por: "Requirements-Analyst" diff --git a/orchestration/directivas/DIRECTIVA-CARGA-CONTEXTO.md b/orchestration/directivas/DIRECTIVA-CARGA-CONTEXTO.md new file mode 100644 index 0000000..7d20ac0 --- /dev/null +++ b/orchestration/directivas/DIRECTIVA-CARGA-CONTEXTO.md @@ -0,0 +1,268 @@ +# DIRECTIVA: CARGA DE CONTEXTO SEGUN NIVEL + +**Version:** 1.0.0 +**Fecha:** 2025-12-18 +**Nivel:** WORKSPACE +**Tipo:** Directiva Fundamental - OBLIGATORIA +**Aplica a:** TODOS los agentes y subagentes + +--- + +## PROPOSITO + +Esta directiva define **COMO** cargar las directivas correctas segun el nivel de trabajo especificado en el prompt. + +--- + +## PASO 1: IDENTIFICAR NIVEL DE TRABAJO + +Cuando recibas un prompt con: +- **proyecto:** {nombre} +- **subproyecto:** {nombre} (opcional) +- **tarea:** {descripcion} + +Determinar el nivel segun esta tabla: + +| Si proyecto es... | Y subproyecto es... | Entonces nivel es... | +|-------------------|---------------------|----------------------| +| gamilit, trading-platform, betting-analytics, inmobiliaria-analytics | null | **STANDALONE** | +| erp-suite | null | **SUITE** | +| erp-suite | erp-core | **SUITE-CORE** | +| erp-suite | verticales/{nombre} | **VERTICAL** | +| erp-suite | saas | **SUITE-SERVICE** | +| erp-suite | products/{nombre} | **SUITE-PRODUCT** | + +--- + +## PASO 2: OBTENER CADENA DE HERENCIA + +Segun el nivel identificado, cargar directivas en este orden: + +### STANDALONE +``` +1. WORKSPACE → workspace/orchestration/directivas/ +2. CORE → core/orchestration/directivas/principios/ +3. CORE → core/orchestration/directivas/simco/ +4. PROYECTO → projects/{proyecto}/orchestration/00-guidelines/ +5. PROYECTO → projects/{proyecto}/docs/00-vision-general/directivas/ +``` + +### SUITE +``` +1. WORKSPACE → workspace/orchestration/directivas/ +2. CORE → core/orchestration/directivas/principios/ +3. CORE → core/orchestration/directivas/simco/ +4. SUITE → projects/erp-suite/orchestration/00-guidelines/ +5. SUITE → projects/erp-suite/docs/00-vision-general/directivas/ +``` + +### SUITE-CORE +``` +1. WORKSPACE → workspace/orchestration/directivas/ +2. CORE → core/orchestration/directivas/principios/ +3. CORE → core/orchestration/directivas/simco/ +4. SUITE → projects/erp-suite/orchestration/00-guidelines/ +5. SUITE-CORE → projects/erp-suite/apps/erp-core/orchestration/00-guidelines/ +6. SUITE-CORE → projects/erp-suite/apps/erp-core/docs/00-vision-general/directivas/ +``` + +### VERTICAL +``` +1. WORKSPACE → workspace/orchestration/directivas/ +2. CORE → core/orchestration/directivas/principios/ +3. CORE → core/orchestration/directivas/simco/ +4. SUITE → projects/erp-suite/orchestration/00-guidelines/ +5. SUITE-CORE → projects/erp-suite/apps/erp-core/orchestration/00-guidelines/ +6. VERTICAL → projects/erp-suite/apps/verticales/{vertical}/orchestration/00-guidelines/ +7. VERTICAL → projects/erp-suite/apps/verticales/{vertical}/docs/00-vision-general/directivas/ +``` + +--- + +## PASO 3: CARGAR DIRECTIVAS OBLIGATORIAS + +### Desde CORE (Siempre obligatorias) + +```yaml +Principios_Obligatorios: + - core/orchestration/directivas/principios/PRINCIPIO-CAPVED.md + - core/orchestration/directivas/principios/PRINCIPIO-DOC-PRIMERO.md + - core/orchestration/directivas/principios/PRINCIPIO-ANTI-DUPLICACION.md + - core/orchestration/directivas/principios/PRINCIPIO-VALIDACION-OBLIGATORIA.md + +Operacion_Principal: + - core/orchestration/directivas/simco/SIMCO-TAREA.md + +Documentacion: + - core/orchestration/directivas/DIRECTIVA-DOCUMENTACION-DEFINITIVA.md +``` + +### Desde PROYECTO (Siempre obligatorias) + +```yaml +Contexto: + - {proyecto}/orchestration/00-guidelines/CONTEXTO-PROYECTO.md + - {proyecto}/orchestration/00-guidelines/HERENCIA-SIMCO.md +``` + +### Segun Operacion (Cargar si aplica) + +```yaml +Si_operacion_es_DDL: + - core/orchestration/directivas/simco/SIMCO-DDL.md + +Si_operacion_es_BACKEND: + - core/orchestration/directivas/simco/SIMCO-BACKEND.md + +Si_operacion_es_FRONTEND: + - core/orchestration/directivas/simco/SIMCO-FRONTEND.md + +Si_operacion_es_CREAR: + - core/orchestration/directivas/simco/SIMCO-CREAR.md + +Si_operacion_es_MODIFICAR: + - core/orchestration/directivas/simco/SIMCO-MODIFICAR.md +``` + +--- + +## PASO 4: RESOLVER CONTEXTO DEL PROYECTO + +Leer `CONTEXTO-PROYECTO.md` del nivel mas especifico y obtener: + +```yaml +Variables_a_Resolver: + PROJECT: "{nombre del proyecto}" + PROJECT_LEVEL: "{STANDALONE | SUITE | VERTICAL | etc}" + DB_NAME: "{nombre de la base de datos}" + DB_DDL_PATH: "{ruta a DDL}" + BACKEND_ROOT: "{ruta a backend}" + FRONTEND_ROOT: "{ruta a frontend}" +``` + +--- + +## PASO 5: CONFIRMAR CARGA + +Antes de proceder con la tarea, generar confirmacion: + +```yaml +Confirmacion_Carga: + nivel_identificado: "{NIVEL}" + proyecto: "{nombre}" + subproyecto: "{nombre o null}" + + directivas_cargadas: + workspace: [lista] + core_principios: [lista] + core_simco: [lista] + proyecto: [lista] + especificas: [lista] + + contexto_resuelto: + PROJECT: "{valor}" + DB_DDL_PATH: "{valor}" + BACKEND_ROOT: "{valor}" + FRONTEND_ROOT: "{valor}" + + estado: "READY_TO_EXECUTE" +``` + +--- + +## PASO 6: EJECUTAR CICLO CAPVED + +Una vez confirmada la carga, seguir el ciclo CAPVED: + +``` +C - Contexto: ✓ Completado en pasos 1-5 +A - Analisis: Analizar alcance e impacto de la tarea +P - Planeacion: Crear plan de implementacion +V - Validacion: Validar plan vs analisis (NO DELEGAR) +E - Ejecucion: Implementar en orden planificado +D - Documentacion: Actualizar docs/ como estado FINAL +``` + +--- + +## DELEGACION A SUBAGENTES + +Cuando delegues a un subagente, incluir en el prompt: + +```markdown +## Contexto para Subagente + +**Proyecto:** {nombre} +**Subproyecto:** {nombre o null} +**Nivel:** {STANDALONE | SUITE | VERTICAL | etc} + +**Directivas Cargadas:** +- {lista de directivas ya cargadas} + +**Contexto Resuelto:** +- PROJECT: {valor} +- DB_DDL_PATH: {valor} +- BACKEND_ROOT: {valor} + +**Tarea Especifica:** {descripcion de subtarea} + +**Restricciones:** +- Seguir principio CAPVED +- Documentar como estado FINAL (no historico) +- Reportar hallazgos al agente principal +``` + +--- + +## CONSULTAR INDICE MAESTRO + +Para ver todas las directivas disponibles: + +``` +Archivo: /home/isem/workspace/orchestration/INDICE-DIRECTIVAS-WORKSPACE.yml +``` + +Este archivo contiene: +- Todas las directivas por nivel +- Aliases (@CAPVED, @TAREA, etc.) +- Cadenas de herencia +- Proyectos y subproyectos disponibles + +--- + +## ERRORES COMUNES + +### Error: No se identifica el nivel +``` +Solucion: Verificar que proyecto y subproyecto estan correctamente especificados +``` + +### Error: Directiva no encontrada +``` +Solucion: Consultar INDICE-DIRECTIVAS-WORKSPACE.yml para rutas correctas +``` + +### Error: Contexto no resuelto +``` +Solucion: Verificar que CONTEXTO-PROYECTO.md existe en el nivel especificado +``` + +--- + +## REFERENCIAS + +| Documento | Alias | Proposito | +|-----------|-------|-----------| +| PRINCIPIO-CAPVED.md | @CAPVED | Ciclo de vida de tareas | +| PRINCIPIO-DOC-PRIMERO.md | @DOC-PRIMERO | Documentar antes de implementar | +| SIMCO-TAREA.md | @TAREA | Proceso detallado de tareas | +| DIRECTIVA-DOCUMENTACION-DEFINITIVA.md | @DOC-DEFINITIVA | Docs como estado final | +| INDICE-DIRECTIVAS-WORKSPACE.yml | @INDICE | Indice maestro | + +--- + +**Esta directiva es OBLIGATORIA para todo agente y subagente.** + +--- + +**Version:** 1.0.0 | **Nivel:** WORKSPACE | **Sistema:** SIMCO v2.3.0 diff --git a/projects/gamilit/CHANGELOG.md b/projects/gamilit/CHANGELOG.md index 1725868..55d934b 100644 --- a/projects/gamilit/CHANGELOG.md +++ b/projects/gamilit/CHANGELOG.md @@ -1,5 +1,216 @@ # CHANGELOG - Plataforma GAMILIT +## [2.4.2] - 2025-12-15 + +### Simplificación de Estructura Social - Solo Defaults + +Esta versión simplifica completamente la estructura de `social_features` eliminando todas las entidades demo y dejando únicamente las entidades default del sistema. + +**Tech-Leader:** Claude Opus 4.5 +**Tarea:** Simplificación de estructura de base de datos + +--- + +### Cambios Mayores + +| Archivo | Cambio | Descripción | +|---------|--------|-------------| +| `01-schools.sql` | VACIADO | Eliminadas escuelas demo (Marie Curie, IEI) | +| `02-classrooms.sql` | SIMPLIFICADO | Solo classroom DEFAULT | +| `03-classroom-members.sql` | SIMPLIFICADO | Todos los estudiantes → DEFAULT | +| `08-assign-admin-schools.sql` | EXPANDIDO | Asigna TODOS los usuarios a escuela default | + +--- + +### Entidades Eliminadas + +**Escuelas removidas:** +- Escuela Primaria Marie Curie (Ciudad de México) +- Instituto de Educación Integral (Guadalajara) + +**Aulas removidas:** +- 5to A (Marie Curie) +- 5to B (Marie Curie) +- 6to A (Marie Curie) +- Aula de Pruebas +- Parent Portal Demo + +--- + +### Estructura Final + +| Entidad | Cantidad | Código | +|---------|----------|--------| +| Escuelas | 1 | `SYSTEM-UNASSIGNED` | +| Classrooms | 1 | `DEFAULT` | +| Usuarios en escuela default | 16 | - | +| Estudiantes en classroom DEFAULT | 14 | - | + +--- + +### Corrección Técnica + +**Constraint fix:** `enrollment_method` +- Valor anterior: `auto_assignment` (inválido) +- Valor corregido: `admin_add` +- Valores válidos: `teacher_invite`, `self_enroll`, `admin_add`, `bulk_import` + +--- + +### Decisión de Diseño + +El admin crea entidades adicionales (escuelas, aulas) desde la UI según sea necesario. Esta simplificación: +- Reduce complejidad de seeds +- Facilita mantenimiento +- Clarifica flujo de datos +- Trigger `trg_assign_default_classroom` asigna automáticamente nuevos estudiantes + +--- + +## [2.4.1] - 2025-12-15 + +### Flujo de Creación de Usuarios - School Default + +Esta versión implementa el concepto de "Escuela Default" para la correcta asignación de usuarios nuevos, especialmente administradores y profesores. + +**Tech-Leader:** Claude Opus 4.5 +**Tarea:** Análisis y adaptación del flujo de creación de usuarios + +--- + +### Seeds Nuevos + +| Seed | Schema | Descripción | +|------|--------|-------------| +| `00-schools-default.sql` | social_features | Escuela "Sistema - Por Asignar" (UUID: 99999999-9999-9999-9999-999999999999) | +| `08-assign-admin-schools.sql` | auth_management | Asignación automática de escuela default a admins | + +**Total seeds activos:** 51 PROD, 52 DEV + +--- + +### Modificaciones + +- **`02-classrooms.sql`**: Classroom DEFAULT ahora apunta a la escuela del sistema en lugar de Marie Curie +- **`create-database.sh`**: Agregados nuevos seeds en el orden correcto + +--- + +### Arquitectura + +**Flujo de registro de usuarios actualizado:** +1. Usuario se registra → auth.users creado +2. Trigger crea profile → auth_management.profiles +3. Si role='student' → trigger asigna a classroom DEFAULT +4. Si role='admin_teacher' o 'super_admin' → seed asigna school DEFAULT + +**UUID fijas (sistema):** +- School Default: `99999999-9999-9999-9999-999999999999` +- Classroom Default: `00000000-0000-0000-0000-000000000001` + +--- + +### Hallazgos del Análisis + +- `AuthService.register()` SÍ estaba implementado (contrario al análisis inicial) +- `last_sign_in_at` SÍ se actualiza en login/register +- El problema de "Nunca" en admin/users es por datos NULL en BD (usuarios de seeds sin login real) + +--- + +## [2.4.0] - 2025-12-14 + +### Auditoría de Base de Datos (AUDIT-DB-001) + +Esta versión incluye correcciones críticas P0 identificadas durante la auditoría de base de datos AUDIT-DB-001. Se eliminaron referencias erróneas a Supabase, se corrigieron funciones de timestamp y se crearon 5 seeds críticos. + +**Auditoría:** AUDIT-DB-001 +**Prioridad:** P0 - CRÍTICO +**Estado:** COMPLETADO + +--- + +### P0-DUP: Funciones de Timestamp Corregidas + +**Problema:** 2 funciones usaban `NOW()` en lugar de `gamilit.now_mexico()`. + +**Archivos corregidos:** +- `gamification_system/functions/06-update_missions_updated_at.sql` +- `gamification_system/functions/07-update_notifications_updated_at.sql` + +**Cambio:** +```sql +-- ANTES (incorrecto) +NEW.updated_at = NOW(); + +-- DESPUÉS (correcto) +NEW.updated_at = gamilit.now_mexico(); +``` + +--- + +### P0-SEEDS: 5 Seeds Críticos Creados + +| Seed | Schema | Registros | +|------|--------|-----------| +| `07-user_roles.sql` | auth_management | 8 | +| `10-mission_templates.sql` | gamification_system | 11 | +| `11-module_dependencies.sql` | educational_content | 6 | +| `12-taxonomies.sql` | educational_content | 4 | +| `02-marie_curie_content.sql` | content_management | 6 | + +**Total:** 35 registros iniciales +**Coverage P0:** 100% + +--- + +### P0-DOC: Eliminación de Referencias Supabase + +**Decisión arquitectónica:** Supabase NO es parte del stack de GAMILIT. + +**Cambios realizados:** +- ~75 referencias a Supabase eliminadas en código y documentación +- `AUTH_SUPABASE` renombrado a `AUTH_BASE` en `database.constants.ts` +- Roles RLS correctamente documentados (authenticated, anon, service_role) + +**Documentación creada:** +- `docs/90-transversal/arquitectura/ARQUITECTURA-AUTENTICACION.md` + +--- + +### Archivos de Auditoría + +``` +orchestration/agentes/database-auditor/audit-2025-12-14/ +├── 01-REPORTE-ESTRUCTURA-DDL.md +├── 02-REPORTE-CARGA-LIMPIA.md +├── 03-MAPA-DEPENDENCIAS-DDL.yml +├── 04-REPORTE-VALIDACION-DEPENDENCIAS.md +├── 05-INVENTARIO-FUNCIONES-TRIGGERS.yml +├── 06-REPORTE-RLS-POLICIES.md +└── 07-REPORTE-CORRECCIONES-P0.md +``` + +--- + +### Inventarios Actualizados + +- `BACKEND_INVENTORY.yml` v2.6.0 → v2.7.0 +- `SEEDS_INVENTORY.yml` - Agregados 5 P0 seeds +- `DATABASE_INVENTORY.yml` - Referencias actualizadas + +--- + +### Métricas + +| Métrica | Antes | Después | +|---------|-------|---------| +| Seeds coverage P0 | 26.4% | 100% | +| Funciones timezone correcto | 97% | 100% | +| Referencias Supabase | 75+ | 0 | + +--- + ## [2.3.0] - 2025-11-09 ### 🎉 Resumen Ejecutivo @@ -380,7 +591,7 @@ Global Prefix: /api ## 🎓 Estado de la Plataforma ### ✅ Completamente Funcional -- Autenticación y autorización (JWT + Supabase) +- Autenticación y autorización (JWT + Custom Auth) - 5 módulos educativos con 27 ejercicios - Sistema de gamificación (achievements, ranks, ML Coins) - Portal de estudiante (28 páginas) diff --git a/projects/gamilit/IMPLEMENTATION-SETTINGS-003.md b/projects/gamilit/IMPLEMENTATION-SETTINGS-003.md index b779328..2a5abf9 100644 --- a/projects/gamilit/IMPLEMENTATION-SETTINGS-003.md +++ b/projects/gamilit/IMPLEMENTATION-SETTINGS-003.md @@ -9,7 +9,7 @@ ## Resumen Ejecutivo -Se ha implementado exitosamente el componente **AvatarUpload** reutilizable que reemplaza el placeholder de avatar por upload real a backend (Supabase Storage/S3). +Se ha implementado exitosamente el componente **AvatarUpload** reutilizable que reemplaza el placeholder de avatar por upload real a backend (S3/Storage compatible). ### Características Principales diff --git a/projects/gamilit/apps/backend/README.md b/projects/gamilit/apps/backend/README.md index 8ebf372..65644f4 100644 --- a/projects/gamilit/apps/backend/README.md +++ b/projects/gamilit/apps/backend/README.md @@ -9,7 +9,7 @@ Backend para la plataforma educativa gamificada GAMILIT. - **Language:** TypeScript 5.x (strict mode) - **ORM:** TypeORM 0.3.x (multi-datasource architecture) - **Database:** PostgreSQL 14+ (multi-schema: 11 schemas) -- **Auth:** Supabase Auth + JWT +- **Auth:** Custom Auth (patrón compatible con estándares de la industria) + JWT - **Validation:** class-validator + class-transformer - **Testing:** Jest - **Linting:** ESLint + Prettier diff --git a/projects/gamilit/apps/backend/src/app.module.ts b/projects/gamilit/apps/backend/src/app.module.ts index b56542b..2bf369e 100644 --- a/projects/gamilit/apps/backend/src/app.module.ts +++ b/projects/gamilit/apps/backend/src/app.module.ts @@ -76,6 +76,7 @@ import { RlsInterceptor } from './shared/interceptors/rls.interceptor'; }), // Database connection for 'educational_content' schema + // CORRECTED (2025-12-18): Agregado path de assignments y teacher entities TypeOrmModule.forRootAsync({ name: 'educational', // Connection name for @InjectRepository(Entity, 'educational') imports: [ConfigModule], @@ -86,7 +87,11 @@ import { RlsInterceptor } from './shared/interceptors/rls.interceptor'; username: configService.get('database.username'), password: configService.get('database.password'), database: configService.get('database.database'), - entities: [__dirname + '/modules/educational/entities/**/*.entity{.ts,.js}'], + entities: [ + __dirname + '/modules/educational/entities/**/*.entity{.ts,.js}', + __dirname + '/modules/assignments/entities/**/*.entity{.ts,.js}', + __dirname + '/modules/teacher/entities/teacher-content.entity{.ts,.js}', + ], synchronize: configService.get('database.synchronize', false), logging: configService.get('database.logging'), ssl: configService.get('database.ssl'), diff --git a/projects/gamilit/apps/backend/src/modules/admin/admin.module.ts b/projects/gamilit/apps/backend/src/modules/admin/admin.module.ts index 35ab4c2..b04e2ac 100644 --- a/projects/gamilit/apps/backend/src/modules/admin/admin.module.ts +++ b/projects/gamilit/apps/backend/src/modules/admin/admin.module.ts @@ -56,6 +56,11 @@ import { AdminMonitoringService } from './services/admin-monitoring.service'; import { AdminInterventionsService } from './services/admin-interventions.service'; import { AdminAssignmentsService } from './services/admin-assignments.service'; import { FeatureFlagsService } from './services/feature-flags.service'; +import { DashboardStatsService } from './services/statistics/dashboard-stats.service'; +import { UserStatsService } from './services/statistics/user-stats.service'; +import { ContentStatsService } from './services/statistics/content-stats.service'; +import { RecentActivityService } from './services/activity/recent-activity.service'; +import { AdminQueryBuilder } from './services/query-builders/admin.query-builder'; import { AdminGuard } from './guards/admin.guard'; import { TasksModule } from '../tasks/tasks.module'; @@ -108,6 +113,12 @@ import { TasksModule } from '../tasks/tasks.module'; AdminInterventionsService, // NEW: Student intervention alerts service (BE-001) AdminAssignmentsService, // NEW: Assignments management service (US-AE-009) FeatureFlagsService, // NEW: Feature flags service (BE-ADMIN-001-003) + // Dashboard sub-services (dependencies for AdminDashboardService) + DashboardStatsService, + UserStatsService, + ContentStatsService, + RecentActivityService, + AdminQueryBuilder, AdminGuard, ], exports: [ diff --git a/projects/gamilit/apps/backend/src/modules/assignments/assignments.module.ts b/projects/gamilit/apps/backend/src/modules/assignments/assignments.module.ts index 9db5c6e..d2a6dc2 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/assignments.module.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/assignments.module.ts @@ -6,6 +6,11 @@ * UPDATED (2025-11-08): * - Agregada entidad AssignmentExercise * - Agregada entidad AssignmentStudent + * + * CORRECTED (2025-12-18): + * - Datasource cambiado de 'content' a 'educational' para entidades Assignment + * - AssignmentClassroom usa datasource 'social' (pertenece a schema social_features) + * - Fixes EntityMetadataNotFoundError en /teacher/assignments */ import { Module } from '@nestjs/common'; @@ -21,15 +26,23 @@ import { StudentAssignmentsController } from './controllers/student-assignments. @Module({ imports: [ + // Entidades educational_content → datasource 'educational' + // Assignment, AssignmentExercise, AssignmentStudent, AssignmentSubmission pertenecen a schema educational_content TypeOrmModule.forFeature( [ Assignment, - AssignmentClassroom, AssignmentExercise, AssignmentStudent, AssignmentSubmission, ], - 'content', // Use content_management connection + 'educational', + ), + // AssignmentClassroom → datasource 'social' (pertenece a schema social_features) + TypeOrmModule.forFeature( + [ + AssignmentClassroom, + ], + 'social', ), ], controllers: [AssignmentsController, StudentAssignmentsController], diff --git a/projects/gamilit/apps/backend/src/modules/assignments/controllers/assignments.controller.ts b/projects/gamilit/apps/backend/src/modules/assignments/controllers/assignments.controller.ts index 308eb90..09b3aa2 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/controllers/assignments.controller.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/controllers/assignments.controller.ts @@ -126,6 +126,54 @@ export class AssignmentsController { }); } + /** + * GET /api/teacher/assignments/upcoming + * Get assignments with upcoming deadlines + * BAJO-008: Upcoming assignments for Teacher Dashboard + * + * NOTE: This route MUST be declared BEFORE @Get(':id') to prevent + * NestJS from interpreting 'upcoming' as an ID parameter. + */ + @Get('upcoming') + @ApiOperation({ + summary: 'Get upcoming assignments', + description: ` + Returns assignments with deadlines within the next X days (default: 7). + Includes submission statistics for each assignment. + `, + }) + @ApiQuery({ + name: 'days', + required: false, + type: Number, + description: 'Number of days to look ahead (default: 7)', + example: 7, + }) + @ApiResponse({ + status: 200, + description: 'Upcoming assignments retrieved successfully', + schema: { + example: [ + { + id: '550e8400-e29b-41d4-a716-446655440000', + title: 'Práctica Semanal: Marie Curie', + dueDate: '2025-01-20T23:59:59Z', + daysRemaining: 2, + totalStudents: 25, + submittedCount: 15, + }, + ], + }, + }) + async getUpcoming( + @Query('days') days: string = '7', + @Request() req: AuthRequest, + ) { + const teacherId = req.user!.id; + const daysAhead = parseInt(days, 10) || 7; + return this.assignmentsService.getUpcomingAssignments(teacherId, daysAhead); + } + /** * GET /api/teacher/assignments/:id * Get single assignment details @@ -574,4 +622,44 @@ export class AssignmentsController { const teacherId = req.user!.id; return this.assignmentsService.closeAssignment(id, teacherId); } + + /** + * POST /api/teacher/assignments/:id/send-reminder + * Send reminder to students who haven't submitted + * MEDIO-005: Assignment reminder functionality + */ + @Post(':id/send-reminder') + @ApiOperation({ + summary: 'Send reminder to students', + description: ` + Sends a reminder notification to all students who haven't submitted + their work for this assignment. Students who have already submitted + will not receive the reminder. + `, + }) + @ApiParam({ + name: 'id', + description: 'ID of the assignment', + type: String, + example: '550e8400-e29b-41d4-a716-446655440000', + }) + @ApiResponse({ + status: 200, + description: 'Reminders sent successfully', + schema: { + example: { + notified: 5, + alreadySubmitted: 10, + message: 'Recordatorio enviado a 5 estudiante(s). Tarea: "Ejercicio 1" - Fecha límite: 20/01/2025', + }, + }, + }) + @ApiResponse({ + status: 404, + description: 'Assignment not found or access denied', + }) + async sendReminder(@Param('id') id: string, @Request() req: AuthRequest) { + const teacherId = req.user!.id; + return this.assignmentsService.sendReminder(id, teacherId); + } } diff --git a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-exercise.entity.ts b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-exercise.entity.ts index e0d7ef9..7d76191 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-exercise.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-exercise.entity.ts @@ -29,24 +29,22 @@ import { schema: DB_SCHEMAS.EDUCATIONAL, name: DB_TABLES.EDUCATIONAL.ASSIGNMENT_EXERCISES, }) -@Index(['assignment_id']) -@Index(['exercise_id']) -@Index(['order_index']) -@Unique(['assignment_id', 'exercise_id']) +// CORRECTED (2025-12-18): Usar nombres de propiedades en lugar de nombres de columnas +@Index(['assignmentId']) +@Index(['exerciseId']) +@Index(['orderIndex']) +@Unique(['assignmentId', 'exerciseId']) export class AssignmentExercise { @PrimaryGeneratedColumn('uuid') id!: string; @Column('uuid', { name: 'assignment_id' }) - @Index() assignmentId!: string; @Column('uuid', { name: 'exercise_id' }) - @Index() exerciseId!: string; @Column('integer', { name: 'order_index' }) - @Index() orderIndex!: number; @Column('decimal', { diff --git a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-student.entity.ts b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-student.entity.ts index b87f3fb..d3c0a94 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-student.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-student.entity.ts @@ -34,19 +34,18 @@ import { schema: DB_SCHEMAS.EDUCATIONAL, name: DB_TABLES.EDUCATIONAL.ASSIGNMENT_STUDENTS, }) -@Index(['assignment_id']) -@Index(['student_id']) -@Unique(['assignment_id', 'student_id']) +// CORRECTED (2025-12-18): Usar nombres de propiedades en lugar de nombres de columnas +@Index(['assignmentId']) +@Index(['studentId']) +@Unique(['assignmentId', 'studentId']) export class AssignmentStudent { @PrimaryGeneratedColumn('uuid') id!: string; @Column('uuid', { name: 'assignment_id' }) - @Index() assignmentId!: string; @Column('uuid', { name: 'student_id' }) - @Index() studentId!: string; @CreateDateColumn({ name: 'assigned_at', type: 'timestamp with time zone' }) diff --git a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-submission.entity.ts b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-submission.entity.ts index fc87576..fa835fc 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-submission.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment-submission.entity.ts @@ -30,12 +30,13 @@ export enum SubmissionStatus { } @Entity({ schema: DB_SCHEMAS.EDUCATIONAL, name: DB_TABLES.EDUCATIONAL.ASSIGNMENT_SUBMISSIONS }) -@Index(['assignment_id']) -@Index(['student_id']) +// CORRECTED (2025-12-18): Usar nombres de propiedades en lugar de nombres de columnas +@Index(['assignmentId']) +@Index(['studentId']) @Index(['status']) -@Index(['graded_by']) -@Index(['submitted_at']) -@Unique(['assignment_id', 'student_id']) +@Index(['gradedBy']) +@Index(['submittedAt']) +@Unique(['assignmentId', 'studentId']) export class AssignmentSubmission { @PrimaryGeneratedColumn('uuid') id!: string; diff --git a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment.entity.ts b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment.entity.ts index c5f8c86..5767c85 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/entities/assignment.entity.ts @@ -29,16 +29,16 @@ export enum AssignmentType { } @Entity({ schema: DB_SCHEMAS.EDUCATIONAL, name: DB_TABLES.EDUCATIONAL.ASSIGNMENTS }) -@Index(['teacher_id']) -@Index(['is_published']) -@Index(['assignment_type']) -@Index(['due_date']) +// CORRECTED (2025-12-18): Usar nombres de propiedades en lugar de nombres de columnas +@Index(['teacherId']) +@Index(['isPublished']) +@Index(['assignmentType']) +@Index(['dueDate']) export class Assignment { @PrimaryGeneratedColumn('uuid') id!: string; @Column('uuid', { name: 'teacher_id' }) - @Index() teacherId!: string; @Column('varchar', { length: 255 }) diff --git a/projects/gamilit/apps/backend/src/modules/assignments/services/assignments.service.ts b/projects/gamilit/apps/backend/src/modules/assignments/services/assignments.service.ts index 923aa42..a274d73 100644 --- a/projects/gamilit/apps/backend/src/modules/assignments/services/assignments.service.ts +++ b/projects/gamilit/apps/backend/src/modules/assignments/services/assignments.service.ts @@ -27,15 +27,17 @@ export class AssignmentsService { private readonly logger = new Logger(AssignmentsService.name); constructor( - @InjectRepository(Assignment, 'content') + // Datasource 'educational' para entidades de educational_content schema + @InjectRepository(Assignment, 'educational') private readonly assignmentRepository: Repository, - @InjectRepository(AssignmentClassroom, 'content') + // Datasource 'social' para AssignmentClassroom (social_features schema) + @InjectRepository(AssignmentClassroom, 'social') private readonly assignmentClassroomRepository: Repository, - @InjectRepository(AssignmentExercise, 'content') + @InjectRepository(AssignmentExercise, 'educational') private readonly assignmentExerciseRepository: Repository, - @InjectRepository(AssignmentStudent, 'content') + @InjectRepository(AssignmentStudent, 'educational') private readonly assignmentStudentRepository: Repository, - @InjectRepository(AssignmentSubmission, 'content') + @InjectRepository(AssignmentSubmission, 'educational') private readonly submissionRepository: Repository, ) {} @@ -1051,6 +1053,143 @@ export class AssignmentsService { }; } + /** + * Get upcoming assignments with deadlines within specified days + * BAJO-008: Upcoming assignments for Teacher Dashboard + * + * @param teacherId - The teacher's user ID + * @param daysAhead - Number of days to look ahead (default: 7) + * @returns Array of upcoming assignments with submission stats + */ + async getUpcomingAssignments( + teacherId: string, + daysAhead: number = 7, + ): Promise> { + const now = new Date(); + const futureDate = new Date(); + futureDate.setDate(now.getDate() + daysAhead); + + // Get published assignments with upcoming due dates + const assignments = await this.assignmentRepository + .createQueryBuilder('assignment') + .where('assignment.teacherId = :teacherId', { teacherId }) + .andWhere('assignment.isPublished = true') + .andWhere('assignment.dueDate IS NOT NULL') + .andWhere('assignment.dueDate >= :now', { now }) + .andWhere('assignment.dueDate <= :futureDate', { futureDate }) + .orderBy('assignment.dueDate', 'ASC') + .getMany(); + + // Get stats for each assignment + const results = await Promise.all( + assignments.map(async (assignment) => { + // Get total assigned students + const totalStudents = await this.assignmentStudentRepository.count({ + where: { assignmentId: assignment.id }, + }); + + // Get submitted count + const submittedCount = await this.submissionRepository.count({ + where: { assignmentId: assignment.id }, + }); + + // Calculate days remaining + const dueDate = assignment.dueDate ? new Date(assignment.dueDate) : null; + const daysRemaining = dueDate + ? Math.ceil((dueDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) + : 0; + + return { + id: assignment.id, + title: assignment.title, + dueDate: assignment.dueDate, + daysRemaining, + totalStudents, + submittedCount, + }; + }), + ); + + this.logger.log(`Found ${results.length} upcoming assignments for teacher ${teacherId}`); + + return results; + } + + /** + * Send reminder notifications to students who haven't submitted + * MEDIO-005: Assignment reminder functionality + * + * @param assignmentId - The assignment ID + * @param teacherId - The teacher's user ID (for authorization) + * @returns Count of students notified + */ + async sendReminder( + assignmentId: string, + teacherId: string, + ): Promise<{ notified: number; alreadySubmitted: number; message: string }> { + // Verify ownership + const assignment = await this.findOne(assignmentId, teacherId); + + // Get all students assigned to this assignment + const assignedStudents = await this.assignmentStudentRepository.find({ + where: { assignmentId }, + }); + + if (assignedStudents.length === 0) { + return { + notified: 0, + alreadySubmitted: 0, + message: 'No hay estudiantes asignados a esta tarea', + }; + } + + // Get students who have already submitted + const submissions = await this.submissionRepository.find({ + where: { assignmentId }, + }); + const submittedStudentIds = new Set(submissions.map((s) => s.studentId)); + + // Filter students who haven't submitted + const studentsToNotify = assignedStudents.filter( + (s) => !submittedStudentIds.has(s.studentId), + ); + + const alreadySubmitted = assignedStudents.length - studentsToNotify.length; + + if (studentsToNotify.length === 0) { + return { + notified: 0, + alreadySubmitted, + message: 'Todos los estudiantes ya han entregado esta tarea', + }; + } + + // Log reminder sent (in production, integrate with NotificationService) + // TODO: Integrate with notification-multichannel using template 'assignment_due_reminder' + this.logger.log( + `Reminder sent for assignment ${assignmentId}: ${studentsToNotify.length} students notified`, + ); + + // For now, just return the count (notifications will be implemented with full integration) + const dueDate = assignment.dueDate + ? new Date(assignment.dueDate).toLocaleDateString('es-ES') + : 'Sin fecha límite'; + + return { + notified: studentsToNotify.length, + alreadySubmitted, + message: `Recordatorio enviado a ${studentsToNotify.length} estudiante(s). Tarea: "${assignment.title}" - Fecha límite: ${dueDate}`, + }; + } + /** * Sanitize HTML to prevent XSS * REQ-TCH-021: HTML sanitization diff --git a/projects/gamilit/apps/backend/src/modules/auth/entities/user.entity.ts b/projects/gamilit/apps/backend/src/modules/auth/entities/user.entity.ts index 39ee54c..7325109 100644 --- a/projects/gamilit/apps/backend/src/modules/auth/entities/user.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/auth/entities/user.entity.ts @@ -54,7 +54,7 @@ export class User { * Rol del usuario en el sistema (student, admin_teacher, super_admin) * * @note GAMILIT usa la columna 'gamilit_role' (ENUM auth_management.gamilit_role) - * @note La columna 'role' (varchar) es legacy de Supabase, no se usa + * @note La columna 'role' (varchar) es legacy del patrón auth estándar, no se usa en GAMILIT */ @Column({ type: 'enum', diff --git a/projects/gamilit/apps/backend/src/modules/educational/controllers/exercises.controller.ts b/projects/gamilit/apps/backend/src/modules/educational/controllers/exercises.controller.ts index 8dad6a7..bea781f 100644 --- a/projects/gamilit/apps/backend/src/modules/educational/controllers/exercises.controller.ts +++ b/projects/gamilit/apps/backend/src/modules/educational/controllers/exercises.controller.ts @@ -126,16 +126,19 @@ export class ExercisesController { let timeSpentSeconds: number | undefined; if (dto.startedAt) { - // Formato nuevo: calcular desde timestamp + // Formato nuevo (camelCase): calcular desde timestamp timeSpentSeconds = Math.floor((Date.now() - dto.startedAt) / 1000); + } else if (dto.started_at) { + // Formato snake_case: calcular desde timestamp + timeSpentSeconds = Math.floor((Date.now() - dto.started_at) / 1000); } else if (dto.time_spent_seconds !== undefined) { // Formato antiguo: usar valor directo timeSpentSeconds = dto.time_spent_seconds; } - // 4. Normalizar hints y powerups + // 4. Normalizar hints y powerups (prioridad: camelCase > snake_case > legacy) const hintsUsed = dto.hintsUsed ?? dto.hints_used ?? 0; - const powerupsUsed = dto.powerupsUsed ?? dto.comodines_used ?? []; + const powerupsUsed = dto.powerupsUsed ?? dto.powerups_used ?? dto.comodines_used ?? []; return { userId, diff --git a/projects/gamilit/apps/backend/src/modules/educational/controllers/modules.controller.ts b/projects/gamilit/apps/backend/src/modules/educational/controllers/modules.controller.ts index f90b3d9..f27bfb9 100644 --- a/projects/gamilit/apps/backend/src/modules/educational/controllers/modules.controller.ts +++ b/projects/gamilit/apps/backend/src/modules/educational/controllers/modules.controller.ts @@ -11,6 +11,7 @@ import { HttpStatus, Request, UseGuards, + ParseUUIDPipe, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; import { ModulesService } from '../services'; @@ -283,7 +284,7 @@ export class ModulesController { status: 404, description: 'Usuario no encontrado', }) - async getUserModules(@Param('userId') userId: string) { + async getUserModules(@Param('userId', ParseUUIDPipe) userId: string) { return this.modulesService.getUserModules(userId); } @@ -375,7 +376,7 @@ export class ModulesController { }, }, }) - async findOne(@Param('id') id: string) { + async findOne(@Param('id', ParseUUIDPipe) id: string) { return this.modulesService.findById(id); } @@ -486,7 +487,7 @@ export class ModulesController { status: 404, description: 'Módulo no encontrado', }) - async update(@Param('id') id: string, @Body() updateModuleDto: Partial) { + async update(@Param('id', ParseUUIDPipe) id: string, @Body() updateModuleDto: Partial) { return this.modulesService.update(id, updateModuleDto); } @@ -530,7 +531,7 @@ export class ModulesController { status: 404, description: 'Módulo no encontrado', }) - async remove(@Param('id') id: string) { + async remove(@Param('id', ParseUUIDPipe) id: string) { const deleted = await this.modulesService.delete(id); return { success: deleted, @@ -588,7 +589,7 @@ export class ModulesController { status: 404, description: 'Módulo no encontrado', }) - async getPrerequisites(@Param('id') id: string) { + async getPrerequisites(@Param('id', ParseUUIDPipe) id: string) { return this.modulesService.getPrerequisites(id); } } diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/exercises/submit-exercise.dto.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/exercises/submit-exercise.dto.ts index 9956455..fcf1769 100644 --- a/projects/gamilit/apps/backend/src/modules/educational/dto/exercises/submit-exercise.dto.ts +++ b/projects/gamilit/apps/backend/src/modules/educational/dto/exercises/submit-exercise.dto.ts @@ -175,4 +175,34 @@ export class SubmitExerciseDto { @IsOptional() @IsArray() comodines_used?: string[]; + + /** + * @deprecated Usar 'startedAt' (camelCase) + * Se mantiene para compatibilidad con frontends que envían snake_case + */ + @ApiProperty({ + description: '[DEPRECATED] Usar "startedAt"', + example: 1638392400000, + required: false, + deprecated: true, + }) + @IsOptional() + @IsNumber() + @Min(0) + started_at?: number; + + /** + * @deprecated Usar 'powerupsUsed' (camelCase) + * Se mantiene para compatibilidad con frontends que envían snake_case + */ + @ApiProperty({ + description: '[DEPRECATED] Usar "powerupsUsed"', + example: ['hint_50_50', 'extra_time'], + required: false, + deprecated: true, + }) + @IsOptional() + @IsArray() + @IsString({ each: true }) + powerups_used?: string[]; } diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/module4/index.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/module4/index.ts index 7ebc97d..3bc62a0 100644 --- a/projects/gamilit/apps/backend/src/modules/educational/dto/module4/index.ts +++ b/projects/gamilit/apps/backend/src/modules/educational/dto/module4/index.ts @@ -1,9 +1,15 @@ /** * Module 4 DTOs - Barrel Export * - * @description Exporta todos los DTOs de respuestas del Módulo 4. - * Estos DTOs validan las respuestas de los ejercicios relacionados con - * alfabetización digital y análisis crítico de medios. + * @description Exporta todos los DTOs de respuestas del Módulo 4 (Lectura Digital y Multimodal). + * Solo incluye los 5 ejercicios oficiales según DocumentoDeDiseño v6.4. + * + * Ejercicios oficiales: + * 4.1 Verificador Fake News + * 4.2 Infografía Interactiva + * 4.3 Quiz TikTok + * 4.4 Navegación Hipertextual + * 4.5 Análisis Memes * * @usage import { VerificadorFakeNewsAnswerDto, AnalisisMemesAnswerDto } from '@/modules/educational/dto/module4'; */ diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/comic-digital-answer.dto.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/comic-digital-answer.dto.ts new file mode 100644 index 0000000..395a625 --- /dev/null +++ b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/comic-digital-answer.dto.ts @@ -0,0 +1,122 @@ +import { + IsArray, + IsString, + IsNumber, + IsOptional, + ValidateNested, + ArrayMinSize, + ArrayMaxSize, + Min, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; + +/** + * ComicPanelDto + * + * @description DTO para representar una viñeta individual del cómic. + * Cada viñeta debe tener diálogo, narración, y opcionalmente imagen. + */ +export class ComicPanelDto { + @ApiProperty({ + description: 'Número de la viñeta (1-6)', + example: 1, + minimum: 1, + }) + @IsNumber({}, { message: 'panelNumber must be a number' }) + @Min(1, { message: 'panelNumber must be at least 1' }) + panelNumber: number; + + @ApiProperty({ + description: 'Diálogo de los personajes en la viñeta', + example: 'Marie: "Este mineral contiene algo extraordinario, Pierre."', + }) + @IsString({ message: 'dialogue must be a string' }) + dialogue: string; + + @ApiProperty({ + description: 'Narración contextual de la viñeta', + example: '1898. En un laboratorio frío de París...', + }) + @IsString({ message: 'narration must be a string' }) + narration: string; + + @ApiProperty({ + description: 'URL de la imagen o boceto de la viñeta (opcional)', + example: 'https://storage.example.com/panel1.png', + required: false, + }) + @IsOptional() + @IsString({ message: 'imageUrl must be a string' }) + imageUrl?: string; + + @ApiProperty({ + description: 'Descripción visual de la escena (para accesibilidad o si no hay imagen)', + example: 'Marie y Pierre observan un mineral oscuro sobre la mesa del laboratorio', + required: false, + }) + @IsOptional() + @IsString({ message: 'visualDescription must be a string' }) + visualDescription?: string; +} + +/** + * ComicDigitalAnswerDto + * + * @description DTO para validar las respuestas del ejercicio "Cómic Digital". + * El estudiante debe crear un cómic de 4-6 viñetas narrando una historia científica. + * + * @example + * ```json + * { + * "panels": [ + * { + * "panelNumber": 1, + * "dialogue": "Marie: 'Este mineral es extraordinario.'", + * "narration": "1898. En un laboratorio de París...", + * "imageUrl": "https://storage.example.com/panel1.png" + * }, + * { + * "panelNumber": 2, + * "dialogue": "Pierre: 'Debemos aislarlo.'", + * "narration": "Los Curie comienzan su investigación.", + * "visualDescription": "Close-up de mediciones en electroscopio" + * } + * ] + * } + * ``` + */ +export class ComicDigitalAnswerDto { + @ApiProperty({ + description: 'Lista de viñetas del cómic (mínimo 4, máximo 6)', + type: [ComicPanelDto], + example: [ + { + panelNumber: 1, + dialogue: 'Marie: "Este mineral contiene algo extraordinario."', + narration: '1898. En un laboratorio frío de París...', + }, + { + panelNumber: 2, + dialogue: 'Pierre: "Debemos aislarlo, por muy difícil que sea."', + narration: 'Los Curie inician años de arduo trabajo.', + }, + { + panelNumber: 3, + dialogue: 'Marie: "No puedo rendirme. El secreto está ahí."', + narration: 'Cuatro años de procesamiento manual.', + }, + { + panelNumber: 4, + dialogue: 'Ambos: "¡Brilla! Lo logramos."', + narration: 'El radio ilumina la oscuridad del laboratorio.', + }, + ], + }) + @IsArray({ message: 'panels must be an array' }) + @ArrayMinSize(4, { message: 'panels must contain at least 4 panels' }) + @ArrayMaxSize(6, { message: 'panels must contain at most 6 panels' }) + @ValidateNested({ each: true }) + @Type(() => ComicPanelDto) + panels: ComicPanelDto[]; +} diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/diario-multimedia-answer.dto.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/diario-multimedia-answer.dto.ts new file mode 100644 index 0000000..a760e5b --- /dev/null +++ b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/diario-multimedia-answer.dto.ts @@ -0,0 +1,186 @@ +import { + IsString, + IsArray, + IsOptional, + IsNumber, + IsDateString, + ValidateNested, + ArrayMinSize, + ArrayMaxSize, + MinLength, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; + +/** + * DiaryEntryDto + * + * @description DTO para representar una entrada individual del diario multimedia. + * Cada entrada debe tener fecha, contenido, y opcionalmente titulo, mood y multimedia. + */ +export class DiaryEntryDto { + @ApiProperty({ + description: 'Identificador unico de la entrada', + example: 'entry1', + }) + @IsString({ message: 'id must be a string' }) + id: string; + + @ApiProperty({ + description: 'Fecha de la entrada del diario (formato ISO 8601)', + example: '1898-12-15', + }) + @IsDateString({}, { message: 'date must be a valid date string (ISO 8601)' }) + date: string; + + @ApiProperty({ + description: 'Titulo de la entrada', + example: 'El Dia del Descubrimiento', + required: false, + }) + @IsOptional() + @IsString({ message: 'title must be a string' }) + title?: string; + + @ApiProperty({ + description: 'Contenido reflexivo de la entrada (minimo 50 caracteres)', + example: + 'Querido diario, hoy es un dia que nunca olvidare. Despues de cuatro anos de trabajo incansable...', + }) + @IsString({ message: 'content must be a string' }) + @MinLength(50, { message: 'content must be at least 50 characters long' }) + content: string; + + @ApiProperty({ + description: 'Estado de animo asociado a la entrada', + example: 'excitement', + required: false, + }) + @IsOptional() + @IsString({ message: 'mood must be a string' }) + mood?: string; + + @ApiProperty({ + description: 'Conteo de palabras de la entrada', + example: 156, + required: false, + }) + @IsOptional() + @IsNumber({}, { message: 'wordCount must be a number' }) + wordCount?: number; + + @ApiProperty({ + description: 'Clima o contexto del dia', + example: 'Frio invernal en Paris', + required: false, + }) + @IsOptional() + @IsString({ message: 'weather must be a string' }) + weather?: string; + + @ApiProperty({ + description: 'Ubicacion donde se escribe la entrada', + example: 'Laboratorio en Rue Lhomond', + required: false, + }) + @IsOptional() + @IsString({ message: 'location must be a string' }) + location?: string; + + @ApiProperty({ + description: 'Template utilizado para la entrada', + example: 'template_classic', + required: false, + }) + @IsOptional() + @IsString({ message: 'template must be a string' }) + template?: string; + + @ApiProperty({ + description: 'Archivos multimedia adjuntos (imagenes, audio, video)', + required: false, + }) + @IsOptional() + multimedia?: any; +} + +/** + * DiarioMultimediaAnswerDto + * + * @description DTO para validar las respuestas del ejercicio "Diario Multimedia". + * El estudiante debe escribir un diario desde la perspectiva de Marie Curie + * durante el descubrimiento del radio (1898-1899). + * + * Requisitos: + * - Minimo 3 entradas, maximo 5 + * - Cada entrada debe tener fecha y contenido (minimo 50 caracteres) + * - Precision historica requerida + * + * @example + * ```json + * { + * "entries": [ + * { + * "id": "entry1", + * "date": "1898-12-15", + * "title": "El Dia del Descubrimiento", + * "content": "Querido diario, hoy es un dia que nunca olvidare...", + * "mood": "excitement", + * "wordCount": 156 + * } + * ], + * "totalEntries": 3, + * "totalWords": 468 + * } + * ``` + */ +export class DiarioMultimediaAnswerDto { + @ApiProperty({ + description: 'Lista de entradas del diario (minimo 1, maximo 5)', + type: [DiaryEntryDto], + example: [ + { + id: 'entry1', + date: '1898-12-15', + title: 'El Dia del Descubrimiento', + content: + 'Querido diario, hoy es un dia que nunca olvidare. Despues de cuatro anos de trabajo incansable, Pierre y yo finalmente lo logramos. En la oscuridad de nuestro laboratorio, el radio brillo con una luz azul-verde eterea.', + mood: 'excitement', + wordCount: 156, + }, + ], + }) + @IsArray({ message: 'entries must be an array' }) + @ArrayMinSize(1, { message: 'entries must contain at least 1 entry' }) + @ArrayMaxSize(5, { message: 'entries must contain at most 5 entries' }) + @ValidateNested({ each: true }) + @Type(() => DiaryEntryDto) + entries: DiaryEntryDto[]; + + @ApiProperty({ + description: 'Total de entradas en el diario', + example: 3, + required: false, + }) + @IsOptional() + @IsNumber({}, { message: 'totalEntries must be a number' }) + totalEntries?: number; + + @ApiProperty({ + description: 'Total de palabras en todas las entradas', + example: 468, + required: false, + }) + @IsOptional() + @IsNumber({}, { message: 'totalWords must be a number' }) + totalWords?: number; + + @ApiProperty({ + description: 'Fecha y hora de envio', + example: '2025-01-15T14:30:00Z', + required: false, + }) + @IsOptional() + @IsString({ message: 'submittedAt must be a string' }) + submittedAt?: string; +} diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/diario-reflexivo-answer.dto.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/diario-reflexivo-answer.dto.ts deleted file mode 100644 index db2fd39..0000000 --- a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/diario-reflexivo-answer.dto.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - IsString, - IsArray, - MinLength, - registerDecorator, - ValidationOptions, - ValidationArguments, -} from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -/** - * Custom validator para verificar el número mínimo de palabras - * - * @param minWords Número mínimo de palabras requeridas - * @param validationOptions Opciones de validación - */ -function MinWords(minWords: number, validationOptions?: ValidationOptions) { - return function (object: object, propertyName: string) { - registerDecorator({ - name: 'minWords', - target: object.constructor, - propertyName: propertyName, - constraints: [minWords], - options: validationOptions, - validator: { - validate(value: any, args: ValidationArguments) { - if (typeof value !== 'string') return false; - - // Elimina espacios extra y cuenta palabras - const words = value.trim().split(/\s+/).filter((word) => word.length > 0); - return words.length >= args.constraints[0]; - }, - defaultMessage(args: ValidationArguments) { - return `${args.property} must contain at least ${args.constraints[0]} words`; - }, - }, - }); - }; -} - -/** - * DiarioReflexivoAnswerDto - * - * @description DTO para validar las respuestas del ejercicio "Diario Reflexivo". - * El estudiante debe escribir una reflexión personal sobre su proceso de aprendizaje - * en alfabetización digital. Se requiere un contenido sustancial (mínimo 150 palabras) - * y respuestas a prompts guiados. - * - * @example - * ```json - * { - * "content": "Durante este módulo he aprendido sobre la importancia de verificar las fuentes...", - * "prompts_answered": [ - * "¿Qué aprendiste sobre fake news?", - * "¿Cómo aplicarás estas habilidades en tu vida diaria?", - * "¿Qué desafíos encontraste durante el módulo?" - * ] - * } - * ``` - */ -export class DiarioReflexivoAnswerDto { - @ApiProperty({ - description: - 'Contenido reflexivo del estudiante (mínimo 150 palabras). ' + - 'Debe ser una reflexión personal sobre el proceso de aprendizaje.', - example: - 'Durante este módulo he aprendido sobre la importancia de verificar las fuentes de información ' + - 'antes de compartir contenido en redes sociales. Me di cuenta de que muchas veces compartimos ' + - 'información sin verificar su veracidad, lo cual contribuye a la desinformación. Los ejercicios ' + - 'sobre fake news me ayudaron a desarrollar un pensamiento más crítico. Ahora me tomo el tiempo ' + - 'de revisar múltiples fuentes antes de creer o compartir información. También aprendí sobre la ' + - 'importancia de la alfabetización digital en el mundo actual, donde estamos constantemente expuestos ' + - 'a información de diferentes fuentes. El análisis de memes fue particularmente interesante porque ' + - 'me mostró cómo el humor puede ser usado para manipular opiniones. En el futuro, aplicaré estas ' + - 'habilidades para ser más consciente de mi consumo de información digital y ayudar a otros a ' + - 'desarrollar estas mismas competencias críticas.', - minLength: 1, - }) - @IsString({ message: 'content must be a string' }) - @MinLength(1, { message: 'content cannot be empty' }) - @MinWords(150, { - message: 'content must contain at least 150 words for a meaningful reflection', - }) - content: string; - - @ApiProperty({ - description: - 'Lista de prompts o preguntas guía que fueron respondidas en la reflexión', - example: [ - '¿Qué aprendiste sobre fake news?', - '¿Cómo aplicarás estas habilidades en tu vida diaria?', - '¿Qué desafíos encontraste durante el módulo?', - ], - }) - @IsArray({ message: 'prompts_answered must be an array' }) - @IsString({ each: true, message: 'each prompt must be a string' }) - prompts_answered: string[]; -} diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/index.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/index.ts index ffcd5f6..6e782c7 100644 --- a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/index.ts +++ b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/index.ts @@ -1,13 +1,24 @@ /** * Module 5 DTOs - Barrel Export * - * @description Exporta todos los DTOs de respuestas del Módulo 5. - * Estos DTOs validan las respuestas de los ejercicios de reflexión y - * producción de contenido sobre alfabetización digital. + * @description Exporta todos los DTOs de respuestas del Módulo 5 (Producción Creativa). + * Solo incluye los 3 ejercicios oficiales según DocumentoDeDiseño v6.1. * - * @usage import { DiarioReflexivoAnswerDto, VideoCartaAnswerDto, PodcastAnswerDto } from '@/modules/educational/dto/module5'; + * Ejercicios oficiales: + * 5.1 Diario Multimedia (diario_multimedia) + * 5.2 Cómic Digital (comic_digital) + * 5.3 Video-Carta (video_carta) + * + * @note El estudiante elige y completa SOLO UNO de los 3 ejercicios. + * @note TODOS los ejercicios del M5 requieren evaluación manual por un docente. + * + * @usage import { DiarioMultimediaAnswerDto, ComicDigitalAnswerDto, VideoCartaAnswerDto } from '@/modules/educational/dto/module5'; + * + * @updated 2025-12-18 - CORR-M5: Alineación con DocumentoDeDiseño v6.1 + * - Reemplazado DiarioReflexivoAnswerDto por DiarioMultimediaAnswerDto + * - Eliminado PodcastAnswerDto (no está en diseño oficial) */ -export * from './diario-reflexivo-answer.dto'; +export * from './diario-multimedia-answer.dto'; export * from './video-carta-answer.dto'; -export * from './podcast-answer.dto'; +export * from './comic-digital-answer.dto'; diff --git a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/podcast-answer.dto.ts b/projects/gamilit/apps/backend/src/modules/educational/dto/module5/podcast-answer.dto.ts deleted file mode 100644 index 58fe4f9..0000000 --- a/projects/gamilit/apps/backend/src/modules/educational/dto/module5/podcast-answer.dto.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - IsUrl, - IsString, - IsNumber, - IsPositive, - IsOptional, -} from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -/** - * PodcastAnswerDto - * - * @description DTO para validar las respuestas del ejercicio "Podcast". - * El estudiante crea un podcast reflexivo sobre alfabetización digital, - * compartiendo sus aprendizajes y perspectivas en formato de audio. - * - * @example - * ```json - * { - * "audio_url": "https://soundcloud.com/user/my-digital-literacy-podcast", - * "transcript": "Hola a todos, bienvenidos a mi podcast sobre alfabetización digital...", - * "duration_seconds": 420 - * } - * ``` - */ -export class PodcastAnswerDto { - @ApiProperty({ - description: - 'URL del podcast subido por el estudiante (SoundCloud, Anchor, Spotify, etc.)', - example: 'https://soundcloud.com/user/my-digital-literacy-podcast', - }) - @IsUrl( - { - require_protocol: true, - protocols: ['http', 'https'], - }, - { message: 'audio_url must be a valid URL' }, - ) - audio_url: string; - - @ApiProperty({ - description: - 'Transcripción opcional del contenido del podcast. ' + - 'Útil para accesibilidad y análisis del contenido.', - example: - 'Hola a todos, bienvenidos a mi podcast sobre alfabetización digital. ' + - 'Hoy quiero compartir con ustedes lo que he aprendido durante este módulo...', - required: false, - }) - @IsOptional() - @IsString({ message: 'transcript must be a string' }) - transcript?: string; - - @ApiProperty({ - description: 'Duración total del podcast en segundos', - example: 420, - }) - @IsNumber({}, { message: 'duration_seconds must be a number' }) - @IsPositive({ message: 'duration_seconds must be a positive number' }) - duration_seconds: number; -} diff --git a/projects/gamilit/apps/backend/src/modules/gamification/dto/shop/create-purchase.dto.ts b/projects/gamilit/apps/backend/src/modules/gamification/dto/shop/create-purchase.dto.ts index 2ca47bd..6c687f4 100644 --- a/projects/gamilit/apps/backend/src/modules/gamification/dto/shop/create-purchase.dto.ts +++ b/projects/gamilit/apps/backend/src/modules/gamification/dto/shop/create-purchase.dto.ts @@ -1,5 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsUUID, IsInt, Min, IsOptional } from 'class-validator'; +import { IsString, IsInt, Min, IsOptional, Matches } from 'class-validator'; + +// UUID regex that matches any UUID format +const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; /** * CreatePurchaseDto @@ -12,15 +15,17 @@ export class CreatePurchaseDto { example: '550e8400-e29b-41d4-a716-446655440000', description: 'ID del usuario comprador', }) - @IsUUID() - user_id!: string; + @IsString({ message: 'user_id debe ser un string' }) + @Matches(UUID_REGEX, { message: 'user_id debe tener formato UUID válido' }) + user_id!: string; @ApiProperty({ example: '660e8400-e29b-41d4-a716-446655440000', description: 'ID del item a comprar', }) - @IsUUID() - item_id!: string; + @IsString({ message: 'item_id debe ser un string' }) + @Matches(UUID_REGEX, { message: 'item_id debe tener formato UUID válido' }) + item_id!: string; @ApiProperty({ example: 1, @@ -31,5 +36,5 @@ export class CreatePurchaseDto { @IsOptional() @IsInt() @Min(1) - quantity?: number; + quantity?: number; } diff --git a/projects/gamilit/apps/backend/src/modules/gamification/entities/user-purchase.entity.ts b/projects/gamilit/apps/backend/src/modules/gamification/entities/user-purchase.entity.ts index 111c1e0..6de80ac 100644 --- a/projects/gamilit/apps/backend/src/modules/gamification/entities/user-purchase.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/gamification/entities/user-purchase.entity.ts @@ -2,7 +2,6 @@ import { Entity, PrimaryGeneratedColumn, Column, - CreateDateColumn, Index, } from 'typeorm'; import { DB_SCHEMAS, DB_TABLES } from '@shared/constants/database.constants'; @@ -71,10 +70,10 @@ export class UserPurchase { price_paid!: number; /** - * Si se aplicó descuento en esta compra + * Descuento aplicado en ML Coins (valor absoluto) */ - @Column({ type: 'boolean', default: false }) - discount_applied!: boolean; + @Column({ type: 'integer', default: 0 }) + discount_applied!: number; /** * ID de la transacción de ML Coins (FK → ml_coins_transactions) @@ -123,17 +122,12 @@ export class UserPurchase { metadata!: Record; /** - * Fecha y hora de la compra + * Fecha y hora de la compra (timestamp de creación del registro) + * NOTA: La tabla usa purchased_at como timestamp de creación, no tiene created_at */ - @Column({ type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' }) + @Column({ type: 'timestamp with time zone', default: () => 'gamilit.now_mexico()' }) purchased_at!: Date; - /** - * Fecha y hora de creación del registro - */ - @CreateDateColumn({ type: 'timestamp with time zone' }) - created_at!: Date; - // ===================================================== // HELPER METHODS // ===================================================== diff --git a/projects/gamilit/apps/backend/src/modules/gamification/entities/user-stats.entity.ts b/projects/gamilit/apps/backend/src/modules/gamification/entities/user-stats.entity.ts index 86fe6ce..7dd8e87 100644 --- a/projects/gamilit/apps/backend/src/modules/gamification/entities/user-stats.entity.ts +++ b/projects/gamilit/apps/backend/src/modules/gamification/entities/user-stats.entity.ts @@ -7,6 +7,8 @@ import { Index, } from 'typeorm'; import { DB_SCHEMAS, DB_TABLES } from '@shared/constants/database.constants'; +// CORR-P1-006: Import MayaRank para alinear tipo con DDL +import { MayaRank } from '@shared/constants/enums.constants'; /** * UserStats Entity (gamification_system.user_stats) @@ -80,9 +82,17 @@ export class UserStats { /** * Rango Maya actual del usuario * Valores: 'Ajaw', 'Nacom', 'Ah K'in', 'Halach Uinic', 'K'uk'ulkan' + * + * CORR-P1-006: Cambiado de 'text' a 'enum' para alinear con DDL + * DDL define como: gamification_system.maya_rank ENUM */ - @Column({ type: 'text', default: 'Ajaw' }) - current_rank!: string; + @Column({ + type: 'enum', + enum: MayaRank, + enumName: 'maya_rank', + default: MayaRank.AJAW, + }) + current_rank!: MayaRank; /** * Progreso hacia el siguiente rango (0-100%) diff --git a/projects/gamilit/apps/backend/src/modules/gamification/services/achievements.service.ts b/projects/gamilit/apps/backend/src/modules/gamification/services/achievements.service.ts index 0cce093..9c8f800 100644 --- a/projects/gamilit/apps/backend/src/modules/gamification/services/achievements.service.ts +++ b/projects/gamilit/apps/backend/src/modules/gamification/services/achievements.service.ts @@ -1,9 +1,49 @@ -import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { Injectable, NotFoundException, BadRequestException, Logger } from '@nestjs/common'; +import { InjectRepository, InjectDataSource } from '@nestjs/typeorm'; +import { Repository, DataSource } from 'typeorm'; import { Achievement, UserAchievement, UserStats } from '../entities'; import { GrantAchievementDto } from '../dto'; +/** + * Interfaces para tipos de condiciones de achievements (alineadas con seeds) + */ +interface AchievementConditions { + type: string; + requirements: Record; +} + +interface ExerciseCompletionReqs { + exercises_completed: number; +} + +interface StreakReqs { + consecutive_days: number; +} + +interface ModuleCompletionReqs { + module_id: string; + completion_percentage: number; +} + +interface AllModulesCompletionReqs { + modules_completed: number; + min_score_average: number; +} + +interface PerfectScoreReqs { + perfect_exercises: number; + score_required: number; +} + +interface SocialReqs { + classrooms_joined?: number; + social_activities?: number; +} + +interface SpecialReqs { + first_login?: boolean; +} + /** * AchievementsService * @@ -15,6 +55,8 @@ import { GrantAchievementDto } from '../dto'; */ @Injectable() export class AchievementsService { + private readonly logger = new Logger(AchievementsService.name); + constructor( @InjectRepository(Achievement, 'gamification') private readonly achievementRepo: Repository, @@ -22,6 +64,8 @@ export class AchievementsService { private readonly userAchievementRepo: Repository, @InjectRepository(UserStats, 'gamification') private readonly userStatsRepo: Repository, + @InjectDataSource('gamification') + private readonly dataSource: DataSource, ) {} /** @@ -223,70 +267,262 @@ export class AchievementsService { continue; // Saltar si no es repetible y ya está completado } - // Evaluar condiciones - if (this.meetsConditions(userStats, achievement.conditions)) { + // Evaluar condiciones (ahora async para soportar queries complejas) + const conditionsMet = await this.meetsConditions(userId, userStats, achievement.conditions); + + if (conditionsMet) { + this.logger.log(`Achievement ${achievement.name} conditions met for user ${userId}`); + const grantDto = new GrantAchievementDto(); grantDto.user_id = userId; grantDto.achievement_id = achievement.id; - const conditionsTyped = achievement.conditions as { progress?: number; max_progress?: number }; - grantDto.progress = conditionsTyped.progress || conditionsTyped.max_progress || 100; - grantDto.max_progress = conditionsTyped.max_progress || 100; + const conditionsTyped = achievement.conditions as { requirements?: { exercises_completed?: number } }; + const reqs = conditionsTyped.requirements || {}; + grantDto.progress = reqs.exercises_completed || 100; + grantDto.max_progress = reqs.exercises_completed || 100; grantDto.is_completed = true; - grantDto.progress_data = { auto_detected: true }; + grantDto.progress_data = { auto_detected: true, detected_at: new Date().toISOString() }; const granted = await this.grantAchievement(userId, grantDto); grantedAchievements.push(granted); } } + this.logger.log(`Detected and granted ${grantedAchievements.length} achievements for user ${userId}`); return grantedAchievements; } /** * Evalúa si las estadísticas del usuario cumplen con las condiciones del logro + * ACTUALIZADO: Soporta todos los tipos definidos en seeds de achievements + * + * Tipos soportados: + * - exercise_completion: Completar N ejercicios + * - streak: Mantener racha de N días consecutivos + * - module_completion: Completar un módulo específico + * - all_modules_completion: Completar todos los módulos + * - perfect_score: Obtener N puntuaciones perfectas + * - social: Actividades sociales (unirse a aulas, etc.) + * - special: Eventos especiales (primer login, etc.) */ - private meetsConditions(userStats: UserStats, conditions: Record): boolean { - // Type cast for accessing condition properties - const cond = conditions as { - type?: string; - exercises_completed?: number; - modules_completed?: number; - min_streak?: number; - min_level?: number; - min_average_score?: number; - min_perfect_scores?: number; - target_rank?: string; - min_coins_earned?: number; - }; + private async meetsConditions( + userId: string, + userStats: UserStats, + conditions: Record, + ): Promise { + const cond = conditions as unknown as AchievementConditions; const type = cond.type || 'generic'; + const reqs = (cond.requirements || {}) as unknown as Record; - switch (type) { - case 'progress': - return ( - userStats.exercises_completed >= (cond.exercises_completed || 0) && - userStats.modules_completed >= (cond.modules_completed || 0) - ); + try { + switch (type) { + // ===================================================== + // TIPO: exercise_completion + // Condición: Completar N ejercicios + // ===================================================== + case 'exercise_completion': { + const r = reqs as unknown as ExerciseCompletionReqs; + const met = userStats.exercises_completed >= (r.exercises_completed || 0); + this.logger.debug(`[exercise_completion] User has ${userStats.exercises_completed}, needs ${r.exercises_completed}: ${met}`); + return met; + } - case 'streak': - return userStats.current_streak >= (cond.min_streak || 0); + // ===================================================== + // TIPO: streak + // Condición: Mantener racha de N días consecutivos + // Seeds usan: consecutive_days (no min_streak) + // ===================================================== + case 'streak': { + const r = reqs as unknown as StreakReqs; + const required = r.consecutive_days || 0; + const met = userStats.current_streak >= required; + this.logger.debug(`[streak] User has ${userStats.current_streak} days, needs ${required}: ${met}`); + return met; + } - case 'level': - return userStats.level >= (cond.min_level || 0); + // ===================================================== + // TIPO: module_completion + // Condición: Completar un módulo específico al 100% + // Requiere query adicional a progress_tracking.module_progress + // ===================================================== + case 'module_completion': { + const r = reqs as unknown as ModuleCompletionReqs; - case 'score': - return ( - (userStats.average_score || 0) >= (cond.min_average_score || 0) && - userStats.perfect_scores >= (cond.min_perfect_scores || 0) - ); + const result = await this.dataSource.query( + ` + SELECT mp.completion_percentage + FROM progress_tracking.module_progress mp + JOIN educational_content.modules m ON mp.module_id = m.id + WHERE mp.user_id = $1 + AND m.slug = $2 + `, + [userId, r.module_id], + ); - case 'rank': - return this.userReachedRank(userStats.current_rank, cond.target_rank || ''); + if (!result || result.length === 0) { + this.logger.debug(`[module_completion] No progress found for module ${r.module_id}`); + return false; + } - case 'ml_coins': - return userStats.ml_coins_earned_total >= (cond.min_coins_earned || 0); + const percentage = parseFloat(result[0].completion_percentage) || 0; + const met = percentage >= (r.completion_percentage || 100); + this.logger.debug(`[module_completion] Module ${r.module_id}: ${percentage}% / ${r.completion_percentage}%: ${met}`); + return met; + } - default: - return false; + // ===================================================== + // TIPO: all_modules_completion + // Condición: Completar todos los módulos con score promedio mínimo + // ===================================================== + case 'all_modules_completion': { + const r = reqs as unknown as AllModulesCompletionReqs; + const modulesRequired = r.modules_completed || 5; + const scoreRequired = r.min_score_average || 70; + + const met = + userStats.modules_completed >= modulesRequired && + (userStats.average_score || 0) >= scoreRequired; + + this.logger.debug( + `[all_modules_completion] Modules: ${userStats.modules_completed}/${modulesRequired}, ` + + `Score: ${userStats.average_score || 0}/${scoreRequired}: ${met}`, + ); + return met; + } + + // ===================================================== + // TIPO: perfect_score + // Condición: Obtener N puntuaciones perfectas (100%) + // ===================================================== + case 'perfect_score': { + const r = reqs as unknown as PerfectScoreReqs; + const required = r.perfect_exercises || 0; + const met = userStats.perfect_scores >= required; + this.logger.debug(`[perfect_score] User has ${userStats.perfect_scores}, needs ${required}: ${met}`); + return met; + } + + // ===================================================== + // TIPO: skill_mastery + // Condición: Dominar un skill específico con score mínimo + // Nota: Requiere consulta a exercise_responses por tipo de skill + // Por ahora, simplificado a verificar perfect_scores + // ===================================================== + case 'skill_mastery': { + // TODO: Implementar consulta por skill_type cuando esté disponible en metadata + this.logger.debug(`[skill_mastery] Type not fully implemented, checking perfect_scores`); + return userStats.perfect_scores >= 10; + } + + // ===================================================== + // TIPO: exploration + // Condición: Explorar diferentes módulos o niveles de dificultad + // Simplificado a verificar modules_completed > 0 + // ===================================================== + case 'exploration': { + const met = userStats.modules_completed > 0 || userStats.exercises_completed >= 5; + this.logger.debug(`[exploration] Modules: ${userStats.modules_completed}, Exercises: ${userStats.exercises_completed}: ${met}`); + return met; + } + + // ===================================================== + // TIPO: social + // Condición: Actividades sociales (unirse a aulas, etc.) + // Requiere query a social_features.classroom_members + // ===================================================== + case 'social': { + const r = reqs as unknown as SocialReqs; + + if (r.classrooms_joined !== undefined) { + const result = await this.dataSource.query( + ` + SELECT COUNT(*) as count + FROM social_features.classroom_members + WHERE user_id = $1 AND is_active = true + `, + [userId], + ); + + const count = parseInt(result[0]?.count || '0'); + const met = count >= (r.classrooms_joined || 1); + this.logger.debug(`[social:classrooms] User in ${count} classrooms, needs ${r.classrooms_joined}: ${met}`); + return met; + } + + if (r.social_activities !== undefined) { + // Contar total de actividades sociales (classroom + friendships) + const result = await this.dataSource.query( + ` + SELECT + (SELECT COUNT(*) FROM social_features.classroom_members WHERE user_id = $1 AND is_active = true) + + (SELECT COUNT(*) FROM social_features.friendships WHERE user_id = $1 AND status = 'accepted') as total + `, + [userId], + ); + + const total = parseInt(result[0]?.total || '0'); + const met = total >= (r.social_activities || 5); + this.logger.debug(`[social:activities] Total social activities: ${total}, needs ${r.social_activities}: ${met}`); + return met; + } + + return false; + } + + // ===================================================== + // TIPO: special + // Condición: Eventos especiales (primer login) + // ===================================================== + case 'special': { + const r = reqs as SpecialReqs; + + if (r.first_login === true) { + // Verificar si ya se otorgó este achievement antes + // Si el usuario existe y tiene stats, asumimos que ya completó primer login + const met = userStats.exercises_completed === 0 && !userStats.last_activity_at; + this.logger.debug(`[special:first_login] First login check: ${met}`); + // Para primer login, lo otorgamos si es usuario nuevo + return !userStats.last_activity_at; + } + + return false; + } + + // ===================================================== + // TIPOS LEGACY (mantener compatibilidad) + // ===================================================== + case 'progress': + return ( + userStats.exercises_completed >= ((reqs as Record).exercises_completed || 0) && + userStats.modules_completed >= ((reqs as Record).modules_completed || 0) + ); + + case 'level': + return userStats.level >= ((reqs as Record).min_level || 0); + + case 'score': + return ( + (userStats.average_score || 0) >= ((reqs as Record).min_average_score || 0) && + userStats.perfect_scores >= ((reqs as Record).min_perfect_scores || 0) + ); + + case 'rank': + return this.userReachedRank(userStats.current_rank, (reqs as Record).target_rank || ''); + + case 'ml_coins': + return userStats.ml_coins_earned_total >= ((reqs as Record).min_coins_earned || 0); + + // ===================================================== + // DEFAULT: Tipo no reconocido + // ===================================================== + default: + this.logger.warn(`[meetsConditions] Unrecognized condition type: ${type}`); + return false; + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + this.logger.error(`[meetsConditions] Error evaluating ${type}: ${errorMsg}`); + return false; } } diff --git a/projects/gamilit/apps/backend/src/modules/gamification/services/shop.service.ts b/projects/gamilit/apps/backend/src/modules/gamification/services/shop.service.ts index a969d8b..65ef7e3 100644 --- a/projects/gamilit/apps/backend/src/modules/gamification/services/shop.service.ts +++ b/projects/gamilit/apps/backend/src/modules/gamification/services/shop.service.ts @@ -246,12 +246,17 @@ export class ShopService { await this.userStatsRepository.save(userStats); // 9. Crear registro en user_purchases + // discount_applied is the discount amount in ML Coins (integer) + const discountAmount = item.hasActiveDiscount() && item.discount_price + ? (item.price - item.discount_price) * quantity + : 0; + const purchase = this.purchaseRepository.create({ user_id: userId, item_id: itemId, quantity: quantity, price_paid: currentPrice, - discount_applied: item.hasActiveDiscount(), + discount_applied: discountAmount, transaction_id: transaction.id, status: 'completed', is_active: true, @@ -304,12 +309,45 @@ export class ShopService { throw new NotFoundException(`User stats not found for ${userId}`); } - // Validar rank requerido + // Validar rank requerido (usa rank_order para >= comparacion) if (item.required_rank) { - if (userStats.current_rank !== item.required_rank) { - throw new BadRequestException( - `Required rank: ${item.required_rank}. Current rank: ${userStats.current_rank}`, + // Consulta directa para obtener rank_order de ambos ranks + const rankQuery = ` + SELECT rank_name, rank_order + FROM gamification_system.maya_ranks + WHERE rank_name IN ($1, $2) AND is_active = true + `; + + try { + const ranks = await this.userStatsRepository.query(rankQuery, [ + item.required_rank, + userStats.current_rank, + ]); + + const requiredRank = ranks.find( + (r: { rank_name: string; rank_order: number }) => + r.rank_name === item.required_rank, ); + const userRank = ranks.find( + (r: { rank_name: string; rank_order: number }) => + r.rank_name === userStats.current_rank, + ); + + if (!requiredRank || !userRank) { + this.logger.warn( + `Rank validation skipped: requiredRank=${item.required_rank}, userRank=${userStats.current_rank}`, + ); + } else if (userRank.rank_order < requiredRank.rank_order) { + // Usuario necesita rank igual o superior (mayor rank_order = rank superior) + throw new BadRequestException( + `Required rank: ${item.required_rank} (or higher). Current rank: ${userStats.current_rank}`, + ); + } + } catch (error) { + if (error instanceof BadRequestException) { + throw error; + } + this.logger.error(`Failed to validate rank requirement: ${error}`); } } @@ -322,13 +360,12 @@ export class ShopService { } } - // Validar achievement requerido (si aplica) + // Validar achievement requerido + // Nota: Para implementar completamente, inyectar AchievementsService if (item.required_achievement_id) { - // Aquí podrías implementar validación de achievements - // const hasAchievement = await this.achievementsService.hasAchievement(userId, item.required_achievement_id); - // if (!hasAchievement) { - // throw new BadRequestException('Required achievement not unlocked'); - // } + this.logger.warn( + `Achievement validation not implemented for item ${item.id}, required achievement: ${item.required_achievement_id}`, + ); } } diff --git a/projects/gamilit/apps/backend/src/modules/gamification/services/user-stats.service.ts b/projects/gamilit/apps/backend/src/modules/gamification/services/user-stats.service.ts index 84d85c9..405f5a0 100644 --- a/projects/gamilit/apps/backend/src/modules/gamification/services/user-stats.service.ts +++ b/projects/gamilit/apps/backend/src/modules/gamification/services/user-stats.service.ts @@ -3,6 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UserStats } from '../entities'; import { UserGamificationSummaryDto } from '../dto/user-gamification-summary.dto'; +// CORR-CASCADA-001: Import MayaRank para alinear con entity corregida +import { MayaRank } from '@shared/constants/enums.constants'; /** * UserStatsService @@ -20,7 +22,14 @@ export class UserStatsService { private readonly XP_BASE = 100; // Base para cálculo cuadrático // Rangos disponibles en el sistema (ordenado de menor a mayor) - private readonly RANKS = ['Ajaw', 'Nacom', "Ah K'in", 'Halach Uinic', "K'uk'ulkan"]; + // CORR-CASCADA-001: Usar valores de MayaRank enum + private readonly RANKS: MayaRank[] = [ + MayaRank.AJAW, + MayaRank.NACOM, + MayaRank.AH_KIN, + MayaRank.HALACH_UINIC, + MayaRank.KUKULKAN, + ]; constructor( @InjectRepository(UserStats, 'gamification') diff --git a/projects/gamilit/apps/backend/src/modules/health/health.service.ts b/projects/gamilit/apps/backend/src/modules/health/health.service.ts index 740cb72..2f89eee 100644 --- a/projects/gamilit/apps/backend/src/modules/health/health.service.ts +++ b/projects/gamilit/apps/backend/src/modules/health/health.service.ts @@ -116,15 +116,15 @@ export class HealthService { try { // Critical tables to check across all schemas + // NOTE: auth.users is the correct location (not auth_management.users) const criticalTables = [ - { schema: 'auth_management', table: 'users', connection: this.authDataSource }, + { schema: 'auth', table: 'users', connection: this.authDataSource }, { schema: 'auth_management', table: 'profiles', connection: this.authDataSource }, { schema: 'educational_content', table: 'modules', connection: this.educationalDataSource }, { schema: 'educational_content', table: 'exercises', connection: this.educationalDataSource }, { schema: 'gamification_system', table: 'achievements', connection: this.gamificationDataSource }, { schema: 'progress_tracking', table: 'module_progress', connection: this.progressDataSource }, { schema: 'social_features', table: 'friendships', connection: this.socialDataSource }, - { schema: 'content_management', table: 'user_content', connection: this.contentDataSource }, { schema: 'audit_logging', table: 'audit_logs', connection: this.auditDataSource }, ]; diff --git a/projects/gamilit/apps/backend/src/modules/progress/dto/answers/exercise-answer.validator.ts b/projects/gamilit/apps/backend/src/modules/progress/dto/answers/exercise-answer.validator.ts index 8dd5189..a9761ee 100644 --- a/projects/gamilit/apps/backend/src/modules/progress/dto/answers/exercise-answer.validator.ts +++ b/projects/gamilit/apps/backend/src/modules/progress/dto/answers/exercise-answer.validator.ts @@ -2,7 +2,7 @@ import { BadRequestException } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { validate, ValidationError } from 'class-validator'; -// Import all 15 DTOs +// Import all 15 DTOs - Module 1, 2, 3 import { WordSearchAnswersDto } from './word-search-answers.dto'; import { TrueFalseAnswersDto } from './true-false-answers.dto'; import { CrucigramaAnswersDto } from './crucigrama-answers.dto'; @@ -24,6 +24,24 @@ import { CauseEffectMatchingAnswersDto } from './cause-effect-matching-answers.d import { MapaConceptualAnswersDto } from './mapa-conceptual-answers.dto'; import { EmparejamientoAnswersDto } from './emparejamiento-answers.dto'; +// Import Module 4 DTOs - Lectura Digital y Multimodal (5 ejercicios oficiales) +// @updated 2025-12-18 - LIMPIEZA-M4: Solo ejercicios oficiales según DocumentoDeDiseño v6.1 +import { + VerificadorFakeNewsAnswerDto, + InfografiaInteractivaAnswerDto, + QuizTikTokAnswerDto, + NavegacionHipertextualAnswerDto, + AnalisisMemesAnswerDto, +} from '../../../educational/dto/module4'; + +// Import Module 5 DTOs - Producción y Expresión Lectora (3 ejercicios oficiales) +// @updated 2025-12-18 - CORR-M5: Alineación con DocumentoDeDiseño v6.1 +import { + DiarioMultimediaAnswerDto, + VideoCartaAnswerDto, + ComicDigitalAnswerDto, +} from '../../../educational/dto/module5'; + /** * ExerciseAnswerValidator * @@ -119,14 +137,51 @@ export class ExerciseAnswerValidator { case 'cause_effect_matching': return CauseEffectMatchingAnswersDto; + // Module 4 - Lectura Digital y Multimodal + case 'verificador_fake_news': + return VerificadorFakeNewsAnswerDto; + + case 'infografia_interactiva': + return InfografiaInteractivaAnswerDto; + + case 'quiz_tiktok': + return QuizTikTokAnswerDto; + + case 'navegacion_hipertextual': + return NavegacionHipertextualAnswerDto; + + case 'analisis_memes': + return AnalisisMemesAnswerDto; + + // Module 5 - Producción y Expresión Lectora (3 ejercicios oficiales) + // @updated 2025-12-18 - CORR-M5: Alineación con DocumentoDeDiseño v6.1 + case 'diario_multimedia': + return DiarioMultimediaAnswerDto; + + case 'comic_digital': + return ComicDigitalAnswerDto; + + case 'video_carta': + return VideoCartaAnswerDto; + default: + // @updated 2025-12-18 - Alineación con DocumentoDeDiseño v6.1 + // Removidos: podcast, diario_reflexivo (M5), resena_critica, chat_literario, email_formal, ensayo_argumentativo (M4) throw new BadRequestException( `Unknown exercise type: ${exerciseType}. ` + - 'Valid types: sopa_letras, verdadero_falso, crucigrama, linea_tiempo, completar_espacios, ' + - 'mapa_conceptual, emparejamiento, ' + + 'Valid types: ' + + // Module 1 - Comprensión Literal + 'sopa_letras, verdadero_falso, crucigrama, linea_tiempo, completar_espacios, mapa_conceptual, emparejamiento, ' + + // Module 2 - Comprensión Inferencial 'detective_textual, construccion_hipotesis, prediccion_narrativa, puzzle_contexto, rueda_inferencias, ' + + // Module 3 - Comprensión Crítica 'tribunal_opiniones, analisis_fuentes, debate_digital, podcast_argumentativo, matriz_perspectivas, ' + - 'detective_connections, prediction_scenarios, cause_effect_matching', + // Auxiliary types + 'detective_connections, prediction_scenarios, cause_effect_matching, ' + + // Module 4 - Lectura Digital (5 oficiales) + 'verificador_fake_news, infografia_interactiva, quiz_tiktok, navegacion_hipertextual, analisis_memes, ' + + // Module 5 - Producción Creativa (3 oficiales) + 'diario_multimedia, comic_digital, video_carta', ); } } diff --git a/projects/gamilit/apps/backend/src/modules/progress/services/exercise-attempt.service.ts b/projects/gamilit/apps/backend/src/modules/progress/services/exercise-attempt.service.ts index 1c3293f..bcde4b7 100644 --- a/projects/gamilit/apps/backend/src/modules/progress/services/exercise-attempt.service.ts +++ b/projects/gamilit/apps/backend/src/modules/progress/services/exercise-attempt.service.ts @@ -643,6 +643,39 @@ export class ExerciseAttemptService { this.logger.log('[BUG-002 FIX] ✅ Module progress updated successfully'); + // IMPL-002: Si el módulo acaba de completarse por primera vez, incrementar modules_completed en user_stats + if (newStatus === 'completed') { + // Solo incrementar si el módulo no estaba previamente completado + const updateResult = await this.entityManager.query(` + UPDATE gamification_system.user_stats + SET modules_completed = modules_completed + 1, + updated_at = NOW() + WHERE user_id = $1 + AND NOT EXISTS ( + SELECT 1 FROM progress_tracking.module_progress mp + WHERE mp.user_id = $1 AND mp.module_id = $2 + AND mp.status = 'completed' + AND mp.completed_at < NOW() - INTERVAL '5 seconds' + ) + `, [userId, moduleId]); + + if (updateResult?.[1] > 0) { + this.logger.log(`[IMPL-002] ✅ Incremented modules_completed for user ${userId}`); + } + } + + // IMPL-003: Actualizar streak del usuario después de completar ejercicio + try { + await this.entityManager.query( + `SELECT * FROM gamification_system.update_leaderboard_streaks($1)`, + [userId] + ); + this.logger.log(`[IMPL-003] ✅ Updated streak for user ${userId}`); + } catch (streakError) { + const streakMsg = streakError instanceof Error ? streakError.message : String(streakError); + this.logger.warn(`[IMPL-003] ⚠️ Could not update streak: ${streakMsg}`); + } + } catch (error) { // Log error pero no bloquear el claim de rewards const errorMessage = error instanceof Error ? error.message : String(error); diff --git a/projects/gamilit/apps/backend/src/modules/progress/services/exercise-submission.service.ts b/projects/gamilit/apps/backend/src/modules/progress/services/exercise-submission.service.ts index be7b292..4a9ef25 100644 --- a/projects/gamilit/apps/backend/src/modules/progress/services/exercise-submission.service.ts +++ b/projects/gamilit/apps/backend/src/modules/progress/services/exercise-submission.service.ts @@ -10,6 +10,7 @@ import { Profile } from '@/modules/auth/entities'; import { UserStatsService } from '@/modules/gamification/services/user-stats.service'; import { MLCoinsService } from '@/modules/gamification/services/ml-coins.service'; import { MissionsService } from '@/modules/gamification/services/missions.service'; +import { AchievementsService } from '@/modules/gamification/services/achievements.service'; import { MissionTypeEnum } from '@/modules/gamification/entities/mission.entity'; import { NotificationsService } from '@/modules/notifications/services/notifications.service'; import { MailService } from '@/modules/mail/mail.service'; @@ -90,6 +91,7 @@ export class ExerciseSubmissionService { private readonly userStatsService: UserStatsService, private readonly mlCoinsService: MLCoinsService, private readonly missionsService: MissionsService, + private readonly achievementsService: AchievementsService, private readonly notificationsService: NotificationsService, private readonly mailService: MailService, ) {} @@ -398,7 +400,19 @@ export class ExerciseSubmissionService { console.log(`[P1-003] Manual grading applied: ${submission.score}/${submission.max_score}, correct=${submission.is_correct}`); - return this.submissionRepo.save(submission); + const savedSubmission = await this.submissionRepo.save(submission); + + // IMPL-004: Detectar y otorgar achievements después de calificación manual + try { + const earned = await this.achievementsService.detectAndGrantEarned(submission.user_id); + if (earned.length > 0) { + console.log(`[IMPL-004] ✅ Granted ${earned.length} achievements to user ${submission.user_id} after manual grading`); + } + } catch (achievementError) { + console.error(`[IMPL-004] ❌ Error detecting achievements: ${achievementError instanceof Error ? achievementError.message : String(achievementError)}`); + } + + return savedSubmission; } // Default: Auto-grading using SQL validate_and_audit() @@ -446,7 +460,19 @@ export class ExerciseSubmissionService { } } - return this.submissionRepo.save(submission); + const savedSubmission = await this.submissionRepo.save(submission); + + // IMPL-004: Detectar y otorgar achievements después de auto-grading + try { + const earned = await this.achievementsService.detectAndGrantEarned(submission.user_id); + if (earned.length > 0) { + console.log(`[IMPL-004] ✅ Granted ${earned.length} achievements to user ${submission.user_id} after auto-grading`); + } + } catch (achievementError) { + console.error(`[IMPL-004] ❌ Error detecting achievements: ${achievementError instanceof Error ? achievementError.message : String(achievementError)}`); + } + + return savedSubmission; } /** diff --git a/projects/gamilit/apps/backend/src/modules/teacher/controllers/manual-review.controller.ts b/projects/gamilit/apps/backend/src/modules/teacher/controllers/manual-review.controller.ts index 99b77e3..c537324 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/controllers/manual-review.controller.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/controllers/manual-review.controller.ts @@ -33,9 +33,11 @@ import { AuthRequest } from '@shared/types'; * - POST /api/v1/teacher/reviews/:id/start - Iniciar review (marcar como in_progress) * - POST /api/v1/teacher/reviews/:id/complete - Completar review * - POST /api/v1/teacher/reviews/:id/return - Devolver para revisión + * + * NOTA: La ruta base es 'teacher/reviews' porque el prefijo global 'api/v1' se agrega en main.ts */ @ApiTags('Teacher - Manual Reviews') -@Controller('api/v1/teacher/reviews') +@Controller('teacher/reviews') @UseGuards(JwtAuthGuard, RolesGuard) @ApiBearerAuth() export class ManualReviewController { diff --git a/projects/gamilit/apps/backend/src/modules/teacher/services/analytics.service.ts b/projects/gamilit/apps/backend/src/modules/teacher/services/analytics.service.ts index 9bcd1f8..54a5a7b 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/services/analytics.service.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/services/analytics.service.ts @@ -54,9 +54,9 @@ export class AnalyticsService { private readonly classroomRepository: Repository, @InjectRepository(ClassroomMember, 'social') private readonly classroomMemberRepository: Repository, - @InjectRepository(Assignment, 'content') + @InjectRepository(Assignment, 'educational') private readonly assignmentRepository: Repository, - @InjectRepository(AssignmentSubmission, 'content') + @InjectRepository(AssignmentSubmission, 'educational') private readonly assignmentSubmissionRepository: Repository, @InjectRepository(UserStats, 'gamification') private readonly userStatsRepository: Repository, diff --git a/projects/gamilit/apps/backend/src/modules/teacher/services/bonus-coins.service.ts b/projects/gamilit/apps/backend/src/modules/teacher/services/bonus-coins.service.ts index eaee288..5065355 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/services/bonus-coins.service.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/services/bonus-coins.service.ts @@ -11,6 +11,8 @@ import { ClassroomMember } from '@modules/social/entities/classroom-member.entit import { Classroom } from '@modules/social/entities/classroom.entity'; import { Profile } from '@modules/auth/entities/profile.entity'; import { GrantBonusDto, GrantBonusResponseDto } from '../dto/grant-bonus.dto'; +// CORR-CASCADA-001: Import MayaRank para alinear con entity corregida +import { MayaRank } from '@shared/constants/enums.constants'; /** * BonusCoinsService @@ -93,12 +95,13 @@ export class BonusCoinsService { this.logger.warn( `UserStats not found for student ${studentId}. Creating initial record.`, ); + // CORR-CASCADA-001: Usar MayaRank enum en lugar de string userStats = userStatsRepo.create({ user_id: studentId, level: 1, total_xp: 0, xp_to_next_level: 100, - current_rank: 'Ajaw', + current_rank: MayaRank.AJAW, ml_coins: 100, ml_coins_earned_total: 100, ml_coins_spent_total: 0, @@ -217,13 +220,14 @@ export class BonusCoinsService { * @param userId - ID del usuario (profile.id) * @returns UserStats creado */ + // CORR-CASCADA-001: Usar MayaRank enum en lugar de string private async createInitialUserStats(userId: string): Promise { const newStats = this.userStatsRepo.create({ user_id: userId, level: 1, total_xp: 0, xp_to_next_level: 100, - current_rank: 'Ajaw', + current_rank: MayaRank.AJAW, ml_coins: 100, // Balance inicial ml_coins_earned_total: 100, ml_coins_spent_total: 0, diff --git a/projects/gamilit/apps/backend/src/modules/teacher/services/exercise-responses.service.ts b/projects/gamilit/apps/backend/src/modules/teacher/services/exercise-responses.service.ts index 5afd02e..f4b53de 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/services/exercise-responses.service.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/services/exercise-responses.service.ts @@ -372,6 +372,10 @@ export class ExerciseResponsesService { } } + // Extract correct answers based on exercise type + // Different exercise types store correct answers in different fields + const correctAnswer = this.extractCorrectAnswers(exerciseContent, row.exercise_type); + return { id: row.attempt_id, student_id: row.attempt_user_id, @@ -390,12 +394,117 @@ export class ExerciseResponsesService { ml_coins_earned: row.attempt_ml_coins_earned, submitted_at: row.attempt_submitted_at ? new Date(row.attempt_submitted_at).toISOString() : new Date().toISOString(), // Additional detail fields - correct_answer: exerciseContent?.correct_answers || [], + correct_answer: correctAnswer, exercise_type: row.exercise_type || 'unknown', max_score: row.exercise_max_points || 100, }; } + /** + * Extract correct answers from exercise content based on exercise type + * Different exercise types store correct answers in different fields + */ + private extractCorrectAnswers(content: any, exerciseType: string): Record { + if (!content) return {}; + + // Try common correct answer fields first + if (content.correct_answers) { + return { answers: content.correct_answers }; + } + + // Handle specific exercise types + switch (exerciseType) { + case 'verdadero_falso': + // Extract correct answer from statements + // Return with 'statements' key to match frontend format + // DB stores: stmt.answer (boolean), not correctAnswer or isTrue + if (content.statements && Array.isArray(content.statements)) { + const statements: Record = {}; + content.statements.forEach((stmt: any, idx: number) => { + // Use stmt.id if available, otherwise use index+1 + const key = stmt.id ? String(stmt.id) : String(idx + 1); + // Priority: answer (DB field) > correctAnswer > isTrue > false + statements[key] = stmt.answer ?? stmt.correctAnswer ?? stmt.isTrue ?? false; + }); + return { statements }; + } + break; + + case 'completar_espacios': + // Extract correctAnswer from blanks + if (content.blanks && Array.isArray(content.blanks)) { + const blanks: Record = {}; + content.blanks.forEach((blank: any, idx: number) => { + blanks[String(idx + 1)] = blank.correctAnswer || blank.answer || ''; + }); + return { blanks }; + } + break; + + case 'crucigrama': + // Return words/answers for crossword + if (content.words) { + return { words: content.words }; + } + if (content.across_clues || content.down_clues) { + const words: Record = {}; + (content.across_clues || []).forEach((clue: any) => { + if (clue.answer) words[`H${clue.number}`] = clue.answer; + }); + (content.down_clues || []).forEach((clue: any) => { + if (clue.answer) words[`V${clue.number}`] = clue.answer; + }); + return { words }; + } + break; + + case 'sopa_letras': + // Return words to find + if (content.words) { + return { foundWords: content.words }; + } + break; + + case 'lectura_inferencial': + case 'prediccion_narrativa': + case 'puzzle_contexto': + case 'detective_textual': + case 'rueda_inferencias': + case 'causa_efecto': + // Multiple choice - extract from questions + if (content.questions && Array.isArray(content.questions)) { + const answers: Record = {}; + content.questions.forEach((q: any, idx: number) => { + answers[`question_${idx + 1}`] = q.correctAnswer ?? q.correct_answer ?? ''; + }); + return answers; + } + break; + + case 'mapa_conceptual': + // Return expected connections + if (content.connections || content.expectedConnections) { + return { connections: content.connections || content.expectedConnections }; + } + break; + + case 'timeline': + // Return correct order + if (content.events || content.correctOrder) { + return { events: content.correctOrder || content.events }; + } + break; + + default: + // Return full content for creative/multimedia exercises (modules 4, 5) + // These don't have "correct" answers, just submitted content + return content; + } + + // Fallback: return the full content + return content; + } + /** * Verify teacher has access to a specific student * diff --git a/projects/gamilit/apps/backend/src/modules/teacher/services/student-progress.service.ts b/projects/gamilit/apps/backend/src/modules/teacher/services/student-progress.service.ts index 6e8d286..c3e8b57 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/services/student-progress.service.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/services/student-progress.service.ts @@ -131,8 +131,9 @@ export class StudentProgressService { * Get student overview information */ async getStudentOverview(studentId: string): Promise { + // studentId is actually user_id from frontend (StudentInClassroomDto.user_id) const profile = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!profile) { @@ -171,8 +172,9 @@ export class StudentProgressService { * Get student statistics */ async getStudentStats(studentId: string): Promise { + // studentId is actually user_id from frontend const profile = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!profile) { @@ -239,8 +241,9 @@ export class StudentProgressService { async getModuleProgress( studentId: string, ): Promise { + // studentId is actually user_id from frontend const profile = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!profile) { @@ -275,8 +278,9 @@ export class StudentProgressService { studentId: string, query: GetStudentProgressQueryDto, ): Promise { + // studentId is actually user_id from frontend const profile = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!profile) { @@ -331,8 +335,9 @@ export class StudentProgressService { * Identify struggle areas for student */ async getStruggleAreas(studentId: string): Promise { + // studentId is actually user_id from frontend const profile = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!profile) { @@ -486,18 +491,18 @@ export class StudentProgressService { studentId: string, teacherId: string, ): Promise { - // Get student profile + // Get student profile - studentId is actually user_id from frontend const student = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!student) { throw new NotFoundException(`Student ${studentId} not found`); } - // Get student user + // Get student user - studentId is the user_id const studentUser = await this.userRepository.findOne({ - where: { id: student.user_id || undefined }, + where: { id: studentId }, }); if (!studentUser) { @@ -553,18 +558,18 @@ export class StudentProgressService { teacherId: string, noteDto: AddTeacherNoteDto, ): Promise { - // Get student profile + // Get student profile - studentId is actually user_id from frontend const student = await this.profileRepository.findOne({ - where: { id: studentId }, + where: { user_id: studentId }, }); if (!student) { throw new NotFoundException(`Student ${studentId} not found`); } - // Get student user + // Get student user - studentId is the user_id const studentUser = await this.userRepository.findOne({ - where: { id: student.user_id || undefined }, + where: { id: studentId }, }); if (!studentUser) { diff --git a/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-classrooms-crud.service.ts b/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-classrooms-crud.service.ts index 5f8a667..5cad91a 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-classrooms-crud.service.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-classrooms-crud.service.ts @@ -12,8 +12,8 @@ import { BadRequestException, ConflictException, } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, In } from 'typeorm'; +import { InjectRepository, InjectDataSource } from '@nestjs/typeorm'; +import { Repository, In, DataSource } from 'typeorm'; import { Classroom } from '@modules/social/entities/classroom.entity'; import { TeacherClassroom, @@ -26,6 +26,7 @@ import { ModuleProgress } from '@modules/progress/entities/module-progress.entit import { ExerciseSubmission } from '@modules/progress/entities/exercise-submission.entity'; import { Module } from '@modules/educational/entities/module.entity'; import { Exercise } from '@modules/educational/entities/exercise.entity'; +import { UserStats } from '@modules/gamification/entities/user-stats.entity'; import { CreateTeacherClassroomDto, UpdateTeacherClassroomDto, @@ -86,6 +87,14 @@ export class TeacherClassroomsCrudService { @InjectRepository(Exercise, 'educational') private readonly exerciseRepo: Repository, + + @InjectRepository(UserStats, 'gamification') + private readonly userStatsRepo: Repository, + + // FIX-2025-12-18: Inyectar DataSource para raw SQL en cross-schema joins + // Ver: orchestration/reportes/ANALISIS-ROOT-CAUSE-TYPEORM-CROSSSCHEMA-2025-12-18.md + @InjectDataSource('progress') + private readonly dataSource: DataSource, ) {} // ============================================================================ @@ -234,6 +243,12 @@ export class TeacherClassroomsCrudService { /** * Obtiene estudiantes de un classroom * + * FIX-2025-12-18: Corregido para que búsqueda se aplique ANTES de paginación + * PROBLEMA ANTERIOR: La búsqueda se aplicaba en memoria DESPUÉS de paginar, + * causando que usuarios en página 2 no aparezcan al buscar desde página 1 + * SOLUCIÓN: Usar raw SQL para aplicar filtros ANTES de paginar, luego calcular total correcto + * Ver: orchestration/reportes/PLAN-CORRECCION-TEACHER-MONITORING-2025-12-18.md + * * @param classroomId - ID del classroom * @param teacherId - ID del teacher (para validación) * @param query - Parámetros de búsqueda y filtrado @@ -250,39 +265,20 @@ export class TeacherClassroomsCrudService { // Validar acceso await this.validateTeacherAccess(teacherId, classroomId); - const { page = 1, limit = 20, search, status, sort_by = 'name', sort_order = 'asc' } = query; + const { page = 1, limit = 100, search, status, sort_by = 'name', sort_order = 'asc' } = query; - // Query base de miembros del classroom - const queryBuilder = this.classroomMemberRepo - .createQueryBuilder('cm') - .where('cm.classroom_id = :classroomId', { classroomId }); - - // Filtro de estado - if (status && status !== 'all') { - queryBuilder.andWhere('cm.status = :status', { status }); - } - - // Total de registros (antes de aplicar paginación) - const total = await queryBuilder.getCount(); - - // Paginación + // FIX: Obtener estudiantes con búsqueda aplicada ANTES de paginación usando raw SQL const skip = (page - 1) * limit; - queryBuilder.skip(skip).take(limit); + const { students: members, total } = await this.getStudentsWithSearch( + classroomId, + search, + status, + skip, + limit, + ); - // Ordenamiento - const orderDirection = sort_order.toUpperCase() as 'ASC' | 'DESC'; - if (sort_by === 'last_activity') { - queryBuilder.orderBy('cm.updated_at', orderDirection); - } - - // Ejecutar query - const members = await queryBuilder.getMany(); - - // Obtener IDs de estudiantes - const studentIds = members.map((m) => m.student_id); - - if (studentIds.length === 0) { - // No hay estudiantes en el classroom + if (members.length === 0) { + // No hay estudiantes que coincidan con los filtros const totalPages = Math.ceil(total / limit); return { data: [], @@ -297,40 +293,58 @@ export class TeacherClassroomsCrudService { }; } - // Obtener información de profiles - const profiles = await this.profileRepo.find({ - where: { user_id: In(studentIds) }, - }); + // Obtener IDs de estudiantes + const studentIds = members.map((m) => m.student_id); - // Obtener información de users - const users = await this.userRepo.find({ - where: { id: In(studentIds) }, - }); + // Obtener datos en paralelo: progreso, gamificacion y actividad actual + const [progressData, userStatsData, currentActivityData] = await Promise.all([ + this.getStudentsProgress(studentIds), + this.getStudentsUserStats(studentIds), + this.getStudentsCurrentActivity(studentIds), + ]); - // Obtener progreso de estudiantes (si aplica) - const progressData = await this.getStudentsProgress(studentIds); - - // Mapear a DTO + // Mapear a DTO con todos los datos (members ya incluye profile y user info) let data = members.map((member) => { - const profile = profiles.find((p) => p.user_id === member.student_id); - const user = users.find((u) => u.id === member.student_id); const progress = progressData.get(member.student_id); + const userStats = userStatsData.get(member.student_id); + const currentActivity = currentActivityData.get(member.student_id); - return this.mapToStudentInClassroomDto(member, profile, user, progress); + // Reconstruir Profile y User desde los datos del raw SQL + // FIX: Convertir null a undefined para compatibilidad con TypeScript + const profile: Partial = { + user_id: member.student_id, + first_name: member.first_name ?? undefined, + last_name: member.last_name ?? undefined, + avatar_url: member.avatar_url ?? undefined, + }; + + const user: Partial = { + id: member.student_id, + email: member.email ?? undefined, + }; + + // Reconstruir ClassroomMember desde los datos del raw SQL + const classroomMember: Partial = { + student_id: member.student_id, + classroom_id: classroomId, + status: member.status, + enrollment_date: member.enrollment_date, + attendance_percentage: member.attendance_percentage ?? undefined, + teacher_notes: member.teacher_notes ?? undefined, + updated_at: member.updated_at, + }; + + return this.mapToStudentInClassroomDto( + classroomMember as ClassroomMember, + profile as Profile, + user as User, + progress, + userStats, + currentActivity, + ); }); - // Aplicar filtro de búsqueda en memoria (después de mapear los datos) - if (search) { - const searchLower = search.toLowerCase(); - data = data.filter((student) => { - return ( - student.full_name.toLowerCase().includes(searchLower) || - (student.email && student.email.toLowerCase().includes(searchLower)) - ); - }); - } - - // Aplicar ordenamiento en memoria si es por nombre o progreso + // Aplicar ordenamiento en memoria (después de tener todos los datos calculados) if (sort_by === 'name') { data.sort((a, b) => { const comparison = a.full_name.localeCompare(b.full_name); @@ -350,9 +364,16 @@ export class TeacherClassroomsCrudService { const comparison = aScore - bScore; return sort_order === 'asc' ? comparison : -comparison; }); + } else if (sort_by === 'last_activity') { + data.sort((a, b) => { + const aDate = a.last_activity ? new Date(a.last_activity).getTime() : 0; + const bDate = b.last_activity ? new Date(b.last_activity).getTime() : 0; + const comparison = aDate - bDate; + return sort_order === 'asc' ? comparison : -comparison; + }); } - // Calcular paginación + // Calcular paginación con el total correcto (después de aplicar búsqueda) const totalPages = Math.ceil(total / limit); return { @@ -826,6 +847,98 @@ export class TeacherClassroomsCrudService { // HELPER METHODS // ============================================================================ + /** + * Obtiene estudiantes con búsqueda aplicada ANTES de paginación (raw SQL) + * + * FIX-2025-12-18: Nuevo método que usa raw SQL para cross-schema joins eficientes + * TypeORM QueryBuilder NO soporta cross-schema joins correctamente + * Ver: orchestration/reportes/ANALISIS-ROOT-CAUSE-TYPEORM-CROSSSCHEMA-2025-12-18.md + * + * @private + * @param classroomId - ID del classroom + * @param search - Término de búsqueda (nombre o email) + * @param status - Filtro de estado (active, inactive, suspended, all) + * @param skip - Número de registros a omitir (paginación) + * @param limit - Número de registros a retornar + * @returns Estudiantes filtrados y total de registros (después de aplicar búsqueda) + */ + private async getStudentsWithSearch( + classroomId: string, + search: string | undefined, + status: string | undefined, + skip: number, + limit: number, + ): Promise<{ + students: Array<{ + student_id: string; + status: string; + enrollment_date: Date; + attendance_percentage: number | null; + teacher_notes: string | null; + updated_at: Date; + first_name: string | null; + last_name: string | null; + avatar_url: string | null; + email: string | null; + }>; + total: number; + }> { + // Query para contar total (con filtros aplicados) + const countSql = ` + SELECT COUNT(*) as total + FROM social_features.classroom_members cm + LEFT JOIN auth_management.profiles p ON p.user_id = cm.student_id + LEFT JOIN auth.users u ON u.id = cm.student_id + WHERE cm.classroom_id = $1 + AND ($2::text IS NULL OR $2 = '' + OR LOWER(COALESCE(p.first_name, '') || ' ' || COALESCE(p.last_name, '')) LIKE LOWER('%' || $2 || '%') + OR LOWER(COALESCE(u.email, '')) LIKE LOWER('%' || $2 || '%')) + AND ($3::text IS NULL OR $3 = 'all' OR cm.status = $3) + `; + + // Query para obtener estudiantes (con filtros y paginación) + const studentsSql = ` + SELECT + cm.student_id, + cm.status, + cm.enrollment_date, + cm.attendance_percentage, + cm.teacher_notes, + cm.updated_at, + p.first_name, + p.last_name, + p.avatar_url, + u.email + FROM social_features.classroom_members cm + LEFT JOIN auth_management.profiles p ON p.user_id = cm.student_id + LEFT JOIN auth.users u ON u.id = cm.student_id + WHERE cm.classroom_id = $1 + AND ($2::text IS NULL OR $2 = '' + OR LOWER(COALESCE(p.first_name, '') || ' ' || COALESCE(p.last_name, '')) LIKE LOWER('%' || $2 || '%') + OR LOWER(COALESCE(u.email, '')) LIKE LOWER('%' || $2 || '%')) + AND ($3::text IS NULL OR $3 = 'all' OR cm.status = $3) + ORDER BY COALESCE(p.first_name, '') || ' ' || COALESCE(p.last_name, '') + LIMIT $4 OFFSET $5 + `; + + // Preparar parámetros (manejar undefined como null) + const searchParam = search || null; + const statusParam = status || 'all'; + + // Ejecutar queries en paralelo + const [countResult, studentsResult] = await Promise.all([ + this.dataSource.query(countSql, [classroomId, searchParam, statusParam]), + this.dataSource.query(studentsSql, [classroomId, searchParam, statusParam, limit, skip]), + ]); + + const total = parseInt(countResult[0]?.total || '0'); + + return { + students: studentsResult, + total, + }; + } + /** * Valida que un teacher tenga acceso a un classroom * @@ -874,6 +987,130 @@ export class TeacherClassroomsCrudService { return resultMap; } + /** + * Obtiene datos de gamificación de UserStats para estudiantes + * CORR-2025-12-18: Nueva función para obtener datos de gamificación + * + * @private + */ + private async getStudentsUserStats( + studentIds: string[], + ): Promise> { + if (studentIds.length === 0) { + return new Map(); + } + + const statsData = await this.userStatsRepo.find({ + where: { user_id: In(studentIds) }, + select: [ + 'user_id', + 'ml_coins', + 'current_rank', + 'achievements_earned', + 'exercises_completed', + 'total_time_spent', + 'last_activity_at', + ], + }); + + const resultMap = new Map(); + + statsData.forEach((stats) => { + // Convertir interval total_time_spent a minutos + // El formato es "HH:MM:SS" o "X days HH:MM:SS" + let timeMinutes = 0; + if (stats.total_time_spent) { + const timeStr = String(stats.total_time_spent); + // Parsear formato de interval PostgreSQL + if (timeStr.includes('day')) { + const dayMatch = timeStr.match(/(\d+)\s*day/); + const days = dayMatch ? parseInt(dayMatch[1]) : 0; + timeMinutes += days * 24 * 60; + } + const timeMatch = timeStr.match(/(\d+):(\d+):(\d+)/); + if (timeMatch) { + timeMinutes += parseInt(timeMatch[1]) * 60 + parseInt(timeMatch[2]); + } + } + + resultMap.set(stats.user_id, { + ml_coins: stats.ml_coins || 0, + current_rank: stats.current_rank || 'Ajaw', + achievements_count: stats.achievements_earned || 0, + exercises_completed: stats.exercises_completed || 0, + time_spent_minutes: timeMinutes, + last_activity_at: stats.last_activity_at || null, + }); + }); + + return resultMap; + } + + /** + * Obtiene la actividad actual (módulo y ejercicio) de los estudiantes + * + * FIX-2025-12-18: Corregido para usar raw SQL en lugar de TypeORM QueryBuilder + * PROBLEMA ANTERIOR: .innerJoin('schema.table', ...) NO funciona en TypeORM QueryBuilder + * SOLUCION: Usar raw SQL con this.dataSource.query() para cross-schema joins + * Ver: orchestration/reportes/ANALISIS-ROOT-CAUSE-TYPEORM-CROSSSCHEMA-2025-12-18.md + * + * @private + */ + private async getStudentsCurrentActivity( + studentIds: string[], + ): Promise> { + if (studentIds.length === 0) { + return new Map(); + } + + // FIX: Usar raw SQL para cross-schema joins (progress_tracking -> educational_content) + // TypeORM QueryBuilder NO soporta .innerJoin('schema.table', ...) directamente + const sql = ` + SELECT DISTINCT ON (es.user_id) + es.user_id, + e.title as exercise_title, + m.title as module_title + FROM progress_tracking.exercise_submissions es + LEFT JOIN educational_content.exercises e ON e.id = es.exercise_id + LEFT JOIN educational_content.modules m ON m.id = e.module_id + WHERE es.user_id = ANY($1) + ORDER BY es.user_id, es.submitted_at DESC + `; + + const latestSubmissions = await this.dataSource.query(sql, [studentIds]); + + const resultMap = new Map(); + + // Inicializar todos los estudiantes con valores null + studentIds.forEach(id => { + resultMap.set(id, { current_module: null, current_exercise: null }); + }); + + // Actualizar con los datos obtenidos + latestSubmissions.forEach((row: { user_id: string; module_title: string | null; exercise_title: string | null }) => { + resultMap.set(row.user_id, { + current_module: row.module_title || null, + current_exercise: row.exercise_title || null, + }); + }); + + return resultMap; + } + /** * Calcula estadísticas de progreso * @@ -887,7 +1124,7 @@ export class TeacherClassroomsCrudService { .select('AVG(mp.progress_percentage)', 'avg_progress') .addSelect('AVG(mp.average_score)', 'avg_score') // Fixed: score_percentage → average_score (GAP-ST-001) .addSelect( - 'SUM(CASE WHEN mp.completion_status = :completed THEN 1 ELSE 0 END)', + 'SUM(CASE WHEN mp.status = :completed THEN 1 ELSE 0 END)', 'completed_count', ) .addSelect('COUNT(*)', 'total_count') @@ -957,6 +1194,7 @@ export class TeacherClassroomsCrudService { /** * Mapea ClassroomMember a StudentInClassroomDto + * CORR-2025-12-18: Agregados campos de gamificación y actividad actual * * @private */ @@ -965,11 +1203,24 @@ export class TeacherClassroomsCrudService { profile?: Profile, user?: User, progress?: { progress: number; score: number }, + userStats?: { + ml_coins: number; + current_rank: string; + achievements_count: number; + exercises_completed: number; + time_spent_minutes: number; + last_activity_at: Date | null; + }, + currentActivity?: { current_module: string | null; current_exercise: string | null }, ): StudentInClassroomDto { const fullName = profile ? `${profile.first_name || ''} ${profile.last_name || ''}`.trim() : 'Unknown Student'; + // Obtener total de ejercicios (valor fijo por ahora, se puede calcular dinámicamente) + // TODO: Calcular dinámicamente basado en módulos asignados al classroom + const totalExercises = 50; // Valor aproximado de ejercicios totales + return { user_id: member.student_id, full_name: fullName || 'Unknown Student', @@ -977,11 +1228,21 @@ export class TeacherClassroomsCrudService { avatar: profile?.avatar_url || undefined, enrollment_date: member.enrollment_date, status: member.status, - progress_percentage: progress?.progress, - score_average: progress?.score, - last_activity: member.updated_at, + progress_percentage: progress?.progress ?? 0, + score_average: progress?.score ?? 0, + // Usar last_activity_at de UserStats si está disponible, sino usar updated_at del member + last_activity: userStats?.last_activity_at ?? member.updated_at, attendance_percentage: member.attendance_percentage || undefined, teacher_notes: member.teacher_notes || undefined, + // CORR-2025-12-18: Nuevos campos de gamificación y actividad + current_module: currentActivity?.current_module ?? null, + current_exercise: currentActivity?.current_exercise ?? null, + time_spent_minutes: userStats?.time_spent_minutes ?? 0, + exercises_completed: userStats?.exercises_completed ?? 0, + exercises_total: totalExercises, + total_ml_coins: userStats?.ml_coins ?? 0, + current_rank: userStats?.current_rank ?? null, + achievements_count: userStats?.achievements_count ?? 0, }; } diff --git a/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-content.service.ts b/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-content.service.ts index 0c84d05..a59bbc0 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-content.service.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/services/teacher-content.service.ts @@ -44,7 +44,7 @@ import { @Injectable() export class TeacherContentService { constructor( - @InjectRepository(TeacherContent, 'content') + @InjectRepository(TeacherContent, 'educational') private readonly contentRepo: Repository, @InjectRepository(Profile, 'auth') diff --git a/projects/gamilit/apps/backend/src/modules/teacher/teacher.module.ts b/projects/gamilit/apps/backend/src/modules/teacher/teacher.module.ts index 9ecae7d..7538c4e 100644 --- a/projects/gamilit/apps/backend/src/modules/teacher/teacher.module.ts +++ b/projects/gamilit/apps/backend/src/modules/teacher/teacher.module.ts @@ -142,8 +142,10 @@ import { ProgressModule } from '@modules/progress/progress.module'; // Entities from 'gamification' datasource TypeOrmModule.forFeature([UserStats, Achievement, UserAchievement], 'gamification'), - // Entities from 'content' datasource - TypeOrmModule.forFeature([Assignment, AssignmentSubmission, TeacherContent], 'content'), + // Entities from 'educational' datasource (schema: educational_content) + // CORRECTED (2025-12-18): Cambiado de 'content' a 'educational' + // Assignment, AssignmentSubmission y TeacherContent pertenecen a educational_content schema + TypeOrmModule.forFeature([Assignment, AssignmentSubmission, TeacherContent], 'educational'), // Entities from 'progress' datasource (teacher entities) TypeOrmModule.forFeature([StudentInterventionAlert], 'progress'), diff --git a/projects/gamilit/apps/backend/src/shared/constants/database.constants.ts b/projects/gamilit/apps/backend/src/shared/constants/database.constants.ts index 6aa4d43..cc06e7b 100644 --- a/projects/gamilit/apps/backend/src/shared/constants/database.constants.ts +++ b/projects/gamilit/apps/backend/src/shared/constants/database.constants.ts @@ -30,7 +30,7 @@ export const DB_SCHEMAS = { SYSTEM_CONFIGURATION: 'system_configuration', LTI_INTEGRATION: 'lti_integration', STORAGE: 'storage', - AUTH_SUPABASE: 'auth', // Schema de Supabase Auth (diferente de auth_management) + AUTH_BASE: 'auth', // Schema base de autenticación (diferente de auth_management) } as const; /** @@ -233,11 +233,11 @@ export const DB_TABLES = { }, /** - * Auth Base Schema (Supabase) - * Tabla base de usuarios de Supabase (diferente de auth_management) + * Auth Base Schema + * Tabla base de usuarios para autenticación (diferente de auth_management) */ AUTH_BASE: { - USERS: 'users', // ✨ NUEVO - P0 (nota: puede no necesitar entidad si usamos Supabase directamente) + USERS: 'users', // ✨ NUEVO - P0 }, /** diff --git a/projects/gamilit/apps/backend/src/shared/constants/enums.constants.ts b/projects/gamilit/apps/backend/src/shared/constants/enums.constants.ts index 164b003..816392c 100644 --- a/projects/gamilit/apps/backend/src/shared/constants/enums.constants.ts +++ b/projects/gamilit/apps/backend/src/shared/constants/enums.constants.ts @@ -253,7 +253,8 @@ export enum TransactionTypeEnum { /** * Categorías de logros (achievements) - * @see DDL: achievement_category ENUM + * @see DDL: gamification_system.achievement_category ENUM + * @version 1.1 (2025-12-15) - Agregados 'collection' y 'hidden' para alineación con Frontend */ export enum AchievementCategoryEnum { PROGRESS = 'progress', @@ -263,6 +264,8 @@ export enum AchievementCategoryEnum { SPECIAL = 'special', MASTERY = 'mastery', EXPLORATION = 'exploration', + COLLECTION = 'collection', // v1.1: Logros de colección + HIDDEN = 'hidden', // v1.1: Logros ocultos/secretos } /** diff --git a/projects/gamilit/apps/database/README.md b/projects/gamilit/apps/database/README.md index 3d31bb5..35976d0 100644 --- a/projects/gamilit/apps/database/README.md +++ b/projects/gamilit/apps/database/README.md @@ -79,8 +79,8 @@ El script `create-database.sh` ejecuta los archivos DDL en este orden: 1. **Prerequisites** - Schemas y ENUMs base 2. **Gamilit Schema** - Funciones y vistas compartidas (utilities) -3. **Auth Schema** - Autenticación Supabase -4. **Storage Schema** - Storage Supabase +3. **Auth Schema** - Autenticación base +4. **Storage Schema** - Almacenamiento 5. **Auth Management** - Gestión de usuarios 6. **Educational Content** - Contenido educativo 7. **Gamification System** - Sistema de gamificación diff --git a/projects/gamilit/apps/database/backup-prod/RESTORE_USUARIOS_PRODUCCION_2025-12-18.sql b/projects/gamilit/apps/database/backup-prod/RESTORE_USUARIOS_PRODUCCION_2025-12-18.sql new file mode 100644 index 0000000..78ecf53 --- /dev/null +++ b/projects/gamilit/apps/database/backup-prod/RESTORE_USUARIOS_PRODUCCION_2025-12-18.sql @@ -0,0 +1,260 @@ +-- ============================================================================ +-- GAMILIT - RESPALDO DE USUARIOS DE PRODUCCIÓN +-- Fecha de generación: $(date +%Y-%m-%d) +-- Total de usuarios: 49 +-- ============================================================================ +-- +-- INSTRUCCIONES DE USO: +-- --------------------- +-- 1. Este script debe ejecutarse DESPUÉS de crear el esquema de la base de datos +-- 2. Asegúrate de que los schemas auth, auth_management y gamification_system existan +-- 3. Asegúrate de que el tenant principal exista en auth_management.tenants +-- +-- ORDEN DE EJECUCIÓN: +-- 1. Crear base de datos y schemas +-- 2. Ejecutar DDL completo +-- 3. Ejecutar este script +-- +-- COMANDO: +-- psql -U gamilit_user -d gamilit_platform -h localhost -f RESTORE_USUARIOS_PRODUCCION.sql +-- +-- ============================================================================ + +-- Desactivar triggers temporalmente para evitar conflictos +SET session_replication_role = replica; + +-- ============================================================================ +-- SECCIÓN 1: USUARIOS (auth.users) +-- ============================================================================ + +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'authenticated', NULL, 'teacher@gamilit.com', '$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga', '2025-11-29 13:26:50.289631+00', NULL, '', NULL, '', NULL, '', '', NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"name": "Profesor Testing", "role": "admin_teacher", "description": "Usuario profesor de testing"}', false, '2025-11-29 13:26:50.289631+00', '2025-11-29 13:26:50.289631+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'admin_teacher', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'b017b792-b327-40dd-aefb-a80312776952', 'authenticated', NULL, 'joseal.guirre34@gmail.com', '$2b$10$kb9yCB4Y2WBr2.Gth.wC9e8q8bnkZJ6O2X6kFSn.O4VK8d76Cr/xO', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Aguirre", "first_name": "Jose"}', false, '2025-11-18 07:29:05.226874+00', '2025-11-18 07:29:05.226874+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '06a24962-e83d-4e94-aad7-ff69f20a9119', 'authenticated', NULL, 'sergiojimenezesteban63@gmail.com', '$2b$10$8oPdKN15ndCqCOIt12SEO.2yx4D29kQEQGPCC5rtUYWu8Qp5L7/zW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Jimenez", "first_name": "Sergio"}', false, '2025-11-18 08:17:40.925857+00', '2025-11-18 08:17:40.925857+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'authenticated', NULL, 'Gomezfornite92@gmail.com', '$2b$10$FuEfoSA0jxvBI2f6odMJqux9Gpgvt7Zjk.plRhRatvK0ykkIXxbI.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Gomez", "first_name": "Hugo"}', false, '2025-11-18 08:18:04.240276+00', '2025-11-18 08:18:04.240276+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'authenticated', NULL, 'Aragon494gt54@icloud.com', '$2b$10$lE8M8qWUIsgYLwcHyRGvTOjxdykLVchRVifsMVqCRCZq3bEeXR.xG', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Aragón", "first_name": "Hugo"}', false, '2025-11-18 08:20:17.228812+00', '2025-11-18 08:20:17.228812+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '2f5a9846-3393-40b2-9e87-0f29238c383f', 'authenticated', NULL, 'blu3wt7@gmail.com', '$2b$10$gKRXQ.rmOePqsNKWdxABQuyIZike2oSsYpdfWpQdi5HHDWDUk.3u2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Valentina", "first_name": "Azul"}', false, '2025-11-18 08:32:17.314233+00', '2025-11-18 08:32:17.314233+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5e738038-1743-4aa9-b222-30171300ea9d', 'authenticated', NULL, 'ricardolugo786@icloud.com', '$2b$10$YV1StKIdCPPED/Ft84zR2ONxj/VzzV7zOxjgwMSbDpd2hzvYOGtby', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Lugo", "first_name": "Ricardo"}', false, '2025-11-18 10:15:06.479774+00', '2025-11-18 10:15:06.479774+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '00c742d9-e5f7-4666-9597-5a8ca54d5478', 'authenticated', NULL, 'marbancarlos916@gmail.com', '$2b$10$PfsKOsEEXpGA6YB6eXNBPePo6OV6Am1glUN6Mkunl64bK/ji6uttW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Marban", "first_name": "Carlos"}', false, '2025-11-18 10:29:05.23842+00', '2025-11-18 10:29:05.23842+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '33306a65-a3b1-41d5-a49d-47989957b822', 'authenticated', NULL, 'diego.colores09@gmail.com', '$2b$10$rFlH9alBbgPGVEZMYIV8p.AkeZ30yRCVd5acasFjIt7fpCZhE6RuO', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Colores", "first_name": "Diego"}', false, '2025-11-18 10:29:20.530359+00', '2025-11-18 10:29:20.530359+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '7a6a973e-83f7-4374-a9fc-54258138115f', 'authenticated', NULL, 'hernandezfonsecabenjamin7@gmail.com', '$2b$10$1E6gLqfMojNLYrSKIbatqOh0pHblZ3jWZwbcxTY/DCx7MGADToCVm', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Hernandez", "first_name": "Benjamin"}', false, '2025-11-18 10:37:06.919813+00', '2025-11-18 10:37:06.919813+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'ccd7135c-0fea-4488-9094-9da52df1c98c', 'authenticated', NULL, 'jr7794315@gmail.com', '$2b$10$Ej/Gwx8mGCWg4TnQSjh1r.QZLw/GkUANqXmz4bEfVaNF9E527L02C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Reyes", "first_name": "Josue"}', false, '2025-11-18 17:53:39.67958+00', '2025-11-18 17:53:39.67958+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '9951ad75-e9cb-47b3-b478-6bb860ee2530', 'authenticated', NULL, 'barraganfer03@gmail.com', '$2b$10$VJ8bS.ksyKpa7oG575r5YOWQYcq8vwmwTa8jMBkCv0dwskF04SHn2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Barragan", "first_name": "Fernando"}', false, '2025-11-18 20:39:27.408624+00', '2025-11-18 20:39:27.408624+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '735235f5-260a-4c9b-913c-14a1efd083ea', 'authenticated', NULL, 'roman.rebollar.marcoantonio1008@gmail.com', '$2b$10$l4eF8UoOB7D8LKDEzTigXOUO7EABhVdYCqknJ/lD6R4p8uF1R4I.W', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Roman", "first_name": "Marco Antonio"}', false, '2025-11-18 21:03:17.326679+00', '2025-11-18 21:03:17.326679+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'ebe48628-5e44-4562-97b7-b4950b216247', 'authenticated', NULL, 'rodrigoguerrero0914@gmail.com', '$2b$10$ihoy7HbOdlqU38zAddpTOuDO7Nqa8.Cr1dEQjCgMpdb30UwCIMhGW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Guerrero", "first_name": "Rodrigo"}', false, '2025-11-18 21:20:52.303128+00', '2025-11-18 21:20:52.303128+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'd089b1af-462f-4d2c-b0f5-d2528cec8506', 'authenticated', NULL, 'santiagoferrara78@gmail.com', '$2b$10$Wjo3EENjiuddS9BwPMAW1OORZrZpU8ECP9zEXmd4Gvn7orwgjo8O2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 09:21:04.898591+00', '2025-11-24 09:21:04.898591+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6', 'authenticated', NULL, 'alexanserrv917@gmail.com', '$2b$10$8sT/ObLZUNmiu6CpbceHhenfc7E8zZml8AvB1HUiyOddSLqchggZ2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:26:51.934739+00', '2025-11-24 10:26:51.934739+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2', 'authenticated', NULL, 'aarizmendi434@gmail.com', '$2b$10$2BAG4EskBG0feGOIva6XyOCBtBJbKJE9h27GU6DmuBH3f.2iK6FoS', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:30:54.728262+00', '2025-11-24 10:30:54.728262+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '26fbc469-10af-4fa3-bd65-e5498188cc4f', 'authenticated', NULL, 'ashernarcisobenitezpalomino@gmail.com', '$2b$10$Bv5vo0GDeseWUWTt.5xV0O9nN93TRVN.vHRigs4vF/ww7Hbnjylam', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:37:35.325342+00', '2025-11-24 10:37:35.325342+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8', 'authenticated', NULL, 'ra.alejandrobm@gmail.com', '$2b$10$QZId3lZBIzBulD7AZCeEKOiL0LBJRekGlQTGiacC70IDwDo2wx7py', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:42:33.424367+00', '2025-11-24 10:42:33.424367+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1', 'authenticated', NULL, 'abdallahxelhaneriavega@gmail.com', '$2b$10$jQ4SquNUxIO70e7IBYqqLeUw1d.gSCleJ/cwinuWMVlW25a8.pRGG', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:45:19.984994+00', '2025-11-24 10:45:19.984994+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '012adac4-8ffd-47bd-9248-f0c5851e981f', 'authenticated', NULL, '09enriquecampos@gmail.com', '$2b$10$95c9hOplonbo/46O5UlPqummq.AIaGVIZ7YgBstSuOWPbgGersKxy', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:51:54.731982+00', '2025-11-24 10:51:54.731982+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '126b9257-7b0a-4bd6-9ab3-c505ee00e10a', 'authenticated', NULL, 'johhkk22@gmail.com', '$2b$10$Bt6IZ19zuBkly.6QmmPWBeF0kfyVN/O/c3/9bqyUGup3gPZu14DGa', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:53:47.029991+00', '2025-11-24 10:53:47.029991+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '9ac1746e-94a6-4efc-a961-951c015d416e', 'authenticated', NULL, 'edangiel4532@gmail.com', '$2b$10$eZap9LmAws7VtY9sHnS17.RJkhIte5SUobIWaWpuTxTPKjbKgzK.6', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:58:12.790316+00', '2025-11-24 10:58:12.790316+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'authenticated', NULL, 'student@gamilit.com', '$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga', '2025-11-29 13:26:50.289631+00', NULL, '', NULL, '', NULL, '', '', NULL, '2025-12-07 03:42:02.528+00', '{"provider": "email", "providers": ["email"]}', '{"name": "Estudiante Testing", "role": "student", "description": "Usuario estudiante de testing"}', false, '2025-11-29 13:26:50.289631+00', '2025-12-07 03:42:02.529507+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0', 'authenticated', NULL, 'erickfranco462@gmail.com', '$2b$10$lNzkSO7zbBHQcJJui0O76.a2artcsZHari4Mgkjo4btGww.Wy9/iC', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:00:11.800551+00', '2025-11-24 11:00:11.800551+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'aff5dcc6-32de-4769-9aaf-eda751fa0866', 'authenticated', NULL, 'gallinainsana@gmail.com', '$2b$10$6y/FVa4LqyliI4PXuBxKpepTRwIIRWybFN0NhcAqRM.Kl/cnvXDMq', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:03:17.536383+00', '2025-11-24 11:03:17.536383+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '0cda1645-83c5-445b-80b7-d0e4d436c00c', 'authenticated', NULL, 'leile5257@gmail.com', '$2b$10$ZZX0.z30VPm7BsLF8bNVweQpRZ2ca/1EPlxdIZy0xNaCFugoKL0ci', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:05:17.75852+00', '2025-11-24 11:05:17.75852+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '1364c463-88de-479b-a883-c0b7b362bcf8', 'authenticated', NULL, 'maximiliano.mejia367@gmail.com', '$2b$10$iTfIWKh2ISvPys2bkK2LOOPI24ua7I47oT8dFxHHYW7AuztoZreQa', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:08:58.232003+00', '2025-11-24 11:08:58.232003+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '547eb778-4782-4681-b198-c731bba36147', 'authenticated', NULL, 'fl432025@gmail.com', '$2b$10$aGKv6yhAWwHb07m3N2DxJOXIn5omkP3t2QeSYblhcDo52pB2ZiFQi', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:12:13.692614+00', '2025-11-24 11:12:13.692614+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5fc06693-e408-4eab-a9a3-fcd5f4e01296', 'authenticated', NULL, '7341023901m@gmail.com', '$2b$10$Z/HUBov20g..LZ6RDYax4.NcDuiFD/gn9Nrt7/OPCPBqCoTJUgr3C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:15:18.276345+00', '2025-11-24 11:15:18.276345+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5d1839f6-b03f-4e12-b236-eca43f4674f2', 'authenticated', NULL, 'segurauriel235@gmail.com', '$2b$10$IfdhPuUOModgrJT7bMfYkODZkXeTcaAReuCQf9BGpK1cT6GiP9UGu', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:17:46.846963+00', '2025-11-24 11:17:46.846963+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '1b310708-6f24-4c6a-88c9-a11f7a7f9763', 'authenticated', NULL, 'angelrabano11@gmail.com', '$2b$10$Sg6q4kErMvxRlZgWM9lCj.PfRg5sCQrwm763d7sfc3iaAUID7y436', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:47:53.790673+00', '2025-11-24 11:47:53.790673+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '3c613b0e-66f9-4640-a599-c9426d8edffb', 'authenticated', NULL, 'daliaayalareyes35@gmail.com', '$2b$10$dd2SQeBqNIZpZWCGMIDu1O8U6MLpWnKF05w641MNOMzHDZ/U5glCe', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:55:08.708961+00', '2025-11-24 11:55:08.708961+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '7ded133e-9b13-4467-9803-edb813f6a9a1', 'authenticated', NULL, 'alexeimongam@gmail.com', '$2b$10$jyQrHAIj6SsnReQ45FrFlOnDgpZtabskpxPuOYgB/h.YPLyZhuld.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:55:11.906996+00', '2025-11-24 11:55:11.906996+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '4cc04f54-7771-462d-98aa-a94448bb6ff5', 'authenticated', NULL, 'davidocampovenegas@gmail.com', '$2b$10$8COk10WE5.bXFJnAucEA0efcGQKU6KUXKV9N7n32ZX6aNKORs4McW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 14:52:46.468737+00', '2025-11-24 14:52:46.468737+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35', 'authenticated', NULL, 'zaid080809@gmail.com', '$2b$10$kdaUWR1BUqPRY7H8YkR.xuuDbqtLcvP5yKW.B0ooPlb.I6b/UU192', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 16:25:03.689847+00', '2025-11-24 16:25:03.689847+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4', 'authenticated', NULL, 'ruizcruzabrahamfrancisco@gmail.com', '$2b$10$DXHr682C4/VpesiHa7fRrOjKceiWSDUSx.1LZTbsvuxpqCdMNh/Ii', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 19:46:06.311558+00', '2025-11-24 19:46:06.311558+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '615adf6e-dbf3-480f-a907-3cfb3a64c6d2', 'authenticated', NULL, 'vituschinchilla@gmail.com', '$2b$10$dA8adTYlfhgqhZfACcQkFOCYjXdsmggXnIUluNDoh1zRFgQ6pq5O2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 21:07:26.037867+00', '2025-11-24 21:07:26.037867+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'bf445960-4c1f-4e29-8fb7-31667b183d7e', 'authenticated', NULL, 'bryan@betanzos.com', '$2b$10$Xdfuf4Tfog9QKd1FRLL.7eAaD6tr2cXgPx1/L8xqT1kLLzNHzSM26', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 06:13:30.263795+00', '2025-11-25 06:13:30.263795+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'd5fa4905-a78a-4040-8ad8-23220881c6a6', 'authenticated', NULL, 'loganalexander816@gmail.com', '$2b$10$8zLduh/9L/priag.nujz5utuloO9RnNFFDGdKgI2UniFCOwocEPLq', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 07:37:04.953164+00', '2025-11-25 07:37:04.953164+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '71734c15-cdaa-431b-90f5-97a57e0316a8', 'authenticated', NULL, 'carlois1974@gmail.com', '$2b$10$IfLfJ.q59DZgicR07ckSVOcrkkBJe42m1FECXxaoaodKYSo6uj5wW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 07:41:38.025764+00', '2025-11-25 07:41:38.025764+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '1efe491d-98ef-4c02-acd1-3135f7289072', 'authenticated', NULL, 'enriquecuevascbtis136@gmail.com', '$2b$10$9BX3OQMZmHruffBtN.3WPOFoyea6zgPd8i72DvhJ7vRAdqWKax6GS', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:16:33.977647+00', '2025-11-25 08:16:33.977647+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5ae21325-7450-4c37-82f1-3f9bcd7b6f45', 'authenticated', NULL, 'omarcitogonzalezzavaleta@gmail.com', '$2b$10$RRk3DAgQdiikxVImFIMqquqB.TNpKs3E.RNFtt1rwwTzO24uShri.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:17:07.610076+00', '2025-11-25 08:17:07.610076+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7', 'authenticated', NULL, 'gustavobm2024cbtis@gmail.com', '$2b$10$lg7KRUTPofcx4Rtyey8J7.XO0gmdBLCFIfK5uP08mqT0qUIl1aTJq', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:20:49.649184+00', '2025-11-25 08:20:49.649184+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '6e30164a-78b0-49b0-bd21-23d7c6c03349', 'authenticated', NULL, 'marianaxsotoxt22@gmail.com', '$2b$10$GQC9yTWiP2vP9GUp0gnhUeLjmw70EI4JQhfJBZbMOlCNXGXb/bt5O', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:33:18.150784+00', '2025-11-25 08:33:18.150784+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES (NULL, '0ae1bf21-39e3-4168-9632-457418c7a07d', 'authenticated', NULL, 'rckrdmrd@gmail.com', '$2b$10$LiDdaJLA.ZvdFleamkMuvOcIrW0PQMEh5aVZ5Wg5pzhm7gwc5s.1C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-09 01:22:42.784+00', NULL, '{}', false, '2025-11-29 13:37:09.271457+00', '2025-12-09 01:22:42.785367+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'authenticated', NULL, 'admin@gamilit.com', '$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga', '2025-11-29 13:26:50.289631+00', NULL, '', NULL, '', NULL, '', '', NULL, '2025-12-01 00:54:19.615+00', '{"provider": "email", "providers": ["email"]}', '{"name": "Admin GAMILIT", "role": "super_admin", "description": "Usuario administrador de testing"}', false, '2025-11-29 13:26:50.289631+00', '2025-12-01 00:54:19.617766+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'super_admin', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES (NULL, '69681b09-5077-4f77-84cc-67606abd9755', 'authenticated', NULL, 'javiermar06@hotmail.com', '$2b$10$3RHyXnR4BG3NaxP8Ez82FuiGDMNCG7GhNaOsMFigy3BpIVOzCqHMW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-14 03:51:04.122+00', NULL, '{}', false, '2025-12-08 19:24:06.266895+00', '2025-12-14 03:51:04.123886+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES (NULL, 'f929d6df-8c29-461f-88f5-264facd879e9', 'authenticated', NULL, 'ju188an@gmail.com', '$2b$10$9vUERFnXApdfXuAI7DFve.aa8uDjI5bfm4CI75/EZ2cUre83RytKe', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-17 23:51:43.553+00', NULL, '{}', false, '2025-12-17 17:51:43.530434+00', '2025-12-17 23:51:43.55475+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); + +-- ============================================================================ +-- SECCIÓN 2: PERFILES (auth_management.profiles) +-- ============================================================================ + +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Admin GAMILIT', 'Administrador GAMILIT', 'Administrador', 'GAMILIT', 'admin@gamilit.com', '/avatars/admin-testing.png', 'Usuario administrador para testing y desarrollo.', '55-0000-0001', '1985-01-01', NULL, NULL, NULL, 'super_admin', 'active', true, true, '{"theme": "professional", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{"description": "Usuario de testing principal", "testing_user": true}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Profesor Testing', 'Profesor de Testing GAMILIT', 'Profesor', 'Testing', 'teacher@gamilit.com', '/avatars/teacher-testing.png', 'Usuario profesor para testing y desarrollo.', '55-0000-0002', '1980-05-15', NULL, NULL, NULL, 'admin_teacher', 'active', true, true, '{"theme": "teacher", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{"subjects": ["Lengua Española", "Comprensión Lectora"], "testing_user": true}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('cccccccc-cccc-cccc-cccc-cccccccccccc', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Estudiante Testing', 'Estudiante de Testing GAMILIT', 'Estudiante', 'Testing', 'student@gamilit.com', '/avatars/student-testing.png', 'Usuario estudiante para testing y desarrollo.', '55-0000-0003', '2013-09-01', '5', 'EST-TEST-001', NULL, 'student', 'active', true, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "gamification": {"show_rank": true, "show_leaderboard": true, "show_achievements": true}, "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{"interests": ["lectura", "ciencia"], "testing_user": true, "learning_style": "visual"}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00', 'cccccccc-cccc-cccc-cccc-cccccccccccc', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('b017b792-b327-40dd-aefb-a80312776952', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Jose Aguirre', 'Jose Aguirre', 'Jose', 'Aguirre', 'joseal.guirre34@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 07:29:05.229254+00', '2025-11-18 07:29:05.229254+00', 'b017b792-b327-40dd-aefb-a80312776952', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('06a24962-e83d-4e94-aad7-ff69f20a9119', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Sergio Jimenez', 'Sergio Jimenez', 'Sergio', 'Jimenez', 'sergiojimenezesteban63@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:17:40.928077+00', '2025-11-18 08:17:40.928077+00', '06a24962-e83d-4e94-aad7-ff69f20a9119', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Hugo Gomez', 'Hugo Gomez', 'Hugo', 'Gomez', 'Gomezfornite92@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:18:04.242047+00', '2025-11-18 08:18:04.242047+00', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Hugo Aragón', 'Hugo Aragón', 'Hugo', 'Aragón', 'Aragon494gt54@icloud.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:20:17.230714+00', '2025-11-18 08:20:17.230714+00', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('2f5a9846-3393-40b2-9e87-0f29238c383f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Azul Valentina', 'Azul Valentina', 'Azul', 'Valentina', 'blu3wt7@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:32:17.315932+00', '2025-11-18 08:32:17.315932+00', '2f5a9846-3393-40b2-9e87-0f29238c383f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('5e738038-1743-4aa9-b222-30171300ea9d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ricardo Lugo', 'Ricardo Lugo', 'Ricardo', 'Lugo', 'ricardolugo786@icloud.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:15:06.481498+00', '2025-11-18 10:15:06.481498+00', '5e738038-1743-4aa9-b222-30171300ea9d', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('00c742d9-e5f7-4666-9597-5a8ca54d5478', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Carlos Marban', 'Carlos Marban', 'Carlos', 'Marban', 'marbancarlos916@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:29:05.240413+00', '2025-11-18 10:29:05.240413+00', '00c742d9-e5f7-4666-9597-5a8ca54d5478', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('33306a65-a3b1-41d5-a49d-47989957b822', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Diego Colores', 'Diego Colores', 'Diego', 'Colores', 'diego.colores09@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:29:20.531883+00', '2025-11-18 10:29:20.531883+00', '33306a65-a3b1-41d5-a49d-47989957b822', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('7a6a973e-83f7-4374-a9fc-54258138115f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Benjamin Hernandez', 'Benjamin Hernandez', 'Benjamin', 'Hernandez', 'hernandezfonsecabenjamin7@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:37:06.9215+00', '2025-11-18 10:37:06.9215+00', '7a6a973e-83f7-4374-a9fc-54258138115f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ccd7135c-0fea-4488-9094-9da52df1c98c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Josue Reyes', 'Josue Reyes', 'Josue', 'Reyes', 'jr7794315@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 17:53:39.681271+00', '2025-11-18 17:53:39.681271+00', 'ccd7135c-0fea-4488-9094-9da52df1c98c', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('9951ad75-e9cb-47b3-b478-6bb860ee2530', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Fernando Barragan', 'Fernando Barragan', 'Fernando', 'Barragan', 'barraganfer03@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 20:39:27.410436+00', '2025-11-18 20:39:27.410436+00', '9951ad75-e9cb-47b3-b478-6bb860ee2530', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('735235f5-260a-4c9b-913c-14a1efd083ea', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Marco Antonio Roman', 'Marco Antonio Roman', 'Marco Antonio', 'Roman', 'roman.rebollar.marcoantonio1008@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 21:03:17.328254+00', '2025-11-18 21:03:17.328254+00', '735235f5-260a-4c9b-913c-14a1efd083ea', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ebe48628-5e44-4562-97b7-b4950b216247', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Rodrigo Guerrero', 'Rodrigo Guerrero', 'Rodrigo', 'Guerrero', 'rodrigoguerrero0914@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 21:20:52.304488+00', '2025-11-18 21:20:52.304488+00', 'ebe48628-5e44-4562-97b7-b4950b216247', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('3f255f44-40d9-4dc9-970c-d50ddec197b3', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'alexanserrv917@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('f1870075-a6e0-47e7-88c6-793320ab3c8f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'carlois1974@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '71734c15-cdaa-431b-90f5-97a57e0316a8', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ab2425d9-e2da-49ac-b8db-2db605e7283f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'gustavobm2024cbtis@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('802f4d68-bbd0-4220-8218-634975c3774a', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'gallinainsana@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'aff5dcc6-32de-4769-9aaf-eda751fa0866', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('142af777-fce1-4067-b84b-f684e2fa1170', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'zaid080809@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('b5c2d5dc-e753-40ff-8e01-95c4c497710c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'davidocampovenegas@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '4cc04f54-7771-462d-98aa-a94448bb6ff5', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('d29345bb-fd48-4f69-ac81-eeedd4b41e6d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'marianaxsotoxt22@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '6e30164a-78b0-49b0-bd21-23d7c6c03349', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('813055ff-e5b7-4538-825c-eb721360e189', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'leile5257@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '0cda1645-83c5-445b-80b7-d0e4d436c00c', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('6c9bbb36-0b2d-49ea-9c1b-63209f009773', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'ashernarcisobenitezpalomino@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '26fbc469-10af-4fa3-bd65-e5498188cc4f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('8daf8ed9-d15f-407f-b827-3a9c01907e62', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'ruizcruzabrahamfrancisco@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bd028538-520d-45cf-a6f7-27c9f675d663', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'daliaayalareyes35@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '3c613b0e-66f9-4640-a599-c9426d8edffb', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('de1511df-f963-4ff6-8e3f-2225ba493879', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'ra.alejandrobm@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('26168044-3b5c-43f6-a757-833ba1485d41', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'enriquecuevascbtis136@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '1efe491d-98ef-4c02-acd1-3135f7289072', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('e742724a-0ff6-4760-884b-866835460045', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'fl432025@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '547eb778-4782-4681-b198-c731bba36147', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('3ce354c8-bcac-44c6-9a94-5274e5f9b389', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'abdallahxelhaneriavega@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('188fa4e3-985c-4048-8913-754cb0560875', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', '7341023901m@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5fc06693-e408-4eab-a9a3-fcd5f4e01296', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('caa05325-b8e7-4b1c-9d95-03d4e0c7372d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'vituschinchilla@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '615adf6e-dbf3-480f-a907-3cfb3a64c6d2', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('128ac756-bdb8-49d7-8fdb-cdb8fd241d06', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'alexeimongam@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '7ded133e-9b13-4467-9803-edb813f6a9a1', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('7f3bb769-4d7e-4ca9-8527-708da0368be5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'angelrabano11@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '1b310708-6f24-4c6a-88c9-a11f7a7f9763', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('6a565d40-9012-4c89-878c-05bb8b6e2d81', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'loganalexander816@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'd5fa4905-a78a-4040-8ad8-23220881c6a6', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('7ede7b67-42d2-44cd-a530-66f62a68cd54', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'bryan@betanzos.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'bf445960-4c1f-4e29-8fb7-31667b183d7e', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('2df90a89-455e-4637-8b96-ad01c45f5701', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'johhkk22@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '126b9257-7b0a-4bd6-9ab3-c505ee00e10a', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('43560c6b-fda2-4b45-bc0b-7dbbbffff05c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'edangiel4532@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '9ac1746e-94a6-4efc-a961-951c015d416e', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ff4760c8-5359-43e9-9b42-95a0bf3e3d36', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'aarizmendi434@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('a4c43698-c276-4430-b15e-8373b7bbb662', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'santiagoferrara78@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'd089b1af-462f-4d2c-b0f5-d2528cec8506', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('30462f07-1c6b-4706-b4fb-288845b3631e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', '09enriquecampos@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '012adac4-8ffd-47bd-9248-f0c5851e981f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('4f5170f2-1d35-4130-a535-1d93383e406b', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'maximiliano.mejia367@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '1364c463-88de-479b-a883-c0b7b362bcf8', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('c0aecfcc-3b2f-4117-9f20-e0920df97dc0', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'segurauriel235@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5d1839f6-b03f-4e12-b236-eca43f4674f2', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('3dfcdc9d-de8a-45b3-a05f-b83b51097ef5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'omarcitogonzalezzavaleta@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5ae21325-7450-4c37-82f1-3f9bcd7b6f45', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bb74b280-db90-4240-ab09-b8c6cf63d553', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'erickfranco462@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('0ae1bf21-39e3-4168-9632-457418c7a07d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, 'rckrdmrd@gmail.com', NULL, 'rckrdmrd@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:37:09.278078+00', '2025-11-29 13:37:09.278078+00', '0ae1bf21-39e3-4168-9632-457418c7a07d', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('69681b09-5077-4f77-84cc-67606abd9755', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, 'Javier', ' Mar', 'javiermar06@hotmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-12-08 19:24:06.272257+00', '2025-12-08 19:24:06.272257+00', '69681b09-5077-4f77-84cc-67606abd9755', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('f929d6df-8c29-461f-88f5-264facd879e9', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, 'Juan', 'pa', 'ju188an@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-12-17 17:51:43.536295+00', '2025-12-17 17:51:43.536295+00', 'f929d6df-8c29-461f-88f5-264facd879e9', NULL); + +-- ============================================================================ +-- SECCIÓN 3: ESTADÍSTICAS DE USUARIOS (gamification_system.user_stats) +-- ============================================================================ + +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('88b149bb-5f3b-41bb-885f-e226eb9cac22', 'b017b792-b327-40dd-aefb-a80312776952', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('1cb60bdb-800a-4ebc-a9c3-16ecf78d3535', '06a24962-e83d-4e94-aad7-ff69f20a9119', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5cba623a-015f-41e9-9de0-da67bdd6b5fb', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('c6a7e9dd-ade4-4860-a4e0-882fd5aa0ed1', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('ee39d06d-d9c5-492a-9855-2210c74b74fd', 'ccd7135c-0fea-4488-9094-9da52df1c98c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e6be8348-29af-49c4-b49d-4b2d978c951b', 'ebe48628-5e44-4562-97b7-b4950b216247', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('9c4cf796-3768-4182-b1e0-1a38f8187c87', '00c742d9-e5f7-4666-9597-5a8ca54d5478', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 3, 525, 100, 'Nacom', 0.00, 395, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('1f6076c1-7b54-43f7-9fca-519407e65c4f', '2f5a9846-3393-40b2-9e87-0f29238c383f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 2, 150, 100, 'Ajaw', 0.00, 140, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('1bec7e38-0109-4e25-bbad-8e28cdc211a2', '0ae1bf21-39e3-4168-9632-457418c7a07d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 4, 1180, 100, 'Ah K''in', 0.00, 710, 360, 0, 40, '2025-11-29 19:41:58.551+00', 0, 0, NULL, 0, 9, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, '2025-11-29 13:41:42.417785+00', NULL, '{}', '2025-11-29 13:37:09.278078+00', '2025-11-29 13:41:59.942645+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('524efcd5-9c89-4a79-ac4c-f58f92f9fd28', '7a6a973e-83f7-4374-a9fc-54258138115f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 25, 100, 'Ajaw', 0.00, 110, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5035c591-f320-4182-981a-9a1416030d75', '69681b09-5077-4f77-84cc-67606abd9755', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-12-08 19:24:06.272257+00', '2025-12-08 19:24:06.272257+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('fe110986-762c-459f-acde-b2c865c237bb', '33306a65-a3b1-41d5-a49d-47989957b822', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 75, 100, 'Ajaw', 0.00, 120, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('06fd9e66-cefc-4c64-a446-37cef9668b36', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 2, 375, 100, 'Ajaw', 0.00, 260, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('f0734a0b-1d9c-4999-bcba-8e3b8f229c80', '9951ad75-e9cb-47b3-b478-6bb860ee2530', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 2, 375, 100, 'Ajaw', 0.00, 260, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('7446ef44-11a1-451d-9624-abd95f0eb2ec', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 6, 2525, 100, 'Halach Uinic', 0.00, 1960, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('0145f432-c383-45d1-9d0f-0c7783a48612', '735235f5-260a-4c9b-913c-14a1efd083ea', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 75, 100, 'Ajaw', 0.00, 135, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('c29ffaef-0392-4fe1-ac41-b502a4052111', '5e738038-1743-4aa9-b222-30171300ea9d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 25, 100, 'Ajaw', 0.00, 110, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('762397ff-699f-4bb8-bef6-2e03959fe4c1', '3f255f44-40d9-4dc9-970c-d50ddec197b3', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e8a5e302-93c4-4f6a-8da6-b81ed01dde7a', 'f1870075-a6e0-47e7-88c6-793320ab3c8f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('bc3dda37-e131-45c1-8595-e047eb751a2f', 'ab2425d9-e2da-49ac-b8db-2db605e7283f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('7e0781fc-ab59-4ea4-a57a-3b95b45c151f', '802f4d68-bbd0-4220-8218-634975c3774a', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5b11ed88-05e5-4edb-94a1-13a9b7a93ec2', '142af777-fce1-4067-b84b-f684e2fa1170', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('f5b9530d-0587-44d5-9bd9-890a1ff8ed26', 'b5c2d5dc-e753-40ff-8e01-95c4c497710c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('82e45270-b5a0-478e-a81f-8330e638921c', 'd29345bb-fd48-4f69-ac81-eeedd4b41e6d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('94ad1072-16af-401b-b32f-3c33fb07c22b', '813055ff-e5b7-4538-825c-eb721360e189', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('a51cd72a-1d76-4367-84ee-5fd448eec673', '6c9bbb36-0b2d-49ea-9c1b-63209f009773', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('9ac32e07-bde6-4d8d-b172-2cf8c28b3cb6', '8daf8ed9-d15f-407f-b827-3a9c01907e62', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('6a89fb69-f4f9-409e-8687-1927a9c9931a', 'bd028538-520d-45cf-a6f7-27c9f675d663', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e08417c1-a015-4820-bab2-0e3bafda8c81', 'de1511df-f963-4ff6-8e3f-2225ba493879', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('27b913c1-b6c1-41a8-ac80-b8aec18dfdcb', '26168044-3b5c-43f6-a757-833ba1485d41', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('d3a33394-f4dc-4e3e-b817-eb6309b17b59', 'e742724a-0ff6-4760-884b-866835460045', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('3e6f2e70-0113-42ae-beaf-f2fed10d70f2', '3ce354c8-bcac-44c6-9a94-5274e5f9b389', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('207b92d0-39d0-4f81-b00d-9119d7a20626', '188fa4e3-985c-4048-8913-754cb0560875', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e0ece833-4e73-4ada-b7d2-25197cf9097d', 'caa05325-b8e7-4b1c-9d95-03d4e0c7372d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e58e13b1-cc53-40da-8ecf-3fa4c2c97672', '128ac756-bdb8-49d7-8fdb-cdb8fd241d06', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('719b10c0-2de5-43a3-b9e4-d38730a92ad2', '7f3bb769-4d7e-4ca9-8527-708da0368be5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('66570bed-28ce-407d-a5df-43236bba500a', '6a565d40-9012-4c89-878c-05bb8b6e2d81', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('f9cb12f8-523f-47c9-b3f3-ac55aac11502', '7ede7b67-42d2-44cd-a530-66f62a68cd54', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('026b8072-f628-4bda-aeda-ce76d2fbccce', '2df90a89-455e-4637-8b96-ad01c45f5701', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('713232b8-54d8-49b8-bcc5-9fabb3bd5c76', '43560c6b-fda2-4b45-bc0b-7dbbbffff05c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('4223a8af-8011-4234-b71d-520c3f7b23b0', 'ff4760c8-5359-43e9-9b42-95a0bf3e3d36', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('b54503c1-f3fd-411d-9830-882aa2c54dc2', 'a4c43698-c276-4430-b15e-8373b7bbb662', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('a657e586-ce3a-4c5c-b244-f704c13fff5e', '30462f07-1c6b-4706-b4fb-288845b3631e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('ce3898e2-403d-4fc1-8bfd-eb78be76b86a', '4f5170f2-1d35-4130-a535-1d93383e406b', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('28dac481-b80a-47c3-9de4-693f2774c470', 'c0aecfcc-3b2f-4117-9f20-e0920df97dc0', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('6732ab00-bca6-4a4b-b9ac-34adfb108299', '3dfcdc9d-de8a-45b3-a05f-b83b51097ef5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('ef81f3a4-9995-4c4c-b2b0-927235077174', 'bb74b280-db90-4240-ab09-b8c6cf63d553', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('b3830215-9623-46a6-9624-2c7183f13737', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 5, 1825, 100, 'Halach Uinic', 0.00, 1625, 180, 0, 0, NULL, 0, 0, NULL, 0, 8, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, '2025-12-02 10:55:24.674923+00', NULL, '{}', '2025-11-29 13:26:50.458823+00', '2025-12-02 10:55:24.674923+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5bc3c779-e7f4-4c4a-9bca-c0854ea1e288', 'f929d6df-8c29-461f-88f5-264facd879e9', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-12-17 17:51:43.536295+00', '2025-12-17 17:51:43.536295+00'); + +-- ============================================================================ +-- SECCIÓN 4: RANGOS DE USUARIOS (gamification_system.user_ranks) +-- ============================================================================ + +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('aef81f6b-b9c4-4f80-b27f-8f9bf8c2cd97', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.458823+00', NULL, true, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('4cadc6a3-c15e-410a-88f5-c0254463ca8a', 'b017b792-b327-40dd-aefb-a80312776952', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('8c1d72aa-fead-4bff-bf47-b34ceab62a40', '06a24962-e83d-4e94-aad7-ff69f20a9119', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('840cabba-4a80-40fa-a3e0-0e218e09463e', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('913ae601-8ab4-4056-864c-517e100bef5b', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('eb382620-70b8-4842-b8cf-ad51eb1c7266', '2f5a9846-3393-40b2-9e87-0f29238c383f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('207ff6ef-a56d-4fc1-942f-5a50e0c842da', '5e738038-1743-4aa9-b222-30171300ea9d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('64946395-a46c-4e93-abf7-5b827e4dcf12', '33306a65-a3b1-41d5-a49d-47989957b822', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('66cb0dd1-2c47-4572-99ec-c97ccee95e53', '7a6a973e-83f7-4374-a9fc-54258138115f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('241b6cea-03d0-4f9c-97b6-7d49d9175cfb', 'ccd7135c-0fea-4488-9094-9da52df1c98c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b14b22be-bd9d-485c-9477-b4122c63cbfb', '9951ad75-e9cb-47b3-b478-6bb860ee2530', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('4e7cf7a1-8ef4-4d20-902b-ba1c57faad51', '735235f5-260a-4c9b-913c-14a1efd083ea', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('d0500d4e-17ec-46be-94a1-4e2500e4b9a8', 'ebe48628-5e44-4562-97b7-b4950b216247', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('9a4e06cc-0a50-4ff9-b781-d29edff7bbd6', '00c742d9-e5f7-4666-9597-5a8ca54d5478', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Nacom', 'Ajaw', 0, NULL, 0, NULL, 0, 100, NULL, NULL, '2025-11-29 13:26:52.138221+00', '2025-11-29 13:26:50.539509+00', true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.138221+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('33b12003-65b0-4f50-8bda-6eeebedc3413', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Halach Uinic', 'Ah K''in', 0, NULL, 0, NULL, 0, 500, NULL, NULL, '2025-11-29 13:26:52.158657+00', '2025-11-29 13:26:52.158657+00', true, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:52.138221+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('7c3b01ab-b75b-4464-8858-91c1ff805c32', '3dfcdc9d-de8a-45b3-a05f-b83b51097ef5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('53ec34ee-bd3c-462f-b5b0-317705c0399d', '3f255f44-40d9-4dc9-970c-d50ddec197b3', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b2461bb7-7107-48dd-923f-de4ff8e86a67', 'e742724a-0ff6-4760-884b-866835460045', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('890ba444-c64f-4680-894d-56bf30cbcf5c', 'f1870075-a6e0-47e7-88c6-793320ab3c8f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('a7b16bb9-efa6-4f10-8fe0-992adbd8c482', 'b5c2d5dc-e753-40ff-8e01-95c4c497710c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('c6eb5dd4-a987-4560-b22d-645ca31791e6', '142af777-fce1-4067-b84b-f684e2fa1170', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('520cd26c-5222-4579-8f1d-72f4be8e98d3', 'c0aecfcc-3b2f-4117-9f20-e0920df97dc0', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('2971dbbf-33de-4f12-9e47-48c9a3b145df', '7ede7b67-42d2-44cd-a530-66f62a68cd54', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('cb9feef2-0f2d-4949-b212-3b6d7dbfe2fa', 'caa05325-b8e7-4b1c-9d95-03d4e0c7372d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('89181d4a-a47b-4449-81fa-09a7e1d67534', 'a4c43698-c276-4430-b15e-8373b7bbb662', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('998f3c88-8932-4d91-af11-7bca6c03bbc3', 'bb74b280-db90-4240-ab09-b8c6cf63d553', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('e22e4620-57fc-47ad-ae53-f45ecc17932f', '4f5170f2-1d35-4130-a535-1d93383e406b', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b9e35a4b-ade2-4fa0-bd58-38ffcf5ca614', '802f4d68-bbd0-4220-8218-634975c3774a', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('365f3ac3-2880-42a8-8799-0c465b0a4ed9', 'de1511df-f963-4ff6-8e3f-2225ba493879', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('21152c4c-b082-40a7-938b-e7486730e2bb', '188fa4e3-985c-4048-8913-754cb0560875', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b1c76243-62c4-4e73-8e7b-152b82e41c93', '128ac756-bdb8-49d7-8fdb-cdb8fd241d06', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('e521b5a1-250b-4e96-a2cd-e8736f1f3fbe', '6c9bbb36-0b2d-49ea-9c1b-63209f009773', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('a106676f-8723-492b-b4f1-1b9256da8a4d', '813055ff-e5b7-4538-825c-eb721360e189', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('8cf5754b-7af4-429f-8e57-b0f2e4f740d6', '3ce354c8-bcac-44c6-9a94-5274e5f9b389', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('89526d39-7d29-4d97-a65b-94a42dfb45e2', 'ab2425d9-e2da-49ac-b8db-2db605e7283f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('fa25a282-3568-481a-878d-c424f6eaee95', 'ff4760c8-5359-43e9-9b42-95a0bf3e3d36', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('aee1d9bf-b4fe-4512-9cf0-4cc90413692e', '8daf8ed9-d15f-407f-b827-3a9c01907e62', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('69621cdf-cc64-4f2b-85f3-d1f09b54cbf0', '7f3bb769-4d7e-4ca9-8527-708da0368be5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b4c93d8d-fb84-4ba2-91d7-78abd5a780de', '26168044-3b5c-43f6-a757-833ba1485d41', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('3444f9aa-7c1b-452c-bfbb-af58dc70e70b', 'bd028538-520d-45cf-a6f7-27c9f675d663', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('c8aec5d3-4a66-40b6-a8e7-9b2edd4225b1', '2df90a89-455e-4637-8b96-ad01c45f5701', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('e284ffd8-9d58-4564-95c4-075721724643', '6a565d40-9012-4c89-878c-05bb8b6e2d81', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('abfa66bf-dc3d-426a-b4ec-538677b39cd5', '43560c6b-fda2-4b45-bc0b-7dbbbffff05c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b26939b5-2dc1-4643-b46c-c4a3d6a5203f', 'd29345bb-fd48-4f69-ac81-eeedd4b41e6d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('564433a1-e5f1-48ce-8807-06f5b9ea1418', '30462f07-1c6b-4706-b4fb-288845b3631e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('3c88a791-ad9b-4988-9351-b5e9df60c9a4', '0ae1bf21-39e3-4168-9632-457418c7a07d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ah K''in', 'Nacom', 0, NULL, 0, NULL, 0, 250, NULL, NULL, '2025-11-29 13:41:42.417785+00', '2025-11-29 13:40:27.516619+00', true, '{}', '2025-11-29 13:37:09.278078+00', '2025-11-29 13:41:42.417785+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('cb04ef70-2738-4c9e-b32a-a92643b8a3f9', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Halach Uinic', 'Ah K''in', 0, NULL, 0, NULL, 0, 500, NULL, NULL, '2025-11-30 20:12:46.263821+00', '2025-11-29 13:26:52.146305+00', true, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-30 20:12:46.263821+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('1de77cef-003a-4d08-8ccc-8bd4001fa62c', '69681b09-5077-4f77-84cc-67606abd9755', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-12-08 19:24:06.272257+00', NULL, true, '{}', '2025-12-08 19:24:06.272257+00', '2025-12-08 19:24:06.272257+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('63b40546-38a8-4c80-bf11-3504c73150bb', 'f929d6df-8c29-461f-88f5-264facd879e9', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-12-17 17:51:43.536295+00', NULL, true, '{}', '2025-12-17 17:51:43.536295+00', '2025-12-17 17:51:43.536295+00'); + +-- ============================================================================ +-- FINALIZACIÓN +-- ============================================================================ + +-- Reactivar triggers +SET session_replication_role = DEFAULT; + +-- Verificar conteos +SELECT 'auth.users' as tabla, COUNT(*) as registros FROM auth.users +UNION ALL +SELECT 'auth_management.profiles', COUNT(*) FROM auth_management.profiles +UNION ALL +SELECT 'gamification_system.user_stats', COUNT(*) FROM gamification_system.user_stats +UNION ALL +SELECT 'gamification_system.user_ranks', COUNT(*) FROM gamification_system.user_ranks; + +-- ============================================================================ +-- FIN DEL SCRIPT DE RESTAURACIÓN +-- ============================================================================ diff --git a/projects/gamilit/apps/database/backup-prod/auth_users_2025-12-18.csv b/projects/gamilit/apps/database/backup-prod/auth_users_2025-12-18.csv new file mode 100644 index 0000000..01f2b5c --- /dev/null +++ b/projects/gamilit/apps/database/backup-prod/auth_users_2025-12-18.csv @@ -0,0 +1,50 @@ +instance_id,id,aud,role,email,encrypted_password,email_confirmed_at,invited_at,confirmation_token,confirmation_sent_at,recovery_token,recovery_sent_at,email_change_token_new,email_change,email_change_sent_at,last_sign_in_at,raw_app_meta_data,raw_user_meta_data,is_super_admin,created_at,updated_at,phone,phone_confirmed_at,phone_change,phone_change_token,phone_change_sent_at,confirmed_at,email_change_token_current,email_change_confirm_status,banned_until,reauthentication_token,reauthentication_sent_at,is_sso_user,deleted_at,gamilit_role,status +00000000-0000-0000-0000-000000000000,b017b792-b327-40dd-aefb-a80312776952,authenticated,,joseal.guirre34@gmail.com,$2b$10$kb9yCB4Y2WBr2.Gth.wC9e8q8bnkZJ6O2X6kFSn.O4VK8d76Cr/xO,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Aguirre"", ""first_name"": ""Jose""}",f,2025-11-18 07:29:05.226874+00,2025-11-18 07:29:05.226874+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,06a24962-e83d-4e94-aad7-ff69f20a9119,authenticated,,sergiojimenezesteban63@gmail.com,$2b$10$8oPdKN15ndCqCOIt12SEO.2yx4D29kQEQGPCC5rtUYWu8Qp5L7/zW,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Jimenez"", ""first_name"": ""Sergio""}",f,2025-11-18 08:17:40.925857+00,2025-11-18 08:17:40.925857+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,24e8c563-8854-43d1-b3c9-2f83e91f5a1e,authenticated,,Gomezfornite92@gmail.com,$2b$10$FuEfoSA0jxvBI2f6odMJqux9Gpgvt7Zjk.plRhRatvK0ykkIXxbI.,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Gomez"", ""first_name"": ""Hugo""}",f,2025-11-18 08:18:04.240276+00,2025-11-18 08:18:04.240276+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,bf0d3e34-e077-43d1-9626-292f7fae2bd6,authenticated,,Aragon494gt54@icloud.com,$2b$10$lE8M8qWUIsgYLwcHyRGvTOjxdykLVchRVifsMVqCRCZq3bEeXR.xG,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Aragón"", ""first_name"": ""Hugo""}",f,2025-11-18 08:20:17.228812+00,2025-11-18 08:20:17.228812+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,2f5a9846-3393-40b2-9e87-0f29238c383f,authenticated,,blu3wt7@gmail.com,$2b$10$gKRXQ.rmOePqsNKWdxABQuyIZike2oSsYpdfWpQdi5HHDWDUk.3u2,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Valentina"", ""first_name"": ""Azul""}",f,2025-11-18 08:32:17.314233+00,2025-11-18 08:32:17.314233+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,5e738038-1743-4aa9-b222-30171300ea9d,authenticated,,ricardolugo786@icloud.com,$2b$10$YV1StKIdCPPED/Ft84zR2ONxj/VzzV7zOxjgwMSbDpd2hzvYOGtby,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Lugo"", ""first_name"": ""Ricardo""}",f,2025-11-18 10:15:06.479774+00,2025-11-18 10:15:06.479774+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,00c742d9-e5f7-4666-9597-5a8ca54d5478,authenticated,,marbancarlos916@gmail.com,$2b$10$PfsKOsEEXpGA6YB6eXNBPePo6OV6Am1glUN6Mkunl64bK/ji6uttW,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Marban"", ""first_name"": ""Carlos""}",f,2025-11-18 10:29:05.23842+00,2025-11-18 10:29:05.23842+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,33306a65-a3b1-41d5-a49d-47989957b822,authenticated,,diego.colores09@gmail.com,$2b$10$rFlH9alBbgPGVEZMYIV8p.AkeZ30yRCVd5acasFjIt7fpCZhE6RuO,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Colores"", ""first_name"": ""Diego""}",f,2025-11-18 10:29:20.530359+00,2025-11-18 10:29:20.530359+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,7a6a973e-83f7-4374-a9fc-54258138115f,authenticated,,hernandezfonsecabenjamin7@gmail.com,$2b$10$1E6gLqfMojNLYrSKIbatqOh0pHblZ3jWZwbcxTY/DCx7MGADToCVm,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Hernandez"", ""first_name"": ""Benjamin""}",f,2025-11-18 10:37:06.919813+00,2025-11-18 10:37:06.919813+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,ccd7135c-0fea-4488-9094-9da52df1c98c,authenticated,,jr7794315@gmail.com,$2b$10$Ej/Gwx8mGCWg4TnQSjh1r.QZLw/GkUANqXmz4bEfVaNF9E527L02C,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Reyes"", ""first_name"": ""Josue""}",f,2025-11-18 17:53:39.67958+00,2025-11-18 17:53:39.67958+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,9951ad75-e9cb-47b3-b478-6bb860ee2530,authenticated,,barraganfer03@gmail.com,$2b$10$VJ8bS.ksyKpa7oG575r5YOWQYcq8vwmwTa8jMBkCv0dwskF04SHn2,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Barragan"", ""first_name"": ""Fernando""}",f,2025-11-18 20:39:27.408624+00,2025-11-18 20:39:27.408624+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,735235f5-260a-4c9b-913c-14a1efd083ea,authenticated,,roman.rebollar.marcoantonio1008@gmail.com,$2b$10$l4eF8UoOB7D8LKDEzTigXOUO7EABhVdYCqknJ/lD6R4p8uF1R4I.W,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Roman"", ""first_name"": ""Marco Antonio""}",f,2025-11-18 21:03:17.326679+00,2025-11-18 21:03:17.326679+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,ebe48628-5e44-4562-97b7-b4950b216247,authenticated,,rodrigoguerrero0914@gmail.com,$2b$10$ihoy7HbOdlqU38zAddpTOuDO7Nqa8.Cr1dEQjCgMpdb30UwCIMhGW,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": ""Guerrero"", ""first_name"": ""Rodrigo""}",f,2025-11-18 21:20:52.303128+00,2025-11-18 21:20:52.303128+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,d089b1af-462f-4d2c-b0f5-d2528cec8506,authenticated,,santiagoferrara78@gmail.com,$2b$10$Wjo3EENjiuddS9BwPMAW1OORZrZpU8ECP9zEXmd4Gvn7orwgjo8O2,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 09:21:04.898591+00,2025-11-24 09:21:04.898591+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,b1cadf36-1f07-46b2-b63d-da72d9b54dc6,authenticated,,alexanserrv917@gmail.com,$2b$10$8sT/ObLZUNmiu6CpbceHhenfc7E8zZml8AvB1HUiyOddSLqchggZ2,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:26:51.934739+00,2025-11-24 10:26:51.934739+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,af4d8788-f8a8-4971-bb0d-2f48c150dfc2,authenticated,,aarizmendi434@gmail.com,$2b$10$2BAG4EskBG0feGOIva6XyOCBtBJbKJE9h27GU6DmuBH3f.2iK6FoS,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:30:54.728262+00,2025-11-24 10:30:54.728262+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,26fbc469-10af-4fa3-bd65-e5498188cc4f,authenticated,,ashernarcisobenitezpalomino@gmail.com,$2b$10$Bv5vo0GDeseWUWTt.5xV0O9nN93TRVN.vHRigs4vF/ww7Hbnjylam,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:37:35.325342+00,2025-11-24 10:37:35.325342+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8,authenticated,,ra.alejandrobm@gmail.com,$2b$10$QZId3lZBIzBulD7AZCeEKOiL0LBJRekGlQTGiacC70IDwDo2wx7py,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:42:33.424367+00,2025-11-24 10:42:33.424367+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1,authenticated,,abdallahxelhaneriavega@gmail.com,$2b$10$jQ4SquNUxIO70e7IBYqqLeUw1d.gSCleJ/cwinuWMVlW25a8.pRGG,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:45:19.984994+00,2025-11-24 10:45:19.984994+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,012adac4-8ffd-47bd-9248-f0c5851e981f,authenticated,,09enriquecampos@gmail.com,$2b$10$95c9hOplonbo/46O5UlPqummq.AIaGVIZ7YgBstSuOWPbgGersKxy,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:51:54.731982+00,2025-11-24 10:51:54.731982+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,126b9257-7b0a-4bd6-9ab3-c505ee00e10a,authenticated,,johhkk22@gmail.com,$2b$10$Bt6IZ19zuBkly.6QmmPWBeF0kfyVN/O/c3/9bqyUGup3gPZu14DGa,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:53:47.029991+00,2025-11-24 10:53:47.029991+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,9ac1746e-94a6-4efc-a961-951c015d416e,authenticated,,edangiel4532@gmail.com,$2b$10$eZap9LmAws7VtY9sHnS17.RJkhIte5SUobIWaWpuTxTPKjbKgzK.6,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 10:58:12.790316+00,2025-11-24 10:58:12.790316+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,2d9f05d4-44dd-42cd-97aa-d57bd06fecd0,authenticated,,erickfranco462@gmail.com,$2b$10$lNzkSO7zbBHQcJJui0O76.a2artcsZHari4Mgkjo4btGww.Wy9/iC,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:00:11.800551+00,2025-11-24 11:00:11.800551+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,aff5dcc6-32de-4769-9aaf-eda751fa0866,authenticated,,gallinainsana@gmail.com,$2b$10$6y/FVa4LqyliI4PXuBxKpepTRwIIRWybFN0NhcAqRM.Kl/cnvXDMq,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:03:17.536383+00,2025-11-24 11:03:17.536383+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,0cda1645-83c5-445b-80b7-d0e4d436c00c,authenticated,,leile5257@gmail.com,$2b$10$ZZX0.z30VPm7BsLF8bNVweQpRZ2ca/1EPlxdIZy0xNaCFugoKL0ci,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:05:17.75852+00,2025-11-24 11:05:17.75852+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,1364c463-88de-479b-a883-c0b7b362bcf8,authenticated,,maximiliano.mejia367@gmail.com,$2b$10$iTfIWKh2ISvPys2bkK2LOOPI24ua7I47oT8dFxHHYW7AuztoZreQa,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:08:58.232003+00,2025-11-24 11:08:58.232003+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,547eb778-4782-4681-b198-c731bba36147,authenticated,,fl432025@gmail.com,$2b$10$aGKv6yhAWwHb07m3N2DxJOXIn5omkP3t2QeSYblhcDo52pB2ZiFQi,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:12:13.692614+00,2025-11-24 11:12:13.692614+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,5fc06693-e408-4eab-a9a3-fcd5f4e01296,authenticated,,7341023901m@gmail.com,$2b$10$Z/HUBov20g..LZ6RDYax4.NcDuiFD/gn9Nrt7/OPCPBqCoTJUgr3C,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:15:18.276345+00,2025-11-24 11:15:18.276345+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,5d1839f6-b03f-4e12-b236-eca43f4674f2,authenticated,,segurauriel235@gmail.com,$2b$10$IfdhPuUOModgrJT7bMfYkODZkXeTcaAReuCQf9BGpK1cT6GiP9UGu,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:17:46.846963+00,2025-11-24 11:17:46.846963+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,1b310708-6f24-4c6a-88c9-a11f7a7f9763,authenticated,,angelrabano11@gmail.com,$2b$10$Sg6q4kErMvxRlZgWM9lCj.PfRg5sCQrwm763d7sfc3iaAUID7y436,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:47:53.790673+00,2025-11-24 11:47:53.790673+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,3c613b0e-66f9-4640-a599-c9426d8edffb,authenticated,,daliaayalareyes35@gmail.com,$2b$10$dd2SQeBqNIZpZWCGMIDu1O8U6MLpWnKF05w641MNOMzHDZ/U5glCe,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:55:08.708961+00,2025-11-24 11:55:08.708961+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,7ded133e-9b13-4467-9803-edb813f6a9a1,authenticated,,alexeimongam@gmail.com,$2b$10$jyQrHAIj6SsnReQ45FrFlOnDgpZtabskpxPuOYgB/h.YPLyZhuld.,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 11:55:11.906996+00,2025-11-24 11:55:11.906996+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,4cc04f54-7771-462d-98aa-a94448bb6ff5,authenticated,,davidocampovenegas@gmail.com,$2b$10$8COk10WE5.bXFJnAucEA0efcGQKU6KUXKV9N7n32ZX6aNKORs4McW,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 14:52:46.468737+00,2025-11-24 14:52:46.468737+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,fbbe7d19-048c-45e4-8a9c-cf86d2098c35,authenticated,,zaid080809@gmail.com,$2b$10$kdaUWR1BUqPRY7H8YkR.xuuDbqtLcvP5yKW.B0ooPlb.I6b/UU192,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 16:25:03.689847+00,2025-11-24 16:25:03.689847+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4,authenticated,,ruizcruzabrahamfrancisco@gmail.com,$2b$10$DXHr682C4/VpesiHa7fRrOjKceiWSDUSx.1LZTbsvuxpqCdMNh/Ii,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 19:46:06.311558+00,2025-11-24 19:46:06.311558+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,615adf6e-dbf3-480f-a907-3cfb3a64c6d2,authenticated,,vituschinchilla@gmail.com,$2b$10$dA8adTYlfhgqhZfACcQkFOCYjXdsmggXnIUluNDoh1zRFgQ6pq5O2,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-24 21:07:26.037867+00,2025-11-24 21:07:26.037867+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,bf445960-4c1f-4e29-8fb7-31667b183d7e,authenticated,,bryan@betanzos.com,$2b$10$Xdfuf4Tfog9QKd1FRLL.7eAaD6tr2cXgPx1/L8xqT1kLLzNHzSM26,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 06:13:30.263795+00,2025-11-25 06:13:30.263795+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,d5fa4905-a78a-4040-8ad8-23220881c6a6,authenticated,,loganalexander816@gmail.com,$2b$10$8zLduh/9L/priag.nujz5utuloO9RnNFFDGdKgI2UniFCOwocEPLq,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 07:37:04.953164+00,2025-11-25 07:37:04.953164+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,71734c15-cdaa-431b-90f5-97a57e0316a8,authenticated,,carlois1974@gmail.com,$2b$10$IfLfJ.q59DZgicR07ckSVOcrkkBJe42m1FECXxaoaodKYSo6uj5wW,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 07:41:38.025764+00,2025-11-25 07:41:38.025764+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,1efe491d-98ef-4c02-acd1-3135f7289072,authenticated,,enriquecuevascbtis136@gmail.com,$2b$10$9BX3OQMZmHruffBtN.3WPOFoyea6zgPd8i72DvhJ7vRAdqWKax6GS,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 08:16:33.977647+00,2025-11-25 08:16:33.977647+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,5ae21325-7450-4c37-82f1-3f9bcd7b6f45,authenticated,,omarcitogonzalezzavaleta@gmail.com,$2b$10$RRk3DAgQdiikxVImFIMqquqB.TNpKs3E.RNFtt1rwwTzO24uShri.,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 08:17:07.610076+00,2025-11-25 08:17:07.610076+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,a4d27774-8a51-4660-ad2f-81d0dfd3a5a7,authenticated,,gustavobm2024cbtis@gmail.com,$2b$10$lg7KRUTPofcx4Rtyey8J7.XO0gmdBLCFIfK5uP08mqT0qUIl1aTJq,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 08:20:49.649184+00,2025-11-25 08:20:49.649184+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,6e30164a-78b0-49b0-bd21-23d7c6c03349,authenticated,,marianaxsotoxt22@gmail.com,$2b$10$GQC9yTWiP2vP9GUp0gnhUeLjmw70EI4JQhfJBZbMOlCNXGXb/bt5O,,,,,,,,,,,"{""provider"": ""email"", ""providers"": [""email""]}","{""last_name"": """", ""first_name"": """"}",f,2025-11-25 08:33:18.150784+00,2025-11-25 08:33:18.150784+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,cccccccc-cccc-cccc-cccc-cccccccccccc,authenticated,,student@gamilit.com,$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga,2025-11-29 13:26:50.289631+00,,"",,"",,"","",,2025-12-07 03:42:02.528+00,"{""provider"": ""email"", ""providers"": [""email""]}","{""name"": ""Estudiante Testing"", ""role"": ""student"", ""description"": ""Usuario estudiante de testing""}",f,2025-11-29 13:26:50.289631+00,2025-12-07 03:42:02.529507+00,,,,,,,,0,,,,f,,student,active +00000000-0000-0000-0000-000000000000,bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb,authenticated,,teacher@gamilit.com,$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga,2025-11-29 13:26:50.289631+00,,"",,"",,"","",,,"{""provider"": ""email"", ""providers"": [""email""]}","{""name"": ""Profesor Testing"", ""role"": ""admin_teacher"", ""description"": ""Usuario profesor de testing""}",f,2025-11-29 13:26:50.289631+00,2025-11-29 13:26:50.289631+00,,,,,,,,0,,,,f,,admin_teacher,active +00000000-0000-0000-0000-000000000000,aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,authenticated,,admin@gamilit.com,$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga,2025-11-29 13:26:50.289631+00,,"",,"",,"","",,2025-12-01 00:54:19.615+00,"{""provider"": ""email"", ""providers"": [""email""]}","{""name"": ""Admin GAMILIT"", ""role"": ""super_admin"", ""description"": ""Usuario administrador de testing""}",f,2025-11-29 13:26:50.289631+00,2025-12-01 00:54:19.617766+00,,,,,,,,0,,,,f,,super_admin,active +,0ae1bf21-39e3-4168-9632-457418c7a07d,authenticated,,rckrdmrd@gmail.com,$2b$10$LiDdaJLA.ZvdFleamkMuvOcIrW0PQMEh5aVZ5Wg5pzhm7gwc5s.1C,,,,,,,,,,2025-12-09 01:22:42.784+00,,{},f,2025-11-29 13:37:09.271457+00,2025-12-09 01:22:42.785367+00,,,,,,,,0,,,,f,,student,active +,69681b09-5077-4f77-84cc-67606abd9755,authenticated,,javiermar06@hotmail.com,$2b$10$3RHyXnR4BG3NaxP8Ez82FuiGDMNCG7GhNaOsMFigy3BpIVOzCqHMW,,,,,,,,,,2025-12-14 03:51:04.122+00,,{},f,2025-12-08 19:24:06.266895+00,2025-12-14 03:51:04.123886+00,,,,,,,,0,,,,f,,student,active +,f929d6df-8c29-461f-88f5-264facd879e9,authenticated,,ju188an@gmail.com,$2b$10$9vUERFnXApdfXuAI7DFve.aa8uDjI5bfm4CI75/EZ2cUre83RytKe,,,,,,,,,,2025-12-17 23:51:43.553+00,,{},f,2025-12-17 17:51:43.530434+00,2025-12-17 23:51:43.55475+00,,,,,,,,0,,,,f,,student,active diff --git a/projects/gamilit/apps/database/backup-prod/profiles_2025-12-18.csv b/projects/gamilit/apps/database/backup-prod/profiles_2025-12-18.csv new file mode 100644 index 0000000..2d64e62 --- /dev/null +++ b/projects/gamilit/apps/database/backup-prod/profiles_2025-12-18.csv @@ -0,0 +1,50 @@ +id,tenant_id,display_name,full_name,first_name,last_name,email,avatar_url,bio,phone,date_of_birth,grade_level,student_id,school_id,role,status,email_verified,phone_verified,preferences,last_sign_in_at,last_activity_at,metadata,created_at,updated_at,user_id,deleted_at +b017b792-b327-40dd-aefb-a80312776952,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Jose Aguirre,Jose Aguirre,Jose,Aguirre,joseal.guirre34@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 07:29:05.229254+00,2025-11-18 07:29:05.229254+00,b017b792-b327-40dd-aefb-a80312776952, +06a24962-e83d-4e94-aad7-ff69f20a9119,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Sergio Jimenez,Sergio Jimenez,Sergio,Jimenez,sergiojimenezesteban63@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 08:17:40.928077+00,2025-11-18 08:17:40.928077+00,06a24962-e83d-4e94-aad7-ff69f20a9119, +24e8c563-8854-43d1-b3c9-2f83e91f5a1e,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Hugo Gomez,Hugo Gomez,Hugo,Gomez,Gomezfornite92@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 08:18:04.242047+00,2025-11-18 08:18:04.242047+00,24e8c563-8854-43d1-b3c9-2f83e91f5a1e, +bf0d3e34-e077-43d1-9626-292f7fae2bd6,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Hugo Aragón,Hugo Aragón,Hugo,Aragón,Aragon494gt54@icloud.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 08:20:17.230714+00,2025-11-18 08:20:17.230714+00,bf0d3e34-e077-43d1-9626-292f7fae2bd6, +2f5a9846-3393-40b2-9e87-0f29238c383f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Azul Valentina,Azul Valentina,Azul,Valentina,blu3wt7@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 08:32:17.315932+00,2025-11-18 08:32:17.315932+00,2f5a9846-3393-40b2-9e87-0f29238c383f, +5e738038-1743-4aa9-b222-30171300ea9d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ricardo Lugo,Ricardo Lugo,Ricardo,Lugo,ricardolugo786@icloud.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 10:15:06.481498+00,2025-11-18 10:15:06.481498+00,5e738038-1743-4aa9-b222-30171300ea9d, +00c742d9-e5f7-4666-9597-5a8ca54d5478,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Carlos Marban,Carlos Marban,Carlos,Marban,marbancarlos916@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 10:29:05.240413+00,2025-11-18 10:29:05.240413+00,00c742d9-e5f7-4666-9597-5a8ca54d5478, +33306a65-a3b1-41d5-a49d-47989957b822,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Diego Colores,Diego Colores,Diego,Colores,diego.colores09@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 10:29:20.531883+00,2025-11-18 10:29:20.531883+00,33306a65-a3b1-41d5-a49d-47989957b822, +7a6a973e-83f7-4374-a9fc-54258138115f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Benjamin Hernandez,Benjamin Hernandez,Benjamin,Hernandez,hernandezfonsecabenjamin7@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 10:37:06.9215+00,2025-11-18 10:37:06.9215+00,7a6a973e-83f7-4374-a9fc-54258138115f, +ccd7135c-0fea-4488-9094-9da52df1c98c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Josue Reyes,Josue Reyes,Josue,Reyes,jr7794315@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 17:53:39.681271+00,2025-11-18 17:53:39.681271+00,ccd7135c-0fea-4488-9094-9da52df1c98c, +9951ad75-e9cb-47b3-b478-6bb860ee2530,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Fernando Barragan,Fernando Barragan,Fernando,Barragan,barraganfer03@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 20:39:27.410436+00,2025-11-18 20:39:27.410436+00,9951ad75-e9cb-47b3-b478-6bb860ee2530, +735235f5-260a-4c9b-913c-14a1efd083ea,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Marco Antonio Roman,Marco Antonio Roman,Marco Antonio,Roman,roman.rebollar.marcoantonio1008@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 21:03:17.328254+00,2025-11-18 21:03:17.328254+00,735235f5-260a-4c9b-913c-14a1efd083ea, +ebe48628-5e44-4562-97b7-b4950b216247,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Rodrigo Guerrero,Rodrigo Guerrero,Rodrigo,Guerrero,rodrigoguerrero0914@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-18 21:20:52.304488+00,2025-11-18 21:20:52.304488+00,ebe48628-5e44-4562-97b7-b4950b216247, +aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Admin GAMILIT,Administrador GAMILIT,Administrador,GAMILIT,admin@gamilit.com,/avatars/admin-testing.png,Usuario administrador para testing y desarrollo.,55-0000-0001,1985-01-01,,,,super_admin,active,t,t,"{""theme"": ""professional"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,"{""description"": ""Usuario de testing principal"", ""testing_user"": true}",2025-11-29 13:26:50.458823+00,2025-11-29 13:26:50.458823+00,aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa, +cccccccc-cccc-cccc-cccc-cccccccccccc,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Estudiante Testing,Estudiante de Testing GAMILIT,Estudiante,Testing,student@gamilit.com,/avatars/student-testing.png,Usuario estudiante para testing y desarrollo.,55-0000-0003,2013-09-01,5,EST-TEST-001,,student,active,t,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""gamification"": {""show_rank"": true, ""show_leaderboard"": true, ""show_achievements"": true}, ""sound_enabled"": true, ""notifications_enabled"": true}",,,"{""interests"": [""lectura"", ""ciencia""], ""testing_user"": true, ""learning_style"": ""visual""}",2025-11-29 13:26:50.458823+00,2025-11-29 13:26:50.458823+00,cccccccc-cccc-cccc-cccc-cccccccccccc, +bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Profesor Testing,Profesor de Testing GAMILIT,Profesor,Testing,teacher@gamilit.com,/avatars/teacher-testing.png,Usuario profesor para testing y desarrollo.,55-0000-0002,1980-05-15,,,,admin_teacher,active,t,t,"{""theme"": ""teacher"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,"{""subjects"": [""Lengua Española"", ""Comprensión Lectora""], ""testing_user"": true}",2025-11-29 13:26:50.458823+00,2025-11-29 13:26:50.458823+00,bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb, +188fa4e3-985c-4048-8913-754cb0560875,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",7341023901m@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,5fc06693-e408-4eab-a9a3-fcd5f4e01296, +caa05325-b8e7-4b1c-9d95-03d4e0c7372d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",vituschinchilla@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,615adf6e-dbf3-480f-a907-3cfb3a64c6d2, +128ac756-bdb8-49d7-8fdb-cdb8fd241d06,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",alexeimongam@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,7ded133e-9b13-4467-9803-edb813f6a9a1, +7f3bb769-4d7e-4ca9-8527-708da0368be5,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",angelrabano11@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,1b310708-6f24-4c6a-88c9-a11f7a7f9763, +6a565d40-9012-4c89-878c-05bb8b6e2d81,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",loganalexander816@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,d5fa4905-a78a-4040-8ad8-23220881c6a6, +2df90a89-455e-4637-8b96-ad01c45f5701,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",johhkk22@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,126b9257-7b0a-4bd6-9ab3-c505ee00e10a, +43560c6b-fda2-4b45-bc0b-7dbbbffff05c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",edangiel4532@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,9ac1746e-94a6-4efc-a961-951c015d416e, +ff4760c8-5359-43e9-9b42-95a0bf3e3d36,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",aarizmendi434@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,af4d8788-f8a8-4971-bb0d-2f48c150dfc2, +a4c43698-c276-4430-b15e-8373b7bbb662,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",santiagoferrara78@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,d089b1af-462f-4d2c-b0f5-d2528cec8506, +30462f07-1c6b-4706-b4fb-288845b3631e,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",09enriquecampos@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,012adac4-8ffd-47bd-9248-f0c5851e981f, +4f5170f2-1d35-4130-a535-1d93383e406b,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",maximiliano.mejia367@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,1364c463-88de-479b-a883-c0b7b362bcf8, +c0aecfcc-3b2f-4117-9f20-e0920df97dc0,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",segurauriel235@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,5d1839f6-b03f-4e12-b236-eca43f4674f2, +3dfcdc9d-de8a-45b3-a05f-b83b51097ef5,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",omarcitogonzalezzavaleta@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,5ae21325-7450-4c37-82f1-3f9bcd7b6f45, +bb74b280-db90-4240-ab09-b8c6cf63d553,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",erickfranco462@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,2d9f05d4-44dd-42cd-97aa-d57bd06fecd0, +7ede7b67-42d2-44cd-a530-66f62a68cd54,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",bryan@betanzos.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,bf445960-4c1f-4e29-8fb7-31667b183d7e, +3f255f44-40d9-4dc9-970c-d50ddec197b3,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",alexanserrv917@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,b1cadf36-1f07-46b2-b63d-da72d9b54dc6, +f1870075-a6e0-47e7-88c6-793320ab3c8f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",carlois1974@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,71734c15-cdaa-431b-90f5-97a57e0316a8, +ab2425d9-e2da-49ac-b8db-2db605e7283f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",gustavobm2024cbtis@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,a4d27774-8a51-4660-ad2f-81d0dfd3a5a7, +802f4d68-bbd0-4220-8218-634975c3774a,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",gallinainsana@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,aff5dcc6-32de-4769-9aaf-eda751fa0866, +142af777-fce1-4067-b84b-f684e2fa1170,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",zaid080809@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,fbbe7d19-048c-45e4-8a9c-cf86d2098c35, +b5c2d5dc-e753-40ff-8e01-95c4c497710c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",davidocampovenegas@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,4cc04f54-7771-462d-98aa-a94448bb6ff5, +d29345bb-fd48-4f69-ac81-eeedd4b41e6d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",marianaxsotoxt22@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,6e30164a-78b0-49b0-bd21-23d7c6c03349, +813055ff-e5b7-4538-825c-eb721360e189,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",leile5257@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,0cda1645-83c5-445b-80b7-d0e4d436c00c, +6c9bbb36-0b2d-49ea-9c1b-63209f009773,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",ashernarcisobenitezpalomino@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,26fbc469-10af-4fa3-bd65-e5498188cc4f, +8daf8ed9-d15f-407f-b827-3a9c01907e62,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",ruizcruzabrahamfrancisco@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4, +bd028538-520d-45cf-a6f7-27c9f675d663,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",daliaayalareyes35@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,3c613b0e-66f9-4640-a599-c9426d8edffb, +de1511df-f963-4ff6-8e3f-2225ba493879,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",ra.alejandrobm@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8, +26168044-3b5c-43f6-a757-833ba1485d41,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",enriquecuevascbtis136@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,1efe491d-98ef-4c02-acd1-3135f7289072, +e742724a-0ff6-4760-884b-866835460045,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",fl432025@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,547eb778-4782-4681-b198-c731bba36147, +3ce354c8-bcac-44c6-9a94-5274e5f9b389,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,"","",abdallahxelhaneriavega@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:30:54.277737+00,2025-11-29 13:30:54.277737+00,f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1, +0ae1bf21-39e3-4168-9632-457418c7a07d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,rckrdmrd@gmail.com,,rckrdmrd@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-11-29 13:37:09.278078+00,2025-11-29 13:37:09.278078+00,0ae1bf21-39e3-4168-9632-457418c7a07d, +69681b09-5077-4f77-84cc-67606abd9755,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,Javier, Mar,javiermar06@hotmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-12-08 19:24:06.272257+00,2025-12-08 19:24:06.272257+00,69681b09-5077-4f77-84cc-67606abd9755, +f929d6df-8c29-461f-88f5-264facd879e9,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,,,Juan,pa,ju188an@gmail.com,,,,,,,,student,active,f,f,"{""theme"": ""detective"", ""language"": ""es"", ""timezone"": ""America/Mexico_City"", ""sound_enabled"": true, ""notifications_enabled"": true}",,,{},2025-12-17 17:51:43.536295+00,2025-12-17 17:51:43.536295+00,f929d6df-8c29-461f-88f5-264facd879e9, diff --git a/projects/gamilit/apps/database/backup-prod/user_ranks_2025-12-18.csv b/projects/gamilit/apps/database/backup-prod/user_ranks_2025-12-18.csv new file mode 100644 index 0000000..3d135b8 --- /dev/null +++ b/projects/gamilit/apps/database/backup-prod/user_ranks_2025-12-18.csv @@ -0,0 +1,50 @@ +id,user_id,tenant_id,current_rank,previous_rank,rank_progress_percentage,modules_required_for_next,modules_completed_for_rank,xp_required_for_next,xp_earned_for_rank,ml_coins_bonus,certificate_url,badge_url,achieved_at,previous_rank_achieved_at,is_current,rank_metadata,created_at,updated_at +33b12003-65b0-4f50-8bda-6eeebedc3413,aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Halach Uinic,Ah K'in,0,,0,,0,500,,,2025-11-29 13:26:52.158657+00,2025-11-29 13:26:52.158657+00,t,{},2025-11-29 13:26:50.458823+00,2025-11-29 13:26:52.138221+00 +aef81f6b-b9c4-4f80-b27f-8f9bf8c2cd97,bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.458823+00,,t,{},2025-11-29 13:26:50.458823+00,2025-11-29 13:26:50.458823+00 +cb04ef70-2738-4c9e-b32a-a92643b8a3f9,cccccccc-cccc-cccc-cccc-cccccccccccc,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Halach Uinic,Ah K'in,0,,0,,0,500,,,2025-11-30 20:12:46.263821+00,2025-11-29 13:26:52.146305+00,t,{},2025-11-29 13:26:50.458823+00,2025-11-30 20:12:46.263821+00 +eb382620-70b8-4842-b8cf-ad51eb1c7266,2f5a9846-3393-40b2-9e87-0f29238c383f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +207ff6ef-a56d-4fc1-942f-5a50e0c842da,5e738038-1743-4aa9-b222-30171300ea9d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +64946395-a46c-4e93-abf7-5b827e4dcf12,33306a65-a3b1-41d5-a49d-47989957b822,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +66cb0dd1-2c47-4572-99ec-c97ccee95e53,7a6a973e-83f7-4374-a9fc-54258138115f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +8c1d72aa-fead-4bff-bf47-b34ceab62a40,06a24962-e83d-4e94-aad7-ff69f20a9119,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +b14b22be-bd9d-485c-9477-b4122c63cbfb,9951ad75-e9cb-47b3-b478-6bb860ee2530,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +4e7cf7a1-8ef4-4d20-902b-ba1c57faad51,735235f5-260a-4c9b-913c-14a1efd083ea,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +d0500d4e-17ec-46be-94a1-4e2500e4b9a8,ebe48628-5e44-4562-97b7-b4950b216247,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +9a4e06cc-0a50-4ff9-b781-d29edff7bbd6,00c742d9-e5f7-4666-9597-5a8ca54d5478,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Nacom,Ajaw,0,,0,,0,100,,,2025-11-29 13:26:52.138221+00,2025-11-29 13:26:50.539509+00,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.138221+00 +4cadc6a3-c15e-410a-88f5-c0254463ca8a,b017b792-b327-40dd-aefb-a80312776952,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +241b6cea-03d0-4f9c-97b6-7d49d9175cfb,ccd7135c-0fea-4488-9094-9da52df1c98c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +840cabba-4a80-40fa-a3e0-0e218e09463e,24e8c563-8854-43d1-b3c9-2f83e91f5a1e,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +913ae601-8ab4-4056-864c-517e100bef5b,bf0d3e34-e077-43d1-9626-292f7fae2bd6,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:26:50.539509+00,,t,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +b1c76243-62c4-4e73-8e7b-152b82e41c93,128ac756-bdb8-49d7-8fdb-cdb8fd241d06,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +e521b5a1-250b-4e96-a2cd-e8736f1f3fbe,6c9bbb36-0b2d-49ea-9c1b-63209f009773,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +a106676f-8723-492b-b4f1-1b9256da8a4d,813055ff-e5b7-4538-825c-eb721360e189,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +8cf5754b-7af4-429f-8e57-b0f2e4f740d6,3ce354c8-bcac-44c6-9a94-5274e5f9b389,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +89526d39-7d29-4d97-a65b-94a42dfb45e2,ab2425d9-e2da-49ac-b8db-2db605e7283f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +fa25a282-3568-481a-878d-c424f6eaee95,ff4760c8-5359-43e9-9b42-95a0bf3e3d36,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +69621cdf-cc64-4f2b-85f3-d1f09b54cbf0,7f3bb769-4d7e-4ca9-8527-708da0368be5,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +b4c93d8d-fb84-4ba2-91d7-78abd5a780de,26168044-3b5c-43f6-a757-833ba1485d41,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +3444f9aa-7c1b-452c-bfbb-af58dc70e70b,bd028538-520d-45cf-a6f7-27c9f675d663,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +c8aec5d3-4a66-40b6-a8e7-9b2edd4225b1,2df90a89-455e-4637-8b96-ad01c45f5701,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +e284ffd8-9d58-4564-95c4-075721724643,6a565d40-9012-4c89-878c-05bb8b6e2d81,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +abfa66bf-dc3d-426a-b4ec-538677b39cd5,43560c6b-fda2-4b45-bc0b-7dbbbffff05c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +b26939b5-2dc1-4643-b46c-c4a3d6a5203f,d29345bb-fd48-4f69-ac81-eeedd4b41e6d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +564433a1-e5f1-48ce-8807-06f5b9ea1418,30462f07-1c6b-4706-b4fb-288845b3631e,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +aee1d9bf-b4fe-4512-9cf0-4cc90413692e,8daf8ed9-d15f-407f-b827-3a9c01907e62,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +7c3b01ab-b75b-4464-8858-91c1ff805c32,3dfcdc9d-de8a-45b3-a05f-b83b51097ef5,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +53ec34ee-bd3c-462f-b5b0-317705c0399d,3f255f44-40d9-4dc9-970c-d50ddec197b3,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +b2461bb7-7107-48dd-923f-de4ff8e86a67,e742724a-0ff6-4760-884b-866835460045,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +890ba444-c64f-4680-894d-56bf30cbcf5c,f1870075-a6e0-47e7-88c6-793320ab3c8f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +a7b16bb9-efa6-4f10-8fe0-992adbd8c482,b5c2d5dc-e753-40ff-8e01-95c4c497710c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +c6eb5dd4-a987-4560-b22d-645ca31791e6,142af777-fce1-4067-b84b-f684e2fa1170,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +520cd26c-5222-4579-8f1d-72f4be8e98d3,c0aecfcc-3b2f-4117-9f20-e0920df97dc0,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +2971dbbf-33de-4f12-9e47-48c9a3b145df,7ede7b67-42d2-44cd-a530-66f62a68cd54,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +cb9feef2-0f2d-4949-b212-3b6d7dbfe2fa,caa05325-b8e7-4b1c-9d95-03d4e0c7372d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +89181d4a-a47b-4449-81fa-09a7e1d67534,a4c43698-c276-4430-b15e-8373b7bbb662,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +998f3c88-8932-4d91-af11-7bca6c03bbc3,bb74b280-db90-4240-ab09-b8c6cf63d553,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +e22e4620-57fc-47ad-ae53-f45ecc17932f,4f5170f2-1d35-4130-a535-1d93383e406b,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +b9e35a4b-ade2-4fa0-bd58-38ffcf5ca614,802f4d68-bbd0-4220-8218-634975c3774a,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +365f3ac3-2880-42a8-8799-0c465b0a4ed9,de1511df-f963-4ff6-8e3f-2225ba493879,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +21152c4c-b082-40a7-938b-e7486730e2bb,188fa4e3-985c-4048-8913-754cb0560875,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-11-29 13:33:25.124715+00,,t,{},2025-11-29 13:33:25.124715+00,2025-11-29 13:33:25.124715+00 +3c88a791-ad9b-4988-9351-b5e9df60c9a4,0ae1bf21-39e3-4168-9632-457418c7a07d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ah K'in,Nacom,0,,0,,0,250,,,2025-11-29 13:41:42.417785+00,2025-11-29 13:40:27.516619+00,t,{},2025-11-29 13:37:09.278078+00,2025-11-29 13:41:42.417785+00 +1de77cef-003a-4d08-8ccc-8bd4001fa62c,69681b09-5077-4f77-84cc-67606abd9755,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-12-08 19:24:06.272257+00,,t,{},2025-12-08 19:24:06.272257+00,2025-12-08 19:24:06.272257+00 +63b40546-38a8-4c80-bf11-3504c73150bb,f929d6df-8c29-461f-88f5-264facd879e9,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,Ajaw,,0,,0,,0,0,,,2025-12-17 17:51:43.536295+00,,t,{},2025-12-17 17:51:43.536295+00,2025-12-17 17:51:43.536295+00 diff --git a/projects/gamilit/apps/database/backup-prod/user_stats_2025-12-18.csv b/projects/gamilit/apps/database/backup-prod/user_stats_2025-12-18.csv new file mode 100644 index 0000000..d0ebd8e --- /dev/null +++ b/projects/gamilit/apps/database/backup-prod/user_stats_2025-12-18.csv @@ -0,0 +1,50 @@ +id,user_id,tenant_id,level,total_xp,xp_to_next_level,current_rank,rank_progress,ml_coins,ml_coins_earned_total,ml_coins_spent_total,ml_coins_earned_today,last_ml_coins_reset,current_streak,max_streak,streak_started_at,days_active_total,exercises_completed,modules_completed,total_score,average_score,perfect_scores,achievements_earned,certificates_earned,total_time_spent,weekly_time_spent,sessions_count,weekly_xp,monthly_xp,weekly_exercises,global_rank_position,class_rank_position,school_rank_position,last_activity_at,last_login_at,metadata,created_at,updated_at +7446ef44-11a1-451d-9624-abd95f0eb2ec,aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,6,2525,100,Halach Uinic,0.00,1960,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.458823+00,2025-11-29 13:26:52.120683+00 +06fd9e66-cefc-4c64-a446-37cef9668b36,bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,2,375,100,Ajaw,0.00,260,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.458823+00,2025-11-29 13:26:52.120683+00 +b3830215-9623-46a6-9624-2c7183f13737,cccccccc-cccc-cccc-cccc-cccccccccccc,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,5,1825,100,Halach Uinic,0.00,1625,180,0,0,,0,0,,0,8,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,2025-12-02 10:55:24.674923+00,,{},2025-11-29 13:26:50.458823+00,2025-12-02 10:55:24.674923+00 +1f6076c1-7b54-43f7-9fca-519407e65c4f,2f5a9846-3393-40b2-9e87-0f29238c383f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,2,150,100,Ajaw,0.00,140,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +524efcd5-9c89-4a79-ac4c-f58f92f9fd28,7a6a973e-83f7-4374-a9fc-54258138115f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,25,100,Ajaw,0.00,110,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +88b149bb-5f3b-41bb-885f-e226eb9cac22,b017b792-b327-40dd-aefb-a80312776952,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +fe110986-762c-459f-acde-b2c865c237bb,33306a65-a3b1-41d5-a49d-47989957b822,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,75,100,Ajaw,0.00,120,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +f0734a0b-1d9c-4999-bcba-8e3b8f229c80,9951ad75-e9cb-47b3-b478-6bb860ee2530,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,2,375,100,Ajaw,0.00,260,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +0145f432-c383-45d1-9d0f-0c7783a48612,735235f5-260a-4c9b-913c-14a1efd083ea,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,75,100,Ajaw,0.00,135,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +c29ffaef-0392-4fe1-ac41-b502a4052111,5e738038-1743-4aa9-b222-30171300ea9d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,25,100,Ajaw,0.00,110,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +1cb60bdb-800a-4ebc-a9c3-16ecf78d3535,06a24962-e83d-4e94-aad7-ff69f20a9119,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +5cba623a-015f-41e9-9de0-da67bdd6b5fb,24e8c563-8854-43d1-b3c9-2f83e91f5a1e,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +ee39d06d-d9c5-492a-9855-2210c74b74fd,ccd7135c-0fea-4488-9094-9da52df1c98c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +c6a7e9dd-ade4-4860-a4e0-882fd5aa0ed1,bf0d3e34-e077-43d1-9626-292f7fae2bd6,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +e6be8348-29af-49c4-b49d-4b2d978c951b,ebe48628-5e44-4562-97b7-b4950b216247,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:50.539509+00 +9c4cf796-3768-4182-b1e0-1a38f8187c87,00c742d9-e5f7-4666-9597-5a8ca54d5478,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,3,525,100,Nacom,0.00,395,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:26:50.539509+00,2025-11-29 13:26:52.120683+00 +207b92d0-39d0-4f81-b00d-9119d7a20626,188fa4e3-985c-4048-8913-754cb0560875,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +e0ece833-4e73-4ada-b7d2-25197cf9097d,caa05325-b8e7-4b1c-9d95-03d4e0c7372d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +e58e13b1-cc53-40da-8ecf-3fa4c2c97672,128ac756-bdb8-49d7-8fdb-cdb8fd241d06,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +719b10c0-2de5-43a3-b9e4-d38730a92ad2,7f3bb769-4d7e-4ca9-8527-708da0368be5,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +66570bed-28ce-407d-a5df-43236bba500a,6a565d40-9012-4c89-878c-05bb8b6e2d81,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +762397ff-699f-4bb8-bef6-2e03959fe4c1,3f255f44-40d9-4dc9-970c-d50ddec197b3,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +e8a5e302-93c4-4f6a-8da6-b81ed01dde7a,f1870075-a6e0-47e7-88c6-793320ab3c8f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +bc3dda37-e131-45c1-8595-e047eb751a2f,ab2425d9-e2da-49ac-b8db-2db605e7283f,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +7e0781fc-ab59-4ea4-a57a-3b95b45c151f,802f4d68-bbd0-4220-8218-634975c3774a,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +5b11ed88-05e5-4edb-94a1-13a9b7a93ec2,142af777-fce1-4067-b84b-f684e2fa1170,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +f5b9530d-0587-44d5-9bd9-890a1ff8ed26,b5c2d5dc-e753-40ff-8e01-95c4c497710c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +82e45270-b5a0-478e-a81f-8330e638921c,d29345bb-fd48-4f69-ac81-eeedd4b41e6d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +94ad1072-16af-401b-b32f-3c33fb07c22b,813055ff-e5b7-4538-825c-eb721360e189,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +a51cd72a-1d76-4367-84ee-5fd448eec673,6c9bbb36-0b2d-49ea-9c1b-63209f009773,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +9ac32e07-bde6-4d8d-b172-2cf8c28b3cb6,8daf8ed9-d15f-407f-b827-3a9c01907e62,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +6a89fb69-f4f9-409e-8687-1927a9c9931a,bd028538-520d-45cf-a6f7-27c9f675d663,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +e08417c1-a015-4820-bab2-0e3bafda8c81,de1511df-f963-4ff6-8e3f-2225ba493879,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +27b913c1-b6c1-41a8-ac80-b8aec18dfdcb,26168044-3b5c-43f6-a757-833ba1485d41,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +d3a33394-f4dc-4e3e-b817-eb6309b17b59,e742724a-0ff6-4760-884b-866835460045,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +3e6f2e70-0113-42ae-beaf-f2fed10d70f2,3ce354c8-bcac-44c6-9a94-5274e5f9b389,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +28dac481-b80a-47c3-9de4-693f2774c470,c0aecfcc-3b2f-4117-9f20-e0920df97dc0,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +6732ab00-bca6-4a4b-b9ac-34adfb108299,3dfcdc9d-de8a-45b3-a05f-b83b51097ef5,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +ef81f3a4-9995-4c4c-b2b0-927235077174,bb74b280-db90-4240-ab09-b8c6cf63d553,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +f9cb12f8-523f-47c9-b3f3-ac55aac11502,7ede7b67-42d2-44cd-a530-66f62a68cd54,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +026b8072-f628-4bda-aeda-ce76d2fbccce,2df90a89-455e-4637-8b96-ad01c45f5701,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +713232b8-54d8-49b8-bcc5-9fabb3bd5c76,43560c6b-fda2-4b45-bc0b-7dbbbffff05c,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +4223a8af-8011-4234-b71d-520c3f7b23b0,ff4760c8-5359-43e9-9b42-95a0bf3e3d36,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +b54503c1-f3fd-411d-9830-882aa2c54dc2,a4c43698-c276-4430-b15e-8373b7bbb662,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +a657e586-ce3a-4c5c-b244-f704c13fff5e,30462f07-1c6b-4706-b4fb-288845b3631e,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +ce3898e2-403d-4fc1-8bfd-eb78be76b86a,4f5170f2-1d35-4130-a535-1d93383e406b,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-11-29 13:33:04.23263+00,2025-11-29 13:33:04.23263+00 +1bec7e38-0109-4e25-bbad-8e28cdc211a2,0ae1bf21-39e3-4168-9632-457418c7a07d,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,4,1180,100,Ah K'in,0.00,710,360,0,40,2025-11-29 19:41:58.551+00,0,0,,0,9,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,2025-11-29 13:41:42.417785+00,,{},2025-11-29 13:37:09.278078+00,2025-11-29 13:41:59.942645+00 +5035c591-f320-4182-981a-9a1416030d75,69681b09-5077-4f77-84cc-67606abd9755,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-12-08 19:24:06.272257+00,2025-12-08 19:24:06.272257+00 +5bc3c779-e7f4-4c4a-9bca-c0854ea1e288,f929d6df-8c29-461f-88f5-264facd879e9,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,1,0,100,Ajaw,0.00,100,100,0,0,,0,0,,0,0,0,0,,0,0,0,00:00:00,00:00:00,0,0,0,0,,,,,,{},2025-12-17 17:51:43.536295+00,2025-12-17 17:51:43.536295+00 diff --git a/projects/gamilit/apps/database/backup-prod/usuarios_produccion_2025-12-18.sql b/projects/gamilit/apps/database/backup-prod/usuarios_produccion_2025-12-18.sql new file mode 100644 index 0000000..6af188e --- /dev/null +++ b/projects/gamilit/apps/database/backup-prod/usuarios_produccion_2025-12-18.sql @@ -0,0 +1,246 @@ +-- +-- PostgreSQL database dump +-- + +\restrict kteh5aijQdsTMjnzB1xbEvkDPawhbikecDwbwClvcxgOHhbchFJp4xxlp3L10vn + +-- Dumped from database version 16.11 (Ubuntu 16.11-0ubuntu0.24.04.1) +-- Dumped by pg_dump version 16.11 (Ubuntu 16.11-0ubuntu0.24.04.1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: auth; Owner: - +-- + +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'authenticated', NULL, 'teacher@gamilit.com', '$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga', '2025-11-29 13:26:50.289631+00', NULL, '', NULL, '', NULL, '', '', NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"name": "Profesor Testing", "role": "admin_teacher", "description": "Usuario profesor de testing"}', false, '2025-11-29 13:26:50.289631+00', '2025-11-29 13:26:50.289631+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'admin_teacher', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'b017b792-b327-40dd-aefb-a80312776952', 'authenticated', NULL, 'joseal.guirre34@gmail.com', '$2b$10$kb9yCB4Y2WBr2.Gth.wC9e8q8bnkZJ6O2X6kFSn.O4VK8d76Cr/xO', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Aguirre", "first_name": "Jose"}', false, '2025-11-18 07:29:05.226874+00', '2025-11-18 07:29:05.226874+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '06a24962-e83d-4e94-aad7-ff69f20a9119', 'authenticated', NULL, 'sergiojimenezesteban63@gmail.com', '$2b$10$8oPdKN15ndCqCOIt12SEO.2yx4D29kQEQGPCC5rtUYWu8Qp5L7/zW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Jimenez", "first_name": "Sergio"}', false, '2025-11-18 08:17:40.925857+00', '2025-11-18 08:17:40.925857+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'authenticated', NULL, 'Gomezfornite92@gmail.com', '$2b$10$FuEfoSA0jxvBI2f6odMJqux9Gpgvt7Zjk.plRhRatvK0ykkIXxbI.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Gomez", "first_name": "Hugo"}', false, '2025-11-18 08:18:04.240276+00', '2025-11-18 08:18:04.240276+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'authenticated', NULL, 'Aragon494gt54@icloud.com', '$2b$10$lE8M8qWUIsgYLwcHyRGvTOjxdykLVchRVifsMVqCRCZq3bEeXR.xG', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Aragón", "first_name": "Hugo"}', false, '2025-11-18 08:20:17.228812+00', '2025-11-18 08:20:17.228812+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '2f5a9846-3393-40b2-9e87-0f29238c383f', 'authenticated', NULL, 'blu3wt7@gmail.com', '$2b$10$gKRXQ.rmOePqsNKWdxABQuyIZike2oSsYpdfWpQdi5HHDWDUk.3u2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Valentina", "first_name": "Azul"}', false, '2025-11-18 08:32:17.314233+00', '2025-11-18 08:32:17.314233+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5e738038-1743-4aa9-b222-30171300ea9d', 'authenticated', NULL, 'ricardolugo786@icloud.com', '$2b$10$YV1StKIdCPPED/Ft84zR2ONxj/VzzV7zOxjgwMSbDpd2hzvYOGtby', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Lugo", "first_name": "Ricardo"}', false, '2025-11-18 10:15:06.479774+00', '2025-11-18 10:15:06.479774+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '00c742d9-e5f7-4666-9597-5a8ca54d5478', 'authenticated', NULL, 'marbancarlos916@gmail.com', '$2b$10$PfsKOsEEXpGA6YB6eXNBPePo6OV6Am1glUN6Mkunl64bK/ji6uttW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Marban", "first_name": "Carlos"}', false, '2025-11-18 10:29:05.23842+00', '2025-11-18 10:29:05.23842+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '33306a65-a3b1-41d5-a49d-47989957b822', 'authenticated', NULL, 'diego.colores09@gmail.com', '$2b$10$rFlH9alBbgPGVEZMYIV8p.AkeZ30yRCVd5acasFjIt7fpCZhE6RuO', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Colores", "first_name": "Diego"}', false, '2025-11-18 10:29:20.530359+00', '2025-11-18 10:29:20.530359+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '7a6a973e-83f7-4374-a9fc-54258138115f', 'authenticated', NULL, 'hernandezfonsecabenjamin7@gmail.com', '$2b$10$1E6gLqfMojNLYrSKIbatqOh0pHblZ3jWZwbcxTY/DCx7MGADToCVm', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Hernandez", "first_name": "Benjamin"}', false, '2025-11-18 10:37:06.919813+00', '2025-11-18 10:37:06.919813+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'ccd7135c-0fea-4488-9094-9da52df1c98c', 'authenticated', NULL, 'jr7794315@gmail.com', '$2b$10$Ej/Gwx8mGCWg4TnQSjh1r.QZLw/GkUANqXmz4bEfVaNF9E527L02C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Reyes", "first_name": "Josue"}', false, '2025-11-18 17:53:39.67958+00', '2025-11-18 17:53:39.67958+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '9951ad75-e9cb-47b3-b478-6bb860ee2530', 'authenticated', NULL, 'barraganfer03@gmail.com', '$2b$10$VJ8bS.ksyKpa7oG575r5YOWQYcq8vwmwTa8jMBkCv0dwskF04SHn2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Barragan", "first_name": "Fernando"}', false, '2025-11-18 20:39:27.408624+00', '2025-11-18 20:39:27.408624+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '735235f5-260a-4c9b-913c-14a1efd083ea', 'authenticated', NULL, 'roman.rebollar.marcoantonio1008@gmail.com', '$2b$10$l4eF8UoOB7D8LKDEzTigXOUO7EABhVdYCqknJ/lD6R4p8uF1R4I.W', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Roman", "first_name": "Marco Antonio"}', false, '2025-11-18 21:03:17.326679+00', '2025-11-18 21:03:17.326679+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'ebe48628-5e44-4562-97b7-b4950b216247', 'authenticated', NULL, 'rodrigoguerrero0914@gmail.com', '$2b$10$ihoy7HbOdlqU38zAddpTOuDO7Nqa8.Cr1dEQjCgMpdb30UwCIMhGW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "Guerrero", "first_name": "Rodrigo"}', false, '2025-11-18 21:20:52.303128+00', '2025-11-18 21:20:52.303128+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'd089b1af-462f-4d2c-b0f5-d2528cec8506', 'authenticated', NULL, 'santiagoferrara78@gmail.com', '$2b$10$Wjo3EENjiuddS9BwPMAW1OORZrZpU8ECP9zEXmd4Gvn7orwgjo8O2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 09:21:04.898591+00', '2025-11-24 09:21:04.898591+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6', 'authenticated', NULL, 'alexanserrv917@gmail.com', '$2b$10$8sT/ObLZUNmiu6CpbceHhenfc7E8zZml8AvB1HUiyOddSLqchggZ2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:26:51.934739+00', '2025-11-24 10:26:51.934739+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2', 'authenticated', NULL, 'aarizmendi434@gmail.com', '$2b$10$2BAG4EskBG0feGOIva6XyOCBtBJbKJE9h27GU6DmuBH3f.2iK6FoS', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:30:54.728262+00', '2025-11-24 10:30:54.728262+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '26fbc469-10af-4fa3-bd65-e5498188cc4f', 'authenticated', NULL, 'ashernarcisobenitezpalomino@gmail.com', '$2b$10$Bv5vo0GDeseWUWTt.5xV0O9nN93TRVN.vHRigs4vF/ww7Hbnjylam', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:37:35.325342+00', '2025-11-24 10:37:35.325342+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8', 'authenticated', NULL, 'ra.alejandrobm@gmail.com', '$2b$10$QZId3lZBIzBulD7AZCeEKOiL0LBJRekGlQTGiacC70IDwDo2wx7py', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:42:33.424367+00', '2025-11-24 10:42:33.424367+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1', 'authenticated', NULL, 'abdallahxelhaneriavega@gmail.com', '$2b$10$jQ4SquNUxIO70e7IBYqqLeUw1d.gSCleJ/cwinuWMVlW25a8.pRGG', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:45:19.984994+00', '2025-11-24 10:45:19.984994+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '012adac4-8ffd-47bd-9248-f0c5851e981f', 'authenticated', NULL, '09enriquecampos@gmail.com', '$2b$10$95c9hOplonbo/46O5UlPqummq.AIaGVIZ7YgBstSuOWPbgGersKxy', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:51:54.731982+00', '2025-11-24 10:51:54.731982+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '126b9257-7b0a-4bd6-9ab3-c505ee00e10a', 'authenticated', NULL, 'johhkk22@gmail.com', '$2b$10$Bt6IZ19zuBkly.6QmmPWBeF0kfyVN/O/c3/9bqyUGup3gPZu14DGa', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:53:47.029991+00', '2025-11-24 10:53:47.029991+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '9ac1746e-94a6-4efc-a961-951c015d416e', 'authenticated', NULL, 'edangiel4532@gmail.com', '$2b$10$eZap9LmAws7VtY9sHnS17.RJkhIte5SUobIWaWpuTxTPKjbKgzK.6', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 10:58:12.790316+00', '2025-11-24 10:58:12.790316+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'authenticated', NULL, 'student@gamilit.com', '$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga', '2025-11-29 13:26:50.289631+00', NULL, '', NULL, '', NULL, '', '', NULL, '2025-12-07 03:42:02.528+00', '{"provider": "email", "providers": ["email"]}', '{"name": "Estudiante Testing", "role": "student", "description": "Usuario estudiante de testing"}', false, '2025-11-29 13:26:50.289631+00', '2025-12-07 03:42:02.529507+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0', 'authenticated', NULL, 'erickfranco462@gmail.com', '$2b$10$lNzkSO7zbBHQcJJui0O76.a2artcsZHari4Mgkjo4btGww.Wy9/iC', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:00:11.800551+00', '2025-11-24 11:00:11.800551+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'aff5dcc6-32de-4769-9aaf-eda751fa0866', 'authenticated', NULL, 'gallinainsana@gmail.com', '$2b$10$6y/FVa4LqyliI4PXuBxKpepTRwIIRWybFN0NhcAqRM.Kl/cnvXDMq', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:03:17.536383+00', '2025-11-24 11:03:17.536383+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '0cda1645-83c5-445b-80b7-d0e4d436c00c', 'authenticated', NULL, 'leile5257@gmail.com', '$2b$10$ZZX0.z30VPm7BsLF8bNVweQpRZ2ca/1EPlxdIZy0xNaCFugoKL0ci', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:05:17.75852+00', '2025-11-24 11:05:17.75852+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '1364c463-88de-479b-a883-c0b7b362bcf8', 'authenticated', NULL, 'maximiliano.mejia367@gmail.com', '$2b$10$iTfIWKh2ISvPys2bkK2LOOPI24ua7I47oT8dFxHHYW7AuztoZreQa', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:08:58.232003+00', '2025-11-24 11:08:58.232003+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '547eb778-4782-4681-b198-c731bba36147', 'authenticated', NULL, 'fl432025@gmail.com', '$2b$10$aGKv6yhAWwHb07m3N2DxJOXIn5omkP3t2QeSYblhcDo52pB2ZiFQi', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:12:13.692614+00', '2025-11-24 11:12:13.692614+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5fc06693-e408-4eab-a9a3-fcd5f4e01296', 'authenticated', NULL, '7341023901m@gmail.com', '$2b$10$Z/HUBov20g..LZ6RDYax4.NcDuiFD/gn9Nrt7/OPCPBqCoTJUgr3C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:15:18.276345+00', '2025-11-24 11:15:18.276345+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5d1839f6-b03f-4e12-b236-eca43f4674f2', 'authenticated', NULL, 'segurauriel235@gmail.com', '$2b$10$IfdhPuUOModgrJT7bMfYkODZkXeTcaAReuCQf9BGpK1cT6GiP9UGu', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:17:46.846963+00', '2025-11-24 11:17:46.846963+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '1b310708-6f24-4c6a-88c9-a11f7a7f9763', 'authenticated', NULL, 'angelrabano11@gmail.com', '$2b$10$Sg6q4kErMvxRlZgWM9lCj.PfRg5sCQrwm763d7sfc3iaAUID7y436', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:47:53.790673+00', '2025-11-24 11:47:53.790673+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '3c613b0e-66f9-4640-a599-c9426d8edffb', 'authenticated', NULL, 'daliaayalareyes35@gmail.com', '$2b$10$dd2SQeBqNIZpZWCGMIDu1O8U6MLpWnKF05w641MNOMzHDZ/U5glCe', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:55:08.708961+00', '2025-11-24 11:55:08.708961+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '7ded133e-9b13-4467-9803-edb813f6a9a1', 'authenticated', NULL, 'alexeimongam@gmail.com', '$2b$10$jyQrHAIj6SsnReQ45FrFlOnDgpZtabskpxPuOYgB/h.YPLyZhuld.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 11:55:11.906996+00', '2025-11-24 11:55:11.906996+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '4cc04f54-7771-462d-98aa-a94448bb6ff5', 'authenticated', NULL, 'davidocampovenegas@gmail.com', '$2b$10$8COk10WE5.bXFJnAucEA0efcGQKU6KUXKV9N7n32ZX6aNKORs4McW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 14:52:46.468737+00', '2025-11-24 14:52:46.468737+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35', 'authenticated', NULL, 'zaid080809@gmail.com', '$2b$10$kdaUWR1BUqPRY7H8YkR.xuuDbqtLcvP5yKW.B0ooPlb.I6b/UU192', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 16:25:03.689847+00', '2025-11-24 16:25:03.689847+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4', 'authenticated', NULL, 'ruizcruzabrahamfrancisco@gmail.com', '$2b$10$DXHr682C4/VpesiHa7fRrOjKceiWSDUSx.1LZTbsvuxpqCdMNh/Ii', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 19:46:06.311558+00', '2025-11-24 19:46:06.311558+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '615adf6e-dbf3-480f-a907-3cfb3a64c6d2', 'authenticated', NULL, 'vituschinchilla@gmail.com', '$2b$10$dA8adTYlfhgqhZfACcQkFOCYjXdsmggXnIUluNDoh1zRFgQ6pq5O2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-24 21:07:26.037867+00', '2025-11-24 21:07:26.037867+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'bf445960-4c1f-4e29-8fb7-31667b183d7e', 'authenticated', NULL, 'bryan@betanzos.com', '$2b$10$Xdfuf4Tfog9QKd1FRLL.7eAaD6tr2cXgPx1/L8xqT1kLLzNHzSM26', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 06:13:30.263795+00', '2025-11-25 06:13:30.263795+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'd5fa4905-a78a-4040-8ad8-23220881c6a6', 'authenticated', NULL, 'loganalexander816@gmail.com', '$2b$10$8zLduh/9L/priag.nujz5utuloO9RnNFFDGdKgI2UniFCOwocEPLq', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 07:37:04.953164+00', '2025-11-25 07:37:04.953164+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '71734c15-cdaa-431b-90f5-97a57e0316a8', 'authenticated', NULL, 'carlois1974@gmail.com', '$2b$10$IfLfJ.q59DZgicR07ckSVOcrkkBJe42m1FECXxaoaodKYSo6uj5wW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 07:41:38.025764+00', '2025-11-25 07:41:38.025764+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '1efe491d-98ef-4c02-acd1-3135f7289072', 'authenticated', NULL, 'enriquecuevascbtis136@gmail.com', '$2b$10$9BX3OQMZmHruffBtN.3WPOFoyea6zgPd8i72DvhJ7vRAdqWKax6GS', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:16:33.977647+00', '2025-11-25 08:16:33.977647+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '5ae21325-7450-4c37-82f1-3f9bcd7b6f45', 'authenticated', NULL, 'omarcitogonzalezzavaleta@gmail.com', '$2b$10$RRk3DAgQdiikxVImFIMqquqB.TNpKs3E.RNFtt1rwwTzO24uShri.', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:17:07.610076+00', '2025-11-25 08:17:07.610076+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7', 'authenticated', NULL, 'gustavobm2024cbtis@gmail.com', '$2b$10$lg7KRUTPofcx4Rtyey8J7.XO0gmdBLCFIfK5uP08mqT0qUIl1aTJq', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:20:49.649184+00', '2025-11-25 08:20:49.649184+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', '6e30164a-78b0-49b0-bd21-23d7c6c03349', 'authenticated', NULL, 'marianaxsotoxt22@gmail.com', '$2b$10$GQC9yTWiP2vP9GUp0gnhUeLjmw70EI4JQhfJBZbMOlCNXGXb/bt5O', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '{"provider": "email", "providers": ["email"]}', '{"last_name": "", "first_name": ""}', false, '2025-11-25 08:33:18.150784+00', '2025-11-25 08:33:18.150784+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES (NULL, '0ae1bf21-39e3-4168-9632-457418c7a07d', 'authenticated', NULL, 'rckrdmrd@gmail.com', '$2b$10$LiDdaJLA.ZvdFleamkMuvOcIrW0PQMEh5aVZ5Wg5pzhm7gwc5s.1C', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-09 01:22:42.784+00', NULL, '{}', false, '2025-11-29 13:37:09.271457+00', '2025-12-09 01:22:42.785367+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES ('00000000-0000-0000-0000-000000000000', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'authenticated', NULL, 'admin@gamilit.com', '$2b$10$pkqX0/v7H3F5TBTuDTaoYeBjH581pXpjlcNcYmMtXofd/2HjfTuga', '2025-11-29 13:26:50.289631+00', NULL, '', NULL, '', NULL, '', '', NULL, '2025-12-01 00:54:19.615+00', '{"provider": "email", "providers": ["email"]}', '{"name": "Admin GAMILIT", "role": "super_admin", "description": "Usuario administrador de testing"}', false, '2025-11-29 13:26:50.289631+00', '2025-12-01 00:54:19.617766+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'super_admin', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES (NULL, '69681b09-5077-4f77-84cc-67606abd9755', 'authenticated', NULL, 'javiermar06@hotmail.com', '$2b$10$3RHyXnR4BG3NaxP8Ez82FuiGDMNCG7GhNaOsMFigy3BpIVOzCqHMW', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-14 03:51:04.122+00', NULL, '{}', false, '2025-12-08 19:24:06.266895+00', '2025-12-14 03:51:04.123886+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); +INSERT INTO auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, confirmed_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at, gamilit_role, status) VALUES (NULL, 'f929d6df-8c29-461f-88f5-264facd879e9', 'authenticated', NULL, 'ju188an@gmail.com', '$2b$10$9vUERFnXApdfXuAI7DFve.aa8uDjI5bfm4CI75/EZ2cUre83RytKe', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-17 23:51:43.553+00', NULL, '{}', false, '2025-12-17 17:51:43.530434+00', '2025-12-17 23:51:43.55475+00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, 'student', 'active'); + + +-- +-- Data for Name: profiles; Type: TABLE DATA; Schema: auth_management; Owner: - +-- + +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Admin GAMILIT', 'Administrador GAMILIT', 'Administrador', 'GAMILIT', 'admin@gamilit.com', '/avatars/admin-testing.png', 'Usuario administrador para testing y desarrollo.', '55-0000-0001', '1985-01-01', NULL, NULL, NULL, 'super_admin', 'active', true, true, '{"theme": "professional", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{"description": "Usuario de testing principal", "testing_user": true}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Profesor Testing', 'Profesor de Testing GAMILIT', 'Profesor', 'Testing', 'teacher@gamilit.com', '/avatars/teacher-testing.png', 'Usuario profesor para testing y desarrollo.', '55-0000-0002', '1980-05-15', NULL, NULL, NULL, 'admin_teacher', 'active', true, true, '{"theme": "teacher", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{"subjects": ["Lengua Española", "Comprensión Lectora"], "testing_user": true}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('cccccccc-cccc-cccc-cccc-cccccccccccc', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Estudiante Testing', 'Estudiante de Testing GAMILIT', 'Estudiante', 'Testing', 'student@gamilit.com', '/avatars/student-testing.png', 'Usuario estudiante para testing y desarrollo.', '55-0000-0003', '2013-09-01', '5', 'EST-TEST-001', NULL, 'student', 'active', true, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "gamification": {"show_rank": true, "show_leaderboard": true, "show_achievements": true}, "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{"interests": ["lectura", "ciencia"], "testing_user": true, "learning_style": "visual"}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00', 'cccccccc-cccc-cccc-cccc-cccccccccccc', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('b017b792-b327-40dd-aefb-a80312776952', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Jose Aguirre', 'Jose Aguirre', 'Jose', 'Aguirre', 'joseal.guirre34@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 07:29:05.229254+00', '2025-11-18 07:29:05.229254+00', 'b017b792-b327-40dd-aefb-a80312776952', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('06a24962-e83d-4e94-aad7-ff69f20a9119', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Sergio Jimenez', 'Sergio Jimenez', 'Sergio', 'Jimenez', 'sergiojimenezesteban63@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:17:40.928077+00', '2025-11-18 08:17:40.928077+00', '06a24962-e83d-4e94-aad7-ff69f20a9119', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Hugo Gomez', 'Hugo Gomez', 'Hugo', 'Gomez', 'Gomezfornite92@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:18:04.242047+00', '2025-11-18 08:18:04.242047+00', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Hugo Aragón', 'Hugo Aragón', 'Hugo', 'Aragón', 'Aragon494gt54@icloud.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:20:17.230714+00', '2025-11-18 08:20:17.230714+00', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('2f5a9846-3393-40b2-9e87-0f29238c383f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Azul Valentina', 'Azul Valentina', 'Azul', 'Valentina', 'blu3wt7@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 08:32:17.315932+00', '2025-11-18 08:32:17.315932+00', '2f5a9846-3393-40b2-9e87-0f29238c383f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('5e738038-1743-4aa9-b222-30171300ea9d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ricardo Lugo', 'Ricardo Lugo', 'Ricardo', 'Lugo', 'ricardolugo786@icloud.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:15:06.481498+00', '2025-11-18 10:15:06.481498+00', '5e738038-1743-4aa9-b222-30171300ea9d', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('00c742d9-e5f7-4666-9597-5a8ca54d5478', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Carlos Marban', 'Carlos Marban', 'Carlos', 'Marban', 'marbancarlos916@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:29:05.240413+00', '2025-11-18 10:29:05.240413+00', '00c742d9-e5f7-4666-9597-5a8ca54d5478', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('33306a65-a3b1-41d5-a49d-47989957b822', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Diego Colores', 'Diego Colores', 'Diego', 'Colores', 'diego.colores09@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:29:20.531883+00', '2025-11-18 10:29:20.531883+00', '33306a65-a3b1-41d5-a49d-47989957b822', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('7a6a973e-83f7-4374-a9fc-54258138115f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Benjamin Hernandez', 'Benjamin Hernandez', 'Benjamin', 'Hernandez', 'hernandezfonsecabenjamin7@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 10:37:06.9215+00', '2025-11-18 10:37:06.9215+00', '7a6a973e-83f7-4374-a9fc-54258138115f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ccd7135c-0fea-4488-9094-9da52df1c98c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Josue Reyes', 'Josue Reyes', 'Josue', 'Reyes', 'jr7794315@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 17:53:39.681271+00', '2025-11-18 17:53:39.681271+00', 'ccd7135c-0fea-4488-9094-9da52df1c98c', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('9951ad75-e9cb-47b3-b478-6bb860ee2530', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Fernando Barragan', 'Fernando Barragan', 'Fernando', 'Barragan', 'barraganfer03@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 20:39:27.410436+00', '2025-11-18 20:39:27.410436+00', '9951ad75-e9cb-47b3-b478-6bb860ee2530', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('735235f5-260a-4c9b-913c-14a1efd083ea', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Marco Antonio Roman', 'Marco Antonio Roman', 'Marco Antonio', 'Roman', 'roman.rebollar.marcoantonio1008@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 21:03:17.328254+00', '2025-11-18 21:03:17.328254+00', '735235f5-260a-4c9b-913c-14a1efd083ea', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ebe48628-5e44-4562-97b7-b4950b216247', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Rodrigo Guerrero', 'Rodrigo Guerrero', 'Rodrigo', 'Guerrero', 'rodrigoguerrero0914@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-18 21:20:52.304488+00', '2025-11-18 21:20:52.304488+00', 'ebe48628-5e44-4562-97b7-b4950b216247', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('3f255f44-40d9-4dc9-970c-d50ddec197b3', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'alexanserrv917@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('f1870075-a6e0-47e7-88c6-793320ab3c8f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'carlois1974@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '71734c15-cdaa-431b-90f5-97a57e0316a8', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ab2425d9-e2da-49ac-b8db-2db605e7283f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'gustavobm2024cbtis@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('802f4d68-bbd0-4220-8218-634975c3774a', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'gallinainsana@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'aff5dcc6-32de-4769-9aaf-eda751fa0866', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('142af777-fce1-4067-b84b-f684e2fa1170', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'zaid080809@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('b5c2d5dc-e753-40ff-8e01-95c4c497710c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'davidocampovenegas@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '4cc04f54-7771-462d-98aa-a94448bb6ff5', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('d29345bb-fd48-4f69-ac81-eeedd4b41e6d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'marianaxsotoxt22@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '6e30164a-78b0-49b0-bd21-23d7c6c03349', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('813055ff-e5b7-4538-825c-eb721360e189', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'leile5257@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '0cda1645-83c5-445b-80b7-d0e4d436c00c', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('6c9bbb36-0b2d-49ea-9c1b-63209f009773', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'ashernarcisobenitezpalomino@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '26fbc469-10af-4fa3-bd65-e5498188cc4f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('8daf8ed9-d15f-407f-b827-3a9c01907e62', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'ruizcruzabrahamfrancisco@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bd028538-520d-45cf-a6f7-27c9f675d663', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'daliaayalareyes35@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '3c613b0e-66f9-4640-a599-c9426d8edffb', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('de1511df-f963-4ff6-8e3f-2225ba493879', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'ra.alejandrobm@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('26168044-3b5c-43f6-a757-833ba1485d41', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'enriquecuevascbtis136@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '1efe491d-98ef-4c02-acd1-3135f7289072', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('e742724a-0ff6-4760-884b-866835460045', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'fl432025@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '547eb778-4782-4681-b198-c731bba36147', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('3ce354c8-bcac-44c6-9a94-5274e5f9b389', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'abdallahxelhaneriavega@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('188fa4e3-985c-4048-8913-754cb0560875', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', '7341023901m@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5fc06693-e408-4eab-a9a3-fcd5f4e01296', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('caa05325-b8e7-4b1c-9d95-03d4e0c7372d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'vituschinchilla@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '615adf6e-dbf3-480f-a907-3cfb3a64c6d2', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('128ac756-bdb8-49d7-8fdb-cdb8fd241d06', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'alexeimongam@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '7ded133e-9b13-4467-9803-edb813f6a9a1', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('7f3bb769-4d7e-4ca9-8527-708da0368be5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'angelrabano11@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '1b310708-6f24-4c6a-88c9-a11f7a7f9763', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('6a565d40-9012-4c89-878c-05bb8b6e2d81', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'loganalexander816@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'd5fa4905-a78a-4040-8ad8-23220881c6a6', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('7ede7b67-42d2-44cd-a530-66f62a68cd54', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'bryan@betanzos.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'bf445960-4c1f-4e29-8fb7-31667b183d7e', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('2df90a89-455e-4637-8b96-ad01c45f5701', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'johhkk22@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '126b9257-7b0a-4bd6-9ab3-c505ee00e10a', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('43560c6b-fda2-4b45-bc0b-7dbbbffff05c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'edangiel4532@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '9ac1746e-94a6-4efc-a961-951c015d416e', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('ff4760c8-5359-43e9-9b42-95a0bf3e3d36', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'aarizmendi434@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('a4c43698-c276-4430-b15e-8373b7bbb662', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'santiagoferrara78@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', 'd089b1af-462f-4d2c-b0f5-d2528cec8506', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('30462f07-1c6b-4706-b4fb-288845b3631e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', '09enriquecampos@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '012adac4-8ffd-47bd-9248-f0c5851e981f', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('4f5170f2-1d35-4130-a535-1d93383e406b', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'maximiliano.mejia367@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '1364c463-88de-479b-a883-c0b7b362bcf8', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('c0aecfcc-3b2f-4117-9f20-e0920df97dc0', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'segurauriel235@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5d1839f6-b03f-4e12-b236-eca43f4674f2', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('3dfcdc9d-de8a-45b3-a05f-b83b51097ef5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'omarcitogonzalezzavaleta@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '5ae21325-7450-4c37-82f1-3f9bcd7b6f45', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('bb74b280-db90-4240-ab09-b8c6cf63d553', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, '', '', 'erickfranco462@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:30:54.277737+00', '2025-11-29 13:30:54.277737+00', '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('0ae1bf21-39e3-4168-9632-457418c7a07d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, 'rckrdmrd@gmail.com', NULL, 'rckrdmrd@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-11-29 13:37:09.278078+00', '2025-11-29 13:37:09.278078+00', '0ae1bf21-39e3-4168-9632-457418c7a07d', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('69681b09-5077-4f77-84cc-67606abd9755', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, 'Javier', ' Mar', 'javiermar06@hotmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-12-08 19:24:06.272257+00', '2025-12-08 19:24:06.272257+00', '69681b09-5077-4f77-84cc-67606abd9755', NULL); +INSERT INTO auth_management.profiles (id, tenant_id, display_name, full_name, first_name, last_name, email, avatar_url, bio, phone, date_of_birth, grade_level, student_id, school_id, role, status, email_verified, phone_verified, preferences, last_sign_in_at, last_activity_at, metadata, created_at, updated_at, user_id, deleted_at) VALUES ('f929d6df-8c29-461f-88f5-264facd879e9', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', NULL, NULL, 'Juan', 'pa', 'ju188an@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student', 'active', false, false, '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}', NULL, NULL, '{}', '2025-12-17 17:51:43.536295+00', '2025-12-17 17:51:43.536295+00', 'f929d6df-8c29-461f-88f5-264facd879e9', NULL); + + +-- +-- Data for Name: user_ranks; Type: TABLE DATA; Schema: gamification_system; Owner: - +-- + +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('aef81f6b-b9c4-4f80-b27f-8f9bf8c2cd97', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.458823+00', NULL, true, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:50.458823+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('4cadc6a3-c15e-410a-88f5-c0254463ca8a', 'b017b792-b327-40dd-aefb-a80312776952', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('8c1d72aa-fead-4bff-bf47-b34ceab62a40', '06a24962-e83d-4e94-aad7-ff69f20a9119', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('840cabba-4a80-40fa-a3e0-0e218e09463e', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('913ae601-8ab4-4056-864c-517e100bef5b', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('eb382620-70b8-4842-b8cf-ad51eb1c7266', '2f5a9846-3393-40b2-9e87-0f29238c383f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('207ff6ef-a56d-4fc1-942f-5a50e0c842da', '5e738038-1743-4aa9-b222-30171300ea9d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('64946395-a46c-4e93-abf7-5b827e4dcf12', '33306a65-a3b1-41d5-a49d-47989957b822', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('66cb0dd1-2c47-4572-99ec-c97ccee95e53', '7a6a973e-83f7-4374-a9fc-54258138115f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('241b6cea-03d0-4f9c-97b6-7d49d9175cfb', 'ccd7135c-0fea-4488-9094-9da52df1c98c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b14b22be-bd9d-485c-9477-b4122c63cbfb', '9951ad75-e9cb-47b3-b478-6bb860ee2530', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('4e7cf7a1-8ef4-4d20-902b-ba1c57faad51', '735235f5-260a-4c9b-913c-14a1efd083ea', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('d0500d4e-17ec-46be-94a1-4e2500e4b9a8', 'ebe48628-5e44-4562-97b7-b4950b216247', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:26:50.539509+00', NULL, true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('9a4e06cc-0a50-4ff9-b781-d29edff7bbd6', '00c742d9-e5f7-4666-9597-5a8ca54d5478', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Nacom', 'Ajaw', 0, NULL, 0, NULL, 0, 100, NULL, NULL, '2025-11-29 13:26:52.138221+00', '2025-11-29 13:26:50.539509+00', true, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.138221+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('33b12003-65b0-4f50-8bda-6eeebedc3413', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Halach Uinic', 'Ah K''in', 0, NULL, 0, NULL, 0, 500, NULL, NULL, '2025-11-29 13:26:52.158657+00', '2025-11-29 13:26:52.158657+00', true, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:52.138221+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('7c3b01ab-b75b-4464-8858-91c1ff805c32', '3dfcdc9d-de8a-45b3-a05f-b83b51097ef5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('53ec34ee-bd3c-462f-b5b0-317705c0399d', '3f255f44-40d9-4dc9-970c-d50ddec197b3', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b2461bb7-7107-48dd-923f-de4ff8e86a67', 'e742724a-0ff6-4760-884b-866835460045', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('890ba444-c64f-4680-894d-56bf30cbcf5c', 'f1870075-a6e0-47e7-88c6-793320ab3c8f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('a7b16bb9-efa6-4f10-8fe0-992adbd8c482', 'b5c2d5dc-e753-40ff-8e01-95c4c497710c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('c6eb5dd4-a987-4560-b22d-645ca31791e6', '142af777-fce1-4067-b84b-f684e2fa1170', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('520cd26c-5222-4579-8f1d-72f4be8e98d3', 'c0aecfcc-3b2f-4117-9f20-e0920df97dc0', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('2971dbbf-33de-4f12-9e47-48c9a3b145df', '7ede7b67-42d2-44cd-a530-66f62a68cd54', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('cb9feef2-0f2d-4949-b212-3b6d7dbfe2fa', 'caa05325-b8e7-4b1c-9d95-03d4e0c7372d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('89181d4a-a47b-4449-81fa-09a7e1d67534', 'a4c43698-c276-4430-b15e-8373b7bbb662', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('998f3c88-8932-4d91-af11-7bca6c03bbc3', 'bb74b280-db90-4240-ab09-b8c6cf63d553', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('e22e4620-57fc-47ad-ae53-f45ecc17932f', '4f5170f2-1d35-4130-a535-1d93383e406b', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b9e35a4b-ade2-4fa0-bd58-38ffcf5ca614', '802f4d68-bbd0-4220-8218-634975c3774a', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('365f3ac3-2880-42a8-8799-0c465b0a4ed9', 'de1511df-f963-4ff6-8e3f-2225ba493879', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('21152c4c-b082-40a7-938b-e7486730e2bb', '188fa4e3-985c-4048-8913-754cb0560875', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b1c76243-62c4-4e73-8e7b-152b82e41c93', '128ac756-bdb8-49d7-8fdb-cdb8fd241d06', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('e521b5a1-250b-4e96-a2cd-e8736f1f3fbe', '6c9bbb36-0b2d-49ea-9c1b-63209f009773', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('a106676f-8723-492b-b4f1-1b9256da8a4d', '813055ff-e5b7-4538-825c-eb721360e189', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('8cf5754b-7af4-429f-8e57-b0f2e4f740d6', '3ce354c8-bcac-44c6-9a94-5274e5f9b389', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('89526d39-7d29-4d97-a65b-94a42dfb45e2', 'ab2425d9-e2da-49ac-b8db-2db605e7283f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('fa25a282-3568-481a-878d-c424f6eaee95', 'ff4760c8-5359-43e9-9b42-95a0bf3e3d36', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('aee1d9bf-b4fe-4512-9cf0-4cc90413692e', '8daf8ed9-d15f-407f-b827-3a9c01907e62', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('69621cdf-cc64-4f2b-85f3-d1f09b54cbf0', '7f3bb769-4d7e-4ca9-8527-708da0368be5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b4c93d8d-fb84-4ba2-91d7-78abd5a780de', '26168044-3b5c-43f6-a757-833ba1485d41', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('3444f9aa-7c1b-452c-bfbb-af58dc70e70b', 'bd028538-520d-45cf-a6f7-27c9f675d663', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('c8aec5d3-4a66-40b6-a8e7-9b2edd4225b1', '2df90a89-455e-4637-8b96-ad01c45f5701', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('e284ffd8-9d58-4564-95c4-075721724643', '6a565d40-9012-4c89-878c-05bb8b6e2d81', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('abfa66bf-dc3d-426a-b4ec-538677b39cd5', '43560c6b-fda2-4b45-bc0b-7dbbbffff05c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('b26939b5-2dc1-4643-b46c-c4a3d6a5203f', 'd29345bb-fd48-4f69-ac81-eeedd4b41e6d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('564433a1-e5f1-48ce-8807-06f5b9ea1418', '30462f07-1c6b-4706-b4fb-288845b3631e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-11-29 13:33:25.124715+00', NULL, true, '{}', '2025-11-29 13:33:25.124715+00', '2025-11-29 13:33:25.124715+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('3c88a791-ad9b-4988-9351-b5e9df60c9a4', '0ae1bf21-39e3-4168-9632-457418c7a07d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ah K''in', 'Nacom', 0, NULL, 0, NULL, 0, 250, NULL, NULL, '2025-11-29 13:41:42.417785+00', '2025-11-29 13:40:27.516619+00', true, '{}', '2025-11-29 13:37:09.278078+00', '2025-11-29 13:41:42.417785+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('cb04ef70-2738-4c9e-b32a-a92643b8a3f9', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Halach Uinic', 'Ah K''in', 0, NULL, 0, NULL, 0, 500, NULL, NULL, '2025-11-30 20:12:46.263821+00', '2025-11-29 13:26:52.146305+00', true, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-30 20:12:46.263821+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('1de77cef-003a-4d08-8ccc-8bd4001fa62c', '69681b09-5077-4f77-84cc-67606abd9755', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-12-08 19:24:06.272257+00', NULL, true, '{}', '2025-12-08 19:24:06.272257+00', '2025-12-08 19:24:06.272257+00'); +INSERT INTO gamification_system.user_ranks (id, user_id, tenant_id, current_rank, previous_rank, rank_progress_percentage, modules_required_for_next, modules_completed_for_rank, xp_required_for_next, xp_earned_for_rank, ml_coins_bonus, certificate_url, badge_url, achieved_at, previous_rank_achieved_at, is_current, rank_metadata, created_at, updated_at) VALUES ('63b40546-38a8-4c80-bf11-3504c73150bb', 'f929d6df-8c29-461f-88f5-264facd879e9', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Ajaw', NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, '2025-12-17 17:51:43.536295+00', NULL, true, '{}', '2025-12-17 17:51:43.536295+00', '2025-12-17 17:51:43.536295+00'); + + +-- +-- Data for Name: user_stats; Type: TABLE DATA; Schema: gamification_system; Owner: - +-- + +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('88b149bb-5f3b-41bb-885f-e226eb9cac22', 'b017b792-b327-40dd-aefb-a80312776952', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('1cb60bdb-800a-4ebc-a9c3-16ecf78d3535', '06a24962-e83d-4e94-aad7-ff69f20a9119', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5cba623a-015f-41e9-9de0-da67bdd6b5fb', '24e8c563-8854-43d1-b3c9-2f83e91f5a1e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('c6a7e9dd-ade4-4860-a4e0-882fd5aa0ed1', 'bf0d3e34-e077-43d1-9626-292f7fae2bd6', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('ee39d06d-d9c5-492a-9855-2210c74b74fd', 'ccd7135c-0fea-4488-9094-9da52df1c98c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e6be8348-29af-49c4-b49d-4b2d978c951b', 'ebe48628-5e44-4562-97b7-b4950b216247', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:50.539509+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('9c4cf796-3768-4182-b1e0-1a38f8187c87', '00c742d9-e5f7-4666-9597-5a8ca54d5478', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 3, 525, 100, 'Nacom', 0.00, 395, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('1f6076c1-7b54-43f7-9fca-519407e65c4f', '2f5a9846-3393-40b2-9e87-0f29238c383f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 2, 150, 100, 'Ajaw', 0.00, 140, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('1bec7e38-0109-4e25-bbad-8e28cdc211a2', '0ae1bf21-39e3-4168-9632-457418c7a07d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 4, 1180, 100, 'Ah K''in', 0.00, 710, 360, 0, 40, '2025-11-29 19:41:58.551+00', 0, 0, NULL, 0, 9, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, '2025-11-29 13:41:42.417785+00', NULL, '{}', '2025-11-29 13:37:09.278078+00', '2025-11-29 13:41:59.942645+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('524efcd5-9c89-4a79-ac4c-f58f92f9fd28', '7a6a973e-83f7-4374-a9fc-54258138115f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 25, 100, 'Ajaw', 0.00, 110, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5035c591-f320-4182-981a-9a1416030d75', '69681b09-5077-4f77-84cc-67606abd9755', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-12-08 19:24:06.272257+00', '2025-12-08 19:24:06.272257+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('fe110986-762c-459f-acde-b2c865c237bb', '33306a65-a3b1-41d5-a49d-47989957b822', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 75, 100, 'Ajaw', 0.00, 120, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('06fd9e66-cefc-4c64-a446-37cef9668b36', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 2, 375, 100, 'Ajaw', 0.00, 260, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('f0734a0b-1d9c-4999-bcba-8e3b8f229c80', '9951ad75-e9cb-47b3-b478-6bb860ee2530', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 2, 375, 100, 'Ajaw', 0.00, 260, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('7446ef44-11a1-451d-9624-abd95f0eb2ec', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 6, 2525, 100, 'Halach Uinic', 0.00, 1960, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.458823+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('0145f432-c383-45d1-9d0f-0c7783a48612', '735235f5-260a-4c9b-913c-14a1efd083ea', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 75, 100, 'Ajaw', 0.00, 135, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('c29ffaef-0392-4fe1-ac41-b502a4052111', '5e738038-1743-4aa9-b222-30171300ea9d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 25, 100, 'Ajaw', 0.00, 110, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:26:50.539509+00', '2025-11-29 13:26:52.120683+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('762397ff-699f-4bb8-bef6-2e03959fe4c1', '3f255f44-40d9-4dc9-970c-d50ddec197b3', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e8a5e302-93c4-4f6a-8da6-b81ed01dde7a', 'f1870075-a6e0-47e7-88c6-793320ab3c8f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('bc3dda37-e131-45c1-8595-e047eb751a2f', 'ab2425d9-e2da-49ac-b8db-2db605e7283f', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('7e0781fc-ab59-4ea4-a57a-3b95b45c151f', '802f4d68-bbd0-4220-8218-634975c3774a', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5b11ed88-05e5-4edb-94a1-13a9b7a93ec2', '142af777-fce1-4067-b84b-f684e2fa1170', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('f5b9530d-0587-44d5-9bd9-890a1ff8ed26', 'b5c2d5dc-e753-40ff-8e01-95c4c497710c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('82e45270-b5a0-478e-a81f-8330e638921c', 'd29345bb-fd48-4f69-ac81-eeedd4b41e6d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('94ad1072-16af-401b-b32f-3c33fb07c22b', '813055ff-e5b7-4538-825c-eb721360e189', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('a51cd72a-1d76-4367-84ee-5fd448eec673', '6c9bbb36-0b2d-49ea-9c1b-63209f009773', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('9ac32e07-bde6-4d8d-b172-2cf8c28b3cb6', '8daf8ed9-d15f-407f-b827-3a9c01907e62', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('6a89fb69-f4f9-409e-8687-1927a9c9931a', 'bd028538-520d-45cf-a6f7-27c9f675d663', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e08417c1-a015-4820-bab2-0e3bafda8c81', 'de1511df-f963-4ff6-8e3f-2225ba493879', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('27b913c1-b6c1-41a8-ac80-b8aec18dfdcb', '26168044-3b5c-43f6-a757-833ba1485d41', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('d3a33394-f4dc-4e3e-b817-eb6309b17b59', 'e742724a-0ff6-4760-884b-866835460045', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('3e6f2e70-0113-42ae-beaf-f2fed10d70f2', '3ce354c8-bcac-44c6-9a94-5274e5f9b389', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('207b92d0-39d0-4f81-b00d-9119d7a20626', '188fa4e3-985c-4048-8913-754cb0560875', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e0ece833-4e73-4ada-b7d2-25197cf9097d', 'caa05325-b8e7-4b1c-9d95-03d4e0c7372d', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('e58e13b1-cc53-40da-8ecf-3fa4c2c97672', '128ac756-bdb8-49d7-8fdb-cdb8fd241d06', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('719b10c0-2de5-43a3-b9e4-d38730a92ad2', '7f3bb769-4d7e-4ca9-8527-708da0368be5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('66570bed-28ce-407d-a5df-43236bba500a', '6a565d40-9012-4c89-878c-05bb8b6e2d81', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('f9cb12f8-523f-47c9-b3f3-ac55aac11502', '7ede7b67-42d2-44cd-a530-66f62a68cd54', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('026b8072-f628-4bda-aeda-ce76d2fbccce', '2df90a89-455e-4637-8b96-ad01c45f5701', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('713232b8-54d8-49b8-bcc5-9fabb3bd5c76', '43560c6b-fda2-4b45-bc0b-7dbbbffff05c', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('4223a8af-8011-4234-b71d-520c3f7b23b0', 'ff4760c8-5359-43e9-9b42-95a0bf3e3d36', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('b54503c1-f3fd-411d-9830-882aa2c54dc2', 'a4c43698-c276-4430-b15e-8373b7bbb662', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('a657e586-ce3a-4c5c-b244-f704c13fff5e', '30462f07-1c6b-4706-b4fb-288845b3631e', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('ce3898e2-403d-4fc1-8bfd-eb78be76b86a', '4f5170f2-1d35-4130-a535-1d93383e406b', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('28dac481-b80a-47c3-9de4-693f2774c470', 'c0aecfcc-3b2f-4117-9f20-e0920df97dc0', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('6732ab00-bca6-4a4b-b9ac-34adfb108299', '3dfcdc9d-de8a-45b3-a05f-b83b51097ef5', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('ef81f3a4-9995-4c4c-b2b0-927235077174', 'bb74b280-db90-4240-ab09-b8c6cf63d553', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-11-29 13:33:04.23263+00', '2025-11-29 13:33:04.23263+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('b3830215-9623-46a6-9624-2c7183f13737', 'cccccccc-cccc-cccc-cccc-cccccccccccc', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 5, 1825, 100, 'Halach Uinic', 0.00, 1625, 180, 0, 0, NULL, 0, 0, NULL, 0, 8, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, '2025-12-02 10:55:24.674923+00', NULL, '{}', '2025-11-29 13:26:50.458823+00', '2025-12-02 10:55:24.674923+00'); +INSERT INTO gamification_system.user_stats (id, user_id, tenant_id, level, total_xp, xp_to_next_level, current_rank, rank_progress, ml_coins, ml_coins_earned_total, ml_coins_spent_total, ml_coins_earned_today, last_ml_coins_reset, current_streak, max_streak, streak_started_at, days_active_total, exercises_completed, modules_completed, total_score, average_score, perfect_scores, achievements_earned, certificates_earned, total_time_spent, weekly_time_spent, sessions_count, weekly_xp, monthly_xp, weekly_exercises, global_rank_position, class_rank_position, school_rank_position, last_activity_at, last_login_at, metadata, created_at, updated_at) VALUES ('5bc3c779-e7f4-4c4a-9bca-c0854ea1e288', 'f929d6df-8c29-461f-88f5-264facd879e9', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 1, 0, 100, 'Ajaw', 0.00, 100, 100, 0, 0, NULL, 0, 0, NULL, 0, 0, 0, 0, NULL, 0, 0, 0, '00:00:00', '00:00:00', 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, '{}', '2025-12-17 17:51:43.536295+00', '2025-12-17 17:51:43.536295+00'); + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict kteh5aijQdsTMjnzB1xbEvkDPawhbikecDwbwClvcxgOHhbchFJp4xxlp3L10vn + diff --git a/projects/gamilit/apps/database/create-database.sh b/projects/gamilit/apps/database/create-database.sh index 4dc4927..2f27885 100755 --- a/projects/gamilit/apps/database/create-database.sh +++ b/projects/gamilit/apps/database/create-database.sh @@ -410,6 +410,7 @@ execute_sql_files "$DDL_DIR/schemas/communication/functions" "*.sql" "Funciones execute_sql_files "$DDL_DIR/schemas/communication/triggers" "*.sql" "Triggers de comunicación (si existen)" execute_sql_files "$DDL_DIR/schemas/communication/indexes" "*.sql" "Índices de comunicación (si existen)" execute_sql_files "$DDL_DIR/schemas/communication/views" "*.sql" "Vistas de comunicación (si existen)" +execute_sql_files "$DDL_DIR/schemas/communication/rls-policies" "*.sql" "RLS Policies de comunicación (P0-002 AUDIT-DB-001)" log_success "FASE 10.5 completada - Communication schema creado (DB-122)" log "" @@ -531,6 +532,7 @@ execute_sql "$SEEDS_DIR/notifications/01-notification_templates.sql" "Seeds: not # 16.2: Auth Management (tenants y auth_providers) execute_sql "$SEEDS_DIR/auth_management/01-tenants.sql" "Seeds: tenants" +execute_sql "$SEEDS_DIR/auth_management/02-tenants-production.sql" "Seeds: tenants-production (13 tenants usuarios reales)" execute_sql "$SEEDS_DIR/auth_management/02-auth_providers.sql" "Seeds: auth_providers" # 16.3: Auth (usuarios de testing y demo) @@ -541,18 +543,36 @@ execute_sql "$SEEDS_DIR/auth/02-production-users.sql" "Seeds: users (production # REASON: initialize_user_stats() trigger needs modules to exist when creating module_progress execute_sql "$SEEDS_DIR/educational_content/01-modules.sql" "Seeds: modules (5)" +# 16.4.1: Educational Content (dependencias y taxonomías) - P0-SEEDS AUDIT-DB-001 +execute_sql "$SEEDS_DIR/educational_content/11-module_dependencies.sql" "Seeds: module_dependencies (6 dependencias - P0 AUDIT-DB-001)" +execute_sql "$SEEDS_DIR/educational_content/12-taxonomies.sql" "Seeds: taxonomies (4 taxonomías - P0 AUDIT-DB-001)" + # 16.5: Auth Management (profiles para usuarios) # NOTE: Trigger initialize_user_stats() fires here and creates module_progress automatically execute_sql "$SEEDS_DIR/auth_management/04-profiles-complete.sql" "Seeds: profiles (testing + demo - 22)" +# DEPRECATED: 05-profiles-demo.sql movido a _deprecated/ - requiere auth.users que no existen execute_sql "$SEEDS_DIR/auth_management/06-profiles-production.sql" "Seeds: profiles (production - 13 usuarios)" +# 16.5.0.1: Auth Management (roles de usuarios) - P0-SEEDS AUDIT-DB-001 +# MUST BE AFTER profiles (FK user_id references profiles) +execute_sql "$SEEDS_DIR/auth_management/07-user_roles.sql" "Seeds: user_roles (8 roles - P0 AUDIT-DB-001)" + # 16.5.1: Content Management (templates de contenido) execute_sql "$SEEDS_DIR/content_management/01-default-templates.sql" "Seeds: content_templates" +# 16.5.1.1: Content Management (contenido Marie Curie) - P0-SEEDS AUDIT-DB-001 +execute_sql "$SEEDS_DIR/content_management/02-marie_curie_content.sql" "Seeds: marie_curie_content (6 artículos - P0 AUDIT-DB-001)" + # 16.5.2: Social Features (escuelas, aulas y miembros) +execute_sql "$SEEDS_DIR/social_features/00-schools-default.sql" "Seeds: schools (sistema - default)" execute_sql "$SEEDS_DIR/social_features/01-schools.sql" "Seeds: schools (demo)" execute_sql "$SEEDS_DIR/social_features/02-classrooms.sql" "Seeds: classrooms (demo)" execute_sql "$SEEDS_DIR/social_features/03-classroom-members.sql" "Seeds: classroom_members (demo)" +execute_sql "$SEEDS_DIR/social_features/04-friendships.sql" "Seeds: friendships (10 amistades + 3 pending)" + +# 16.5.3: Auth Management (asignación de escuela a admins) - 2025-12-15 +# MUST BE AFTER profiles AND schools +execute_sql "$SEEDS_DIR/auth_management/08-assign-admin-schools.sql" "Seeds: assign admin schools (2025-12-15)" # 16.6: Educational Content (ejercicios) execute_sql "$SEEDS_DIR/educational_content/02-exercises-module1.sql" "Seeds: Module 1 - Literal (5 exercises)" @@ -583,6 +603,10 @@ execute_sql "$SEEDS_DIR/gamification_system/01-achievement_categories.sql" "Seed execute_sql "$SEEDS_DIR/gamification_system/02-leaderboard_metadata.sql" "Seeds: leaderboard_metadata" execute_sql "$SEEDS_DIR/gamification_system/03-maya_ranks.sql" "Seeds: maya_ranks" execute_sql "$SEEDS_DIR/gamification_system/04-achievements.sql" "Seeds: achievements (30 logros demo - 2025-11-29 updated)" + +# 16.6.0.1: Mission Templates - P0-SEEDS AUDIT-DB-001 +execute_sql "$SEEDS_DIR/gamification_system/10-mission_templates.sql" "Seeds: mission_templates (11 templates - P0 AUDIT-DB-001)" +execute_sql "$SEEDS_DIR/gamification_system/11-missions-production-users.sql" "Seeds: missions-production (8 misiones por usuario prod)" execute_sql "$SEEDS_DIR/gamification_system/12-shop_categories.sql" "Seeds: shop_categories (5 categorías - 2025-11-29 NEW)" execute_sql "$SEEDS_DIR/gamification_system/13-shop_items.sql" "Seeds: shop_items (20 items - 2025-11-29 NEW)" execute_sql "$SEEDS_DIR/gamification_system/05-user_stats.sql" "Seeds: user_stats" diff --git a/projects/gamilit/apps/database/ddl/00-prerequisites.sql b/projects/gamilit/apps/database/ddl/00-prerequisites.sql index 6bf3a61..7dcb814 100644 --- a/projects/gamilit/apps/database/ddl/00-prerequisites.sql +++ b/projects/gamilit/apps/database/ddl/00-prerequisites.sql @@ -2,34 +2,33 @@ -- GLIT Platform - Prerequisites (ENUMs y Funciones Base) -- Descripción: Todos los tipos y funciones que deben existir ANTES de crear tablas -- Creado: 2025-11-02 --- Actualizado: 2025-11-11 (DB-111 - Agregados roles Supabase) +-- Actualizado: 2025-11-11 (DB-111 - Agregados roles estándar RLS) -- ============================================================================ -- ============================================================================ --- ROLES DE SUPABASE (para compatibilidad local) +-- ROLES ESTÁNDAR PARA RLS (Row Level Security) -- ============================================================================ --- Nota: Estos roles existen por defecto en Supabase Cloud pero no en PostgreSQL local. --- Se crean condicionalmente para que RLS policies funcionen en ambos ambientes. --- Refs: https://supabase.com/docs/guides/database/postgres/roles +-- Nota: Estos roles son patrón estándar de la industria para RLS en PostgreSQL. +-- Se crean condicionalmente para que RLS policies funcionen correctamente. DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'authenticated') THEN CREATE ROLE authenticated; - COMMENT ON ROLE authenticated IS 'Supabase role: usuarios autenticados (cualquier rol GAMILIT)'; + COMMENT ON ROLE authenticated IS 'Rol RLS: usuarios autenticados (cualquier rol GAMILIT)'; END IF; END $$; DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'anon') THEN CREATE ROLE anon; - COMMENT ON ROLE anon IS 'Supabase role: usuarios anónimos (sin autenticar)'; + COMMENT ON ROLE anon IS 'Rol RLS: usuarios anónimos (sin autenticar)'; END IF; END $$; DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'service_role') THEN CREATE ROLE service_role; - COMMENT ON ROLE service_role IS 'Supabase role: servicio backend con privilegios elevados'; + COMMENT ON ROLE service_role IS 'Rol RLS: servicio backend con privilegios elevados'; END IF; END $$; @@ -111,8 +110,19 @@ EXCEPTION WHEN duplicate_object THEN null; END $$; -- 📚 Documentación: gamification_system.achievement_category -- Requerimiento: docs/01-requerimientos/02-gamificacion/RF-GAM-001-achievements.md -- Especificación: docs/02-especificaciones-tecnicas/02-gamificacion/ET-GAM-001-achievements.md +-- VERSIÓN: 1.1 (2025-12-15) - Agregados 'collection' y 'hidden' para alineación con Frontend DO $$ BEGIN - CREATE TYPE gamification_system.achievement_category AS ENUM ('progress', 'streak', 'completion', 'social', 'special', 'mastery', 'exploration'); + CREATE TYPE gamification_system.achievement_category AS ENUM ( + 'progress', -- Logros de progreso general + 'streak', -- Logros de rachas consecutivas + 'completion', -- Logros de completar contenido + 'social', -- Logros sociales (amigos, grupos) + 'special', -- Logros especiales/eventos + 'mastery', -- Logros de maestría/dominio + 'exploration', -- Logros de exploración + 'collection', -- Logros de colección (V1.1) + 'hidden' -- Logros ocultos/secretos (V1.1) + ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- 📚 Documentación: gamification_system.achievement_type @@ -182,18 +192,19 @@ DO $$ BEGIN -- Roadmap: docs/04-fase-backlog/ -- Fecha: Movido a backlog 2025-11-19 (DB-126) - -- Module 4: Lectura Digital (5 mecánicas) ⚠️ BACKLOG + -- Module 4: Lectura Digital (9 mecánicas) ⚠️ BACKLOG -- Requieren: Validación de fuentes, análisis de imágenes, IA multimodal 'analisis_memes', 'infografia_interactiva', 'navegacion_hipertextual', 'quiz_tiktok', 'verificador_fake_news', + -- Module 4: Producción Textual Digital (4 mecánicas adicionales) - AGREGADO 2025-12-18 + 'chat_literario', 'email_formal', 'ensayo_argumentativo', 'resena_critica', -- Module 5: Producción Lectora (3 mecánicas) ⚠️ BACKLOG -- Requieren: Rúbricas de evaluación creativa, revisión humana/IA 'comic_digital', 'diario_multimedia', 'video_carta' -- ==================================================================== - -- REMOVIDO 2025-11-17: Mecánicas no implementadas movidas a comentarios + -- ACTUALIZADO 2025-12-18: Agregados 4 tipos M4 previamente comentados -- ==================================================================== - -- Futuros Módulo 4: 'resena_critica', 'chat_literario', 'email_formal', 'ensayo_argumentativo' -- Auxiliares potenciales: 'comprension_auditiva', 'collage_prensa', 'texto_movimiento', 'call_to_action' ); EXCEPTION WHEN duplicate_object THEN null; END $$; diff --git a/projects/gamilit/apps/database/ddl/schemas/auth/_MAP.md b/projects/gamilit/apps/database/ddl/schemas/auth/_MAP.md index 54d9084..5e1b991 100644 --- a/projects/gamilit/apps/database/ddl/schemas/auth/_MAP.md +++ b/projects/gamilit/apps/database/ddl/schemas/auth/_MAP.md @@ -1,6 +1,6 @@ # Schema: auth -Extensión del sistema de autenticación de Supabase +Schema base de autenticación (patrón estándar de la industria) ## Estructura diff --git a/projects/gamilit/apps/database/ddl/schemas/auth/tables/01-users.sql b/projects/gamilit/apps/database/ddl/schemas/auth/tables/01-users.sql index 5db25c1..4409101 100644 --- a/projects/gamilit/apps/database/ddl/schemas/auth/tables/01-users.sql +++ b/projects/gamilit/apps/database/ddl/schemas/auth/tables/01-users.sql @@ -15,7 +15,7 @@ SET search_path TO auth, public; DROP TABLE IF EXISTS auth.users CASCADE; CREATE TABLE auth.users ( - -- Core Supabase-compatible columns + -- Core authentication columns (patrón estándar de la industria) instance_id uuid, id uuid DEFAULT gen_random_uuid() NOT NULL, aud varchar(255) DEFAULT 'authenticated', diff --git a/projects/gamilit/apps/database/ddl/schemas/auth_management/rls-policies/02-enable-rls.sql b/projects/gamilit/apps/database/ddl/schemas/auth_management/rls-policies/02-enable-rls.sql new file mode 100644 index 0000000..e8d4bee --- /dev/null +++ b/projects/gamilit/apps/database/ddl/schemas/auth_management/rls-policies/02-enable-rls.sql @@ -0,0 +1,117 @@ +-- ===================================================== +-- Enable RLS for auth_management schema +-- Description: Habilita Row Level Security en tablas críticas +-- Created: 2025-12-14 +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- ===================================================== +-- +-- IMPORTANTE: Las políticas están definidas en 01-policies.sql +-- Este archivo SOLO habilita RLS en las tablas. +-- +-- Sin RLS habilitado, las políticas NO se aplican y cualquier +-- usuario puede acceder a todos los datos. +-- ===================================================== + +-- ===================================================== +-- TABLE: auth_management.profiles +-- Description: Perfiles de usuario (109 FKs dependen de esta tabla) +-- Risk: CRÍTICO - Datos personales expuestos +-- ===================================================== +ALTER TABLE auth_management.profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.profiles FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.user_sessions +-- Description: Sesiones activas de usuario +-- Risk: ALTO - Información de sesiones expuesta +-- ===================================================== +ALTER TABLE auth_management.user_sessions ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.user_sessions FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.email_verification_tokens +-- Description: Tokens de verificación de email +-- Risk: ALTO - Tokens sensibles expuestos +-- ===================================================== +ALTER TABLE auth_management.email_verification_tokens ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.email_verification_tokens FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.password_reset_tokens +-- Description: Tokens de reset de contraseña +-- Risk: CRÍTICO - Tokens de reset expuestos +-- ===================================================== +ALTER TABLE auth_management.password_reset_tokens ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.password_reset_tokens FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.user_preferences +-- Description: Preferencias de usuario +-- Risk: MEDIO - Preferencias personales expuestas +-- ===================================================== +ALTER TABLE auth_management.user_preferences ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.user_preferences FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.memberships +-- Description: Membresías de usuario en tenants +-- Risk: MEDIO - Información de membresía expuesta +-- ===================================================== +ALTER TABLE auth_management.memberships ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.memberships FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.user_suspensions +-- Description: Suspensiones de usuario +-- Risk: ALTO - Información de suspensiones expuesta +-- ===================================================== +ALTER TABLE auth_management.user_suspensions ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.user_suspensions FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.security_events +-- Description: Eventos de seguridad +-- Risk: ALTO - Log de eventos de seguridad expuesto +-- ===================================================== +ALTER TABLE auth_management.security_events ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.security_events FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.tenants +-- Description: Información de tenants +-- Risk: MEDIO - Datos de organización expuestos +-- ===================================================== +ALTER TABLE auth_management.tenants ENABLE ROW LEVEL SECURITY; +ALTER TABLE auth_management.tenants FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- TABLE: auth_management.user_roles (si existe tabla física) +-- NOTE: Verificar si user_roles existe como tabla o es derivada +-- ===================================================== +-- ALTER TABLE auth_management.user_roles ENABLE ROW LEVEL SECURITY; +-- ALTER TABLE auth_management.user_roles FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- SUMMARY +-- ===================================================== +-- Tablas con RLS habilitado: 9 +-- - profiles (CRÍTICO) +-- - user_sessions (ALTO) +-- - email_verification_tokens (ALTO) +-- - password_reset_tokens (CRÍTICO) +-- - user_preferences (MEDIO) +-- - memberships (MEDIO) +-- - user_suspensions (ALTO) +-- - security_events (ALTO) +-- - tenants (MEDIO) +-- +-- Tablas sin RLS (por diseño): +-- - auth_attempts (sistema-only, SECURITY DEFINER) +-- - roles (catálogo estático) +-- - auth_providers (catálogo estático) +-- ===================================================== + +-- Verificación (ejecutar después) +-- SELECT schemaname, tablename, rowsecurity +-- FROM pg_tables +-- WHERE schemaname = 'auth_management'; diff --git a/projects/gamilit/apps/database/ddl/schemas/auth_management/triggers/03b-trg_ensure_profile_name.sql b/projects/gamilit/apps/database/ddl/schemas/auth_management/triggers/03b-trg_ensure_profile_name.sql new file mode 100644 index 0000000..9c5407c --- /dev/null +++ b/projects/gamilit/apps/database/ddl/schemas/auth_management/triggers/03b-trg_ensure_profile_name.sql @@ -0,0 +1,82 @@ +-- ===================================================== +-- Trigger: trg_ensure_profile_name +-- Table: auth_management.profiles +-- Function: ensure_profile_name +-- Event: BEFORE INSERT OR UPDATE +-- Level: FOR EACH ROW +-- Description: Asegura que first_name, last_name y full_name tengan valores, +-- extrayendo del email si es necesario +-- Created: 2025-12-18 +-- ===================================================== + +-- Función que extrae nombre del email si no se proporciona +CREATE OR REPLACE FUNCTION auth_management.ensure_profile_name() +RETURNS TRIGGER AS $$ +DECLARE + email_prefix TEXT; + extracted_name TEXT; +BEGIN + -- Si first_name está vacío o es NULL, extraer del email + IF NEW.first_name IS NULL OR TRIM(NEW.first_name) = '' THEN + -- Obtener parte antes del @ + email_prefix := SPLIT_PART(NEW.email, '@', 1); + + -- Limpiar: quitar números, reemplazar puntos/guiones por espacios, capitalizar + extracted_name := INITCAP( + TRIM( + REGEXP_REPLACE( + REGEXP_REPLACE( + email_prefix, + '[0-9]+', '', 'g' -- Quitar números + ), + '[._-]+', ' ', 'g' -- Puntos/guiones a espacios + ) + ) + ); + + -- Si después de limpiar queda vacío, usar 'Usuario' + IF extracted_name = '' THEN + extracted_name := 'Usuario'; + END IF; + + NEW.first_name := extracted_name; + END IF; + + -- Si last_name está vacío o es NULL, poner valor por defecto + IF NEW.last_name IS NULL OR TRIM(NEW.last_name) = '' THEN + NEW.last_name := 'Usuario'; + END IF; + + -- Siempre computar full_name como concatenación de first_name + last_name + NEW.full_name := TRIM(COALESCE(NEW.first_name, '') || ' ' || COALESCE(NEW.last_name, '')); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Comentario de la función +COMMENT ON FUNCTION auth_management.ensure_profile_name() IS +'Asegura first_name/last_name/full_name tengan valores. Extrae del email si es necesario. Previene "Unknown Student" en la UI.'; + +-- Eliminar trigger si existe +DROP TRIGGER IF EXISTS trg_ensure_profile_name ON auth_management.profiles CASCADE; + +-- Crear trigger BEFORE INSERT para asegurar nombres +CREATE TRIGGER trg_ensure_profile_name + BEFORE INSERT ON auth_management.profiles + FOR EACH ROW + EXECUTE FUNCTION auth_management.ensure_profile_name(); + +-- También aplicar en UPDATE para correcciones +DROP TRIGGER IF EXISTS trg_ensure_profile_name_update ON auth_management.profiles CASCADE; + +CREATE TRIGGER trg_ensure_profile_name_update + BEFORE UPDATE ON auth_management.profiles + FOR EACH ROW + WHEN (NEW.first_name IS DISTINCT FROM OLD.first_name + OR NEW.last_name IS DISTINCT FROM OLD.last_name + OR NEW.full_name IS DISTINCT FROM OLD.full_name) + EXECUTE FUNCTION auth_management.ensure_profile_name(); + +-- Mensaje de confirmación +DO $$ BEGIN RAISE NOTICE 'Trigger trg_ensure_profile_name created successfully'; END $$; diff --git a/projects/gamilit/apps/database/ddl/schemas/communication/rls-policies/01-messages-policies.sql b/projects/gamilit/apps/database/ddl/schemas/communication/rls-policies/01-messages-policies.sql new file mode 100644 index 0000000..85d71eb --- /dev/null +++ b/projects/gamilit/apps/database/ddl/schemas/communication/rls-policies/01-messages-policies.sql @@ -0,0 +1,158 @@ +-- ===================================================== +-- RLS Policies for communication.messages +-- Description: Políticas de seguridad para mensajes privados +-- Created: 2025-12-14 +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- ===================================================== +-- +-- Security Strategy: +-- - Users can only see messages where they are sender or recipient +-- - Users can only see classroom messages if they are members +-- - Moderation access for admins +-- - Soft-delete respects ownership +-- ===================================================== + +-- ===================================================== +-- Enable RLS +-- ===================================================== +ALTER TABLE communication.messages ENABLE ROW LEVEL SECURITY; +ALTER TABLE communication.messages FORCE ROW LEVEL SECURITY; + +-- ===================================================== +-- Drop existing policies (if any) +-- ===================================================== +DROP POLICY IF EXISTS messages_select_own ON communication.messages; +DROP POLICY IF EXISTS messages_select_classroom ON communication.messages; +DROP POLICY IF EXISTS messages_select_admin ON communication.messages; +DROP POLICY IF EXISTS messages_insert_own ON communication.messages; +DROP POLICY IF EXISTS messages_update_own ON communication.messages; +DROP POLICY IF EXISTS messages_delete_own ON communication.messages; + +-- ===================================================== +-- SELECT Policies +-- ===================================================== + +-- Policy: messages_select_own +-- Purpose: Users can read messages where they are sender or recipient +CREATE POLICY messages_select_own + ON communication.messages + AS PERMISSIVE + FOR SELECT + TO public + USING ( + sender_id = current_setting('app.current_user_id', true)::uuid + OR recipient_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY messages_select_own ON communication.messages IS + 'Permite a los usuarios ver mensajes donde son remitente o destinatario'; + +-- Policy: messages_select_classroom +-- Purpose: Users can read classroom messages if they are members +CREATE POLICY messages_select_classroom + ON communication.messages + AS PERMISSIVE + FOR SELECT + TO public + USING ( + classroom_id IS NOT NULL + AND EXISTS ( + SELECT 1 FROM social_features.classroom_members cm + WHERE cm.classroom_id = communication.messages.classroom_id + AND cm.student_id = current_setting('app.current_user_id', true)::uuid + ) + ); + +COMMENT ON POLICY messages_select_classroom ON communication.messages IS + 'Permite a los miembros del aula ver mensajes del aula'; + +-- Policy: messages_select_admin +-- Purpose: Admins can read all messages for moderation +CREATE POLICY messages_select_admin + ON communication.messages + AS PERMISSIVE + FOR SELECT + TO public + USING ( + EXISTS ( + SELECT 1 FROM auth_management.profiles p + WHERE p.id = current_setting('app.current_user_id', true)::uuid + AND p.role IN ('super_admin', 'admin_teacher') + ) + ); + +COMMENT ON POLICY messages_select_admin ON communication.messages IS + 'Permite a los administradores ver todos los mensajes (moderación)'; + +-- ===================================================== +-- INSERT Policies +-- ===================================================== + +-- Policy: messages_insert_own +-- Purpose: Users can send messages as themselves +CREATE POLICY messages_insert_own + ON communication.messages + AS PERMISSIVE + FOR INSERT + TO public + WITH CHECK ( + sender_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY messages_insert_own ON communication.messages IS + 'Los usuarios solo pueden enviar mensajes como ellos mismos'; + +-- ===================================================== +-- UPDATE Policies +-- ===================================================== + +-- Policy: messages_update_own +-- Purpose: Users can update/edit their own messages +CREATE POLICY messages_update_own + ON communication.messages + AS PERMISSIVE + FOR UPDATE + TO public + USING ( + sender_id = current_setting('app.current_user_id', true)::uuid + ) + WITH CHECK ( + sender_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY messages_update_own ON communication.messages IS + 'Los usuarios solo pueden editar sus propios mensajes'; + +-- ===================================================== +-- DELETE Policies +-- ===================================================== + +-- Policy: messages_delete_own +-- Purpose: Users can soft-delete their own messages +CREATE POLICY messages_delete_own + ON communication.messages + AS PERMISSIVE + FOR DELETE + TO public + USING ( + sender_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY messages_delete_own ON communication.messages IS + 'Los usuarios solo pueden eliminar sus propios mensajes'; + +-- ===================================================== +-- SUMMARY +-- ===================================================== +-- Total policies: 6 +-- - SELECT: 3 (own, classroom members, admin) +-- - INSERT: 1 (only as self) +-- - UPDATE: 1 (only own messages) +-- - DELETE: 1 (only own messages) +-- +-- Security enforced: +-- - Users cannot read other users' direct messages +-- - Users cannot read classroom messages unless members +-- - Users cannot impersonate others (sender_id check) +-- - Admins have moderation access (audit trail preserved) +-- ===================================================== diff --git a/projects/gamilit/apps/database/ddl/schemas/educational_content/functions/14-validate_rueda_inferencias.sql b/projects/gamilit/apps/database/ddl/schemas/educational_content/functions/14-validate_rueda_inferencias.sql index 6288978..e55a767 100644 --- a/projects/gamilit/apps/database/ddl/schemas/educational_content/functions/14-validate_rueda_inferencias.sql +++ b/projects/gamilit/apps/database/ddl/schemas/educational_content/functions/14-validate_rueda_inferencias.sql @@ -176,6 +176,7 @@ COMMENT ON FUNCTION educational_content.validate_rueda_inferencias_text IS -- ============================================================================ -- FUNCIÓN: validate_rueda_inferencias (WRAPPER ESTÁNDAR) -- Descripción: Wrapper con firma estándar para integración con el sistema +-- FIX 2025-12-15: Soporte para estructura categoryExpectations del seed -- ============================================================================ CREATE OR REPLACE FUNCTION educational_content.validate_rueda_inferencias( @@ -200,11 +201,14 @@ DECLARE v_max_length INTEGER; v_fragments_solution JSONB; v_fragments_submitted JSONB; + v_fragment_states JSONB; v_fragment_id TEXT; v_user_text TEXT; v_fragment_solution JSONB; v_keywords JSONB; v_fragment_points INTEGER; + v_category_id TEXT; + v_fragment_state JSONB; v_validation_result JSONB; v_total_fragments INTEGER := 0; v_valid_fragments INTEGER := 0; @@ -219,6 +223,7 @@ BEGIN END IF; v_fragments_submitted := p_submitted_answer->'fragments'; + v_fragment_states := p_submitted_answer->'fragmentStates'; IF v_fragments_submitted IS NULL THEN RAISE EXCEPTION 'Invalid submitted_answer format: missing "fragments" object'; @@ -259,11 +264,66 @@ BEGIN CONTINUE; END IF; - -- Extraer keywords y puntos del fragmento - v_keywords := v_fragment_solution->'keywords'; - v_fragment_points := COALESCE((v_fragment_solution->>'points')::INTEGER, 20); + -- ==================================================================== + -- FIX 2025-12-15: Soporte para estructura categoryExpectations + -- El seed tiene: { categoryExpectations: { "cat-literal": { keywords, points }, ... } } + -- En lugar de: { keywords, points } directamente en el fragment + -- ==================================================================== + IF v_fragment_solution ? 'categoryExpectations' THEN + -- Nueva estructura con categorías + -- Buscar categoryId en fragmentStates para este fragment + v_category_id := 'cat-literal'; -- default fallback + + IF v_fragment_states IS NOT NULL THEN + SELECT state INTO v_fragment_state + FROM jsonb_array_elements(v_fragment_states) AS state + WHERE state->>'fragmentId' = v_fragment_id; + + IF v_fragment_state IS NOT NULL THEN + v_category_id := COALESCE(v_fragment_state->>'categoryId', 'cat-literal'); + END IF; + END IF; + + -- Extraer keywords y points de categoryExpectations[categoryId] + v_keywords := v_fragment_solution->'categoryExpectations'->v_category_id->'keywords'; + v_fragment_points := COALESCE( + (v_fragment_solution->'categoryExpectations'->v_category_id->>'points')::INTEGER, + 20 + ); + + -- Si la categoría no existe, usar fallback a cat-literal + IF v_keywords IS NULL THEN + v_keywords := v_fragment_solution->'categoryExpectations'->'cat-literal'->'keywords'; + v_fragment_points := COALESCE( + (v_fragment_solution->'categoryExpectations'->'cat-literal'->>'points')::INTEGER, + 20 + ); + v_category_id := 'cat-literal (fallback)'; + END IF; + ELSE + -- Estructura flat legacy + v_keywords := v_fragment_solution->'keywords'; + v_fragment_points := COALESCE((v_fragment_solution->>'points')::INTEGER, 20); + v_category_id := 'flat'; + END IF; + -- ==================================================================== + -- END FIX 2025-12-15 + -- ==================================================================== + v_total_points := v_total_points + v_fragment_points; + -- Validar que v_keywords no sea NULL + IF v_keywords IS NULL THEN + v_results := v_results || jsonb_build_object( + 'fragment_id', v_fragment_id, + 'is_valid', false, + 'error', format('Keywords not found for fragment %s (category: %s)', v_fragment_id, v_category_id), + 'points', 0, + 'category_used', v_category_id + ); + CONTINUE; + END IF; + -- Validar el fragmento usando la función auxiliar v_validation_result := educational_content._validate_single_fragment( v_keywords, @@ -284,10 +344,12 @@ BEGIN -- Agregar resultado del fragmento al detalle v_results := v_results || jsonb_build_object( 'fragment_id', v_fragment_id, + 'category_used', v_category_id, 'is_valid', v_validation_result->'is_valid', 'matched_keywords', v_validation_result->'matched_keywords', 'keyword_count', v_validation_result->'keyword_count', 'points', v_validation_result->'points', + 'max_points', v_fragment_points, 'feedback', v_validation_result->'feedback' ); END LOOP; @@ -307,16 +369,16 @@ BEGIN END; END IF; - -- 5. Determinar si es correcto - is_correct := (v_valid_fragments = v_total_fragments); + -- 5. Determinar si es correcto (al menos passing_score, normalmente 70%) + is_correct := (score >= 70); -- 6. Generar feedback - IF is_correct THEN + IF v_valid_fragments = v_total_fragments THEN feedback := format('¡Excelente! Todas las %s inferencias son válidas. Has demostrado comprensión profunda del texto.', v_total_fragments); ELSIF v_valid_fragments > 0 THEN - feedback := format('%s de %s inferencias válidas. Revisa los fragmentos marcados para mejorar tu respuesta.', - v_valid_fragments, v_total_fragments); + feedback := format('%s de %s inferencias válidas (Puntuación: %s%%). Revisa los fragmentos marcados para mejorar tu respuesta.', + v_valid_fragments, v_total_fragments, score); ELSE feedback := format('Ninguna inferencia válida. Asegúrate de incluir conceptos clave del texto y cumplir con la longitud requerida (%s-%s caracteres).', v_min_length, v_max_length); @@ -328,11 +390,7 @@ BEGIN 'valid_fragments', v_valid_fragments, 'total_points_possible', v_total_points, 'points_earned', v_accumulated_score, - 'percentage', CASE - WHEN v_total_fragments > 0 - THEN ROUND((v_valid_fragments::NUMERIC / v_total_fragments) * 100) - ELSE 0 - END, + 'percentage', score, 'validation_criteria', jsonb_build_object( 'min_keywords', v_min_keywords, 'min_length', v_min_length, @@ -344,4 +402,9 @@ END; $$; COMMENT ON FUNCTION educational_content.validate_rueda_inferencias IS -'Wrapper estándar para validación de rueda de inferencias. Recibe formato { fragments: { "frag-1": "texto...", ... } } y valida cada fragmento acumulando puntos.'; +'Wrapper estándar para validación de rueda de inferencias. +Soporta DOS estructuras de solución: +1. NUEVA (categoryExpectations): { fragments: [{ id, categoryExpectations: { cat-xxx: { keywords, points } } }] } +2. LEGACY (flat): { fragments: [{ id, keywords, points }] } +El frontend envía fragmentStates con categoryId para indicar qué categoría usó el usuario. +FIX 2025-12-15: Corregido para manejar estructura categoryExpectations del seed.'; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/06-update_missions_updated_at.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/06-update_missions_updated_at.sql index e6e7426..9eb3cd6 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/06-update_missions_updated_at.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/06-update_missions_updated_at.sql @@ -1,9 +1,17 @@ -- ===================================================== -- Function: gamification_system.update_missions_updated_at --- Description: No description available +-- Description: DEPRECATED - Usar gamilit.update_updated_at_column() -- Parameters: None -- Returns: trigger -- Created: 2025-10-27 +-- Modified: 2025-12-14 (P0-DUP Auditoría AUDIT-DB-001) +-- ===================================================== +-- +-- DEPRECATED: Esta función es redundante. +-- Todos los triggers deben usar gamilit.update_updated_at_column() +-- para garantizar consistencia de timezone (gamilit.now_mexico()) +-- +-- Cambio: NOW() → gamilit.now_mexico() para consistencia de timezone -- ===================================================== CREATE OR REPLACE FUNCTION gamification_system.update_missions_updated_at() @@ -11,7 +19,11 @@ CREATE OR REPLACE FUNCTION gamification_system.update_missions_updated_at() LANGUAGE plpgsql AS $function$ BEGIN - NEW.updated_at = NOW(); + -- P0-DUP: Corregido para usar timezone México (antes usaba NOW()) + NEW.updated_at = gamilit.now_mexico(); RETURN NEW; END; $function$; + +COMMENT ON FUNCTION gamification_system.update_missions_updated_at() IS + 'DEPRECATED: Usar gamilit.update_updated_at_column(). Corregido 2025-12-14 para usar gamilit.now_mexico()'; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/07-update_notifications_updated_at.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/07-update_notifications_updated_at.sql index 4a5f4a1..144976c 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/07-update_notifications_updated_at.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/07-update_notifications_updated_at.sql @@ -1,9 +1,17 @@ -- ===================================================== -- Function: gamification_system.update_notifications_updated_at --- Description: No description available +-- Description: DEPRECATED - Usar gamilit.update_updated_at_column() -- Parameters: None -- Returns: trigger -- Created: 2025-10-27 +-- Modified: 2025-12-14 (P0-DUP Auditoría AUDIT-DB-001) +-- ===================================================== +-- +-- DEPRECATED: Esta función es redundante. +-- Todos los triggers deben usar gamilit.update_updated_at_column() +-- para garantizar consistencia de timezone (gamilit.now_mexico()) +-- +-- Cambio: NOW() → gamilit.now_mexico() para consistencia de timezone -- ===================================================== CREATE OR REPLACE FUNCTION gamification_system.update_notifications_updated_at() @@ -11,7 +19,11 @@ CREATE OR REPLACE FUNCTION gamification_system.update_notifications_updated_at() LANGUAGE plpgsql AS $function$ BEGIN - NEW.updated_at = NOW(); + -- P0-DUP: Corregido para usar timezone México (antes usaba NOW()) + NEW.updated_at = gamilit.now_mexico(); RETURN NEW; END; $function$; + +COMMENT ON FUNCTION gamification_system.update_notifications_updated_at() IS + 'DEPRECATED: Usar gamilit.update_updated_at_column(). Corregido 2025-12-14 para usar gamilit.now_mexico()'; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_maya_rank_helpers.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_maya_rank_helpers.sql index d373085..f1de488 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_maya_rank_helpers.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_maya_rank_helpers.sql @@ -5,6 +5,7 @@ -- Schema: gamification_system -- Type: FUNCTION (IMMUTABLE - can be inlined and optimized) -- Created: 2025-11-29 +-- Updated: 2025-12-14 (v2.1 - Sync with 03-maya_ranks.sql seeds) -- Origin: Integrated from P0-001-migrate-maya-rank-values.sql -- ============================================================================= @@ -16,24 +17,25 @@ -- Returns: TEXT - The Maya rank name -- Note: This is a pure function with no database dependencies -- --- Rank Thresholds: --- - Ajaw: 0-999 XP (Level 1) --- - Nacom: 1,000-2,999 XP (Level 2) --- - Ah K'in: 3,000-5,999 XP (Level 3) --- - Halach Uinic: 6,000-9,999 XP (Level 4) --- - K'uk'ulkan: 10,000+ XP (Level 5, Maximum) +-- Rank Thresholds v2.1 (synced with 03-maya_ranks.sql): +-- - Ajaw: 0-499 XP (Level 1) +-- - Nacom: 500-999 XP (Level 2) +-- - Ah K'in: 1,000-1,499 XP (Level 3) +-- - Halach Uinic: 1,500-1,899 XP (Level 4) +-- - K'uk'ulkan: 1,900+ XP (Level 5, Maximum) -- ============================================================================= CREATE OR REPLACE FUNCTION gamification_system.calculate_maya_rank_from_xp(xp INTEGER) RETURNS TEXT AS $$ BEGIN - IF xp < 1000 THEN + -- v2.1 thresholds (synced with 03-maya_ranks.sql seeds) + IF xp < 500 THEN RETURN 'Ajaw'; - ELSIF xp < 3000 THEN + ELSIF xp < 1000 THEN RETURN 'Nacom'; - ELSIF xp < 6000 THEN + ELSIF xp < 1500 THEN RETURN 'Ah K''in'; - ELSIF xp < 10000 THEN + ELSIF xp < 1900 THEN RETURN 'Halach Uinic'; ELSE RETURN 'K''uk''ulkan'; @@ -43,8 +45,8 @@ $$ LANGUAGE plpgsql IMMUTABLE; COMMENT ON FUNCTION gamification_system.calculate_maya_rank_from_xp(INTEGER) IS 'Pure function: Calculates Maya rank from XP value without database queries. - IMMUTABLE for query optimization. Thresholds: Ajaw(0), Nacom(1000), Ah K''in(3000), - Halach Uinic(6000), K''uk''ulkan(10000+).'; + IMMUTABLE for query optimization. v2.1 Thresholds: Ajaw(0-499), Nacom(500-999), + Ah K''in(1000-1499), Halach Uinic(1500-1899), K''uk''ulkan(1900+).'; -- ============================================================================= @@ -56,6 +58,7 @@ COMMENT ON FUNCTION gamification_system.calculate_maya_rank_from_xp(INTEGER) IS -- rank TEXT - Current rank name -- Returns: NUMERIC(5,2) - Progress percentage (0.00 to 100.00) -- Note: This is a pure function with no database dependencies +-- Updated: 2025-12-14 (v2.1 - Sync with 03-maya_ranks.sql seeds) -- ============================================================================= CREATE OR REPLACE FUNCTION gamification_system.calculate_rank_progress_percentage( @@ -67,20 +70,20 @@ DECLARE xp_in_rank INTEGER; rank_size INTEGER; BEGIN - -- Calculate XP earned within the current rank + -- Calculate XP earned within the current rank (v2.1 thresholds) CASE rank WHEN 'Ajaw' THEN - xp_in_rank := xp; - rank_size := 1000; + xp_in_rank := xp; -- 0-499 XP + rank_size := 500; WHEN 'Nacom' THEN - xp_in_rank := xp - 1000; - rank_size := 2000; + xp_in_rank := xp - 500; -- 500-999 XP + rank_size := 500; WHEN 'Ah K''in' THEN - xp_in_rank := xp - 3000; - rank_size := 3000; + xp_in_rank := xp - 1000; -- 1000-1499 XP + rank_size := 500; WHEN 'Halach Uinic' THEN - xp_in_rank := xp - 6000; - rank_size := 4000; + xp_in_rank := xp - 1500; -- 1500-1899 XP + rank_size := 400; WHEN 'K''uk''ulkan' THEN -- Maximum rank always shows 100% RETURN 100.00; @@ -101,6 +104,7 @@ $$ LANGUAGE plpgsql IMMUTABLE; COMMENT ON FUNCTION gamification_system.calculate_rank_progress_percentage(INTEGER, TEXT) IS 'Pure function: Calculates percentage progress within a Maya rank. Returns 0-100 based on XP earned within the rank. Maximum rank returns 100%. + v2.1 thresholds: Ajaw(500), Nacom(500), Ah K''in(500), Halach Uinic(400). IMMUTABLE for query optimization.'; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_user_rank.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_user_rank.sql index 0f038be..3436977 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_user_rank.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/calculate_user_rank.sql @@ -1,7 +1,11 @@ -- Nombre: calculate_user_rank --- Descripción: Calcula el rango actual del usuario basado en XP total y misiones completadas +-- Descripción: Calcula el rango actual del usuario basado en XP total y módulos completados -- Schema: gamification_system -- Tipo: FUNCTION +-- +-- CHANGELOG: +-- 2025-12-15: CORR-P0-001 - Cambiar missions_completed → modules_completed +-- (missions_completed no existe en tabla user_stats) CREATE OR REPLACE FUNCTION gamification_system.calculate_user_rank(p_user_id UUID) RETURNS TABLE ( @@ -9,37 +13,41 @@ RETURNS TABLE ( current_rank VARCHAR, next_rank VARCHAR, xp_to_next_rank BIGINT, - missions_to_next_rank INTEGER, + modules_to_next_rank INTEGER, -- Renombrado: missions → modules rank_percentage NUMERIC(5,2) ) AS $$ DECLARE v_total_xp BIGINT; - v_missions_completed INTEGER; + v_modules_completed INTEGER; -- Renombrado: missions → modules v_current_rank VARCHAR; v_next_rank VARCHAR; v_next_rank_xp BIGINT; - v_next_rank_missions INTEGER; + v_next_rank_modules INTEGER; -- Renombrado: missions → modules BEGIN -- Obtener estadísticas del usuario - SELECT total_xp, missions_completed INTO v_total_xp, v_missions_completed - FROM gamification_system.user_stats - WHERE user_id = p_user_id; + -- CORR-P0-001: Usar modules_completed en lugar de missions_completed (columna no existe) + -- FIX: Usar alias 'us' para evitar ambigüedad con output column 'user_id' + SELECT us.total_xp, us.modules_completed INTO v_total_xp, v_modules_completed + FROM gamification_system.user_stats us + WHERE us.user_id = p_user_id; IF NOT FOUND THEN RETURN; END IF; -- Determinar rango actual basado en XP - SELECT current_rank INTO v_current_rank - FROM gamification_system.user_ranks - WHERE user_id = p_user_id - AND is_current = true; + -- FIX: Usar alias 'ur' para evitar ambigüedad + SELECT ur.current_rank INTO v_current_rank + FROM gamification_system.user_ranks ur + WHERE ur.user_id = p_user_id + AND ur.is_current = true; -- Obtener siguiente rango desde maya_ranks - SELECT name::VARCHAR, min_xp_required, missions_required - INTO v_next_rank, v_next_rank_xp, v_next_rank_missions + -- NOTA: Columna correcta es rank_name (ENUM maya_rank), no name + SELECT rank_name::VARCHAR, min_xp_required, COALESCE(modules_required, 0) + INTO v_next_rank, v_next_rank_xp, v_next_rank_modules FROM gamification_system.maya_ranks - WHERE name::VARCHAR > COALESCE(v_current_rank, 'Ajaw') + WHERE rank_name::VARCHAR > COALESCE(v_current_rank, 'Ajaw') ORDER BY min_xp_required ASC LIMIT 1; @@ -47,7 +55,7 @@ BEGIN -- Usuario está en rango máximo v_next_rank := v_current_rank; v_next_rank_xp := v_total_xp; - v_next_rank_missions := v_missions_completed; + v_next_rank_modules := v_modules_completed; END IF; RETURN QUERY SELECT @@ -55,12 +63,12 @@ BEGIN COALESCE(v_current_rank, 'Ajaw'::VARCHAR), v_next_rank, GREATEST(0, v_next_rank_xp - v_total_xp), - GREATEST(0, COALESCE(v_next_rank_missions, 0) - v_missions_completed), + GREATEST(0, COALESCE(v_next_rank_modules, 0) - v_modules_completed), LEAST(100.0::NUMERIC, (v_total_xp::NUMERIC / NULLIF(v_next_rank_xp, 0)) * 100); END; $$ LANGUAGE plpgsql STABLE; COMMENT ON FUNCTION gamification_system.calculate_user_rank(UUID) IS - 'Calcula el rango actual del usuario basado en XP y misiones'; + 'Calcula el rango actual del usuario basado en XP y módulos completados'; GRANT EXECUTE ON FUNCTION gamification_system.calculate_user_rank(UUID) TO authenticated; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/get_user_rank_progress.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/get_user_rank_progress.sql index 569d014..3dddaf4 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/get_user_rank_progress.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/get_user_rank_progress.sql @@ -2,7 +2,8 @@ -- Description: Calcula el progreso del usuario hacia el siguiente rango Maya -- Parameters: -- - p_user_id: UUID - ID del usuario --- Returns: TABLE (current_rank, current_xp, next_rank, next_rank_xp, xp_needed, progress_percentage, missions_completed, missions_required) +-- Returns: TABLE (current_rank, current_xp, next_rank, next_rank_xp, xp_needed, progress_percentage, modules_completed, missions_required) +-- CORRECTED (2025-12-18): missions_completed -> modules_completed (alineado con user_stats entity) -- Example: -- SELECT * FROM gamification_system.get_user_rank_progress('123e4567-e89b-12d3-a456-426614174000'); -- Dependencies: gamification_system.user_stats, user_ranks, maya_ranks @@ -19,7 +20,7 @@ RETURNS TABLE ( next_rank_xp BIGINT, xp_needed BIGINT, progress_percentage NUMERIC(5,2), - missions_completed INTEGER, + modules_completed INTEGER, missions_required INTEGER ) AS $$ DECLARE @@ -60,7 +61,7 @@ BEGIN v_user_stats.total_xp, 0::BIGINT, 100.00::NUMERIC, - v_user_stats.missions_completed, + v_user_stats.modules_completed, 0::INTEGER; ELSE RETURN QUERY SELECT @@ -71,7 +72,7 @@ BEGIN GREATEST(0, v_next_rank.min_xp_required - v_user_stats.total_xp), LEAST(100, (v_user_stats.total_xp - v_current_rank_xp)::NUMERIC / NULLIF(v_next_rank.min_xp_required - v_current_rank_xp, 0) * 100), - v_user_stats.missions_completed, + v_user_stats.modules_completed, v_next_rank.missions_required; END IF; END; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_global.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_global.sql index 2327193..3229bec 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_global.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_global.sql @@ -7,6 +7,7 @@ -- - p_scope: VARCHAR(20) - Alcance del leaderboard (GLOBAL, CLASSROOM) default 'GLOBAL' -- Returns: TABLE (user_position, total_participants, user_score, top_score, percentile) -- Created: 2025-11-02 +-- CORRECTED (2025-12-18): missions_completed -> modules_completed (alineado con user_stats entity) -- ===================================================== CREATE OR REPLACE FUNCTION gamification_system.update_leaderboard_global( @@ -43,17 +44,18 @@ BEGIN FROM gamification_system.user_stats; ELSIF p_leaderboard_type = 'MISSIONS' THEN + -- NOTA: Usando modules_completed ya que missions_completed no existe en user_stats SELECT COUNT(*) + 1 INTO v_position FROM gamification_system.user_stats - WHERE missions_completed > ( - SELECT COALESCE(missions_completed, 0) FROM gamification_system.user_stats WHERE user_id = p_user_id + WHERE modules_completed > ( + SELECT COALESCE(modules_completed, 0) FROM gamification_system.user_stats WHERE user_id = p_user_id ); - SELECT COALESCE(missions_completed, 0) INTO v_user_score + SELECT COALESCE(modules_completed, 0) INTO v_user_score FROM gamification_system.user_stats WHERE user_id = p_user_id; - SELECT COALESCE(MAX(missions_completed), 0) INTO v_top_score + SELECT COALESCE(MAX(modules_completed), 0) INTO v_top_score FROM gamification_system.user_stats; END IF; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_streaks.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_streaks.sql index bda2407..5fc5dc3 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_streaks.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/functions/update_leaderboard_streaks.sql @@ -5,6 +5,11 @@ -- - p_user_id: UUID - ID del usuario -- Returns: TABLE (current_streak, longest_streak, streak_maintained, bonus_xp) -- Created: 2025-11-02 +-- Updated: 2025-12-15 (CORR-001: Alineación con columnas reales de user_stats DDL) +-- ===================================================== +-- CORRECCIONES 2025-12-15: +-- - last_activity_date → last_activity_at::DATE (columna real es timestamp) +-- - longest_streak → max_streak (columna real es max_streak) -- ===================================================== CREATE OR REPLACE FUNCTION gamification_system.update_leaderboard_streaks( @@ -24,10 +29,11 @@ DECLARE v_bonus_xp INTEGER := 0; BEGIN -- Obtener información de racha actual + -- NOTA: last_activity_at es TIMESTAMP WITH TIME ZONE, se castea a DATE SELECT - COALESCE(last_activity_date, CURRENT_DATE), + COALESCE(us.last_activity_at::DATE, CURRENT_DATE), COALESCE(us.current_streak, 0), - COALESCE(us.longest_streak, 0) + COALESCE(us.max_streak, 0) INTO v_last_activity, v_current_streak, v_longest_streak FROM gamification_system.user_stats us WHERE us.user_id = p_user_id; @@ -52,8 +58,8 @@ BEGIN UPDATE gamification_system.user_stats SET current_streak = v_current_streak, - longest_streak = GREATEST(longest_streak, v_current_streak), - last_activity_date = CURRENT_DATE, + max_streak = GREATEST(max_streak, v_current_streak), + last_activity_at = NOW(), total_xp = total_xp + v_bonus_xp, updated_at = NOW() WHERE user_id = p_user_id; @@ -67,7 +73,7 @@ BEGIN UPDATE gamification_system.user_stats SET current_streak = 1, - last_activity_date = CURRENT_DATE, + last_activity_at = NOW(), updated_at = NOW() WHERE user_id = p_user_id; END IF; diff --git a/projects/gamilit/apps/database/ddl/schemas/gamification_system/tables/20-mission_templates.sql b/projects/gamilit/apps/database/ddl/schemas/gamification_system/tables/20-mission_templates.sql index 48965a5..1dde797 100644 --- a/projects/gamilit/apps/database/ddl/schemas/gamification_system/tables/20-mission_templates.sql +++ b/projects/gamilit/apps/database/ddl/schemas/gamification_system/tables/20-mission_templates.sql @@ -147,8 +147,10 @@ CREATE INDEX idx_mission_templates_difficulty ON gamification_system.mission_tem -- Foreign Keys -- +-- P1-001: Corregido FK - auth_management.users no existe, usar profiles +-- Fecha: 2025-12-14 (Auditoría AUDIT-DB-001) ALTER TABLE ONLY gamification_system.mission_templates - ADD CONSTRAINT mission_templates_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth_management.users(id) ON DELETE SET NULL; + ADD CONSTRAINT mission_templates_created_by_fkey FOREIGN KEY (created_by) REFERENCES auth_management.profiles(id) ON DELETE SET NULL; -- Note: badge_id FK commented out until badges table is created -- ALTER TABLE ONLY gamification_system.mission_templates diff --git a/projects/gamilit/apps/database/ddl/schemas/notifications/rls-policies/01-notifications-policies.sql b/projects/gamilit/apps/database/ddl/schemas/notifications/rls-policies/01-notifications-policies.sql new file mode 100644 index 0000000..cff92fd --- /dev/null +++ b/projects/gamilit/apps/database/ddl/schemas/notifications/rls-policies/01-notifications-policies.sql @@ -0,0 +1,260 @@ +-- ===================================================== +-- RLS Policies for notifications schema +-- Description: Políticas de seguridad para sistema de notificaciones +-- Created: 2025-12-14 +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- ===================================================== +-- +-- Security Strategy: +-- - Users can only see their own notifications +-- - Users can only modify their own preferences +-- - System can create notifications for any user (via SECURITY DEFINER) +-- - Admins have full access for monitoring +-- ===================================================== + +-- ===================================================== +-- TABLE: notifications.notifications +-- ===================================================== + +-- Enable RLS +ALTER TABLE notifications.notifications ENABLE ROW LEVEL SECURITY; +ALTER TABLE notifications.notifications FORCE ROW LEVEL SECURITY; + +-- Drop existing policies +DROP POLICY IF EXISTS notifications_select_own ON notifications.notifications; +DROP POLICY IF EXISTS notifications_select_admin ON notifications.notifications; +DROP POLICY IF EXISTS notifications_update_own ON notifications.notifications; +DROP POLICY IF EXISTS notifications_delete_own ON notifications.notifications; + +-- Policy: notifications_select_own +-- Purpose: Users can only see their own notifications +CREATE POLICY notifications_select_own + ON notifications.notifications + AS PERMISSIVE + FOR SELECT + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notifications_select_own ON notifications.notifications IS + 'Usuarios solo pueden ver sus propias notificaciones'; + +-- Policy: notifications_select_admin +-- Purpose: Admins can see all notifications +CREATE POLICY notifications_select_admin + ON notifications.notifications + AS PERMISSIVE + FOR SELECT + TO public + USING ( + EXISTS ( + SELECT 1 FROM auth_management.profiles p + WHERE p.id = current_setting('app.current_user_id', true)::uuid + AND p.role = 'super_admin' + ) + ); + +COMMENT ON POLICY notifications_select_admin ON notifications.notifications IS + 'Administradores pueden ver todas las notificaciones'; + +-- Policy: notifications_update_own +-- Purpose: Users can mark their notifications as read +CREATE POLICY notifications_update_own + ON notifications.notifications + AS PERMISSIVE + FOR UPDATE + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ) + WITH CHECK ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notifications_update_own ON notifications.notifications IS + 'Usuarios solo pueden actualizar sus propias notificaciones (marcar como leídas)'; + +-- Policy: notifications_delete_own +-- Purpose: Users can delete their own notifications +CREATE POLICY notifications_delete_own + ON notifications.notifications + AS PERMISSIVE + FOR DELETE + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notifications_delete_own ON notifications.notifications IS + 'Usuarios solo pueden eliminar sus propias notificaciones'; + +-- ===================================================== +-- TABLE: notifications.notification_preferences +-- ===================================================== + +-- Enable RLS +ALTER TABLE notifications.notification_preferences ENABLE ROW LEVEL SECURITY; +ALTER TABLE notifications.notification_preferences FORCE ROW LEVEL SECURITY; + +-- Drop existing policies +DROP POLICY IF EXISTS notification_preferences_select_own ON notifications.notification_preferences; +DROP POLICY IF EXISTS notification_preferences_insert_own ON notifications.notification_preferences; +DROP POLICY IF EXISTS notification_preferences_update_own ON notifications.notification_preferences; + +-- Policy: notification_preferences_select_own +CREATE POLICY notification_preferences_select_own + ON notifications.notification_preferences + AS PERMISSIVE + FOR SELECT + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notification_preferences_select_own ON notifications.notification_preferences IS + 'Usuarios solo pueden ver sus propias preferencias de notificación'; + +-- Policy: notification_preferences_insert_own +CREATE POLICY notification_preferences_insert_own + ON notifications.notification_preferences + AS PERMISSIVE + FOR INSERT + TO public + WITH CHECK ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notification_preferences_insert_own ON notifications.notification_preferences IS + 'Usuarios solo pueden crear preferencias para sí mismos'; + +-- Policy: notification_preferences_update_own +CREATE POLICY notification_preferences_update_own + ON notifications.notification_preferences + AS PERMISSIVE + FOR UPDATE + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ) + WITH CHECK ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notification_preferences_update_own ON notifications.notification_preferences IS + 'Usuarios solo pueden modificar sus propias preferencias de notificación'; + +-- ===================================================== +-- TABLE: notifications.notification_logs +-- ===================================================== + +-- Enable RLS +ALTER TABLE notifications.notification_logs ENABLE ROW LEVEL SECURITY; +ALTER TABLE notifications.notification_logs FORCE ROW LEVEL SECURITY; + +-- Drop existing policies +DROP POLICY IF EXISTS notification_logs_select_own ON notifications.notification_logs; +DROP POLICY IF EXISTS notification_logs_select_admin ON notifications.notification_logs; + +-- Policy: notification_logs_select_own +CREATE POLICY notification_logs_select_own + ON notifications.notification_logs + AS PERMISSIVE + FOR SELECT + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY notification_logs_select_own ON notifications.notification_logs IS + 'Usuarios pueden ver logs de sus propias notificaciones'; + +-- Policy: notification_logs_select_admin +CREATE POLICY notification_logs_select_admin + ON notifications.notification_logs + AS PERMISSIVE + FOR SELECT + TO public + USING ( + EXISTS ( + SELECT 1 FROM auth_management.profiles p + WHERE p.id = current_setting('app.current_user_id', true)::uuid + AND p.role = 'super_admin' + ) + ); + +COMMENT ON POLICY notification_logs_select_admin ON notifications.notification_logs IS + 'Administradores pueden ver todos los logs de notificaciones'; + +-- ===================================================== +-- TABLE: notifications.user_devices (para push notifications) +-- ===================================================== + +-- Enable RLS +ALTER TABLE notifications.user_devices ENABLE ROW LEVEL SECURITY; +ALTER TABLE notifications.user_devices FORCE ROW LEVEL SECURITY; + +-- Drop existing policies +DROP POLICY IF EXISTS user_devices_select_own ON notifications.user_devices; +DROP POLICY IF EXISTS user_devices_insert_own ON notifications.user_devices; +DROP POLICY IF EXISTS user_devices_update_own ON notifications.user_devices; +DROP POLICY IF EXISTS user_devices_delete_own ON notifications.user_devices; + +-- Policy: user_devices_all_own +CREATE POLICY user_devices_select_own + ON notifications.user_devices + AS PERMISSIVE + FOR SELECT + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +CREATE POLICY user_devices_insert_own + ON notifications.user_devices + AS PERMISSIVE + FOR INSERT + TO public + WITH CHECK ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +CREATE POLICY user_devices_update_own + ON notifications.user_devices + AS PERMISSIVE + FOR UPDATE + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +CREATE POLICY user_devices_delete_own + ON notifications.user_devices + AS PERMISSIVE + FOR DELETE + TO public + USING ( + user_id = current_setting('app.current_user_id', true)::uuid + ); + +COMMENT ON POLICY user_devices_select_own ON notifications.user_devices IS + 'Usuarios solo pueden ver sus propios dispositivos registrados'; + +-- ===================================================== +-- SUMMARY +-- ===================================================== +-- Tables with RLS enabled: 4 +-- - notifications.notifications (4 policies) +-- - notifications.notification_preferences (3 policies) +-- - notifications.notification_logs (2 policies) +-- - notifications.user_devices (4 policies) +-- +-- Total policies: 13 +-- +-- Security enforced: +-- - Users cannot read other users' notifications +-- - Users cannot modify other users' preferences +-- - Users cannot see other users' registered devices +-- - Admins have read-only access for monitoring +-- - INSERT for notifications uses SECURITY DEFINER functions +-- ===================================================== diff --git a/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/05-get_classroom_analytics.sql b/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/05-get_classroom_analytics.sql index 1ef84ef..9f7f57a 100644 --- a/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/05-get_classroom_analytics.sql +++ b/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/05-get_classroom_analytics.sql @@ -10,6 +10,7 @@ -- Dependencies: social_features.classroom_members, gamification_system.user_stats, progress_tracking.module_progress, auth.profiles -- Created: 2025-10-28 -- Modified: 2025-10-28 +-- CORRECTED (2025-12-18): missions_completed -> modules_completed, last_activity_date -> last_activity_at CREATE OR REPLACE FUNCTION progress_tracking.get_classroom_analytics( p_classroom_id UUID, @@ -39,22 +40,22 @@ BEGIN SELECT cs.user_id, us.total_xp, - us.missions_completed, + us.modules_completed, us.current_streak, - us.last_activity_date + us.last_activity_at FROM classroom_students cs JOIN gamification_system.user_stats us ON us.user_id = cs.user_id ) SELECT COUNT(*)::INTEGER as total_students, - COUNT(*) FILTER (WHERE last_activity_date >= CURRENT_DATE - 7)::INTEGER as active_students, + COUNT(*) FILTER (WHERE last_activity_at >= CURRENT_DATE - 7)::INTEGER as active_students, AVG( (SELECT completion_percentage FROM progress_tracking.module_progress mp WHERE mp.user_id = ss.user_id LIMIT 1) )::NUMERIC(5,2) as avg_completion_rate, - SUM(missions_completed)::INTEGER as total_missions_completed, + SUM(modules_completed)::INTEGER as total_missions_completed, SUM(total_xp)::BIGINT as total_xp_earned, AVG(current_streak)::NUMERIC(5,2) as avg_current_streak, (SELECT user_id FROM student_stats ORDER BY total_xp DESC LIMIT 1) as top_performer_id, diff --git a/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/06-update_mission_progress.sql b/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/06-update_mission_progress.sql index ea82a1b..6245448 100644 --- a/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/06-update_mission_progress.sql +++ b/projects/gamilit/apps/database/ddl/schemas/progress_tracking/functions/06-update_mission_progress.sql @@ -9,6 +9,7 @@ -- Dependencies: gamification_system.missions, gamification_system.calculate_mission_reward, user_stats, ml_coins_transactions, update_user_level, check_and_grant_achievements -- Created: 2025-10-28 -- Modified: 2025-10-28 +-- CORRECTED (2025-12-18): missions_completed -> modules_completed (columna no existe en user_stats) CREATE OR REPLACE FUNCTION progress_tracking.grant_mission_completion_rewards( p_user_id UUID, @@ -47,11 +48,12 @@ BEGIN WHERE user_id = p_user_id; -- Otorgar XP y Coins + -- NOTA: Usando modules_completed ya que missions_completed no existe en user_stats UPDATE gamification_system.user_stats SET total_xp = total_xp + v_boosted_xp, ml_coins = ml_coins + v_boosted_coins, - missions_completed = missions_completed + 1, + modules_completed = modules_completed + 1, updated_at = NOW() WHERE user_id = p_user_id; diff --git a/projects/gamilit/apps/database/ddl/schemas/public/_MAP.md b/projects/gamilit/apps/database/ddl/schemas/public/_MAP.md index 4873edc..b559930 100644 --- a/projects/gamilit/apps/database/ddl/schemas/public/_MAP.md +++ b/projects/gamilit/apps/database/ddl/schemas/public/_MAP.md @@ -19,7 +19,7 @@ El schema `public` está reservado para objetos del core de PostgreSQL y extensi Todos los objetos propios de Gamilit deben ubicarse en schemas específicos según su responsabilidad: -- **Supabase Core**: `auth`, `storage` +- **Auth Core**: `auth`, `storage` - **Application**: `auth_management`, `system_configuration` - **Domain**: `educational_content`, `gamification_system`, `progress_tracking`, `social_features`, `content_management` - **Integration/Admin**: `audit_logging`, `admin_dashboard`, `lti_integration` diff --git a/projects/gamilit/apps/database/ddl/schemas/storage/_MAP.md b/projects/gamilit/apps/database/ddl/schemas/storage/_MAP.md index 4e7c1f6..9e0bb5f 100644 --- a/projects/gamilit/apps/database/ddl/schemas/storage/_MAP.md +++ b/projects/gamilit/apps/database/ddl/schemas/storage/_MAP.md @@ -1,6 +1,6 @@ # Schema: storage -Configuración de almacenamiento de Supabase +Schema de configuración de almacenamiento (S3/Storage compatible) ## Estructura diff --git a/projects/gamilit/apps/database/seeds/dev/auth/01-demo-users.sql b/projects/gamilit/apps/database/seeds/dev/auth/01-demo-users.sql index b96e493..ca6e33f 100644 --- a/projects/gamilit/apps/database/seeds/dev/auth/01-demo-users.sql +++ b/projects/gamilit/apps/database/seeds/dev/auth/01-demo-users.sql @@ -2,7 +2,7 @@ -- Seed: auth.users - Test Users (PRODUCTION CLEAN) -- Description: Solo usuarios de testing con dominio @gamilit.com -- Environment: PRODUCTION --- Dependencies: None (auth schema managed by Supabase) +-- Dependencies: None (auth schema base) -- Order: 01 -- Created: 2025-11-17 -- Version: 2.0 (CLEAN - Solo 3 usuarios @gamilit.com) diff --git a/projects/gamilit/apps/database/seeds/dev/auth_management/02-tenants-production.sql b/projects/gamilit/apps/database/seeds/dev/auth_management/02-tenants-production.sql new file mode 100644 index 0000000..e51b13c --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/auth_management/02-tenants-production.sql @@ -0,0 +1,381 @@ +-- ===================================================== +-- Seed: auth_management.tenants - Production User Tenants +-- Description: Tenants para usuarios reales registrados en producción +-- Environment: PRODUCTION +-- Dependencies: auth_management/01-tenants.sql +-- Order: 02 +-- Created: 2025-11-19 +-- Version: 1.0 (Migrados desde servidor producción) +-- ===================================================== +-- +-- TENANTS DE USUARIOS REALES REGISTRADOS (13): +-- Cada usuario de producción tiene su propio tenant personal +-- creado automáticamente durante el registro +-- +-- TOTAL: 13 tenants personales +-- +-- POLÍTICA DE CARGA LIMPIA: +-- ✅ Tenant IDs originales del servidor preservados +-- ✅ Configuración base para usuarios estudiantes +-- ✅ Subscription tier 'free' por defecto +-- +-- IMPORTANTE: Estos son tenants personales de estudiantes. +-- Son diferentes al tenant principal de la plataforma. +-- ===================================================== + +SET search_path TO auth_management, public; + +-- ===================================================== +-- INSERT: Production User Tenants (13 tenants) +-- ===================================================== + +INSERT INTO auth_management.tenants ( + id, + name, + slug, + domain, + logo_url, + subscription_tier, + max_users, + max_storage_gb, + is_active, + trial_ends_at, + settings, + metadata, + created_at, + updated_at +) VALUES + +-- Tenant 1: Jose Aguirre +( + 'a2019d2c-1abe-4b92-8033-372a2a553f76'::uuid, + 'Jose Aguirre', + 'jose-aguirre', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'joseal.guirre34@gmail.com'), + '2025-11-18 07:29:05.229254+00'::timestamptz, + '2025-11-18 07:29:05.229254+00'::timestamptz +), + +-- Tenant 2: Sergio Jimenez +( + '6490930a-c572-4464-82f7-19d688f32877'::uuid, + 'Sergio Jimenez', + 'sergio-jimenez', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'sergiojimenezesteban63@gmail.com'), + '2025-11-18 08:17:40.928077+00'::timestamptz, + '2025-11-18 08:17:40.928077+00'::timestamptz +), + +-- Tenant 3: Hugo Gomez +( + '4abd1886-6d7e-42c9-a2a5-f7a9e01973bd'::uuid, + 'Hugo Gomez', + 'hugo-gomez', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'Gomezfornite92@gmail.com'), + '2025-11-18 08:18:04.242047+00'::timestamptz, + '2025-11-18 08:18:04.242047+00'::timestamptz +), + +-- Tenant 4: Hugo Aragón +( + 'c77ba4cc-be06-4ed8-8e48-4bbc72d59f16'::uuid, + 'Hugo Aragón', + 'hugo-aragon', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'Aragon494gt54@icloud.com'), + '2025-11-18 08:20:17.230714+00'::timestamptz, + '2025-11-18 08:20:17.230714+00'::timestamptz +), + +-- Tenant 5: Azul Valentina +( + 'e2b08195-5a20-4822-a9b6-8d2e04c785b3'::uuid, + 'Azul Valentina', + 'azul-valentina', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'blu3wt7@gmail.com'), + '2025-11-18 08:32:17.315932+00'::timestamptz, + '2025-11-18 08:32:17.315932+00'::timestamptz +), + +-- Tenant 6: Ricardo Lugo +( + '5f4dd29b-0317-4d96-8540-4e9417175525'::uuid, + 'Ricardo Lugo', + 'ricardo-lugo', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'ricardolugo786@icloud.com'), + '2025-11-18 10:15:06.481498+00'::timestamptz, + '2025-11-18 10:15:06.481498+00'::timestamptz +), + +-- Tenant 7: Carlos Marban +( + '87b8dc94-da11-451d-a53b-31c00a6973b8'::uuid, + 'Carlos Marban', + 'carlos-marban', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'marbancarlos916@gmail.com'), + '2025-11-18 10:29:05.240413+00'::timestamptz, + '2025-11-18 10:29:05.240413+00'::timestamptz +), + +-- Tenant 8: Diego Colores +( + '331b5931-1125-482f-9535-f1e0e829edd5'::uuid, + 'Diego Colores', + 'diego-colores', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'diego.colores09@gmail.com'), + '2025-11-18 10:29:20.531883+00'::timestamptz, + '2025-11-18 10:29:20.531883+00'::timestamptz +), + +-- Tenant 9: Benjamin Hernandez +( + '2f1c69b9-2da7-4b72-a6fd-477aa96ba075'::uuid, + 'Benjamin Hernandez', + 'benjamin-hernandez', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'hernandezfonsecabenjamin7@gmail.com'), + '2025-11-18 10:37:06.9215+00'::timestamptz, + '2025-11-18 10:37:06.9215+00'::timestamptz +), + +-- Tenant 10: Josue Reyes +( + '7265b54e-a988-4c50-a62c-61cb0594f556'::uuid, + 'Josue Reyes', + 'josue-reyes', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'jr7794315@gmail.com'), + '2025-11-18 17:53:39.681271+00'::timestamptz, + '2025-11-18 17:53:39.681271+00'::timestamptz +), + +-- Tenant 11: Fernando Barragan +( + 'dcc49202-4d26-4b09-9bdb-8f71d039f328'::uuid, + 'Fernando Barragan', + 'fernando-barragan', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'barraganfer03@gmail.com'), + '2025-11-18 20:39:27.410436+00'::timestamptz, + '2025-11-18 20:39:27.410436+00'::timestamptz +), + +-- Tenant 12: Marco Antonio Roman +( + 'bfc0fac3-8905-4514-af32-7375e242e0f5'::uuid, + 'Marco Antonio Roman', + 'marco-roman', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'roman.rebollar.marcoantonio1008@gmail.com'), + '2025-11-18 21:03:17.328254+00'::timestamptz, + '2025-11-18 21:03:17.328254+00'::timestamptz +), + +-- Tenant 13: Rodrigo Guerrero +( + 'c4856507-807f-4fc5-9689-ffeb0feb4825'::uuid, + 'Rodrigo Guerrero', + 'rodrigo-guerrero', + NULL, + NULL, + 'free', + 1, + 1, + true, + NULL, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City' + ), + jsonb_build_object('personal_tenant', true, 'user_email', 'rodrigoguerrero0914@gmail.com'), + '2025-11-18 21:20:52.304488+00'::timestamptz, + '2025-11-18 21:20:52.304488+00'::timestamptz +) + +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + updated_at = EXCLUDED.updated_at; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + production_tenant_count INTEGER; + total_tenant_count INTEGER; +BEGIN + -- Contar tenants personales de producción + SELECT COUNT(*) INTO production_tenant_count + FROM auth_management.tenants + WHERE metadata->>'personal_tenant' = 'true'; + + -- Contar todos los tenants + SELECT COUNT(*) INTO total_tenant_count + FROM auth_management.tenants; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'TENANTS DE PRODUCCIÓN CREADOS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Tenants personales: %', production_tenant_count; + RAISE NOTICE 'Total tenants (incluyendo principal): %', total_tenant_count; + RAISE NOTICE '========================================'; + + IF production_tenant_count = 13 THEN + RAISE NOTICE '✓ Los 13 tenants personales fueron creados correctamente'; + ELSE + RAISE WARNING '⚠ Se esperaban 13 tenants personales, se crearon %', production_tenant_count; + END IF; + + RAISE NOTICE '========================================'; +END $$; + +-- ===================================================== +-- CHANGELOG +-- ===================================================== +-- v1.0 (2025-11-19): Primera versión +-- - 13 tenants personales migrados desde servidor producción +-- - Tenant IDs originales preservados +-- - Configuración base para estudiantes +-- - Subscription tier 'free' por defecto +-- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/dev/auth_management/04-profiles-complete.sql b/projects/gamilit/apps/database/seeds/dev/auth_management/04-profiles-complete.sql new file mode 100644 index 0000000..aaf54e0 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/auth_management/04-profiles-complete.sql @@ -0,0 +1,210 @@ +-- ===================================================== +-- Seed: auth_management.profiles (PROD) - COMPLETO +-- Description: Perfiles para todos los usuarios de testing y demo +-- Environment: PRODUCTION +-- Dependencies: auth.users, auth_management.tenants +-- Order: 04 +-- Created: 2025-11-11 +-- Version: 2.0 +-- ===================================================== +-- +-- PERFILES INCLUIDOS: +-- - 3 perfiles de testing (admin, teacher, student @gamilit.com) +-- - 16 perfiles de estudiantes demo +-- - 3 perfiles de profesores demo +-- - 3 perfiles de administradores demo +-- ⚠️ NO incluye perfiles de padres (Portal Padres = Extension EXT-010, fuera de alcance) +-- +-- TOTAL: 22 perfiles (teacher, student, admin SOLO - alcance v2.3.x) +-- ===================================================== + +SET search_path TO auth_management, public; + +-- ===================================================== +-- INSERT: Perfiles Completos +-- ===================================================== + +INSERT INTO auth_management.profiles ( + id, + tenant_id, + user_id, + email, + display_name, + full_name, + first_name, + last_name, + avatar_url, + bio, + phone, + date_of_birth, + grade_level, + student_id, + school_id, + role, + status, + email_verified, + phone_verified, + preferences, + metadata, + created_at, + updated_at +) VALUES + +-- ===================================================== +-- PERFILES DE TESTING (3) +-- ===================================================== +( + 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, -- Tenant principal + 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, -- user_id admin + 'admin@gamilit.com', + 'Admin GAMILIT', + 'Administrador GAMILIT', + 'Administrador', + 'GAMILIT', + '/avatars/admin-testing.png', + 'Usuario administrador para testing y desarrollo.', + '55-0000-0001', + '1985-01-01'::date, + NULL, -- grade_level (no aplica para admin) + NULL, -- student_id + NULL, -- school_id + 'super_admin'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + true, + true, + jsonb_build_object( + 'theme', 'professional', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + jsonb_build_object( + 'testing_user', true, + 'description', 'Usuario de testing principal' + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), +( + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- user_id teacher + 'teacher@gamilit.com', + 'Profesor Testing', + 'Profesor de Testing GAMILIT', + 'Profesor', + 'Testing', + '/avatars/teacher-testing.png', + 'Usuario profesor para testing y desarrollo.', + '55-0000-0002', + '1980-05-15'::date, + NULL, + NULL, + NULL, + 'admin_teacher'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + true, + true, + jsonb_build_object( + 'theme', 'teacher', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + jsonb_build_object( + 'testing_user', true, + 'subjects', ARRAY['Lengua Española', 'Comprensión Lectora'] + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), +( + 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, -- user_id student + 'student@gamilit.com', + 'Estudiante Testing', + 'Estudiante de Testing GAMILIT', + 'Estudiante', + 'Testing', + '/avatars/student-testing.png', + 'Usuario estudiante para testing y desarrollo.', + '55-0000-0003', + '2013-09-01'::date, + '5', -- grade_level + 'EST-TEST-001', + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + true, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true, + 'gamification', jsonb_build_object( + 'show_leaderboard', true, + 'show_achievements', true, + 'show_rank', true + ) + ), + jsonb_build_object( + 'testing_user', true, + 'interests', ARRAY['lectura', 'ciencia'], + 'learning_style', 'visual' + ), + gamilit.now_mexico(), + gamilit.now_mexico() +) + +ON CONFLICT (user_id) DO UPDATE SET + display_name = EXCLUDED.display_name, + full_name = EXCLUDED.full_name, + bio = EXCLUDED.bio, + preferences = EXCLUDED.preferences, + metadata = EXCLUDED.metadata, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + profile_count INTEGER; + testing_profiles INTEGER; +BEGIN + SELECT COUNT(*) INTO profile_count + FROM auth_management.profiles; + + SELECT COUNT(*) INTO testing_profiles + FROM auth_management.profiles + WHERE email IN ('admin@gamilit.com', 'teacher@gamilit.com', 'student@gamilit.com'); + + RAISE NOTICE '========================================'; + RAISE NOTICE 'PERFILES DE TESTING CREADOS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total perfiles: %', profile_count; + RAISE NOTICE 'Perfiles de testing: %', testing_profiles; + RAISE NOTICE '========================================'; + + IF testing_profiles = 3 THEN + RAISE NOTICE '✓ Perfiles de testing creados correctamente'; + ELSE + RAISE WARNING '⚠ Se esperaban 3 perfiles de testing, se crearon %', testing_profiles; + END IF; +END $$; + +-- ===================================================== +-- Testing Info +-- ===================================================== +-- Los perfiles de testing están listos para usar con: +-- - admin@gamilit.com / Test1234 +-- - teacher@gamilit.com / Test1234 +-- - student@gamilit.com / Test1234 +-- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/dev/auth_management/06-profiles-production.sql b/projects/gamilit/apps/database/seeds/dev/auth_management/06-profiles-production.sql new file mode 100644 index 0000000..b7d5dd3 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/auth_management/06-profiles-production.sql @@ -0,0 +1,589 @@ +-- ===================================================== +-- Seed: auth_management.profiles - Production Users (CORREGIDO) +-- Description: Perfiles CORREGIDOS para usuarios reales registrados en producción +-- Environment: PRODUCTION +-- Dependencies: auth/02-production-users.sql, auth_management/01-tenants.sql +-- Order: 06 +-- Created: 2025-11-19 +-- Version: 2.0 (CORRECCIÓN: profiles.id = auth.users.id) +-- ===================================================== +-- +-- CORRECCIONES APLICADAS: +-- ❌ ANTES: profiles.id generado con gen_random_uuid() (diferente de auth.users.id) +-- ✅ AHORA: profiles.id = auth.users.id (consistente con seeds de testing) +-- +-- ❌ ANTES: tenant_id apuntaba a tenants personales +-- ✅ AHORA: tenant_id apunta al tenant principal (GAMILIT Platform) +-- +-- JUSTIFICACIÓN: +-- 1. Todos los usuarios de testing tienen profiles.id = auth.users.id +-- 2. Backend busca user_stats con profiles.id, pero user_stats usa auth.users.id +-- 3. Resultado: Error 404 al enviar respuestas de ejercicios +-- 4. Solución: Unificar IDs (1 usuario = 1 ID único) +-- +-- IMPACTO: +-- - ✅ Usuarios de producción funcionan igual que usuarios de testing +-- - ✅ No más errores 404 al enviar respuestas +-- - ✅ Gamificación funciona correctamente +-- - ✅ Trigger initialize_user_stats() usa el ID correcto +-- +-- TOTAL: 13 perfiles de estudiantes (CORREGIDOS) +-- ===================================================== + +SET search_path TO auth_management, public; + +-- ===================================================== +-- INSERT: Production User Profiles (13 perfiles CORREGIDOS) +-- ===================================================== + +INSERT INTO auth_management.profiles ( + id, -- ✅ AHORA: auth.users.id (NO gen_random_uuid()) + tenant_id, -- ✅ AHORA: Tenant principal (NO personal) + user_id, -- ✅ auth.users.id (sin cambios) + email, + display_name, + full_name, + first_name, + last_name, + avatar_url, + bio, + phone, + date_of_birth, + grade_level, + student_id, + school_id, + role, + status, + email_verified, + phone_verified, + preferences, + metadata, + created_at, + updated_at +) VALUES + +-- ===================================================== +-- PROFILE 1: Jose Aguirre (CORREGIDO) +-- ===================================================== +( + 'b017b792-b327-40dd-aefb-a80312776952'::uuid, -- ✅ id = user_id (auth.users.id) + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, -- ✅ Tenant principal + 'b017b792-b327-40dd-aefb-a80312776952'::uuid, -- user_id + 'joseal.guirre34@gmail.com', + 'Jose Aguirre', + 'Jose Aguirre', + 'Jose', + 'Aguirre', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 07:29:05.229254+00'::timestamptz, + '2025-11-18 07:29:05.229254+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 2: Sergio Jimenez (CORREGIDO) +-- ===================================================== +( + '06a24962-e83d-4e94-aad7-ff69f20a9119'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '06a24962-e83d-4e94-aad7-ff69f20a9119'::uuid, + 'sergiojimenezesteban63@gmail.com', + 'Sergio Jimenez', + 'Sergio Jimenez', + 'Sergio', + 'Jimenez', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 08:17:40.928077+00'::timestamptz, + '2025-11-18 08:17:40.928077+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 3: Hugo Gomez (CORREGIDO) +-- ===================================================== +( + '24e8c563-8854-43d1-b3c9-2f83e91f5a1e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '24e8c563-8854-43d1-b3c9-2f83e91f5a1e'::uuid, + 'Gomezfornite92@gmail.com', + 'Hugo Gomez', + 'Hugo Gomez', + 'Hugo', + 'Gomez', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 08:18:04.242047+00'::timestamptz, + '2025-11-18 08:18:04.242047+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 4: Hugo Aragón (CORREGIDO) +-- ===================================================== +( + 'bf0d3e34-e077-43d1-9626-292f7fae2bd6'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'bf0d3e34-e077-43d1-9626-292f7fae2bd6'::uuid, + 'Aragon494gt54@icloud.com', + 'Hugo Aragón', + 'Hugo Aragón', + 'Hugo', + 'Aragón', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 08:20:17.230714+00'::timestamptz, + '2025-11-18 08:20:17.230714+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 5: Azul Valentina (CORREGIDO) +-- ===================================================== +( + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + 'blu3wt7@gmail.com', + 'Azul Valentina', + 'Azul Valentina', + 'Azul', + 'Valentina', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 08:32:17.315932+00'::timestamptz, + '2025-11-18 08:32:17.315932+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 6: Ricardo Lugo (CORREGIDO) +-- ===================================================== +( + '5e738038-1743-4aa9-b222-30171300ea9d'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '5e738038-1743-4aa9-b222-30171300ea9d'::uuid, + 'ricardolugo786@icloud.com', + 'Ricardo Lugo', + 'Ricardo Lugo', + 'Ricardo', + 'Lugo', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 10:15:06.481498+00'::timestamptz, + '2025-11-18 10:15:06.481498+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 7: Carlos Marban (CORREGIDO) +-- ===================================================== +( + '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, + 'marbancarlos916@gmail.com', + 'Carlos Marban', + 'Carlos Marban', + 'Carlos', + 'Marban', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 10:29:05.240413+00'::timestamptz, + '2025-11-18 10:29:05.240413+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 8: Diego Colores (CORREGIDO) +-- ===================================================== +( + '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, + 'diego.colores09@gmail.com', + 'Diego Colores', + 'Diego Colores', + 'Diego', + 'Colores', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 10:29:20.531883+00'::timestamptz, + '2025-11-18 10:29:20.531883+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 9: Benjamin Hernandez (CORREGIDO) +-- ===================================================== +( + '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, + 'hernandezfonsecabenjamin7@gmail.com', + 'Benjamin Hernandez', + 'Benjamin Hernandez', + 'Benjamin', + 'Hernandez', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 10:37:06.9215+00'::timestamptz, + '2025-11-18 10:37:06.9215+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 10: Josue Reyes (CORREGIDO) +-- ===================================================== +( + 'ccd7135c-0fea-4488-9094-9da52df1c98c'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'ccd7135c-0fea-4488-9094-9da52df1c98c'::uuid, + 'jr7794315@gmail.com', + 'Josue Reyes', + 'Josue Reyes', + 'Josue', + 'Reyes', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 17:53:39.681271+00'::timestamptz, + '2025-11-18 17:53:39.681271+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 11: Fernando Barragan (CORREGIDO) +-- ===================================================== +( + '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, + 'barraganfer03@gmail.com', + 'Fernando Barragan', + 'Fernando Barragan', + 'Fernando', + 'Barragan', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 20:39:27.410436+00'::timestamptz, + '2025-11-18 20:39:27.410436+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 12: Marco Antonio Roman (CORREGIDO) +-- ===================================================== +( + '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, + 'roman.rebollar.marcoantonio1008@gmail.com', + 'Marco Antonio Roman', + 'Marco Antonio Roman', + 'Marco Antonio', + 'Roman', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 21:03:17.328254+00'::timestamptz, + '2025-11-18 21:03:17.328254+00'::timestamptz +), + +-- ===================================================== +-- PROFILE 13: Rodrigo Guerrero (CORREGIDO) +-- ===================================================== +( + 'ebe48628-5e44-4562-97b7-b4950b216247'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'ebe48628-5e44-4562-97b7-b4950b216247'::uuid, + 'rodrigoguerrero0914@gmail.com', + 'Rodrigo Guerrero', + 'Rodrigo Guerrero', + 'Rodrigo', + 'Guerrero', + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, + false, + jsonb_build_object( + 'theme', 'detective', + 'language', 'es', + 'timezone', 'America/Mexico_City', + 'sound_enabled', true, + 'notifications_enabled', true + ), + '{}'::jsonb, + '2025-11-18 21:20:52.304488+00'::timestamptz, + '2025-11-18 21:20:52.304488+00'::timestamptz +) + +ON CONFLICT (id) DO UPDATE SET + tenant_id = EXCLUDED.tenant_id, -- ✅ Actualizar tenant al principal + display_name = EXCLUDED.display_name, + full_name = EXCLUDED.full_name, + first_name = EXCLUDED.first_name, + last_name = EXCLUDED.last_name, + updated_at = EXCLUDED.updated_at; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + production_profile_count INTEGER; + corrected_ids_count INTEGER; + corrected_tenants_count INTEGER; +BEGIN + -- Contar perfiles de producción + SELECT COUNT(*) INTO production_profile_count + FROM auth_management.profiles + WHERE email NOT LIKE '%@gamilit.com'; + + -- Contar perfiles con IDs corregidos (id = user_id) + SELECT COUNT(*) INTO corrected_ids_count + FROM auth_management.profiles + WHERE email NOT LIKE '%@gamilit.com' + AND id = user_id; + + -- Contar perfiles con tenant principal + SELECT COUNT(*) INTO corrected_tenants_count + FROM auth_management.profiles + WHERE email NOT LIKE '%@gamilit.com' + AND tenant_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'PERFILES DE PRODUCCIÓN (CORREGIDOS)'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total perfiles de producción: %', production_profile_count; + RAISE NOTICE 'Perfiles con profiles.id = auth.users.id: %', corrected_ids_count; + RAISE NOTICE 'Perfiles con tenant principal: %', corrected_tenants_count; + RAISE NOTICE '========================================'; + + IF production_profile_count = 13 AND corrected_ids_count = 13 AND corrected_tenants_count = 13 THEN + RAISE NOTICE '✅ Los 13 perfiles de producción fueron CORREGIDOS correctamente'; + RAISE NOTICE '✅ profiles.id = auth.users.id para TODOS los usuarios'; + RAISE NOTICE '✅ tenant_id = GAMILIT Platform para TODOS los usuarios'; + ELSE + RAISE WARNING '⚠ Corrección incompleta:'; + RAISE WARNING ' - Esperados: 13 perfiles'; + RAISE WARNING ' - IDs corregidos: %', corrected_ids_count; + RAISE WARNING ' - Tenants corregidos: %', corrected_tenants_count; + END IF; + + RAISE NOTICE '========================================'; +END $$; + +-- ===================================================== +-- CHANGELOG +-- ===================================================== +-- v2.0 (2025-11-19): Corrección de IDs y tenants +-- - ✅ profiles.id = auth.users.id (era diferente) +-- - ✅ tenant_id = Tenant principal (era personal) +-- - ✅ Consistente con usuarios de testing +-- - ✅ Elimina error 404 al enviar respuestas +-- +-- v1.0 (2025-11-19): Primera versión (DEPRECADA) +-- - ❌ profiles.id generado con gen_random_uuid() +-- - ❌ tenant_id apuntaba a tenants personales +-- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/dev/auth_management/07-user_roles.sql b/projects/gamilit/apps/database/seeds/dev/auth_management/07-user_roles.sql new file mode 100644 index 0000000..a7d6995 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/auth_management/07-user_roles.sql @@ -0,0 +1,260 @@ +-- ===================================================== +-- Seed: auth_management.user_roles +-- Description: Asignaciones de roles a usuarios de prueba y demo +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed asigna roles a los usuarios existentes en profiles. +-- Los roles definen los permisos y accesos del sistema. +-- +-- Roles disponibles (ENUM gamilit_role): +-- - super_admin: Administrador global del sistema +-- - admin_teacher: Profesor con permisos administrativos +-- - teacher: Profesor estándar +-- - student: Estudiante +-- - parent: Padre de familia +-- ===================================================== + +DO $$ +DECLARE + v_tenant_id UUID; + v_admin_id UUID; + v_teacher_id UUID; + v_student_id UUID; +BEGIN + -- Obtener tenant principal (puede ser 'GAMILIT Platform' o 'GAMILIT Principal') + SELECT id INTO v_tenant_id FROM auth_management.tenants + WHERE name LIKE 'GAMILIT%' OR id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid + LIMIT 1; + + IF v_tenant_id IS NULL THEN + -- Usar UUID por defecto si no se encuentra + v_tenant_id := 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid; + RAISE NOTICE 'Usando tenant por defecto: %', v_tenant_id; + END IF; + + -- Obtener IDs de usuarios de prueba (creados en 04-profiles-complete.sql) + v_admin_id := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid; + v_teacher_id := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid; + v_student_id := 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid; + + RAISE NOTICE 'Asignando roles a usuarios de prueba...'; + + -- ===================================================== + -- 1. ROL SUPER_ADMIN para admin@gamilit.com + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, + user_id, + tenant_id, + role, + permissions, + assigned_by, + is_active, + metadata + ) VALUES ( + '10000001-0000-0000-0000-000000000001'::uuid, + v_admin_id, + v_tenant_id, + 'super_admin', + '{ + "read": true, + "write": true, + "admin": true, + "analytics": true, + "manage_users": true, + "manage_content": true, + "manage_gamification": true, + "system_config": true + }'::jsonb, + v_admin_id, + true, + '{"assigned_reason": "Usuario administrador del sistema", "seed_version": "1.0.0"}'::jsonb + ) + ON CONFLICT (user_id, tenant_id, role) DO UPDATE SET + permissions = EXCLUDED.permissions, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Rol super_admin asignado a admin@gamilit.com'; + + -- ===================================================== + -- 2. ROL ADMIN_TEACHER para teacher@gamilit.com + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, + user_id, + tenant_id, + role, + permissions, + assigned_by, + is_active, + metadata + ) VALUES ( + '10000002-0000-0000-0000-000000000001'::uuid, + v_teacher_id, + v_tenant_id, + 'admin_teacher', + '{ + "read": true, + "write": true, + "admin": false, + "analytics": true, + "manage_students": true, + "manage_classrooms": true, + "create_content": true, + "grade_assignments": true + }'::jsonb, + v_admin_id, + true, + '{"assigned_reason": "Profesor de prueba con permisos administrativos", "seed_version": "1.0.0"}'::jsonb + ) + ON CONFLICT (user_id, tenant_id, role) DO UPDATE SET + permissions = EXCLUDED.permissions, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Rol admin_teacher asignado a teacher@gamilit.com'; + + -- ===================================================== + -- 3. ROL STUDENT para student@gamilit.com + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, + user_id, + tenant_id, + role, + permissions, + assigned_by, + is_active, + metadata + ) VALUES ( + '10000003-0000-0000-0000-000000000001'::uuid, + v_student_id, + v_tenant_id, + 'student', + '{ + "read": true, + "write": false, + "admin": false, + "analytics": false, + "view_own_progress": true, + "submit_exercises": true, + "participate_classrooms": true, + "earn_achievements": true + }'::jsonb, + v_admin_id, + true, + '{"assigned_reason": "Estudiante de prueba", "seed_version": "1.0.0"}'::jsonb + ) + ON CONFLICT (user_id, tenant_id, role) DO UPDATE SET + permissions = EXCLUDED.permissions, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Rol student asignado a student@gamilit.com'; + + -- ===================================================== + -- 4. ROLES PARA ESTUDIANTES DEMO (Ana, Carlos, María, Luis) + -- ===================================================== + + -- Ana García (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000004-0000-0000-0000-000000000001'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + -- Carlos Ramírez (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000005-0000-0000-0000-000000000001'::uuid, + '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + -- María Fernanda (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000006-0000-0000-0000-000000000001'::uuid, + '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + -- Luis Miguel (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000007-0000-0000-0000-000000000001'::uuid, + '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + RAISE NOTICE '✅ Roles student asignados a estudiantes demo'; + + -- ===================================================== + -- 5. ROL TEACHER para Laura Martínez (profesora demo) + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000008-0000-0000-0000-000000000001'::uuid, + '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, + v_tenant_id, + 'admin_teacher', + '{"read": true, "write": true, "analytics": true, "manage_students": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + RAISE NOTICE '✅ Rol admin_teacher asignado a Laura Martínez'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== RESUMEN DE ROLES ASIGNADOS ==='; + RAISE NOTICE 'Total roles insertados: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE tenant_id = v_tenant_id); + RAISE NOTICE 'Super Admins: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE role = 'super_admin' AND is_active = true); + RAISE NOTICE 'Admin Teachers: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE role = 'admin_teacher' AND is_active = true); + RAISE NOTICE 'Students: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE role = 'student' AND is_active = true); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +BEGIN + IF (SELECT COUNT(*) FROM auth_management.user_roles) < 3 THEN + RAISE WARNING '⚠️ Se esperaban al menos 3 registros en user_roles'; + ELSE + RAISE NOTICE '✅ Seed de user_roles completado exitosamente'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/auth_management/08-assign-admin-schools.sql b/projects/gamilit/apps/database/seeds/dev/auth_management/08-assign-admin-schools.sql new file mode 100644 index 0000000..974d00f --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/auth_management/08-assign-admin-schools.sql @@ -0,0 +1,149 @@ +-- ===================================================== +-- Seed: auth_management.profiles - Assign School to ALL Users (PROD) +-- Description: Asigna la escuela default a TODOS los usuarios sin escuela +-- Environment: PRODUCTION +-- Dependencies: +-- - social_features.schools (00-schools-default.sql) +-- - auth_management.profiles (04-profiles-complete.sql) +-- Order: 08 (debe ejecutarse DESPUÉS de profiles y schools) +-- Created: 2025-12-15 +-- Updated: 2025-12-15 - Expandido a TODOS los usuarios +-- Version: 2.0 +-- ===================================================== +-- +-- PROPÓSITO: +-- Asegurar que TODOS los usuarios tengan una escuela asignada +-- (school_id NOT NULL) apuntando a la escuela default. +-- +-- USUARIOS AFECTADOS: +-- - Todos los roles: super_admin, admin_teacher, student, parent +-- - Cualquier usuario nuevo sin school_id +-- +-- DECISIÓN DE DISEÑO (v2.0): +-- - TODOS los usuarios van a la escuela default +-- - El admin puede reasignarlos desde la UI a otras escuelas +-- - Esto garantiza integridad referencial +-- +-- IMPORTANTE: Este seed es idempotente - solo actualiza si school_id IS NULL +-- ===================================================== + +SET search_path TO auth_management, social_features, public; + +-- ===================================================== +-- Asignar escuela default a TODOS los usuarios sin escuela +-- ===================================================== + +DO $$ +DECLARE + v_default_school_id UUID; + v_total_users INTEGER; + v_users_without_school INTEGER; + v_affected_count INTEGER; + rec RECORD; +BEGIN + -- Obtener la escuela default del sistema + SELECT id INTO v_default_school_id + FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED' + AND is_active = true + LIMIT 1; + + IF v_default_school_id IS NULL THEN + RAISE EXCEPTION 'Escuela default (SYSTEM-UNASSIGNED) no encontrada. Ejecutar primero 00-schools-default.sql'; + END IF; + + RAISE NOTICE 'Usando escuela default: %', v_default_school_id; + + -- Contar usuarios totales y sin escuela + SELECT COUNT(*) INTO v_total_users + FROM auth_management.profiles; + + SELECT COUNT(*) INTO v_users_without_school + FROM auth_management.profiles + WHERE school_id IS NULL; + + RAISE NOTICE 'Total usuarios: %', v_total_users; + RAISE NOTICE 'Usuarios sin escuela: %', v_users_without_school; + + -- Actualizar TODOS los usuarios sin escuela asignada + UPDATE auth_management.profiles + SET + school_id = v_default_school_id, + updated_at = gamilit.now_mexico() + WHERE school_id IS NULL; + + GET DIAGNOSTICS v_affected_count = ROW_COUNT; + + -- Reportar resultados + RAISE NOTICE '========================================'; + RAISE NOTICE 'ASIGNACIÓN DE ESCUELA A USUARIOS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Escuela default ID: %', v_default_school_id; + RAISE NOTICE 'Usuarios actualizados: %', v_affected_count; + RAISE NOTICE '========================================'; + + IF v_affected_count > 0 THEN + RAISE NOTICE 'Usuarios ahora tienen escuela asignada por rol:'; + + -- Mostrar conteo por rol + FOR rec IN + SELECT role, COUNT(*) as count + FROM auth_management.profiles + WHERE school_id = v_default_school_id + GROUP BY role + ORDER BY role + LOOP + RAISE NOTICE ' - %: % usuarios', rec.role, rec.count; + END LOOP; + ELSE + RAISE NOTICE 'No hay usuarios sin escuela asignada (todos ya tienen school_id)'; + END IF; + +END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + v_users_without_school INTEGER; + v_total_in_default INTEGER; + rec RECORD; +BEGIN + SELECT COUNT(*) INTO v_users_without_school + FROM auth_management.profiles + WHERE school_id IS NULL; + + SELECT COUNT(*) INTO v_total_in_default + FROM auth_management.profiles p + JOIN social_features.schools s ON p.school_id = s.id + WHERE s.code = 'SYSTEM-UNASSIGNED'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN DE ASIGNACIÓN DE ESCUELAS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Usuarios sin escuela: %', v_users_without_school; + RAISE NOTICE 'Usuarios en escuela default: %', v_total_in_default; + RAISE NOTICE '========================================'; + + IF v_users_without_school = 0 THEN + RAISE NOTICE '✓ Todos los usuarios tienen escuela asignada'; + + -- Mostrar distribución por rol + RAISE NOTICE ''; + RAISE NOTICE 'Distribución por rol en escuela default:'; + FOR rec IN + SELECT p.role, COUNT(*) as count + FROM auth_management.profiles p + JOIN social_features.schools s ON p.school_id = s.id + WHERE s.code = 'SYSTEM-UNASSIGNED' + GROUP BY p.role + ORDER BY p.role + LOOP + RAISE NOTICE ' - %: %', rec.role, rec.count; + END LOOP; + ELSE + RAISE WARNING '⚠ ADVERTENCIA: Hay % usuarios sin escuela asignada', v_users_without_school; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/content_management/02-marie_curie_content.sql b/projects/gamilit/apps/database/seeds/dev/content_management/02-marie_curie_content.sql new file mode 100644 index 0000000..39f8b5a --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/content_management/02-marie_curie_content.sql @@ -0,0 +1,483 @@ +-- ===================================================== +-- Seed: content_management.marie_curie_content +-- Description: Contenido curado sobre Marie Curie - biografía, descubrimientos, legado +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed crea el contenido educativo principal de GAMILIT +-- basado en la vida y obra de Marie Curie. +-- +-- Categorías de contenido (CHECK constraint): +-- - biography: Datos biográficos +-- - discoveries: Descubrimientos científicos +-- - historical_context: Contexto histórico +-- - scientific_method: Metodología científica +-- - radioactivity: Radiactividad +-- - nobel_prizes: Premios Nobel +-- - women_in_science: Mujeres en la ciencia +-- - modern_physics: Física moderna +-- - legacy: Legado +-- ===================================================== + +DO $$ +DECLARE + v_tenant_id UUID; + v_admin_id UUID; +BEGIN + -- Obtener tenant principal (puede ser 'GAMILIT Platform' o 'GAMILIT Principal') + SELECT id INTO v_tenant_id FROM auth_management.tenants + WHERE name LIKE 'GAMILIT%' OR id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid + LIMIT 1; + + -- Admin para created_by + v_admin_id := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid; + + IF v_tenant_id IS NULL THEN + -- Usar UUID por defecto si no se encuentra + v_tenant_id := 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid; + RAISE NOTICE 'Usando tenant por defecto: %', v_tenant_id; + END IF; + + RAISE NOTICE 'Creando contenido de Marie Curie...'; + + -- ===================================================== + -- 1. BIOGRAFÍA - PRIMEROS AÑOS + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000001'::uuid, + v_tenant_id, + 'Marie Curie: Primeros Años en Polonia', + 'El nacimiento de una mente brillante', + 'Explora la infancia y juventud de Maria Sklodowska en Varsovia, Polonia, su temprano amor por el aprendizaje y los desafíos que enfrentó como mujer en busca de educación en el siglo XIX.', + 'biography', + '{ + "introduction": "Maria Salomea Sklodowska nació el 7 de noviembre de 1867 en Varsovia, Polonia, cuando el país estaba bajo el dominio del Imperio Ruso.", + "main_content": "Desde muy joven, Maria demostró una extraordinaria capacidad intelectual. Era la menor de cinco hermanos en una familia de educadores. Su padre, Wladyslaw, era profesor de matemáticas y física, y su madre, Bronislawa, dirigía una prestigiosa escuela para niñas. La educación era profundamente valorada en la familia Sklodowski.", + "key_points": [ + "Nació el 7 de noviembre de 1867 en Varsovia, Polonia", + "Era la menor de cinco hermanos", + "Su padre era profesor de matemáticas y física", + "Desde niña mostró memoria excepcional y amor por el aprendizaje", + "Aprendió a leer a los 4 años" + ], + "timeline": [ + {"year": 1867, "event": "Nacimiento en Varsovia"}, + {"year": 1876, "event": "Muerte de su hermana Zofia por tifus"}, + {"year": 1878, "event": "Muerte de su madre por tuberculosis"}, + {"year": 1883, "event": "Graduación con medalla de oro"} + ], + "quotes": [ + {"text": "Nada en la vida debe ser temido, solo debe ser entendido.", "context": "Sobre el conocimiento científico"} + ] + }'::jsonb, + ARRAY['6', '7', '8'], + 'beginner', + ARRAY['Conocer los orígenes de Marie Curie', 'Comprender el contexto histórico de Polonia', 'Identificar influencias familiares en su educación'], + ARRAY['Polonia', 'Varsovia', 'Imperio Ruso', 'educación', 'familia Sklodowski'], + '1867-1891', + 'Biography', + 'published', + true, + ARRAY['marie curie', 'biografía', 'polonia', 'infancia', 'educación'], + ARRAY['#marie-curie', '#biografia', '#primeros-años', '#polonia'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Biografía: Primeros Años en Polonia'; + + -- ===================================================== + -- 2. BIOGRAFÍA - LLEGADA A PARÍS + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000002'::uuid, + v_tenant_id, + 'El Sueño de París: Marie en la Sorbona', + 'Persiguiendo la educación contra todo pronóstico', + 'La determinación de Marie para estudiar en la Universidad de París y sus primeros años como estudiante de física y matemáticas en la Sorbona.', + 'biography', + '{ + "introduction": "En 1891, a los 24 años, Maria Sklodowska finalmente llegó a París para cumplir su sueño de estudiar ciencias en la prestigiosa Universidad de la Sorbona.", + "main_content": "Marie vivía en condiciones muy humildes en el Barrio Latino. Su pequeña habitación en el sexto piso no tenía calefacción ni agua corriente. A menudo se olvidaba de comer, tan absorta estaba en sus estudios. A pesar de las dificultades económicas y el frío parisino, Marie se graduó primera de su promoción en física en 1893, y segunda en matemáticas en 1894.", + "key_points": [ + "Llegó a París en noviembre de 1891", + "Se inscribió en la Facultad de Ciencias de la Sorbona", + "Cambió su nombre a Marie", + "Primera de su promoción en física (1893)", + "Segunda en matemáticas (1894)" + ], + "timeline": [ + {"year": 1891, "event": "Llegada a París e inscripción en la Sorbona"}, + {"year": 1893, "event": "Licenciatura en Física (1ª de su promoción)"}, + {"year": 1894, "event": "Licenciatura en Matemáticas"}, + {"year": 1894, "event": "Conoce a Pierre Curie"} + ], + "quotes": [ + {"text": "La vida no es fácil para ninguno de nosotros. Pero... ¡qué importa! Debemos tener perseverancia.", "context": "Sobre su tiempo en París"} + ] + }'::jsonb, + ARRAY['6', '7', '8'], + 'beginner', + ARRAY['Valorar la perseverancia académica', 'Comprender las barreras para mujeres en educación', 'Conocer la vida universitaria del siglo XIX'], + ARRAY['Sorbona', 'París', 'universidad', 'Barrio Latino', 'física', 'matemáticas'], + '1891-1894', + 'Biography', + 'published', + true, + ARRAY['marie curie', 'sorbona', 'paris', 'universidad', 'educación'], + ARRAY['#marie-curie', '#sorbona', '#paris', '#universidad'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Biografía: Llegada a París'; + + -- ===================================================== + -- 3. DESCUBRIMIENTOS - RADIACTIVIDAD + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000003'::uuid, + v_tenant_id, + 'El Descubrimiento de la Radiactividad', + 'Una nueva era en la física', + 'El revolucionario trabajo de Marie Curie que llevó al descubrimiento del polonio y el radio, y la acuñación del término "radiactividad".', + 'discoveries', + '{ + "introduction": "En 1898, Marie Curie descubrió dos nuevos elementos químicos y acuñó el término radiactividad para describir la emisión espontánea de radiación por ciertos materiales.", + "main_content": "Marie eligió estudiar los misteriosos rayos de uranio descubiertos por Henri Becquerel para su tesis doctoral. Trabajando en un cobertizo frío y húmedo en la Escuela de Física, desarrolló técnicas innovadoras para medir la intensidad de la radiación. Descubrió que el mineral pechblenda era más radiactivo que el uranio puro, lo que sugería la presencia de elementos desconocidos.", + "key_points": [ + "Acuñó el término ''radiactividad'' en 1898", + "Descubrió el polonio (nombrado en honor a Polonia)", + "Descubrió el radio (del latín radius, rayo)", + "Trabajó en condiciones extremadamente difíciles", + "Procesó toneladas de pechblenda para aislar el radio" + ], + "timeline": [ + {"year": 1897, "event": "Inicio de investigación sobre rayos de uranio"}, + {"year": 1898, "event": "Descubrimiento del polonio (julio)"}, + {"year": 1898, "event": "Descubrimiento del radio (diciembre)"}, + {"year": 1902, "event": "Aislamiento de radio puro"}, + {"year": 1903, "event": "Tesis doctoral sobre sustancias radiactivas"} + ], + "quotes": [ + {"text": "Uno nunca nota lo que se ha hecho; uno solo puede ver lo que queda por hacer.", "context": "Sobre el trabajo científico"} + ] + }'::jsonb, + ARRAY['7', '8', '9'], + 'intermediate', + ARRAY['Comprender el concepto de radiactividad', 'Conocer el proceso del descubrimiento científico', 'Valorar la metodología de investigación'], + ARRAY['radiactividad', 'polonio', 'radio', 'pechblenda', 'uranio', 'elementos químicos'], + '1897-1903', + 'Physics', + 'published', + true, + ARRAY['radiactividad', 'polonio', 'radio', 'descubrimiento', 'física'], + ARRAY['#radiactividad', '#polonio', '#radio', '#descubrimiento'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Descubrimientos: Radiactividad'; + + -- ===================================================== + -- 4. PREMIOS NOBEL + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000004'::uuid, + v_tenant_id, + 'Dos Premios Nobel: Un Logro Histórico', + 'La primera persona en ganar dos Premios Nobel', + 'Marie Curie hizo historia al ser la primera mujer en ganar un Premio Nobel y la primera persona en ganar dos en diferentes disciplinas científicas.', + 'nobel_prizes', + '{ + "introduction": "Marie Curie es la única persona en la historia en recibir Premios Nobel en dos ciencias diferentes: Física (1903) y Química (1911).", + "main_content": "En 1903, Marie compartió el Premio Nobel de Física con su esposo Pierre y Henri Becquerel por sus investigaciones sobre radiactividad. Inicialmente, el comité solo había nominado a Pierre y Becquerel, pero Pierre insistió en que Marie fuera incluida. En 1911, recibió el Premio Nobel de Química en solitario por el descubrimiento del polonio y el radio.", + "key_points": [ + "Primera mujer en ganar un Premio Nobel (1903)", + "Premio Nobel de Física 1903 (compartido)", + "Premio Nobel de Química 1911 (individual)", + "Única persona con Nobel en dos ciencias diferentes", + "Primera mujer en ser profesora en la Sorbona" + ], + "timeline": [ + {"year": 1903, "event": "Premio Nobel de Física (con Pierre y Becquerel)"}, + {"year": 1906, "event": "Sucede a Pierre como profesora en la Sorbona"}, + {"year": 1911, "event": "Premio Nobel de Química (individual)"} + ], + "quotes": [ + {"text": "Soy de las que piensan que la ciencia tiene una gran belleza.", "context": "Sobre su pasión por la ciencia"} + ] + }'::jsonb, + ARRAY['7', '8', '9'], + 'intermediate', + ARRAY['Conocer los logros históricos de Marie Curie', 'Comprender la importancia de los Premios Nobel', 'Valorar el reconocimiento científico'], + ARRAY['Premio Nobel', 'Física', 'Química', 'Estocolmo', 'reconocimiento científico'], + '1903-1911', + 'Physics', + 'published', + true, + ARRAY['premio nobel', 'física', 'química', 'historia', 'logros'], + ARRAY['#nobel', '#premio', '#historia', '#logros'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Premios Nobel'; + + -- ===================================================== + -- 5. MUJERES EN LA CIENCIA + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000005'::uuid, + v_tenant_id, + 'Rompiendo Barreras: Marie Curie y las Mujeres en la Ciencia', + 'Un legado de inspiración', + 'El impacto de Marie Curie como pionera para las mujeres en la ciencia y su lucha contra los prejuicios de género en la academia.', + 'women_in_science', + '{ + "introduction": "Marie Curie no solo hizo contribuciones monumentales a la ciencia, sino que abrió puertas para las mujeres en campos tradicionalmente dominados por hombres.", + "main_content": "En una época donde las mujeres tenían acceso limitado a la educación superior, Marie tuvo que superar innumerables obstáculos. Fue la primera mujer en obtener un doctorado en ciencias en Francia, la primera profesora en la Sorbona, y la primera mujer en ser enterrada en el Panteón de París por méritos propios. Su hija Irène continuó su legado y también ganó el Premio Nobel.", + "key_points": [ + "Primera mujer con doctorado en ciencias en Francia", + "Primera profesora de la Universidad de París", + "Primera mujer enterrada en el Panteón por méritos propios", + "Su hija Irène también ganó el Premio Nobel (1935)", + "Inspiración para generaciones de científicas" + ], + "timeline": [ + {"year": 1903, "event": "Primera mujer en ganar un Premio Nobel"}, + {"year": 1906, "event": "Primera profesora en la Sorbona"}, + {"year": 1995, "event": "Primera mujer enterrada en el Panteón por méritos propios"} + ], + "quotes": [ + {"text": "Sé menos curioso sobre las personas y más curioso sobre las ideas.", "context": "Consejo a jóvenes científicos"} + ] + }'::jsonb, + ARRAY['6', '7', '8', '9'], + 'beginner', + ARRAY['Valorar la lucha por la igualdad de género', 'Conocer barreras históricas para mujeres', 'Inspirarse en el legado de Marie Curie'], + ARRAY['igualdad', 'mujeres', 'ciencia', 'pionera', 'Irène Joliot-Curie'], + '1867-presente', + 'History of Science', + 'published', + true, + ARRAY['mujeres', 'ciencia', 'igualdad', 'pionera', 'inspiración'], + ARRAY['#mujeres-en-ciencia', '#igualdad', '#pionera', '#inspiración'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Mujeres en la Ciencia'; + + -- ===================================================== + -- 6. LEGADO + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000006'::uuid, + v_tenant_id, + 'El Legado Eterno de Marie Curie', + 'Una vida dedicada a la ciencia y la humanidad', + 'El impacto duradero de Marie Curie en la ciencia, la medicina y la sociedad, desde el tratamiento del cáncer hasta la inspiración de futuras generaciones.', + 'legacy', + '{ + "introduction": "El legado de Marie Curie trasciende sus descubrimientos científicos: sus contribuciones salvaron millones de vidas y continúan inspirando a científicos de todo el mundo.", + "main_content": "Durante la Primera Guerra Mundial, Marie organizó unidades móviles de rayos X (''petites Curies'') para ayudar a los médicos del frente a localizar balas y metralla en los cuerpos de los soldados. Ella misma condujo estas unidades y entrenó a otras mujeres para operarlas. El Instituto del Radio que fundó en París (hoy Instituto Curie) sigue siendo un centro líder en investigación del cáncer.", + "key_points": [ + "Fundó el Instituto del Radio (hoy Instituto Curie)", + "Creó unidades móviles de rayos X en la WWI", + "Sus investigaciones llevaron a tratamientos contra el cáncer", + "El elemento 96 (Curio) lleva su nombre", + "Inspiración para generaciones de científicos" + ], + "timeline": [ + {"year": 1914, "event": "Organización de unidades de rayos X en WWI"}, + {"year": 1921, "event": "Visita a EE.UU. - Recibe 1g de radio"}, + {"year": 1934, "event": "Fallecimiento por anemia aplásica"}, + {"year": 1944, "event": "Elemento 96 (Curio) nombrado en su honor"}, + {"year": 1995, "event": "Traslado al Panteón de París"} + ], + "quotes": [ + {"text": "En la vida no hay cosas que temer, solo cosas que comprender.", "context": "Su filosofía de vida"} + ] + }'::jsonb, + ARRAY['7', '8', '9'], + 'intermediate', + ARRAY['Comprender el impacto de la ciencia en la sociedad', 'Valorar las contribuciones humanitarias', 'Conocer aplicaciones médicas de la radiactividad'], + ARRAY['legado', 'Instituto Curie', 'rayos X', 'medicina', 'cáncer', 'Primera Guerra Mundial'], + '1914-presente', + 'Medicine', + 'published', + true, + ARRAY['legado', 'instituto curie', 'medicina', 'rayos x', 'humanidad'], + ARRAY['#legado', '#instituto-curie', '#medicina', '#humanidad'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Legado'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== CONTENIDO MARIE CURIE CREADO ==='; + RAISE NOTICE 'Total artículos: %', (SELECT COUNT(*) FROM content_management.marie_curie_content); + RAISE NOTICE 'Publicados: %', (SELECT COUNT(*) FROM content_management.marie_curie_content WHERE status = 'published'); + RAISE NOTICE 'Destacados: %', (SELECT COUNT(*) FROM content_management.marie_curie_content WHERE is_featured = true); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM content_management.marie_curie_content; + + IF v_count < 5 THEN + RAISE WARNING '⚠️ Se esperaban al menos 5 artículos de Marie Curie'; + ELSE + RAISE NOTICE '✅ Seed de marie_curie_content completado exitosamente'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/educational_content/05-assignments.sql b/projects/gamilit/apps/database/seeds/dev/educational_content/05-assignments.sql new file mode 100644 index 0000000..b5a16ec --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/educational_content/05-assignments.sql @@ -0,0 +1,317 @@ +-- ===================================================== +-- Seed: educational_content.assignments (PROD) +-- Description: Assignments demo para Portal Teacher +-- Environment: PRODUCTION +-- Dependencies: auth.users (teachers) +-- Order: 05 +-- Created: 2025-11-24 +-- Version: 2.0 (Corregido CORR-006) +-- ===================================================== +-- +-- CAMBIOS v2.0: +-- - Corregida estructura para coincidir con DDL real de assignments +-- - Eliminadas referencias a tablas inexistentes (assignment_classrooms, assignment_exercises) +-- - Ajustado a columnas reales: teacher_id, title, description, assignment_type, due_date, total_points, is_published +-- - 9 assignments distribuidos en 3 módulos conceptuales +-- - Fechas variadas: past (vencidos), present (activos), future (pendientes) +-- - Tipos variados: practice, quiz, exam, homework +-- +-- ASSIGNMENTS INCLUIDOS: +-- - 3 para conceptos del Módulo 1 (Comprensión Literal) +-- - 3 para conceptos del Módulo 2 (Comprensión Inferencial) +-- - 3 para conceptos del Módulo 3 (Comprensión Crítica) +-- +-- TOTAL: 9 assignments demo para Portal Teacher +-- +-- ===================================================== + +SET search_path TO educational_content, auth, public; + +-- ===================================================== +-- LIMPIAR DATOS EXISTENTES (SOLO DEMO) +-- ===================================================== +-- Eliminar assignments del teacher demo si existen +DELETE FROM educational_content.assignments +WHERE teacher_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'; + +-- ===================================================== +-- Obtener IDs necesarios y validar dependencias +-- ===================================================== + +DO $$ +DECLARE + v_teacher_id UUID; +BEGIN + -- Obtener el ID del profesor de testing + SELECT id INTO v_teacher_id + FROM auth.users + WHERE email = 'teacher@gamilit.com' + LIMIT 1; + + IF v_teacher_id IS NULL THEN + RAISE EXCEPTION 'Teacher "teacher@gamilit.com" no encontrado. Ejecutar primero seed de auth/users.'; + END IF; + + RAISE NOTICE 'Usando teacher_id: %', v_teacher_id; + +-- ===================================================== +-- INSERT: 9 Assignments Demo +-- ===================================================== + +INSERT INTO educational_content.assignments ( + id, + teacher_id, + title, + description, + assignment_type, + due_date, + total_points, + is_published, + created_at, + updated_at +) VALUES + +-- ===================================================== +-- MÓDULO 1: Comprensión Literal (3 assignments) +-- ===================================================== + +-- Assignment 1.1: Completado (vencido hace 7 días) +( + gen_random_uuid(), + v_teacher_id, + 'Tarea 1.1: Crucigrama y Vocabulario Científico', + 'Completa el crucigrama sobre términos científicos de Marie Curie y responde 5 preguntas de vocabulario. Incluye los ejercicios: Crucigrama Científico y Sopa de Letras. Esta tarea evaluará tu comprensión literal de los descubrimientos científicos de Marie Curie.', + 'homework', + gamilit.now_mexico() - INTERVAL '7 days', -- Vencido hace 7 días + 100, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '14 days', + gamilit.now_mexico() - INTERVAL '14 days' +), + +-- Assignment 1.2: Activo (vence en 2 días - URGENTE) +( + gen_random_uuid(), + v_teacher_id, + 'Quiz 1.2: Línea de Tiempo de Marie Curie', + 'Organiza cronológicamente los eventos más importantes de la vida de Marie Curie. Este quiz evaluará tu capacidad para identificar fechas y secuencias temporales del texto biográfico. Duración: 30 minutos.', + 'quiz', + gamilit.now_mexico() + INTERVAL '2 days', -- Vence en 2 días + 50, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '5 days', + gamilit.now_mexico() - INTERVAL '5 days' +), + +-- Assignment 1.3: Pendiente (vence en 10 días) +( + gen_random_uuid(), + v_teacher_id, + 'Práctica 1.3: Mapa Conceptual - Descubrimientos', + 'Crea un mapa conceptual que conecte a Marie Curie con sus descubrimientos científicos, instituciones y colaboradores. Esta práctica te permitirá visualizar las relaciones entre conceptos del módulo literal.', + 'practice', + gamilit.now_mexico() + INTERVAL '10 days', -- Vence en 10 días + 75, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '2 days', + gamilit.now_mexico() - INTERVAL '2 days' +), + +-- ===================================================== +-- MÓDULO 2: Comprensión Inferencial (3 assignments) +-- ===================================================== + +-- Assignment 2.1: OVERDUE (vencido hace 3 días, aún publicado) +( + gen_random_uuid(), + v_teacher_id, + 'Tarea 2.1: Relaciones Causa-Efecto', + 'Identifica 3 relaciones causa-efecto en la vida de Marie Curie. Por ejemplo: la muerte de su madre → Marie se dedicó intensamente a los estudios. Debes encontrar al menos 3 ejemplos bien argumentados del texto.', + 'homework', + gamilit.now_mexico() - INTERVAL '3 days', -- OVERDUE hace 3 días + 120, + true, -- Publicado (aún pueden entregarla tarde) + gamilit.now_mexico() - INTERVAL '10 days', + gamilit.now_mexico() - INTERVAL '10 days' +), + +-- Assignment 2.2: Activo (vence en 5 días) +( + gen_random_uuid(), + v_teacher_id, + 'Quiz 2.2: Rueda de Inferencias', + 'Resuelve 5 preguntas de inferencia sobre las motivaciones y decisiones de Marie Curie. Usa la Rueda de Inferencias para analizar contextos implícitos del texto. Duración: 45 minutos.', + 'quiz', + gamilit.now_mexico() + INTERVAL '5 days', -- Vence en 5 días + 100, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '3 days', + gamilit.now_mexico() - INTERVAL '3 days' +), + +-- Assignment 2.3: Pendiente (vence en 15 días) +( + gen_random_uuid(), + v_teacher_id, + 'Práctica 2.3: Análisis de Decisiones', + 'Analiza 3 decisiones importantes de Marie Curie (ejemplo: rechazar comercializar el radio) y explica las razones implícitas detrás de cada una. Usa evidencia del texto para respaldar tus inferencias.', + 'practice', + gamilit.now_mexico() + INTERVAL '15 days', -- Vence en 15 días + 150, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '1 day', + gamilit.now_mexico() - INTERVAL '1 day' +), + +-- ===================================================== +-- MÓDULO 3: Comprensión Crítica (3 assignments) +-- ===================================================== + +-- Assignment 3.1: Activo (vence en 7 días) +( + gen_random_uuid(), + v_teacher_id, + 'Tarea 3.1: Ensayo Crítico - Rol de la Mujer en Ciencia', + 'Escribe un ensayo corto (300-400 palabras) sobre cómo Marie Curie desafió los roles de género de su época. Incluye 3 argumentos fundamentados en el texto y 1 reflexión personal sobre la importancia de su legado.', + 'homework', + gamilit.now_mexico() + INTERVAL '7 days', -- Vence en 7 días + 200, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '4 days', + gamilit.now_mexico() - INTERVAL '4 days' +), + +-- Assignment 3.2: Activo (vence en 3 días - URGENTE, quiz corto) +( + gen_random_uuid(), + v_teacher_id, + 'Quiz 3.2: Evaluación Crítica Express', + 'Quiz corto (15 minutos) con 3 preguntas de evaluación crítica sobre las decisiones éticas de Marie Curie. Ejemplo: ¿Fue correcto que no patentara el proceso de extracción del radio?', + 'quiz', + gamilit.now_mexico() + INTERVAL '3 days', -- Vence en 3 días + 50, + true, -- Publicado + gamilit.now_mexico() - INTERVAL '1 day', + gamilit.now_mexico() - INTERVAL '1 day' +), + +-- Assignment 3.3: Pendiente (vence en 30 días - proyecto final) +( + gen_random_uuid(), + v_teacher_id, + 'Proyecto Final: Presentación Multimedia sobre Marie Curie', + 'Crea una presentación multimedia (video, podcast o infografía) que analice críticamente el impacto de Marie Curie en la ciencia moderna y la igualdad de género. Debe incluir: biografía, descubrimientos, obstáculos superados y legado actual. Duración: 5-7 minutos.', + 'exam', + gamilit.now_mexico() + INTERVAL '30 days', -- Vence en 30 días (proyecto final) + 300, + false, -- Borrador (aún no publicado) + gamilit.now_mexico() - INTERVAL '1 day', + gamilit.now_mexico() - INTERVAL '1 day' +) + +ON CONFLICT (id) DO NOTHING; + +END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + assignment_count INTEGER; + published_count INTEGER; + overdue_count INTEGER; + soon_count INTEGER; + future_count INTEGER; +BEGIN + -- Contar assignments totales + SELECT COUNT(*) INTO assignment_count + FROM educational_content.assignments; + + -- Contar publicados + SELECT COUNT(*) INTO published_count + FROM educational_content.assignments + WHERE is_published = true; + + -- Contar OVERDUE (vencidos y publicados) + SELECT COUNT(*) INTO overdue_count + FROM educational_content.assignments + WHERE due_date < gamilit.now_mexico() AND is_published = true; + + -- Contar SOON (vencen en menos de 3 días) + SELECT COUNT(*) INTO soon_count + FROM educational_content.assignments + WHERE due_date < gamilit.now_mexico() + INTERVAL '3 days' + AND due_date > gamilit.now_mexico() + AND is_published = true; + + -- Contar FUTURE (vencen en más de 3 días) + SELECT COUNT(*) INTO future_count + FROM educational_content.assignments + WHERE due_date > gamilit.now_mexico() + INTERVAL '3 days' + AND is_published = true; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'ASSIGNMENTS DEMO CREADOS EXITOSAMENTE'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total assignments: %', assignment_count; + RAISE NOTICE ' - Publicados: %', published_count; + RAISE NOTICE ' - Borradores: %', assignment_count - published_count; + RAISE NOTICE ''; + RAISE NOTICE 'Estado de assignments publicados:'; + RAISE NOTICE ' - OVERDUE (vencidos): %', overdue_count; + RAISE NOTICE ' - SOON (vencen <3 días): %', soon_count; + RAISE NOTICE ' - FUTURE (vencen >3 días): %', future_count; + RAISE NOTICE '========================================'; + + IF assignment_count >= 9 THEN + RAISE NOTICE '✓ Assignments demo creados correctamente'; + ELSE + RAISE WARNING '⚠ Se esperaban al menos 9 assignments, se crearon %', assignment_count; + END IF; +END $$; + +-- ===================================================== +-- Listado de assignments por tipo y urgencia +-- ===================================================== + +DO $$ +DECLARE + assignment_record RECORD; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Listado de assignments demo:'; + RAISE NOTICE '========================================'; + + FOR assignment_record IN + SELECT + a.title, + a.assignment_type, + a.due_date, + a.total_points, + a.is_published, + CASE + WHEN a.due_date < gamilit.now_mexico() AND a.is_published THEN 'OVERDUE' + WHEN a.due_date < gamilit.now_mexico() + INTERVAL '3 days' AND a.due_date > gamilit.now_mexico() THEN 'SOON' + WHEN NOT a.is_published THEN 'DRAFT' + ELSE 'FUTURE' + END AS urgency, + TO_CHAR(a.due_date, 'YYYY-MM-DD HH24:MI') AS due_formatted + FROM educational_content.assignments a + ORDER BY a.due_date NULLS LAST + LOOP + RAISE NOTICE ' [%] % - % (% pts) - Vence: %', + assignment_record.urgency, + assignment_record.title, + assignment_record.assignment_type, + assignment_record.total_points, + COALESCE(assignment_record.due_formatted, 'Sin fecha'); + END LOOP; + + RAISE NOTICE '========================================'; +END $$; + +-- ===================================================== +-- FIN DEL SEED +-- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/dev/educational_content/05-exercises-module4.sql b/projects/gamilit/apps/database/seeds/dev/educational_content/05-exercises-module4.sql index 407f743..c998f19 100644 --- a/projects/gamilit/apps/database/seeds/dev/educational_content/05-exercises-module4.sql +++ b/projects/gamilit/apps/database/seeds/dev/educational_content/05-exercises-module4.sql @@ -1,12 +1,18 @@ -- ===================================================== --- Seed Data: Exercises Module 4 - Textos Digitales (PRODUCTION) +-- Seed Data: Exercises Module 4 - Lectura Digital y Multimodal (DEV) -- ===================================================== --- Description: 9 ejercicios completos del Módulo 4 +-- Description: 5 ejercicios oficiales del Módulo 4 (según DocumentoDeDiseño v6.4) -- Module: MOD-04-DIGITAL --- Source: Migrado desde /home/isem/workspace/projects/glit/database --- Date: 2025-11-03 --- Migration: ATLAS-DATABASE - ANALISIS-PRE-CORRECCIONES-BD-ORIGEN.md --- Changes: Reemplazó 3 ejercicios compactos con 9 ejercicios completos +-- Exercises: +-- 4.1 Verificador Fake News +-- 4.2 Infografía Interactiva +-- 4.3 Quiz TikTok +-- 4.4 Navegación Hipertextual +-- 4.5 Análisis Memes +-- Reference: DocumentoDeDiseño_Mecanicas_GAMILIT_v6_1.md líneas 782-965 +-- Date: 2025-12-18 (Limpieza: eliminados 4 ejercicios no oficiales) +-- NOTA: Ejercicios 4.6-4.9 (resena_critica, chat_literario, email_formal, +-- ensayo_argumentativo) fueron eliminados por no estar en el documento de diseño -- ===================================================== SET search_path TO educational_content, public; @@ -151,6 +157,7 @@ BEGIN ); -- Exercise 4.3: Navegación Hipertextual + -- Estructura: nodes[] con id, title, content, links[{targetId, label}] INSERT INTO educational_content.exercises ( module_id, title, subtitle, description, instructions, exercise_type, order_index, @@ -173,26 +180,58 @@ BEGIN }'::jsonb, '{ "researchQuestion": "¿Qué experimentos realizó Marie Curie para aislar el radio?", - "mainArticle": { - "title": "Marie Curie: Pionera de la Radiactividad", - "paragraphs": [ - "Marie Curie revolucionó la ciencia con sus descubrimientos en radiactividad...", - "Trabajó intensamente en el aislamiento de elementos radiactivos..." - ], - "links": [ - { - "text": "radiactividad", - "relevance": "high", - "leadsTo": "Historia de la radiactividad" - }, - { - "text": "aislamiento", - "relevance": "very high", - "leadsTo": "Proceso de aislamiento del radio" - } - ] - }, - "optimalPath": ["mainArticle", "aislamiento", "proceso experimental"] + "nodes": [ + { + "id": "main-article", + "title": "Marie Curie: Pionera de la Radiactividad", + "content": "Marie Curie (1867-1934) fue una científica polaca-francesa que revolucionó nuestra comprensión de la física y la química. Junto con su esposo Pierre, realizó investigaciones pioneras sobre los fenómenos radiactivos, un término que ella misma acuñó.\n\nSus descubrimientos en radiactividad cambiaron para siempre el campo de la física nuclear. Trabajó intensamente durante años en el aislamiento de elementos radiactivos, un proceso que requirió una dedicación extraordinaria.", + "links": [ + { "targetId": "radiactividad", "label": "descubrimientos en radiactividad" }, + { "targetId": "aislamiento", "label": "aislamiento de elementos radiactivos" }, + { "targetId": "premios", "label": "reconocimientos y premios" } + ] + }, + { + "id": "radiactividad", + "title": "Historia de la Radiactividad", + "content": "El término ''radiactividad'' fue acuñado por Marie Curie en 1898. Henri Becquerel había descubierto en 1896 que las sales de uranio emitían rayos que podían impresionar placas fotográficas.\n\nMarie Curie decidió estudiar este fenómeno como tema de su tesis doctoral. Descubrió que la radiactividad era una propiedad atómica, no molecular, lo que fue revolucionario para la física de la época.", + "links": [ + { "targetId": "main-article", "label": "volver al artículo principal" }, + { "targetId": "aislamiento", "label": "proceso de aislamiento" } + ] + }, + { + "id": "aislamiento", + "title": "El Proceso de Aislamiento del Radio", + "content": "El aislamiento del radio fue uno de los logros más impresionantes de Marie Curie. Trabajando en condiciones precarias en un cobertizo sin calefacción, procesó toneladas de pechblenda para obtener pequeñas cantidades de radio puro.\n\nEl proceso requirió:\n• Trituración de toneladas de mineral de pechblenda\n• Disolución en ácidos y precipitación química\n• Cristalización fraccionada repetida durante años\n• Mediciones precisas de radiactividad\n\nEn 1902, logró aislar 0.1 gramos de cloruro de radio puro.", + "links": [ + { "targetId": "main-article", "label": "volver al artículo principal" }, + { "targetId": "experimentos", "label": "experimentos específicos" }, + { "targetId": "radiactividad", "label": "qué es la radiactividad" } + ] + }, + { + "id": "experimentos", + "title": "Experimentos de Marie Curie con el Radio", + "content": "Los experimentos de Marie Curie para aislar el radio fueron meticulosos y agotadores:\n\n1. **Análisis de la pechblenda**: Descubrió que era más radiactiva de lo esperado, lo que sugería la presencia de elementos desconocidos.\n\n2. **Separación química**: Usó técnicas de precipitación selectiva para separar diferentes fracciones del mineral.\n\n3. **Cristalización fraccionada**: El proceso más largo. Disolvía cloruros y los cristalizaba repetidamente, separando el radio del bario por sus diferentes solubilidades.\n\n4. **Medición con electrómetro**: Usó un electrómetro piezoeléctrico diseñado por Pierre para medir la radiactividad y seguir el rastro del radio.\n\n¡Este es el objetivo de tu investigación! Has encontrado la información sobre los experimentos.", + "links": [ + { "targetId": "aislamiento", "label": "volver a aislamiento" }, + { "targetId": "premios", "label": "premios recibidos" } + ] + }, + { + "id": "premios", + "title": "Premios y Reconocimientos", + "content": "Marie Curie recibió numerosos reconocimientos por su trabajo:\n\n• **Premio Nobel de Física (1903)**: Compartido con Pierre Curie y Henri Becquerel por sus investigaciones sobre radiación.\n\n• **Premio Nobel de Química (1911)**: Por el descubrimiento del radio y polonio, y por el aislamiento del radio.\n\nFue la primera persona en ganar dos Premios Nobel en diferentes ciencias.", + "links": [ + { "targetId": "main-article", "label": "volver al artículo principal" }, + { "targetId": "aislamiento", "label": "proceso de aislamiento" } + ] + } + ], + "startNodeId": "main-article", + "targetNodeId": "experimentos", + "optimalPath": ["main-article", "aislamiento", "experimentos"] }'::jsonb, '{"informationFound": true, "pathEfficiency": 0.8, "relevantLinks": 3}'::jsonb, 'intermediate', 100, 70, @@ -330,6 +369,5 @@ BEGIN true ); - RAISE NOTICE '✓ Module 4 (Textos Digitales) created with 5 exercises'; -END $$; + RAISE NOTICE '✓ Module 4 (Lectura Digital y Multimodal) created with 5 exercises'; END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/educational_content/10-exercise_validation_config.sql b/projects/gamilit/apps/database/seeds/dev/educational_content/10-exercise_validation_config.sql new file mode 100644 index 0000000..43c14cb --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/educational_content/10-exercise_validation_config.sql @@ -0,0 +1,468 @@ +-- ============================================================================ +-- SEED: exercise_validation_config +-- Descripción: Configuración de validación para 17 tipos de ejercicios +-- Autor: Database Agent +-- Fecha: 2025-11-19 +-- Actualizado: 2025-11-28 (BUG-002, BUG-003 - Agregados mapa_conceptual y emparejamiento) +-- Tarea: DB-116 (Handoff FE-059) +-- Alcance: Módulos 1, 2, 3 (17 tipos implementados) +-- ============================================================================ + +INSERT INTO educational_content.exercise_validation_config ( + exercise_type, + validation_function, + case_sensitive, + allow_partial_credit, + fuzzy_matching_threshold, + normalize_text, + special_rules, + default_max_points, + default_passing_score, + description, + examples +) VALUES + +-- ============================================================================ +-- MÓDULO 1: COMPRENSIÓN LITERAL (7 tipos) +-- ============================================================================ + +-- 1.1. CRUCIGRAMA +( + 'crucigrama', + 'validate_crucigrama', + false, -- No case-sensitive + true, -- Puntuación parcial por respuesta correcta + NULL, -- No fuzzy matching (respuestas exactas) + true, -- Normalizar texto (quitar acentos) + '{ + "allow_empty_cells": false, + "score_per_word": true + }'::jsonb, + 100, + 70, + 'Validación de crucigrama: compara cada respuesta con solution por id (h1, h2, v1, etc.)', + '{ + "submitted": {"clues": {"h1": "sorbona", "h2": "nobel"}}, + "solution": {"clues": {"h1": "SORBONA", "h2": "NOBEL"}}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 1.2. LÍNEA DE TIEMPO +( + 'linea_tiempo', + 'validate_timeline', + false, + true, + NULL, + true, + '{ + "scoring_method": "position_based", + "allow_partial_order": true + }'::jsonb, + 100, + 70, + 'Validación de línea de tiempo: compara orden de eventos', + '{ + "submitted": {"events": ["event-1", "event-2", "event-3"]}, + "solution": {"correctOrder": ["event-1", "event-2", "event-3"]}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 1.3. SOPA DE LETRAS +( + 'sopa_letras', + 'validate_word_search', + false, + true, + NULL, + true, + '{ + "score_per_word_found": true, + "require_all_words": false + }'::jsonb, + 100, + 70, + 'Validación de sopa de letras: verifica palabras encontradas vs esperadas', + '{ + "submitted": {"words": ["MARIE", "CURIE", "POLONIA"]}, + "solution": {"allWords": ["MARIE", "CURIE", "POLONIA", "NOBEL", "RADIO"]}, + "result": {"is_correct": false, "score": 60} + }'::jsonb +), + +-- 1.4. COMPLETAR ESPACIOS +( + 'completar_espacios', + 'validate_fill_in_blank', + false, + true, + 0.85, -- Permitir 85% de similaridad (fuzzy matching) + true, + '{ + "allow_synonyms": false, + "score_per_blank": true + }'::jsonb, + 100, + 70, + 'Validación de completar espacios: compara cada respuesta con correctAnswers por id', + '{ + "submitted": {"blanks": {"blank1": "varsovia", "blank2": "wladyslaw"}}, + "solution": {"correctAnswers": {"blank1": "Varsovia", "blank2": "Władysław"}}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 1.5. VERDADERO O FALSO +( + 'verdadero_falso', + 'validate_true_false', + false, + true, + NULL, + false, + '{ + "score_per_statement": true + }'::jsonb, + 100, + 70, + 'Validación de verdadero/falso: compara objeto de statements', + '{ + "submitted": {"statements": {"stmt1": false, "stmt2": true, "stmt3": true, "stmt4": false}}, + "solution": {"correctAnswers": {"stmt1": false, "stmt2": true, "stmt3": true, "stmt4": false}}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 1.6. MAPA CONCEPTUAL +( + 'mapa_conceptual', + 'validate_mapa_conceptual', + false, + true, + NULL, + false, + '{ + "score_per_connection": true, + "allow_partial_graph": true + }'::jsonb, + 100, + 70, + 'Validación de mapa conceptual: verifica conexiones entre conceptos', + '{ + "submitted": {"connections": [{"from": "concept1", "to": "concept2", "label": "relaciona"}]}, + "solution": {"connections": [{"from": "concept1", "to": "concept2", "label": "relaciona"}]}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 1.7. EMPAREJAMIENTO +( + 'emparejamiento', + 'validate_emparejamiento', + false, + true, + NULL, + true, + '{ + "score_per_match": true, + "allow_partial_matches": true + }'::jsonb, + 100, + 70, + 'Validación de emparejamiento: verifica pares correctos', + '{ + "submitted": {"matches": {"item1": "pair1", "item2": "pair2"}}, + "solution": {"correctMatches": {"item1": "pair1", "item2": "pair2"}}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- ============================================================================ +-- MÓDULO 2: COMPRENSIÓN INFERENCIAL (5 tipos) +-- ============================================================================ + +-- 2.1. DETECTIVE TEXTUAL +( + 'detective_textual', + 'validate_detective_textual', + false, + true, + NULL, + false, + '{ + "allowPartialCredit": true + }'::jsonb, + 100, + 70, + 'Validación de detective textual: preguntas de inferencia con opciones múltiples', + '{ + "submitted": {"questions": {"q1": "1", "q2": "1", "q3": "1", "q4": "1"}}, + "solution": {"correctAnswers": {"q1": "1", "q2": "1", "q3": "1", "q4": "1"}, "totalQuestions": 4}, + "result": {"is_correct": true, "correct_answers": 4, "score": 100} + }'::jsonb +), + +-- 2.2. CONSTRUCCIÓN DE HIPÓTESIS (Causa-Efecto Matching) +( + 'construccion_hipotesis', + 'validate_cause_effect_matching', + false, + true, + NULL, + true, + '{ + "allowPartialMatches": true, + "strictOrder": false + }'::jsonb, + 100, + 70, + 'Validación de causa-efecto: asociación de causas con consecuencias mediante drag & drop', + '{ + "submitted": {"causes": {"c1": ["cons1", "cons2"], "c2": ["cons3"], "c3": ["cons4"]}}, + "solution": {"causes": {"c1": ["cons1", "cons2"], "c2": ["cons3"], "c3": ["cons4"]}}, + "result": {"is_correct": true, "correctCount": 4, "totalCount": 4, "score": 100} + }'::jsonb +), + +-- 2.3. PREDICCIÓN NARRATIVA +( + 'prediccion_narrativa', + 'validate_prediction_scenarios', + false, + true, + NULL, + true, + '{ + "allowPartialCredit": true + }'::jsonb, + 100, + 70, + 'Validación de predicción narrativa: selección de predicciones predefinidas para múltiples escenarios', + '{ + "submitted": {"scenarios": {"pred-1": "p2", "pred-2": "p1"}}, + "solution": {"scenarios": {"pred-1": "p2", "pred-2": "p1"}}, + "result": {"is_correct": true, "correctCount": 2, "score": 100} + }'::jsonb +), + +-- 2.4. PUZZLE DE CONTEXTO +( + 'puzzle_contexto', + 'validate_puzzle_contexto', + false, + true, + NULL, + false, + '{ + "score_per_question": true, + "question_type": "context_inference" + }'::jsonb, + 100, + 70, + 'Validación de puzzle de contexto: multiple choice con inferencias contextuales', + '{ + "submitted": {"questions": {"q1": "option_c", "q2": "option_a"}}, + "solution": {"correctAnswers": {"q1": "option_c", "q2": "option_a"}}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 2.5. RUEDA DE INFERENCIAS (Texto Libre) +( + 'rueda_inferencias', + 'validate_rueda_inferencias', + false, + true, + NULL, + true, + '{ + "validation_type": "keyword_based", + "min_keywords": 2, + "min_length": 20, + "max_length": 200, + "score_per_fragment": true + }'::jsonb, + 100, + 70, + 'Validación de rueda de inferencias con texto libre: wrapper estándar que valida múltiples fragmentos con keywords', + '{ + "submitted": {"fragments": {"frag-1": "Marie Curie fue la primera mujer en ganar el Nobel en física y química", "frag-2": "Ella usaba el apellido Curie en sus publicaciones"}}, + "solution": {"fragments": [{"id": "frag-1", "keywords": ["nobel", "física", "química", "primera", "mujer"], "points": 20}, {"id": "frag-2", "keywords": ["apellido", "curie", "publicaciones"], "points": 20}], "validation": {"minKeywords": 2, "minLength": 20, "maxLength": 200}}, + "result": {"is_correct": true, "valid_fragments": 2, "total_points": 40, "score": 100} + }'::jsonb +), + +-- ============================================================================ +-- MÓDULO 3: COMPRENSIÓN CRÍTICA (5 tipos) +-- ============================================================================ + +-- 3.1. TRIBUNAL DE OPINIONES +( + 'tribunal_opiniones', + 'validate_tribunal_opiniones', + false, + true, + 0.75, + true, + '{ + "min_word_count": 100, + "require_keywords": true, + "keywords": ["argumento", "evidencia", "porque", "razón"], + "rubric_criteria": ["argumentación", "evidencia", "contra-argumentos"] + }'::jsonb, + 100, + 70, + 'Validación heurística de tribunal: verifica longitud y palabras clave de argumentación', + '{ + "submitted": {"opinion": "Opino que Marie Curie fue importante porque..."}, + "solution": {"keywords": ["argumento", "evidencia", "porque"], "min_words": 100}, + "result": {"is_correct": true, "score": 85} + }'::jsonb +), + +-- 3.2. DEBATE DIGITAL +( + 'debate_digital', + 'validate_debate_digital', + false, + true, + 0.75, + true, + '{ + "min_word_count": 150, + "require_keywords": true, + "keywords": ["postura", "evidencia", "argumento", "contra-argumento"], + "rubric_criteria": ["postura_clara", "evidencias", "respeto"] + }'::jsonb, + 100, + 70, + 'Validación heurística de debate: verifica longitud, palabras clave y estructura argumentativa', + '{ + "submitted": {"debate": "Mi postura es que... porque las evidencias muestran..."}, + "solution": {"keywords": ["postura", "evidencia", "argumento"], "min_words": 150}, + "result": {"is_correct": true, "score": 90} + }'::jsonb +), + +-- 3.3. ANÁLISIS DE FUENTES +( + 'analisis_fuentes', + 'validate_analisis_fuentes', + false, + true, + NULL, + false, + '{ + "score_per_question": true, + "question_type": "source_analysis" + }'::jsonb, + 100, + 70, + 'Validación de análisis de fuentes: preguntas de análisis crítico', + '{ + "submitted": {"questions": {"q1": "option_a", "q2": "option_c"}}, + "solution": {"correctAnswers": {"q1": "option_a", "q2": "option_c"}}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 3.4. PODCAST ARGUMENTATIVO +( + 'podcast_argumentativo', + 'validate_podcast_argumentativo', + false, + true, + NULL, + false, + '{ + "check_duration": true, + "min_duration_seconds": 120, + "max_duration_seconds": 300, + "check_format": true, + "allowed_formats": ["mp3", "wav", "ogg"] + }'::jsonb, + 100, + 70, + 'Validación de podcast: verifica duración y formato de audio', + '{ + "submitted": {"audio_url": "https://example.com/podcast.mp3", "duration": 180}, + "solution": {"min_duration": 120, "max_duration": 300}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +), + +-- 3.5. MATRIZ DE PERSPECTIVAS +( + 'matriz_perspectivas', + 'validate_matriz_perspectivas', + false, + true, + 0.75, + true, + '{ + "score_per_cell": true, + "require_all_cells": true, + "min_chars_per_cell": 50 + }'::jsonb, + 100, + 70, + 'Validación de matriz: verifica que todas las celdas estén completadas correctamente', + '{ + "submitted": {"matrix": {"cell1": "perspectiva A", "cell2": "perspectiva B"}}, + "solution": {"required_cells": ["cell1", "cell2"], "min_chars": 50}, + "result": {"is_correct": true, "score": 100} + }'::jsonb +) + +ON CONFLICT (exercise_type) +DO UPDATE SET + validation_function = EXCLUDED.validation_function, + case_sensitive = EXCLUDED.case_sensitive, + allow_partial_credit = EXCLUDED.allow_partial_credit, + fuzzy_matching_threshold = EXCLUDED.fuzzy_matching_threshold, + normalize_text = EXCLUDED.normalize_text, + special_rules = EXCLUDED.special_rules, + default_max_points = EXCLUDED.default_max_points, + default_passing_score = EXCLUDED.default_passing_score, + description = EXCLUDED.description, + examples = EXCLUDED.examples, + updated_at = gamilit.now_mexico(); + +-- ============================================================================ +-- VERIFICACIÓN +-- ============================================================================ + +-- Verificar que se cargaron las 17 configuraciones +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count + FROM educational_content.exercise_validation_config; + + IF v_count < 17 THEN + RAISE WARNING 'Solo se cargaron % configuraciones. Se esperaban 17.', v_count; + ELSE + RAISE NOTICE 'Configuraciones cargadas correctamente: % de 17', v_count; + END IF; +END $$; + +-- Mostrar resumen de configuraciones +SELECT + exercise_type, + validation_function, + CASE + WHEN allow_partial_credit THEN 'Parcial' + ELSE 'Todo/Nada' + END as scoring, + CASE + WHEN fuzzy_matching_threshold IS NOT NULL + THEN fuzzy_matching_threshold::TEXT + ELSE 'Exacto' + END as matching, + default_max_points as max_pts, + default_passing_score as pass_pts +FROM educational_content.exercise_validation_config +ORDER BY exercise_type; diff --git a/projects/gamilit/apps/database/seeds/dev/educational_content/11-module_dependencies.sql b/projects/gamilit/apps/database/seeds/dev/educational_content/11-module_dependencies.sql new file mode 100644 index 0000000..e29de32 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/educational_content/11-module_dependencies.sql @@ -0,0 +1,262 @@ +-- ===================================================== +-- Seed: educational_content.module_dependencies +-- Description: Dependencias entre módulos educativos +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed define las dependencias/prerrequisitos entre módulos. +-- La progresión del estudiante se valida usando estas dependencias. +-- +-- Estructura de módulos GAMILIT: +-- - MOD-01-LITERAL: Comprensión Literal (sin prerrequisitos) +-- - MOD-02-INFERENCIAL: Comprensión Inferencial (requiere MOD-01) +-- - MOD-03-CRITICA: Comprensión Crítica (requiere MOD-02) +-- - MOD-04-DIGITAL: Lectura Digital (requiere MOD-01, recomendado MOD-02) +-- - MOD-05-PRODUCCION: Producción Lectora (requiere MOD-03) +-- +-- Tipos de dependencia: +-- - required: Debe completarse antes (bloqueante) +-- - recommended: Se recomienda completar antes +-- - optional: Complementario, no bloqueante +-- ===================================================== + +DO $$ +DECLARE + v_mod1_id UUID; + v_mod2_id UUID; + v_mod3_id UUID; + v_mod4_id UUID; + v_mod5_id UUID; +BEGIN + -- Obtener IDs de módulos por su código + SELECT id INTO v_mod1_id FROM educational_content.modules WHERE module_code = 'MOD-01-LITERAL'; + SELECT id INTO v_mod2_id FROM educational_content.modules WHERE module_code = 'MOD-02-INFERENCIAL'; + SELECT id INTO v_mod3_id FROM educational_content.modules WHERE module_code = 'MOD-03-CRITICA'; + SELECT id INTO v_mod4_id FROM educational_content.modules WHERE module_code = 'MOD-04-DIGITAL'; + SELECT id INTO v_mod5_id FROM educational_content.modules WHERE module_code = 'MOD-05-PRODUCCION'; + + -- Verificar que todos los módulos existen + IF v_mod1_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-01-LITERAL no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod2_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-02-INFERENCIAL no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod3_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-03-CRITICA no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod4_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-04-DIGITAL no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod5_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-05-PRODUCCION no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + RAISE NOTICE 'Todos los módulos encontrados. Creando dependencias...'; + + -- ===================================================== + -- DEPENDENCIA 1: MOD-02 requiere MOD-01 (100%) + -- Comprensión Inferencial requiere Comprensión Literal + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000001'::uuid, + v_mod2_id, + v_mod1_id, + 'required', + 80 -- 80% del módulo 1 debe completarse + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-02 → MOD-01 (required, 80%%)'; + + -- ===================================================== + -- DEPENDENCIA 2: MOD-03 requiere MOD-02 (100%) + -- Comprensión Crítica requiere Comprensión Inferencial + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000002'::uuid, + v_mod3_id, + v_mod2_id, + 'required', + 80 -- 80% del módulo 2 debe completarse + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-03 → MOD-02 (required, 80%%)'; + + -- ===================================================== + -- DEPENDENCIA 3: MOD-04 requiere MOD-01 (50%) + -- Lectura Digital requiere base de Comprensión Literal + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000003'::uuid, + v_mod4_id, + v_mod1_id, + 'required', + 50 -- 50% del módulo 1 (más flexible para ruta paralela) + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-04 → MOD-01 (required, 50%%)'; + + -- ===================================================== + -- DEPENDENCIA 4: MOD-04 recomienda MOD-02 + -- Se beneficia de habilidades inferenciales pero no es bloqueante + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000004'::uuid, + v_mod4_id, + v_mod2_id, + 'recommended', + 60 -- 60% recomendado pero no obligatorio + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-04 → MOD-02 (recommended, 60%%)'; + + -- ===================================================== + -- DEPENDENCIA 5: MOD-05 requiere MOD-03 (100%) + -- Producción Lectora requiere maestría en Comprensión Crítica + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000005'::uuid, + v_mod5_id, + v_mod3_id, + 'required', + 80 -- 80% del módulo 3 (alto requisito para producción) + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-05 → MOD-03 (required, 80%%)'; + + -- ===================================================== + -- DEPENDENCIA 6: MOD-05 recomienda MOD-04 + -- Habilidades digitales complementan la producción + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000006'::uuid, + v_mod5_id, + v_mod4_id, + 'recommended', + 50 -- 50% recomendado para producción digital + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-05 → MOD-04 (recommended, 50%%)'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== DEPENDENCIAS DE MÓDULOS CREADAS ==='; + RAISE NOTICE 'Total dependencias: %', (SELECT COUNT(*) FROM educational_content.module_dependencies); + RAISE NOTICE 'Required: %', (SELECT COUNT(*) FROM educational_content.module_dependencies WHERE dependency_type = 'required'); + RAISE NOTICE 'Recommended: %', (SELECT COUNT(*) FROM educational_content.module_dependencies WHERE dependency_type = 'recommended'); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM educational_content.module_dependencies; + + IF v_count < 5 THEN + RAISE WARNING '⚠️ Se esperaban al menos 5 dependencias de módulos'; + ELSE + RAISE NOTICE '✅ Seed de module_dependencies completado exitosamente'; + END IF; +END $$; + +-- ===================================================== +-- MAPA DE DEPENDENCIAS (COMENTARIO) +-- ===================================================== +/* + RUTA DE PROGRESIÓN GAMILIT: + + ┌─────────────────┐ + │ MOD-01-LITERAL │ ← Punto de entrada (sin prerrequisitos) + │ (Comprensión │ + │ Literal) │ + └────────┬────────┘ + │ + │ required (80%) + ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ MOD-02-INFEREN. │ │ MOD-04-DIGITAL │ + │ (Comprensión │ │ (Lectura │ ← Ruta paralela (50% MOD-01) + │ Inferencial) │ ········>│ Digital) │ recommended MOD-02 + └────────┬────────┘ └────────┬────────┘ + │ │ + │ required (80%) │ recommended (50%) + ▼ │ + ┌─────────────────┐ │ + │ MOD-03-CRITICA │ │ + │ (Comprensión │ │ + │ Crítica) │ │ + └────────┬────────┘ │ + │ │ + │ required (80%) │ + ▼ │ + ┌─────────────────┐◀──────────────────┘ + │ MOD-05-PRODUC. │ + │ (Producción │ ← Requiere MOD-03, recomienda MOD-04 + │ Lectora) │ + └─────────────────┘ +*/ diff --git a/projects/gamilit/apps/database/seeds/dev/educational_content/12-taxonomies.sql b/projects/gamilit/apps/database/seeds/dev/educational_content/12-taxonomies.sql new file mode 100644 index 0000000..4eded11 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/educational_content/12-taxonomies.sql @@ -0,0 +1,158 @@ +-- ===================================================== +-- Seed: educational_content.taxonomies +-- Description: Taxonomías educativas para clasificación de ejercicios +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed completa las taxonomías educativas del sistema. +-- La taxonomía de Bloom ya existe en el DDL, aquí agregamos: +-- - SOLO Taxonomy (Structure of Observed Learning Outcomes) +-- - Webb's DOK (Depth of Knowledge) +-- - Taxonomía GAMILIT (personalizada para lectura) +-- +-- Tipos de taxonomía (CHECK constraint): +-- - bloom: Taxonomía de Bloom (cognitiva) +-- - solo: SOLO Taxonomy (estructural) +-- - webb: Webb's Depth of Knowledge +-- - custom: Taxonomías personalizadas +-- ===================================================== + +DO $$ +BEGIN + -- ===================================================== + -- 1. TAXONOMÍA DE BLOOM (ACTUALIZACIÓN/INSERCIÓN) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000001'::uuid, + 'Taxonomía de Bloom', + 'Taxonomía cognitiva de Benjamin Bloom para clasificar objetivos educativos', + 'bloom', + '[ + {"level": 1, "name": "Recordar", "description": "Recuperar conocimiento de la memoria", "verbs": ["definir", "identificar", "listar", "nombrar", "recordar"]}, + {"level": 2, "name": "Comprender", "description": "Construir significado a partir de mensajes", "verbs": ["explicar", "interpretar", "resumir", "clasificar", "comparar"]}, + {"level": 3, "name": "Aplicar", "description": "Usar procedimientos en situaciones dadas", "verbs": ["aplicar", "demostrar", "ejecutar", "implementar", "resolver"]}, + {"level": 4, "name": "Analizar", "description": "Descomponer en partes e identificar relaciones", "verbs": ["analizar", "diferenciar", "organizar", "atribuir", "deconstruir"]}, + {"level": 5, "name": "Evaluar", "description": "Hacer juicios basados en criterios", "verbs": ["evaluar", "criticar", "juzgar", "justificar", "argumentar"]}, + {"level": 6, "name": "Crear", "description": "Reorganizar elementos en nuevo patrón", "verbs": ["crear", "diseñar", "construir", "producir", "inventar"]} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Taxonomía de Bloom creada/actualizada'; + + -- ===================================================== + -- 2. TAXONOMÍA SOLO (Structure of Observed Learning Outcomes) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000002'::uuid, + 'Taxonomía SOLO', + 'Structure of Observed Learning Outcomes - Biggs & Collis para evaluar calidad de respuestas', + 'solo', + '[ + {"level": 1, "name": "Preestructural", "description": "El estudiante no entiende la tarea", "indicator": "Respuesta irrelevante o sin relación"}, + {"level": 2, "name": "Uniestructural", "description": "El estudiante enfoca un aspecto relevante", "indicator": "Un punto relevante identificado"}, + {"level": 3, "name": "Multiestructural", "description": "El estudiante enfoca varios aspectos relevantes independientes", "indicator": "Varios puntos sin conexión"}, + {"level": 4, "name": "Relacional", "description": "El estudiante integra aspectos en una estructura coherente", "indicator": "Puntos conectados y relacionados"}, + {"level": 5, "name": "Abstracto Extendido", "description": "El estudiante generaliza más allá de la tarea", "indicator": "Aplicación a nuevos dominios"} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Taxonomía SOLO creada/actualizada'; + + -- ===================================================== + -- 3. WEBB'S DEPTH OF KNOWLEDGE (DOK) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000003'::uuid, + 'Webb DOK', + 'Depth of Knowledge de Norman Webb para alinear estándares con evaluaciones', + 'webb', + '[ + {"level": 1, "name": "Recordar y Reproducir", "description": "Recuerdo de hechos, definiciones, términos", "examples": ["Identificar", "Definir", "Reconocer", "Localizar"]}, + {"level": 2, "name": "Habilidades y Conceptos", "description": "Usar información, aplicar conceptos", "examples": ["Resumir", "Interpretar", "Organizar", "Clasificar"]}, + {"level": 3, "name": "Pensamiento Estratégico", "description": "Razonamiento complejo, múltiples pasos", "examples": ["Analizar", "Evaluar", "Formular", "Investigar"]}, + {"level": 4, "name": "Pensamiento Extendido", "description": "Pensamiento complejo a largo plazo", "examples": ["Diseñar", "Crear", "Sintetizar", "Aplicar conceptos"]} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Webb DOK creada/actualizada'; + + -- ===================================================== + -- 4. TAXONOMÍA GAMILIT (Personalizada para Comprensión Lectora) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000004'::uuid, + 'Taxonomía GAMILIT', + 'Taxonomía personalizada de GAMILIT para comprensión lectora basada en Marie Curie', + 'custom', + '[ + {"level": 1, "name": "Comprensión Literal", "description": "Identificar información explícita en el texto", "module": "MOD-01-LITERAL", "skills": ["Identificar hechos", "Localizar información", "Reconocer secuencias"]}, + {"level": 2, "name": "Comprensión Inferencial", "description": "Deducir información no explícita del texto", "module": "MOD-02-INFERENCIAL", "skills": ["Inferir causas", "Predecir consecuencias", "Interpretar significados"]}, + {"level": 3, "name": "Comprensión Crítica", "description": "Evaluar y juzgar el contenido del texto", "module": "MOD-03-CRITICA", "skills": ["Evaluar argumentos", "Detectar sesgos", "Contrastar fuentes"]}, + {"level": 4, "name": "Lectura Digital", "description": "Navegar y evaluar información en medios digitales", "module": "MOD-04-DIGITAL", "skills": ["Verificar fuentes", "Navegar hipertexto", "Evaluar credibilidad"]}, + {"level": 5, "name": "Producción Lectora", "description": "Crear textos basados en comprensión profunda", "module": "MOD-05-PRODUCCION", "skills": ["Sintetizar información", "Argumentar posiciones", "Crear contenido"]} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Taxonomía GAMILIT creada/actualizada'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== TAXONOMÍAS EDUCATIVAS ==='; + RAISE NOTICE 'Total taxonomías activas: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE is_active = true); + RAISE NOTICE 'Bloom: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'bloom'); + RAISE NOTICE 'SOLO: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'solo'); + RAISE NOTICE 'Webb DOK: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'webb'); + RAISE NOTICE 'Custom (GAMILIT): %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'custom'); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM educational_content.taxonomies WHERE is_active = true; + + IF v_count < 3 THEN + RAISE WARNING '⚠️ Se esperaban al menos 3 taxonomías'; + ELSE + RAISE NOTICE '✅ Seed de taxonomies completado exitosamente (%s taxonomías)', v_count; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/04-achievements.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/04-achievements.sql index 944a821..671ce4e 100644 --- a/projects/gamilit/apps/database/seeds/dev/gamification_system/04-achievements.sql +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/04-achievements.sql @@ -63,8 +63,8 @@ INSERT INTO gamification_system.achievements ( '90000001-0000-0000-0000-000000000001'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, -- Tenant principal 'Primeros Pasos', - 'Completa tu primer ejercicio de comprensi�n lectora', - '<�', + 'Completa tu primer ejercicio de comprensión lectora', + 'footprints', 'progress'::gamification_system.achievement_category, 'common', 'beginner'::educational_content.difficulty_level, @@ -105,8 +105,8 @@ INSERT INTO gamification_system.achievements ( '90000001-0000-0000-0000-000000000002'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Lector Principiante', - 'Completa 10 ejercicios de comprensi�n lectora', - '=�', + 'Completa 10 ejercicios de comprensión lectora', + 'book-open', 'progress'::gamification_system.achievement_category, 'common', 'elementary'::educational_content.difficulty_level, @@ -146,8 +146,8 @@ INSERT INTO gamification_system.achievements ( '90000001-0000-0000-0000-000000000003'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Lector Experimentado', - 'Completa 50 ejercicios de comprensi�n lectora', - '=�', + 'Completa 50 ejercicios de comprensión lectora', + 'book-open', 'progress'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -188,7 +188,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Lector Experto', 'Completa 100 ejercicios de comprensi�n lectora', - '<�', + 'footprints', 'progress'::gamification_system.achievement_category, 'epic', 'upper_intermediate'::educational_content.difficulty_level, @@ -229,7 +229,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Maestro de la Lectura', 'Completa 200 ejercicios de comprensi�n lectora', - '=Q', + 'graduation-cap', 'progress'::gamification_system.achievement_category, 'legendary', 'proficient'::educational_content.difficulty_level, @@ -274,7 +274,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Racha de 3 D�as', 'Mant�n una racha de 3 d�as consecutivos practicando', - '=%', + 'flame', 'streak'::gamification_system.achievement_category, 'common', 'elementary'::educational_content.difficulty_level, @@ -315,7 +315,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Racha de 7 D�as', 'Mant�n una racha de 7 d�as consecutivos practicando', - '=%=%', + 'flame', 'streak'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -356,7 +356,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Racha de 30 D�as', 'Mant�n una racha de 30 d�as consecutivos practicando', - '=%=%=%', + 'flame', 'streak'::gamification_system.achievement_category, 'epic', 'proficient'::educational_content.difficulty_level, @@ -401,7 +401,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Comprensi�n Literal Dominada', 'Completa todos los ejercicios del M�dulo 1: Comprensi�n Literal', - '', + 'brain', 'completion'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -443,7 +443,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Comprensi�n Inferencial Dominada', 'Completa todos los ejercicios del M�dulo 2: Comprensi�n Inferencial', - '', + 'brain', 'completion'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -485,7 +485,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Comprensi�n Cr�tica Dominada', 'Completa todos los ejercicios del M�dulo 3: Comprensi�n Cr�tica', - '', + 'brain', 'completion'::gamification_system.achievement_category, 'epic', 'upper_intermediate'::educational_content.difficulty_level, @@ -527,7 +527,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Completista Total', 'Completa todos los m�dulos del sistema', - '<�', + 'trophy', 'completion'::gamification_system.achievement_category, 'legendary', 'proficient'::educational_content.difficulty_level, @@ -573,7 +573,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Perfeccionista', 'Obt�n 100% de aciertos en 10 ejercicios', - 'P', + 'target', 'mastery'::gamification_system.achievement_category, 'rare', 'upper_intermediate'::educational_content.difficulty_level, @@ -615,7 +615,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Experto en Inferencias', 'Completa 20 ejercicios de inferencia con 90% o m�s de aciertos', - '>�', + 'brain', 'mastery'::gamification_system.achievement_category, 'epic', 'upper_intermediate'::educational_content.difficulty_level, @@ -659,7 +659,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Cr�tico Avanzado', 'Completa 20 ejercicios de pensamiento cr�tico con 90% o m�s', - '<�', + 'footprints', 'mastery'::gamification_system.achievement_category, 'epic', 'proficient'::educational_content.difficulty_level, @@ -707,7 +707,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Explorador Curioso', 'Explora al menos 3 m�dulos diferentes', - '= ', + 'compass', 'exploration'::gamification_system.achievement_category, 'common', 'elementary'::educational_content.difficulty_level, @@ -749,7 +749,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Aventurero del Conocimiento', 'Completa ejercicios de todos los niveles de dificultad', - '=�', + 'compass', 'exploration'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -795,7 +795,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Compa�ero de Aula', '�nete a tu primera aula virtual', - '=e', + 'users', 'social'::gamification_system.achievement_category, 'common', 'beginner'::educational_content.difficulty_level, @@ -836,7 +836,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Estudiante Colaborativo', 'Participa en 5 actividades sociales (aulas, desaf�os, etc.)', - '>', + 'handshake', 'social'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -881,7 +881,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Primera Visita', 'Inicia sesi�n por primera vez en GAMILIT', - '<�', + 'footprints', 'special'::gamification_system.achievement_category, 'common', 'beginner'::educational_content.difficulty_level, diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/05-user_stats.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/05-user_stats.sql new file mode 100644 index 0000000..2a56b7b --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/05-user_stats.sql @@ -0,0 +1,580 @@ +-- ===================================================== +-- Seed: gamification_system.user_stats (PROD) - v2.0 +-- Description: Estadísticas de gamificación para usuarios demo +-- Environment: PRODUCTION +-- Dependencies: auth_management.profiles, gamification_system.maya_ranks +-- Order: 05 +-- Created: 2025-01-11 +-- Updated: 2025-11-15 +-- Version: 2.0 (Refactored - Trigger-based creation) +-- ===================================================== +-- +-- CAMBIOS v2.0: +-- ============ +-- ❌ ELIMINADO: INSERTs directos a user_stats (causaban duplicados y huérfanos) +-- ✅ NUEVO: El trigger initialize_user_stats() crea automáticamente los registros +-- ✅ NUEVO: UPDATEs para agregar progreso variado a los usuarios demo +-- +-- FUNCIONAMIENTO: +-- =============== +-- 1. El trigger initialize_user_stats() (en profiles) crea automáticamente: +-- - user_stats con 100 ML Coins iniciales +-- - user_ranks con rango 'Ajaw' +-- - comodines_inventory +-- +-- 2. Este seed actualiza los user_stats con progreso variado para demos realistas +-- +-- USUARIOS CON PROGRESO VARIADO: +-- ============================== +-- - 5 estudiantes con diferentes niveles (1-4) +-- - 2 profesores con actividad alta +-- - 2 administradores con stats máximos +-- - 1 padre con actividad mínima +-- +-- TOTAL: 10 usuarios demo con progreso variado +-- ===================================================== + +SET search_path TO gamification_system, auth_management, public; + +-- ===================================================== +-- FASE 1: Verificar que el trigger creó los registros base +-- ===================================================== + +DO $$ +DECLARE + stats_count INTEGER; + expected_count INTEGER; +BEGIN + -- Contar user_stats existentes + SELECT COUNT(*) INTO stats_count + FROM gamification_system.user_stats; + + -- Contar perfiles (debería haber 23: 3 testing + 20 demo) + SELECT COUNT(*) INTO expected_count + FROM auth_management.profiles; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN TRIGGER initialize_user_stats()'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Perfiles existentes: %', expected_count; + RAISE NOTICE 'User stats existentes: %', stats_count; + RAISE NOTICE '========================================'; + + IF stats_count = expected_count THEN + RAISE NOTICE '✓ El trigger funcionó correctamente'; + RAISE NOTICE '✓ Todos los perfiles tienen user_stats'; + ELSIF stats_count < expected_count THEN + RAISE WARNING '⚠ Faltan % user_stats', expected_count - stats_count; + RAISE WARNING '⚠ Algunos perfiles no tienen user_stats (trigger pudo haber fallado)'; + ELSE + RAISE WARNING '⚠ Hay % user_stats extras (posibles huérfanos)', stats_count - expected_count; + END IF; + + RAISE NOTICE ''; +END $$; + +-- ===================================================== +-- FASE 2: Actualizar user_stats con progreso variado +-- ===================================================== +-- Esto da vida a los usuarios demo con diferentes niveles de actividad + +-- Estudiante 1: Ana García - Nivel 2, Progreso Medio +UPDATE gamification_system.user_stats +SET + level = 2, + total_xp = 1250, + xp_to_next_level = 250, + current_rank = 'Ajaw'::gamification_system.maya_rank, + rank_progress = 45.50, + ml_coins = 275, + ml_coins_earned_total = 450, + ml_coins_spent_total = 175, + ml_coins_earned_today = 25, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '3 hours', + current_streak = 3, + max_streak = 5, + streak_started_at = gamilit.now_mexico() - INTERVAL '3 days', + days_active_total = 12, + exercises_completed = 15, + modules_completed = 0, + total_score = 1200, + average_score = 80.00, + perfect_scores = 2, + achievements_earned = 3, + certificates_earned = 0, + total_time_spent = '03:25:00'::interval, + weekly_time_spent = '01:15:00'::interval, + sessions_count = 12, + weekly_xp = 450, + monthly_xp = 1250, + weekly_exercises = 8, + class_rank_position = 1, + last_activity_at = gamilit.now_mexico() - INTERVAL '2 hours', + last_login_at = gamilit.now_mexico() - INTERVAL '2 hours', + metadata = jsonb_build_object( + 'demo_user', true, + 'preferred_theme', 'ocean', + 'favorite_module', 'modulo-01-comprension-literal', + 'learning_pace', 'steady' + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid; + +-- Estudiante 2: Carlos Ramírez - Nivel 1, Principiante +UPDATE gamification_system.user_stats +SET + level = 1, + total_xp = 250, + xp_to_next_level = 750, + current_rank = 'Ajaw'::gamification_system.maya_rank, + rank_progress = 12.50, + ml_coins = 150, + ml_coins_earned_total = 200, + ml_coins_spent_total = 50, + ml_coins_earned_today = 10, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '5 hours', + current_streak = 1, + max_streak = 2, + streak_started_at = gamilit.now_mexico() - INTERVAL '1 day', + days_active_total = 5, + exercises_completed = 5, + modules_completed = 0, + total_score = 350, + average_score = 70.00, + perfect_scores = 0, + achievements_earned = 1, + certificates_earned = 0, + total_time_spent = '01:10:00'::interval, + weekly_time_spent = '00:45:00'::interval, + sessions_count = 5, + weekly_xp = 150, + monthly_xp = 250, + weekly_exercises = 3, + class_rank_position = 2, + last_activity_at = gamilit.now_mexico() - INTERVAL '4 hours', + last_login_at = gamilit.now_mexico() - INTERVAL '4 hours', + metadata = jsonb_build_object( + 'demo_user', true, + 'preferred_theme', 'space', + 'learning_pace', 'slow' + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '02bc5f00-182e-5387-c899-3f269d49c06f'::uuid; + +-- Estudiante 3: María Fernanda - Nivel 3, Avanzada +UPDATE gamification_system.user_stats +SET + level = 3, + total_xp = 3200, + xp_to_next_level = 800, + current_rank = 'Nacom'::gamification_system.maya_rank, + rank_progress = 60.00, + ml_coins = 425, + ml_coins_earned_total = 800, + ml_coins_spent_total = 375, + ml_coins_earned_today = 50, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '2 hours', + current_streak = 7, + max_streak = 7, + streak_started_at = gamilit.now_mexico() - INTERVAL '7 days', + days_active_total = 20, + exercises_completed = 35, + modules_completed = 1, + total_score = 2800, + average_score = 85.00, + perfect_scores = 5, + achievements_earned = 6, + certificates_earned = 1, + total_time_spent = '06:30:00'::interval, + weekly_time_spent = '02:00:00'::interval, + sessions_count = 20, + weekly_xp = 900, + monthly_xp = 3200, + weekly_exercises = 15, + class_rank_position = 1, + last_activity_at = gamilit.now_mexico() - INTERVAL '1 hour', + last_login_at = gamilit.now_mexico() - INTERVAL '1 hour', + metadata = jsonb_build_object( + 'demo_user', true, + 'preferred_theme', 'forest', + 'favorite_module', 'modulo-02-comprension-inferencial', + 'learning_pace', 'fast', + 'achievement_hunter', true + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '03cd6000-282e-6487-d899-40369e49d070'::uuid; + +-- Estudiante 4: Luis Miguel - Nivel 2, Progreso Constante +UPDATE gamification_system.user_stats +SET + level = 2, + total_xp = 1400, + xp_to_next_level = 100, + current_rank = 'Ajaw'::gamification_system.maya_rank, + rank_progress = 52.00, + ml_coins = 300, + ml_coins_earned_total = 500, + ml_coins_spent_total = 200, + ml_coins_earned_today = 30, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '4 hours', + current_streak = 4, + max_streak = 6, + streak_started_at = gamilit.now_mexico() - INTERVAL '4 days', + days_active_total = 15, + exercises_completed = 20, + modules_completed = 0, + total_score = 1500, + average_score = 75.00, + perfect_scores = 1, + achievements_earned = 4, + certificates_earned = 0, + total_time_spent = '04:00:00'::interval, + weekly_time_spent = '01:30:00'::interval, + sessions_count = 15, + weekly_xp = 550, + monthly_xp = 1400, + weekly_exercises = 10, + class_rank_position = 2, + last_activity_at = gamilit.now_mexico() - INTERVAL '3 hours', + last_login_at = gamilit.now_mexico() - INTERVAL '3 hours', + metadata = jsonb_build_object( + 'demo_user', true, + 'preferred_theme', 'detective', + 'learning_pace', 'steady' + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '04de7000-382e-7587-e899-51469f49e081'::uuid; + +-- Estudiante 5: Sofía Martínez - Nivel 4, Muy Avanzada +UPDATE gamification_system.user_stats +SET + level = 4, + total_xp = 6500, + xp_to_next_level = 500, + current_rank = 'Nacom'::gamification_system.maya_rank, + rank_progress = 82.50, + ml_coins = 650, + ml_coins_earned_total = 1200, + ml_coins_spent_total = 550, + ml_coins_earned_today = 75, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '1 hour', + current_streak = 10, + max_streak = 12, + streak_started_at = gamilit.now_mexico() - INTERVAL '10 days', + days_active_total = 30, + exercises_completed = 55, + modules_completed = 2, + total_score = 4800, + average_score = 90.00, + perfect_scores = 10, + achievements_earned = 8, + certificates_earned = 2, + total_time_spent = '10:15:00'::interval, + weekly_time_spent = '03:00:00'::interval, + sessions_count = 30, + weekly_xp = 1500, + monthly_xp = 6500, + weekly_exercises = 25, + class_rank_position = 1, + last_activity_at = gamilit.now_mexico() - INTERVAL '30 minutes', + last_login_at = gamilit.now_mexico() - INTERVAL '30 minutes', + metadata = jsonb_build_object( + 'demo_user', true, + 'preferred_theme', 'galaxy', + 'favorite_module', 'modulo-03-comprension-critica', + 'learning_pace', 'very_fast', + 'achievement_hunter', true, + 'top_performer', true + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '05ef8000-482e-8687-f899-62569049f092'::uuid; + +-- Profesor 1: Roberto Méndez - Nivel 5, Profesor Activo +UPDATE gamification_system.user_stats +SET + level = 5, + total_xp = 10000, + xp_to_next_level = 2000, + current_rank = 'Ah K''in'::gamification_system.maya_rank, + rank_progress = 33.33, + ml_coins = 1000, + ml_coins_earned_total = 2000, + ml_coins_spent_total = 1000, + ml_coins_earned_today = 0, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '8 hours', + current_streak = 15, + max_streak = 20, + streak_started_at = gamilit.now_mexico() - INTERVAL '15 days', + days_active_total = 60, + exercises_completed = 100, + modules_completed = 5, + total_score = 9000, + average_score = 92.00, + perfect_scores = 25, + achievements_earned = 12, + certificates_earned = 5, + total_time_spent = '25:00:00'::interval, + weekly_time_spent = '05:00:00'::interval, + sessions_count = 60, + weekly_xp = 2500, + monthly_xp = 10000, + weekly_exercises = 30, + last_activity_at = gamilit.now_mexico() - INTERVAL '1 hour', + last_login_at = gamilit.now_mexico() - INTERVAL '1 hour', + metadata = jsonb_build_object( + 'demo_user', true, + 'role', 'teacher', + 'teacher_stats', jsonb_build_object( + 'students_count', 10, + 'classrooms_count', 2, + 'avg_student_score', 85.00 + ) + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid; + +-- Profesor 2: Laura González - Nivel 5, Profesora Activa +UPDATE gamification_system.user_stats +SET + level = 5, + total_xp = 9500, + xp_to_next_level = 2500, + current_rank = 'Ah K''in'::gamification_system.maya_rank, + rank_progress = 25.00, + ml_coins = 950, + ml_coins_earned_total = 1900, + ml_coins_spent_total = 950, + ml_coins_earned_today = 0, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '10 hours', + current_streak = 12, + max_streak = 18, + streak_started_at = gamilit.now_mexico() - INTERVAL '12 days', + days_active_total = 55, + exercises_completed = 90, + modules_completed = 5, + total_score = 8500, + average_score = 90.00, + perfect_scores = 20, + achievements_earned = 11, + certificates_earned = 5, + total_time_spent = '22:30:00'::interval, + weekly_time_spent = '04:30:00'::interval, + sessions_count = 55, + weekly_xp = 2300, + monthly_xp = 9500, + weekly_exercises = 28, + last_activity_at = gamilit.now_mexico() - INTERVAL '2 hours', + last_login_at = gamilit.now_mexico() - INTERVAL '2 hours', + metadata = jsonb_build_object( + 'demo_user', true, + 'role', 'teacher', + 'teacher_stats', jsonb_build_object( + 'students_count', 8, + 'classrooms_count', 2, + 'avg_student_score', 82.50 + ) + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '11bc5f00-192e-5397-c919-3f279d49c26f'::uuid; + +-- Admin 1: Admin Sistema - Nivel 10, Super Admin +UPDATE gamification_system.user_stats +SET + level = 10, + total_xp = 50000, + xp_to_next_level = 0, + current_rank = 'K''uk''ulkan'::gamification_system.maya_rank, + rank_progress = 100.00, + ml_coins = 5000, + ml_coins_earned_total = 10000, + ml_coins_spent_total = 5000, + ml_coins_earned_today = 0, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '12 hours', + current_streak = 30, + max_streak = 30, + streak_started_at = gamilit.now_mexico() - INTERVAL '30 days', + days_active_total = 100, + exercises_completed = 250, + modules_completed = 5, + total_score = 24000, + average_score = 96.00, + perfect_scores = 50, + achievements_earned = 20, + certificates_earned = 5, + total_time_spent = '50:00:00'::interval, + weekly_time_spent = '08:00:00'::interval, + sessions_count = 100, + weekly_xp = 5000, + monthly_xp = 50000, + weekly_exercises = 50, + last_activity_at = gamilit.now_mexico() - INTERVAL '30 minutes', + last_login_at = gamilit.now_mexico() - INTERVAL '30 minutes', + metadata = jsonb_build_object( + 'demo_user', true, + 'role', 'super_admin', + 'admin_access', 'full' + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '20ac4f00-002e-4207-b809-2e189c49b25e'::uuid; + +-- Admin 2: Directora - Nivel 8, Director +UPDATE gamification_system.user_stats +SET + level = 8, + total_xp = 25000, + xp_to_next_level = 3000, + current_rank = 'Halach Uinic'::gamification_system.maya_rank, + rank_progress = 75.00, + ml_coins = 2500, + ml_coins_earned_total = 5000, + ml_coins_spent_total = 2500, + ml_coins_earned_today = 0, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '14 hours', + current_streak = 20, + max_streak = 25, + streak_started_at = gamilit.now_mexico() - INTERVAL '20 days', + days_active_total = 80, + exercises_completed = 150, + modules_completed = 5, + total_score = 14000, + average_score = 94.00, + perfect_scores = 35, + achievements_earned = 15, + certificates_earned = 5, + total_time_spent = '35:00:00'::interval, + weekly_time_spent = '06:00:00'::interval, + sessions_count = 80, + weekly_xp = 3500, + monthly_xp = 25000, + weekly_exercises = 40, + last_activity_at = gamilit.now_mexico() - INTERVAL '1 hour', + last_login_at = gamilit.now_mexico() - INTERVAL '1 hour', + metadata = jsonb_build_object( + 'demo_user', true, + 'role', 'director', + 'school_id', '50000000-0000-0000-0000-000000000001' + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '21bc5f00-102e-5307-c829-3f289d49c36f'::uuid; + +-- Padre 1: Jorge García - Nivel 1, Observador +UPDATE gamification_system.user_stats +SET + level = 1, + total_xp = 100, + xp_to_next_level = 900, + current_rank = 'Ajaw'::gamification_system.maya_rank, + rank_progress = 5.00, + ml_coins = 100, + ml_coins_earned_total = 100, + ml_coins_spent_total = 0, + ml_coins_earned_today = 0, + last_ml_coins_reset = gamilit.now_mexico() - INTERVAL '24 hours', + current_streak = 0, + max_streak = 1, + streak_started_at = NULL, + days_active_total = 3, + exercises_completed = 0, + modules_completed = 0, + total_score = 0, + average_score = NULL, + perfect_scores = 0, + achievements_earned = 1, + certificates_earned = 0, + total_time_spent = '00:30:00'::interval, + weekly_time_spent = '00:10:00'::interval, + sessions_count = 3, + weekly_xp = 50, + monthly_xp = 100, + weekly_exercises = 0, + last_activity_at = gamilit.now_mexico() - INTERVAL '1 day', + last_login_at = gamilit.now_mexico() - INTERVAL '1 day', + metadata = jsonb_build_object( + 'demo_user', true, + 'role', 'parent', + 'children_ids', jsonb_build_array('01ac4f00-082e-4287-b899-2e169c49b05e') + ), + updated_at = gamilit.now_mexico() +WHERE user_id = '30ac4f00-012e-4217-b819-2e199c49b35e'::uuid; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + stats_count INTEGER; + updated_count INTEGER; + students_count INTEGER; + teachers_count INTEGER; + admins_count INTEGER; + avg_level NUMERIC; + total_ml_coins INTEGER; +BEGIN + SELECT COUNT(*) INTO stats_count + FROM gamification_system.user_stats; + + SELECT COUNT(*) INTO updated_count + FROM gamification_system.user_stats + WHERE metadata->>'demo_user' = 'true' AND level > 1; + + SELECT COUNT(*) INTO students_count + FROM gamification_system.user_stats us + JOIN auth_management.profiles p ON p.user_id = us.user_id + WHERE us.metadata->>'demo_user' = 'true' AND p.role = 'student'; + + SELECT COUNT(*) INTO teachers_count + FROM gamification_system.user_stats us + JOIN auth_management.profiles p ON p.user_id = us.user_id + WHERE us.metadata->>'demo_user' = 'true' AND p.role = 'admin_teacher'; + + SELECT COUNT(*) INTO admins_count + FROM gamification_system.user_stats us + JOIN auth_management.profiles p ON p.user_id = us.user_id + WHERE us.metadata->>'demo_user' = 'true' AND p.role = 'super_admin'; + + SELECT AVG(level)::NUMERIC(5,2) INTO avg_level + FROM gamification_system.user_stats + WHERE metadata->>'demo_user' = 'true'; + + SELECT SUM(ml_coins) INTO total_ml_coins + FROM gamification_system.user_stats + WHERE metadata->>'demo_user' = 'true'; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'USER STATS ACTUALIZADOS EXITOSAMENTE'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total user stats: %', stats_count; + RAISE NOTICE 'User stats demo actualizados: %', updated_count; + RAISE NOTICE ' - Estudiantes: %', students_count; + RAISE NOTICE ' - Profesores: %', teachers_count; + RAISE NOTICE ' - Administradores: %', admins_count; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Estadísticas Agregadas:'; + RAISE NOTICE ' - Nivel promedio: %', avg_level; + RAISE NOTICE ' - ML Coins totales: %', total_ml_coins; + RAISE NOTICE '========================================'; + + IF updated_count >= 10 THEN + RAISE NOTICE '✓ User stats demo fueron actualizados correctamente con progreso variado'; + ELSE + RAISE WARNING '⚠ Se esperaban al menos 10 updates, se aplicaron %', updated_count; + END IF; + + RAISE NOTICE ''; +END $$; + +-- ===================================================== +-- Testing Info +-- ===================================================== +-- Los user_stats ahora tienen progreso variado realista. +-- +-- Para verificar: +-- SELECT display_name, level, total_xp, ml_coins, exercises_completed +-- FROM auth_management.profiles p +-- JOIN gamification_system.user_stats us ON us.user_id = p.user_id +-- WHERE us.metadata->>'demo_user' = 'true' +-- ORDER BY level DESC, total_xp DESC; +-- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/06-user_ranks.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/06-user_ranks.sql new file mode 100644 index 0000000..8a0bc6a --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/06-user_ranks.sql @@ -0,0 +1,468 @@ +-- ===================================================== +-- Seed: gamification_system.user_ranks (PROD) +-- Description: Rangos maya actuales para usuarios demo +-- Environment: PRODUCTION +-- Dependencies: auth_management.profiles, gamification_system.user_stats +-- Order: 06 +-- Created: 2025-01-11 +-- Version: 1.0 +-- ===================================================== +-- +-- RANGOS INCLUIDOS: +-- - Ajaw: 4 usuarios (nivel 1-2) +-- - Nacom: 2 usuarios (nivel 3-4) +-- - Ah K'in: 2 usuarios (nivel 5) +-- - Halach Uinic: 1 usuario (nivel 8) +-- - K'uk'ulkan: 1 usuario (nivel 10, max rank) +-- +-- TOTAL: 10 user ranks +-- +-- IMPORTANTE: Solo se crea el rango actual (is_current = true). +-- El historial de rangos anteriores se crear cuando el usuario suba de rango. +-- ===================================================== + +SET search_path TO gamification_system, auth_management, public; + +-- ===================================================== +-- INSERT: User Ranks Demo +-- ===================================================== + +INSERT INTO gamification_system.user_ranks ( + id, + user_id, + tenant_id, + current_rank, + previous_rank, + rank_progress_percentage, + modules_required_for_next, + modules_completed_for_rank, + xp_required_for_next, + xp_earned_for_rank, + ml_coins_bonus, + certificate_url, + badge_url, + achieved_at, + previous_rank_achieved_at, + is_current, + rank_metadata, + created_at, + updated_at +) VALUES + +-- ===================================================== +-- Estudiante 1: Ana Garca - Rango Ajaw +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, -- Ana Garca + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Ajaw'::gamification_system.maya_rank, + NULL, -- No hay rango previo (primer rango) + 46, -- rank_progress_percentage (46% hacia Nacom) + 1, -- modules_required_for_next + 0, -- modules_completed_for_rank + 1000, -- xp_required_for_next (1000 XP para Nacom) + 1250, // xp_earned_for_rank (tiene 1250 XP) + 0, -- ml_coins_bonus (Ajaw es gratis) + NULL, -- certificate_url + '/badges/ranks/ajaw.png', + gamilit.now_mexico() - INTERVAL '12 days', -- achieved_at (cuando se registr) + NULL, + true, -- is_current + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 1, + 'rank_name_es', 'Ajaw' + ), + gamilit.now_mexico() - INTERVAL '12 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Estudiante 2: Carlos Ramrez - Rango Ajaw +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000002'::uuid, + '02bc5f00-182e-5387-c899-3f269d49c06f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Ajaw'::gamification_system.maya_rank, + NULL, + 13, -- rank_progress_percentage (13% hacia Nacom) + 1, + 0, + 1000, + 250, + 0, + NULL, + '/badges/ranks/ajaw.png', + gamilit.now_mexico() - INTERVAL '5 days', + NULL, + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 1, + 'rank_name_es', 'Ajaw' + ), + gamilit.now_mexico() - INTERVAL '5 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Estudiante 3: Mara Fernanda - Rango Nacom +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Nacom'::gamification_system.maya_rank, + 'Ajaw'::gamification_system.maya_rank, -- Subi de Ajaw a Nacom + 60, -- rank_progress_percentage (60% hacia Ah K'in) + 2, -- modules_required_for_next + 1, -- modules_completed_for_rank (complet Mdulo 1) + 3000, -- xp_required_for_next (3000 XP para Ah K'in) + 3200, -- xp_earned_for_rank + 50, -- ml_coins_bonus (bonus por alcanzar Nacom) + '/certificates/ranks/nacom.pdf', + '/badges/ranks/nacom.png', + gamilit.now_mexico() - INTERVAL '10 days', -- achieved_at (hace 10 das) + gamilit.now_mexico() - INTERVAL '20 days', // previous_rank_achieved_at (Ajaw hace 20 das) + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 2, + 'rank_name_es', 'Nacom', + 'promotion_date', (gamilit.now_mexico() - INTERVAL '10 days')::text + ), + gamilit.now_mexico() - INTERVAL '10 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Estudiante 4: Luis Miguel - Rango Ajaw +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000004'::uuid, + '04de7000-382e-7587-e899-51469f49e081'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Ajaw'::gamification_system.maya_rank, + NULL, + 52, -- rank_progress_percentage (52% hacia Nacom) + 1, + 0, + 1000, + 1400, + 0, + NULL, + '/badges/ranks/ajaw.png', + gamilit.now_mexico() - INTERVAL '15 days', + NULL, + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 1, + 'rank_name_es', 'Ajaw' + ), + gamilit.now_mexico() - INTERVAL '15 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Estudiante 5: Sofa Martnez - Rango Nacom +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000005'::uuid, + '05ef8000-482e-8687-f899-62569049f092'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Nacom'::gamification_system.maya_rank, + 'Ajaw'::gamification_system.maya_rank, + 83, -- rank_progress_percentage (83% hacia Ah K'in, casi lo alcanza!) + 2, + 2, -- modules_completed_for_rank (complet Mdulos 1 y 2) + 3000, + 6500, + 50, + '/certificates/ranks/nacom.pdf', + '/badges/ranks/nacom.png', + gamilit.now_mexico() - INTERVAL '15 days', + gamilit.now_mexico() - INTERVAL '30 days', + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 2, + 'rank_name_es', 'Nacom', + 'promotion_date', (gamilit.now_mexico() - INTERVAL '15 days')::text, + 'near_promotion', true, + 'next_rank', 'Ah K''in' + ), + gamilit.now_mexico() - INTERVAL '15 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Profesor 1: Juan Prez - Rango Ah K'in +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000006'::uuid, + '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Ah K''in'::gamification_system.maya_rank, + 'Nacom'::gamification_system.maya_rank, + 33, // rank_progress_percentage (33% hacia Halach Uinic) + 3, + 5, -- modules_completed_for_rank (todos los mdulos) + 6000, -- xp_required_for_next + 10000, + 100, -- ml_coins_bonus + '/certificates/ranks/ah_kin.pdf', + '/badges/ranks/ah_kin.png', + gamilit.now_mexico() - INTERVAL '30 days', + gamilit.now_mexico() - INTERVAL '60 days', + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 3, + 'rank_name_es', 'Ah K''in', + 'role', 'teacher', + 'promotion_date', (gamilit.now_mexico() - INTERVAL '30 days')::text + ), + gamilit.now_mexico() - INTERVAL '30 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Profesor 2: Laura Martnez - Rango Ah K'in +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000007'::uuid, + '11bc5f00-192e-5397-c919-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Ah K''in'::gamification_system.maya_rank, + 'Nacom'::gamification_system.maya_rank, + 25, -- rank_progress_percentage (25% hacia Halach Uinic) + 3, + 5, + 6000, + 9500, + 100, + '/certificates/ranks/ah_kin.pdf', + '/badges/ranks/ah_kin.png', + gamilit.now_mexico() - INTERVAL '28 days', + gamilit.now_mexico() - INTERVAL '55 days', + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 3, + 'rank_name_es', 'Ah K''in', + 'role', 'teacher', + 'promotion_date', (gamilit.now_mexico() - INTERVAL '28 days')::text + ), + gamilit.now_mexico() - INTERVAL '28 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Admin: Admin Sistema - Rango K'uk'ulkan (MAX) +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000008'::uuid, + '20ac4f00-102e-5307-c829-3f289d49c36f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'K''uk''ulkan'::gamification_system.maya_rank, + 'Halach Uinic'::gamification_system.maya_rank, + 100, -- rank_progress_percentage (100%, max rank) + 0, -- No hay siguiente rango + 5, + 0, -- No hay siguiente XP requerido + 50000, + 500, -- ml_coins_bonus (bonus mximo) + '/certificates/ranks/kukul kan.pdf', + '/badges/ranks/kukulkan.png', + gamilit.now_mexico() - INTERVAL '50 days', + gamilit.now_mexico() - INTERVAL '100 days', + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 5, + 'rank_name_es', 'K''uk''ulkan', + 'role', 'super_admin', + 'max_rank', true, + 'promotion_date', (gamilit.now_mexico() - INTERVAL '50 days')::text + ), + gamilit.now_mexico() - INTERVAL '50 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Director: Roberto Silva - Rango Halach Uinic +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000009'::uuid, + '21bc5f00-102e-5307-c829-3f289d49c36f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Halach Uinic'::gamification_system.maya_rank, + 'Ah K''in'::gamification_system.maya_rank, + 75, -- rank_progress_percentage (75% hacia K'uk'ulkan) + 4, + 5, + 10000, + 25000, + 250, + '/certificates/ranks/halach_uinic.pdf', + '/badges/ranks/halach_uinic.png', + gamilit.now_mexico() - INTERVAL '40 days', + gamilit.now_mexico() - INTERVAL '80 days', + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 4, + 'rank_name_es', 'Halach Uinic', + 'role', 'director', + 'promotion_date', (gamilit.now_mexico() - INTERVAL '40 days')::text + ), + gamilit.now_mexico() - INTERVAL '40 days', + gamilit.now_mexico() +), + +-- ===================================================== +-- Padre: Carmen Lpez - Rango Ajaw +-- ===================================================== +( + 'b0000001-0000-0000-0000-000000000010'::uuid, + '30ac4f00-202e-6307-d839-4f389e49d47g'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Ajaw'::gamification_system.maya_rank, + NULL, + 5, -- rank_progress_percentage (5% hacia Nacom) + 1, + 0, + 1000, + 100, + 0, + NULL, + '/badges/ranks/ajaw.png', + gamilit.now_mexico() - INTERVAL '3 days', + NULL, + true, + jsonb_build_object( + 'demo_rank', true, + 'rank_tier', 1, + 'rank_name_es', 'Ajaw', + 'role', 'parent' + ), + gamilit.now_mexico() - INTERVAL '3 days', + gamilit.now_mexico() +) + +ON CONFLICT (id) DO UPDATE SET + current_rank = EXCLUDED.current_rank, + rank_progress_percentage = EXCLUDED.rank_progress_percentage, + modules_completed_for_rank = EXCLUDED.modules_completed_for_rank, + xp_earned_for_rank = EXCLUDED.xp_earned_for_rank, + is_current = EXCLUDED.is_current, + rank_metadata = EXCLUDED.rank_metadata, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + ranks_count INTEGER; + ajaw_count INTEGER; + nacom_count INTEGER; + ahkin_count INTEGER; + halach_count INTEGER; + kukulkan_count INTEGER; +BEGIN + SELECT COUNT(*) INTO ranks_count + FROM gamification_system.user_ranks + WHERE rank_metadata->>'demo_rank' = 'true' + AND is_current = true; + + SELECT COUNT(*) INTO ajaw_count + FROM gamification_system.user_ranks + WHERE current_rank = 'Ajaw' AND is_current = true; + + SELECT COUNT(*) INTO nacom_count + FROM gamification_system.user_ranks + WHERE current_rank = 'Nacom' AND is_current = true; + + SELECT COUNT(*) INTO ahkin_count + FROM gamification_system.user_ranks + WHERE current_rank = 'Ah K''in' AND is_current = true; + + SELECT COUNT(*) INTO halach_count + FROM gamification_system.user_ranks + WHERE current_rank = 'Halach Uinic' AND is_current = true; + + SELECT COUNT(*) INTO kukulkan_count + FROM gamification_system.user_ranks + WHERE current_rank = 'K''uk''ulkan' AND is_current = true; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'USER RANKS DEMO CREADOS EXITOSAMENTE'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total user ranks: %', ranks_count; + RAISE NOTICE ' - Ajaw: %', ajaw_count; + RAISE NOTICE ' - Nacom: %', nacom_count; + RAISE NOTICE ' - Ah K''in: %', ahkin_count; + RAISE NOTICE ' - Halach Uinic: %', halach_count; + RAISE NOTICE ' - K''uk''ulkan: %', kukulkan_count; + RAISE NOTICE '========================================'; + + IF ranks_count = 10 THEN + RAISE NOTICE ' Todos los user ranks demo fueron creados correctamente'; + ELSE + RAISE WARNING ' Se esperaban 10 user ranks, se crearon %', ranks_count; + END IF; +END $$; + +-- ===================================================== +-- Listado de ranks +-- ===================================================== + +DO $$ +DECLARE + rank_record RECORD; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Listado de user ranks demo:'; + RAISE NOTICE '========================================'; + + FOR rank_record IN + SELECT + p.display_name, + p.role, + ur.current_rank, + ur.previous_rank, + ur.rank_progress_percentage, + ur.xp_earned_for_rank, + ur.modules_completed_for_rank + FROM gamification_system.user_ranks ur + JOIN auth_management.profiles p ON p.id = ur.user_id + WHERE ur.rank_metadata->>'demo_rank' = 'true' + AND ur.is_current = true + ORDER BY + CASE ur.current_rank + WHEN 'K''uk''ulkan' THEN 5 + WHEN 'Halach Uinic' THEN 4 + WHEN 'Ah K''in' THEN 3 + WHEN 'Nacom' THEN 2 + WHEN 'Ajaw' THEN 1 + END DESC, + ur.rank_progress_percentage DESC + LOOP + RAISE NOTICE ' - % [%]', rank_record.display_name, rank_record.role; + RAISE NOTICE ' Rango Actual: % | Anterior: %', + rank_record.current_rank, + COALESCE(rank_record.previous_rank::text, 'N/A'); + RAISE NOTICE ' Progreso: %%% | XP: % | Mdulos: %', + rank_record.rank_progress_percentage, + rank_record.xp_earned_for_rank, + rank_record.modules_completed_for_rank; + RAISE NOTICE ''; + END LOOP; + + RAISE NOTICE '========================================'; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/07-ml_coins_transactions.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/07-ml_coins_transactions.sql new file mode 100644 index 0000000..937a2ac --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/07-ml_coins_transactions.sql @@ -0,0 +1,895 @@ +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Seed: ML Coins Transactions (Production Demo Data) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Description: Transacciones de ML Coins para demostraci�n del sistema de econom�a +-- Environment: production +-- Dependencies: +-- - auth.users (01-demo-users.sql) +-- - auth_management.profiles (03-profiles.sql) +-- - gamification_system.user_stats (05-user_stats.sql) +-- Execution Order: 7 +-- Created: 2025-01-11 +-- Version: 1.0.0 +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +SET search_path TO gamification_system, public; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 1: Ana Garc�a (275 ML Coins actuales, 450 ganados, 175 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +-- Welcome bonus (100 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0001-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'welcome_bonus'::gamification_system.transaction_type, 100, + 0, 100, 'Bono de bienvenida al registrarte en GAMILIT', + 'profile', '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + jsonb_build_object('demo_transaction', true, 'category', 'welcome'), + gamilit.now_mexico() - INTERVAL '12 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Ejercicio 1 completado (15 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0002-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_exercise'::gamification_system.transaction_type, 15, + 100, 115, 'ML Coins ganados por completar ejercicio de comprensi�n literal', + 'exercise', 'ex-001'::uuid, + jsonb_build_object('demo_transaction', true, 'exercise_score', 85, 'module', 'M�DULO 1'), + gamilit.now_mexico() - INTERVAL '11 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Achievement: Primeros Pasos (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0003-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_achievement'::gamification_system.transaction_type, 50, + 115, 165, 'ML Coins ganados por logro: Primeros Pasos', + 'achievement', '90000001-0001-0000-0000-000000000001'::uuid, + jsonb_build_object('demo_transaction', true, 'achievement_name', 'Primeros Pasos'), + gamilit.now_mexico() - INTERVAL '10 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Varios ejercicios completados (185 ML Coins en total) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0004-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_exercise'::gamification_system.transaction_type, 185, + 165, 350, 'ML Coins acumulados por completar 14 ejercicios adicionales', + 'exercise', NULL, + jsonb_build_object('demo_transaction', true, 'exercises_count', 14, 'module', 'M�DULO 1'), + gamilit.now_mexico() - INTERVAL '8 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Lupa (30 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0005-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -30, + 350, 320, 'Compra de comod�n: Lupa', + 'powerup', 'comodin-lupa'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Lupa'), + gamilit.now_mexico() - INTERVAL '7 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Achievement: Racha de 3 d�as (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0006-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_achievement'::gamification_system.transaction_type, 50, + 320, 370, 'ML Coins ganados por logro: Racha de 3 D�as', + 'achievement', '90000001-0006-0000-0000-000000000001'::uuid, + jsonb_build_object('demo_transaction', true, 'achievement_name', 'Racha de 3 D�as'), + gamilit.now_mexico() - INTERVAL '5 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0007-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 370, 360, 'Compra de pista para ejercicio', + 'exercise', 'ex-015'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 1), + gamilit.now_mexico() - INTERVAL '4 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Bono diario de streak (25 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0008-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_streak'::gamification_system.transaction_type, 25, + 360, 385, 'Bono por mantener racha diaria activa', + NULL, NULL, + jsonb_build_object('demo_transaction', true, 'streak_days', 3), + gamilit.now_mexico() - INTERVAL '3 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Br�jula (25 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0009-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -25, + 385, 360, 'Compra de comod�n: Br�jula', + 'powerup', 'comodin-brujula'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Br�jula'), + gamilit.now_mexico() - INTERVAL '2 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Achievement: Lector Principiante (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0010-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_achievement'::gamification_system.transaction_type, 50, + 360, 410, 'ML Coins ganados por logro: Lector Principiante', + 'achievement', '90000001-0002-0000-0000-000000000001'::uuid, + jsonb_build_object('demo_transaction', true, 'achievement_name', 'Lector Principiante'), + gamilit.now_mexico() - INTERVAL '1 day' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de retry de ejercicio (15 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0011-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_retry'::gamification_system.transaction_type, -15, + 410, 395, 'Compra de reintento para ejercicio', + 'exercise', 'ex-020'::uuid, + jsonb_build_object('demo_transaction', true, 'retry_number', 1), + gamilit.now_mexico() - INTERVAL '12 hours' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Bono diario (20 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0012-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_daily'::gamification_system.transaction_type, 20, + 395, 415, 'Bono diario por iniciar sesi�n', + NULL, NULL, + jsonb_build_object('demo_transaction', true, 'consecutive_days', 3), + gamilit.now_mexico() - INTERVAL '6 hours' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de retry adicional (15 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0013-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_retry'::gamification_system.transaction_type, -15, + 415, 400, 'Compra de segundo reintento para ejercicio', + 'exercise', 'ex-022'::uuid, + jsonb_build_object('demo_transaction', true, 'retry_number', 1), + gamilit.now_mexico() - INTERVAL '4 hours' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Diccionario (25 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0014-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -25, + 400, 375, 'Compra de comod�n: Diccionario Contextual', + 'powerup', 'comodin-diccionario'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Diccionario Contextual'), + gamilit.now_mexico() - INTERVAL '2 hours' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint adicional (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0015-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 375, 365, 'Compra de pista adicional para ejercicio', + 'exercise', 'ex-023'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 2), + gamilit.now_mexico() - INTERVAL '1 hour' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Ajuste de balance para cuadrar (90 ML Coins adicionales de ejercicios) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0016-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_bonus'::gamification_system.transaction_type, 90, + 365, 455, 'Bonos acumulados por racha y ejercicios perfectos', + NULL, NULL, + jsonb_build_object('demo_transaction', true, 'bonus_type', 'perfect_scores'), + gamilit.now_mexico() - INTERVAL '30 minutes' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint adicional (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0017-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 455, 445, 'Compra de pista para ejercicio complejo', + 'exercise', 'ex-024'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 1), + gamilit.now_mexico() - INTERVAL '15 minutes' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Resaltador (20 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0018-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -20, + 445, 425, 'Compra de comod�n: Resaltador', + 'powerup', 'comodin-resaltador'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Resaltador'), + gamilit.now_mexico() - INTERVAL '10 minutes' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Bono adicional de racha (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0019-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_streak'::gamification_system.transaction_type, 50, + 425, 475, 'Bono especial por racha consecutiva de 3 d�as', + NULL, NULL, + jsonb_build_object('demo_transaction', true, 'streak_days', 3, 'bonus_type', 'milestone'), + gamilit.now_mexico() - INTERVAL '5 minutes' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Organizador (50 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0020-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -50, + 475, 425, 'Compra de comod�n: Organizador de Ideas', + 'powerup', 'comodin-organizador'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Organizador de Ideas'), + gamilit.now_mexico() - INTERVAL '2 minutes' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Mapa Mental (50 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0021-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -50, + 425, 375, 'Compra de comod�n: Mapa Mental', + 'powerup', 'comodin-mapa-mental'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Mapa Mental'), + gamilit.now_mexico() - INTERVAL '1 minute' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Ajuste final (balance -100 para llegar a 275) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000001-0022-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'admin_adjustment'::gamification_system.transaction_type, -100, + 375, 275, 'Ajuste administrativo de balance (correcci�n de sistema)', + NULL, NULL, + jsonb_build_object('demo_transaction', true, 'reason', 'balance_correction'), + gamilit.now_mexico() +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 2: Carlos Ram�rez (150 ML Coins actuales, 200 ganados, 50 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +-- Welcome bonus (100 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000002-0001-0000-0000-000000000002'::uuid, + '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'welcome_bonus'::gamification_system.transaction_type, 100, + 0, 100, 'Bono de bienvenida al registrarte en GAMILIT', + 'profile', '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + jsonb_build_object('demo_transaction', true, 'category', 'welcome'), + gamilit.now_mexico() - INTERVAL '8 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Achievement: Primera Visita (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000002-0002-0000-0000-000000000002'::uuid, + '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_achievement'::gamification_system.transaction_type, 50, + 100, 150, 'ML Coins ganados por logro: Primera Visita', + 'achievement', '90000001-0020-0000-0000-000000000001'::uuid, + jsonb_build_object('demo_transaction', true, 'achievement_name', 'Primera Visita'), + gamilit.now_mexico() - INTERVAL '8 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Ejercicios completados (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000002-0003-0000-0000-000000000002'::uuid, + '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_exercise'::gamification_system.transaction_type, 50, + 150, 200, 'ML Coins ganados por completar 5 ejercicios', + 'exercise', NULL, + jsonb_build_object('demo_transaction', true, 'exercises_count', 5, 'module', 'M�DULO 1'), + gamilit.now_mexico() - INTERVAL '5 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000002-0004-0000-0000-000000000002'::uuid, + '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 200, 190, 'Compra de pista para ejercicio', + 'exercise', 'ex-005'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 1), + gamilit.now_mexico() - INTERVAL '4 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Lupa (30 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000002-0005-0000-0000-000000000002'::uuid, + '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -30, + 190, 160, 'Compra de comod�n: Lupa', + 'powerup', 'comodin-lupa'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Lupa'), + gamilit.now_mexico() - INTERVAL '3 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint adicional (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000002-0006-0000-0000-000000000002'::uuid, + '02bc5f00-192e-5397-c909-3f279d49c26f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 160, 150, 'Compra de pista adicional para ejercicio', + 'exercise', 'ex-006'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 2), + gamilit.now_mexico() - INTERVAL '1 day' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 3: Mar�a Fernanda (425 ML Coins, 500 ganados, 75 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +-- Welcome bonus (100 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0001-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'welcome_bonus'::gamification_system.transaction_type, 100, + 0, 100, 'Bono de bienvenida al registrarte en GAMILIT', + 'profile', '03cd6000-282e-6487-d899-40369e49d070'::uuid, + jsonb_build_object('demo_transaction', true, 'category', 'welcome'), + gamilit.now_mexico() - INTERVAL '15 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Ejercicios M�dulo 1 (250 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0002-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_exercise'::gamification_system.transaction_type, 250, + 100, 350, 'ML Coins ganados por completar 25 ejercicios del M�dulo 1', + 'exercise', NULL, + jsonb_build_object('demo_transaction', true, 'exercises_count', 25, 'module', 'M�DULO 1'), + gamilit.now_mexico() - INTERVAL '12 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Lupa (30 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0003-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -30, + 350, 320, 'Compra de comod�n: Lupa', + 'powerup', 'comodin-lupa'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Lupa'), + gamilit.now_mexico() - INTERVAL '11 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Achievement: M�dulo 1 Completado (100 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0004-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_module'::gamification_system.transaction_type, 100, + 320, 420, 'ML Coins ganados por completar M�dulo 1', + 'module', 'modulo-01-comprension-literal'::uuid, + jsonb_build_object('demo_transaction', true, 'module_name', 'M�DULO 1'), + gamilit.now_mexico() - INTERVAL '10 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Achievement: Racha de 7 d�as (50 ML Coins) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0005-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'earned_achievement'::gamification_system.transaction_type, 50, + 420, 470, 'ML Coins ganados por logro: Racha de 7 D�as', + 'achievement', '90000001-0007-0000-0000-000000000001'::uuid, + jsonb_build_object('demo_transaction', true, 'achievement_name', 'Racha de 7 D�as'), + gamilit.now_mexico() - INTERVAL '7 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de comod�n: Diccionario (25 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0006-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_powerup'::gamification_system.transaction_type, -25, + 470, 445, 'Compra de comod�n: Diccionario Contextual', + 'powerup', 'comodin-diccionario'::uuid, + jsonb_build_object('demo_transaction', true, 'powerup_name', 'Diccionario Contextual'), + gamilit.now_mexico() - INTERVAL '6 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0007-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 445, 435, 'Compra de pista para ejercicio del M�dulo 2', + 'exercise', 'ex-026'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 1), + gamilit.now_mexico() - INTERVAL '5 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- Compra de hint adicional (10 ML Coins gastados) +INSERT INTO gamification_system.ml_coins_transactions ( + id, user_id, tenant_id, transaction_type, amount, + balance_before, balance_after, description, + related_entity_type, related_entity_id, + metadata, created_at +) VALUES ( + 'd0000003-0008-0000-0000-000000000003'::uuid, + '03cd6000-282e-6487-d899-40369e49d070'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'spent_hint'::gamification_system.transaction_type, -10, + 435, 425, 'Compra de pista adicional para ejercicio del M�dulo 2', + 'exercise', 'ex-027'::uuid, + jsonb_build_object('demo_transaction', true, 'hint_level', 2), + gamilit.now_mexico() - INTERVAL '2 days' +) ON CONFLICT (id) DO UPDATE SET + amount = EXCLUDED.amount, + balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Continuaci�n con transacciones m�s compactas para el resto de usuarios... +-- ESTUDIANTE 4: Luis Miguel (300 ML Coins, 450 ganados, 150 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000004-0001-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida al registrarte en GAMILIT', 'profile', '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '14 days'), +('d0000004-0002-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_exercise'::gamification_system.transaction_type, 200, 100, 300, 'ML Coins por completar 20 ejercicios', 'exercise', NULL, jsonb_build_object('demo_transaction', true, 'exercises_count', 20), gamilit.now_mexico() - INTERVAL '10 days'), +('d0000004-0003-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_achievement'::gamification_system.transaction_type, 100, 300, 400, 'ML Coins por achievements (Primeros Pasos, Lector Principiante)', 'achievement', NULL, jsonb_build_object('demo_transaction', true, 'achievements_count', 2), gamilit.now_mexico() - INTERVAL '8 days'), +('d0000004-0004-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_streak'::gamification_system.transaction_type, 50, 400, 450, 'Bonos por racha de 4 d�as', NULL, NULL, jsonb_build_object('demo_transaction', true, 'streak_days', 4), gamilit.now_mexico() - INTERVAL '4 days'), +('d0000004-0005-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_powerup'::gamification_system.transaction_type, -80, 450, 370, 'Compra de comodines (Lupa, Br�jula, Diccionario)', 'powerup', NULL, jsonb_build_object('demo_transaction', true, 'powerups_count', 3), gamilit.now_mexico() - INTERVAL '3 days'), +('d0000004-0006-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_hint'::gamification_system.transaction_type, -40, 370, 330, 'Compra de 4 hints', 'exercise', NULL, jsonb_build_object('demo_transaction', true, 'hints_count', 4), gamilit.now_mexico() - INTERVAL '2 days'), +('d0000004-0007-0000-0000-000000000004'::uuid, '04de7f00-382e-7497-e919-5h479f49e38h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_retry'::gamification_system.transaction_type, -30, 330, 300, 'Compra de 2 reintentos', 'exercise', NULL, jsonb_build_object('demo_transaction', true, 'retries_count', 2), gamilit.now_mexico() - INTERVAL '1 day') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 5: Sof�a Mart�nez (650 ML Coins, 800 ganados, 150 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000005-0001-0000-0000-000000000005'::uuid, '05ef8f00-482e-8587-f929-6i589g49f49i'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida al registrarte en GAMILIT', 'profile', '05ef8f00-482e-8587-f929-6i589g49f49i'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '20 days'), +('d0000005-0002-0000-0000-000000000005'::uuid, '05ef8f00-482e-8587-f929-6i589g49f49i'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_exercise'::gamification_system.transaction_type, 500, 100, 600, 'ML Coins por completar 50 ejercicios (M�dulos 1 y 2)', 'exercise', NULL, jsonb_build_object('demo_transaction', true, 'exercises_count', 50), gamilit.now_mexico() - INTERVAL '15 days'), +('d0000005-0003-0000-0000-000000000005'::uuid, '05ef8f00-482e-8587-f929-6i589g49f49i'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_module'::gamification_system.transaction_type, 200, 600, 800, 'ML Coins por completar 2 m�dulos (M�dulo 1 y 2)', 'module', NULL, jsonb_build_object('demo_transaction', true, 'modules_count', 2), gamilit.now_mexico() - INTERVAL '12 days'), +('d0000005-0004-0000-0000-000000000005'::uuid, '05ef8f00-482e-8587-f929-6i589g49f49i'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_powerup'::gamification_system.transaction_type, -120, 800, 680, 'Compra de comodines avanzados', 'powerup', NULL, jsonb_build_object('demo_transaction', true, 'powerups_count', 4), gamilit.now_mexico() - INTERVAL '8 days'), +('d0000005-0005-0000-0000-000000000005'::uuid, '05ef8f00-482e-8587-f929-6i589g49f49i'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_hint'::gamification_system.transaction_type, -30, 680, 650, 'Compra de 3 hints para M�dulo 3', 'exercise', NULL, jsonb_build_object('demo_transaction', true, 'hints_count', 3), gamilit.now_mexico() - INTERVAL '3 days') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- PROFESOR 1: Juan P�rez (1000 ML Coins, 1200 ganados, 200 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000006-0001-0000-0000-000000000006'::uuid, '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida - Profesor', 'profile', '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '30 days'), +('d0000006-0002-0000-0000-000000000006'::uuid, '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_bonus'::gamification_system.transaction_type, 600, 100, 700, 'Bonos por actividades de profesor (creaci�n de contenido, evaluaciones)', NULL, NULL, jsonb_build_object('demo_transaction', true, 'bonus_type', 'teacher_activities'), gamilit.now_mexico() - INTERVAL '20 days'), +('d0000006-0003-0000-0000-000000000006'::uuid, '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_achievement'::gamification_system.transaction_type, 500, 700, 1200, 'ML Coins por achievements de profesor', 'achievement', NULL, jsonb_build_object('demo_transaction', true, 'achievements_count', 5), gamilit.now_mexico() - INTERVAL '15 days'), +('d0000006-0004-0000-0000-000000000006'::uuid, '10ac4f00-092e-4297-b909-2e179c49b15e'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_powerup'::gamification_system.transaction_type, -200, 1200, 1000, 'Compra de herramientas premium para ense�anza', 'powerup', NULL, jsonb_build_object('demo_transaction', true, 'tools_purchased', 4), gamilit.now_mexico() - INTERVAL '5 days') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- PROFESOR 2: Laura Mart�nez (950 ML Coins, 1150 ganados, 200 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000007-0001-0000-0000-000000000007'::uuid, '11bc5f00-1a2e-5397-c919-3f289d49c26f'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida - Profesora', 'profile', '11bc5f00-1a2e-5397-c919-3f289d49c26f'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '28 days'), +('d0000007-0002-0000-0000-000000000007'::uuid, '11bc5f00-1a2e-5397-c919-3f289d49c26f'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_bonus'::gamification_system.transaction_type, 550, 100, 650, 'Bonos por actividades de profesora', NULL, NULL, jsonb_build_object('demo_transaction', true, 'bonus_type', 'teacher_activities'), gamilit.now_mexico() - INTERVAL '18 days'), +('d0000007-0003-0000-0000-000000000007'::uuid, '11bc5f00-1a2e-5397-c919-3f289d49c26f'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_achievement'::gamification_system.transaction_type, 500, 650, 1150, 'ML Coins por achievements de profesora', 'achievement', NULL, jsonb_build_object('demo_transaction', true, 'achievements_count', 5), gamilit.now_mexico() - INTERVAL '12 days'), +('d0000007-0004-0000-0000-000000000007'::uuid, '11bc5f00-1a2e-5397-c919-3f289d49c26f'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_powerup'::gamification_system.transaction_type, -200, 1150, 950, 'Compra de herramientas premium para ense�anza', 'powerup', NULL, jsonb_build_object('demo_transaction', true, 'tools_purchased', 4), gamilit.now_mexico() - INTERVAL '4 days') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ADMIN: Sistema Admin (5000 ML Coins, 5500 ganados, 500 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000008-0001-0000-0000-000000000008'::uuid, '20ac4f00-0a2e-6397-d929-4g399e49d37g'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida - Admin', 'profile', '20ac4f00-0a2e-6397-d929-4g399e49d37g'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '60 days'), +('d0000008-0002-0000-0000-000000000008'::uuid, '20ac4f00-0a2e-6397-d929-4g399e49d37g'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_bonus'::gamification_system.transaction_type, 5400, 100, 5500, 'Bonos acumulados por administraci�n del sistema', NULL, NULL, jsonb_build_object('demo_transaction', true, 'bonus_type', 'admin_activities'), gamilit.now_mexico() - INTERVAL '30 days'), +('d0000008-0003-0000-0000-000000000008'::uuid, '20ac4f00-0a2e-6397-d929-4g399e49d37g'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_powerup'::gamification_system.transaction_type, -500, 5500, 5000, 'Compra de herramientas de administraci�n premium', 'powerup', NULL, jsonb_build_object('demo_transaction', true, 'admin_tools', true), gamilit.now_mexico() - INTERVAL '10 days') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- DIRECTOR: Roberto Director (2500 ML Coins, 2800 ganados, 300 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000009-0001-0000-0000-000000000009'::uuid, '21bc5f00-1b2e-7497-e939-5h4a9f49e48h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida - Director', 'profile', '21bc5f00-1b2e-7497-e939-5h4a9f49e48h'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '45 days'), +('d0000009-0002-0000-0000-000000000009'::uuid, '21bc5f00-1b2e-7497-e939-5h4a9f49e48h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'earned_bonus'::gamification_system.transaction_type, 2700, 100, 2800, 'Bonos acumulados por gesti�n directiva', NULL, NULL, jsonb_build_object('demo_transaction', true, 'bonus_type', 'management_activities'), gamilit.now_mexico() - INTERVAL '25 days'), +('d0000009-0003-0000-0000-000000000009'::uuid, '21bc5f00-1b2e-7497-e939-5h4a9f49e48h'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'spent_powerup'::gamification_system.transaction_type, -300, 2800, 2500, 'Compra de herramientas de gesti�n', 'powerup', NULL, jsonb_build_object('demo_transaction', true, 'management_tools', true), gamilit.now_mexico() - INTERVAL '8 days') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- PADRE: Carmen Madre (100 ML Coins, 100 ganados, 0 gastados) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.ml_coins_transactions +(id, user_id, tenant_id, transaction_type, amount, balance_before, balance_after, description, related_entity_type, related_entity_id, metadata, created_at) +VALUES +('d0000010-0001-0000-0000-000000000010'::uuid, '30cd6f00-2c2e-8587-f949-6i5b9g49f59i'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'welcome_bonus'::gamification_system.transaction_type, 100, 0, 100, 'Bono de bienvenida - Padre/Madre', 'profile', '30cd6f00-2c2e-8587-f949-6i5b9g49f59i'::uuid, jsonb_build_object('demo_transaction', true), gamilit.now_mexico() - INTERVAL '5 days') +ON CONFLICT (id) DO UPDATE SET amount = EXCLUDED.amount, balance_after = EXCLUDED.balance_after; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- VERIFICACI�N DE TRANSACCIONES +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +DO $$ +DECLARE + v_transaction_count INTEGER; + v_total_earned INTEGER; + v_total_spent INTEGER; + v_net_balance INTEGER; +BEGIN + -- Contar transacciones insertadas + SELECT COUNT(*) INTO v_transaction_count + FROM gamification_system.ml_coins_transactions + WHERE metadata->>'demo_transaction' = 'true'; + + -- Calcular totales ganados + SELECT COALESCE(SUM(amount), 0) INTO v_total_earned + FROM gamification_system.ml_coins_transactions + WHERE metadata->>'demo_transaction' = 'true' + AND amount > 0; + + -- Calcular totales gastados + SELECT COALESCE(SUM(ABS(amount)), 0) INTO v_total_spent + FROM gamification_system.ml_coins_transactions + WHERE metadata->>'demo_transaction' = 'true' + AND amount < 0; + + -- Balance neto + v_net_balance := v_total_earned - v_total_spent; + + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + RAISE NOTICE 'ML Coins Transactions - Verificaci�n de Seeds'; + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + RAISE NOTICE 'Total de transacciones insertadas: %', v_transaction_count; + RAISE NOTICE 'Total ML Coins ganados: % ML Coins', v_total_earned; + RAISE NOTICE 'Total ML Coins gastados: % ML Coins', v_total_spent; + RAISE NOTICE 'Balance neto: % ML Coins', v_net_balance; + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + + -- Verificar que tenemos transacciones + IF v_transaction_count = 0 THEN + RAISE WARNING 'No se insertaron transacciones demo'; + ELSIF v_transaction_count < 40 THEN + RAISE WARNING 'Se esperaban al menos 40 transacciones, se insertaron %', v_transaction_count; + ELSE + RAISE NOTICE ' Seeds de transacciones ML Coins insertados correctamente'; + END IF; +END $$; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- LISTADO DE TRANSACCIONES INSERTADAS (para debugging) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +DO $$ +DECLARE + v_user_record RECORD; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Resumen de transacciones por usuario:'; + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + + FOR v_user_record IN ( + SELECT + u.email, + p.display_name, + COUNT(t.id) as transaction_count, + COALESCE(SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END), 0) as total_earned, + COALESCE(SUM(CASE WHEN t.amount < 0 THEN ABS(t.amount) ELSE 0 END), 0) as total_spent, + MAX(t.balance_after) as current_balance + FROM auth.users u + JOIN auth_management.profiles p ON p.user_id = u.id + LEFT JOIN gamification_system.ml_coins_transactions t ON t.user_id = u.id + WHERE t.metadata->>'demo_transaction' = 'true' + GROUP BY u.email, p.display_name + ORDER BY u.email + ) LOOP + RAISE NOTICE '% (%): % transacciones | Ganados: % | Gastados: % | Balance: %', + v_user_record.display_name, + v_user_record.email, + v_user_record.transaction_count, + v_user_record.total_earned, + v_user_record.total_spent, + v_user_record.current_balance; + END LOOP; + + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; +END $$; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- FIN DEL SEED +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/08-user_achievements.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/08-user_achievements.sql new file mode 100644 index 0000000..6fc02c9 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/08-user_achievements.sql @@ -0,0 +1,434 @@ +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Seed: User Achievements (Production Demo Data) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Description: Asociaciones de achievements desbloqueados por usuarios demo +-- Environment: production +-- Dependencies: +-- - auth.users (01-demo-users.sql) +-- - auth_management.profiles (03-profiles.sql) +-- - gamification_system.achievements (04-achievements.sql) +-- - gamification_system.user_stats (05-user_stats.sql) +-- Execution Order: 8 +-- Created: 2025-01-11 +-- Version: 1.0.0 +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +SET search_path TO gamification_system, public; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 1: Ana Garc�a (3 achievements) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +-- Primera Visita (completado) +INSERT INTO gamification_system.user_achievements ( + id, user_id, achievement_id, progress, max_progress, + is_completed, completion_percentage, completed_at, + notified, viewed, rewards_claimed, rewards_received, + progress_data, milestones_reached, metadata, + started_at, created_at +) VALUES ( + 'e0000001-0001-0000-0000-000000000001'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + '90000007-0000-0000-0000-000000000001'::uuid, + 1, 1, true, 100.00, + gamilit.now_mexico() - INTERVAL '12 days', + true, true, true, + jsonb_build_object( + 'xp', 50, + 'ml_coins', 25, + 'badge_url', '/badges/achievements/primera-visita.png' + ), + jsonb_build_object('first_login', true), + ARRAY['first_login'], + jsonb_build_object('demo_achievement', true, 'category', 'special'), + gamilit.now_mexico() - INTERVAL '12 days', + gamilit.now_mexico() - INTERVAL '12 days' +) ON CONFLICT (user_id, achievement_id) DO UPDATE SET + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- Primeros Pasos (completado) +INSERT INTO gamification_system.user_achievements ( + id, user_id, achievement_id, progress, max_progress, + is_completed, completion_percentage, completed_at, + notified, viewed, rewards_claimed, rewards_received, + progress_data, milestones_reached, metadata, + started_at, created_at +) VALUES ( + 'e0000001-0002-0000-0000-000000000001'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + '90000001-0000-0000-0000-000000000001'::uuid, + 1, 1, true, 100.00, + gamilit.now_mexico() - INTERVAL '10 days', + true, true, true, + jsonb_build_object( + 'xp', 100, + 'ml_coins', 50, + 'badge_url', '/badges/achievements/primeros-pasos.png' + ), + jsonb_build_object('exercises_completed', 1), + ARRAY['first_exercise'], + jsonb_build_object('demo_achievement', true, 'category', 'progress'), + gamilit.now_mexico() - INTERVAL '11 days', + gamilit.now_mexico() - INTERVAL '10 days' +) ON CONFLICT (user_id, achievement_id) DO UPDATE SET + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- Racha de 3 D�as (completado) +INSERT INTO gamification_system.user_achievements ( + id, user_id, achievement_id, progress, max_progress, + is_completed, completion_percentage, completed_at, + notified, viewed, rewards_claimed, rewards_received, + progress_data, milestones_reached, metadata, + started_at, created_at +) VALUES ( + 'e0000001-0003-0000-0000-000000000001'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + '90000002-0000-0000-0000-000000000001'::uuid, + 3, 3, true, 100.00, + gamilit.now_mexico() - INTERVAL '5 days', + true, true, true, + jsonb_build_object( + 'xp', 150, + 'ml_coins', 50, + 'badge_url', '/badges/achievements/racha-3-dias.png' + ), + jsonb_build_object('streak_days', 3), + ARRAY['day_1', 'day_2', 'day_3'], + jsonb_build_object('demo_achievement', true, 'category', 'streak'), + gamilit.now_mexico() - INTERVAL '7 days', + gamilit.now_mexico() - INTERVAL '5 days' +) ON CONFLICT (user_id, achievement_id) DO UPDATE SET + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- Lector Principiante (en progreso 60%) +INSERT INTO gamification_system.user_achievements ( + id, user_id, achievement_id, progress, max_progress, + is_completed, completion_percentage, completed_at, + notified, viewed, rewards_claimed, rewards_received, + progress_data, milestones_reached, metadata, + started_at, created_at +) VALUES ( + 'e0000001-0004-0000-0000-000000000001'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + '90000001-0000-0000-0000-000000000002'::uuid, + 15, 25, false, 60.00, NULL, + false, false, false, '{}'::jsonb, + jsonb_build_object('exercises_completed', 15, 'target', 25), + ARRAY['milestone_10'], + jsonb_build_object('demo_achievement', true, 'category', 'progress', 'status', 'in_progress'), + gamilit.now_mexico() - INTERVAL '10 days', + gamilit.now_mexico() - INTERVAL '10 days' +) ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 2: Carlos Ram�rez (1 achievement) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +-- Primera Visita (completado) +INSERT INTO gamification_system.user_achievements ( + id, user_id, achievement_id, progress, max_progress, + is_completed, completion_percentage, completed_at, + notified, viewed, rewards_claimed, rewards_received, + progress_data, milestones_reached, metadata, + started_at, created_at +) VALUES ( + 'e0000002-0001-0000-0000-000000000002'::uuid, + '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, + '90000007-0000-0000-0000-000000000001'::uuid, + 1, 1, true, 100.00, + gamilit.now_mexico() - INTERVAL '8 days', + true, true, true, + jsonb_build_object( + 'xp', 50, + 'ml_coins', 25, + 'badge_url', '/badges/achievements/primera-visita.png' + ), + jsonb_build_object('first_login', true), + ARRAY['first_login'], + jsonb_build_object('demo_achievement', true, 'category', 'special'), + gamilit.now_mexico() - INTERVAL '8 days', + gamilit.now_mexico() - INTERVAL '8 days' +) ON CONFLICT (user_id, achievement_id) DO UPDATE SET + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- Primeros Pasos (en progreso 20%) +INSERT INTO gamification_system.user_achievements ( + id, user_id, achievement_id, progress, max_progress, + is_completed, completion_percentage, completed_at, + notified, viewed, rewards_claimed, rewards_received, + progress_data, milestones_reached, metadata, + started_at, created_at +) VALUES ( + 'e0000002-0002-0000-0000-000000000002'::uuid, + '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, + '90000001-0000-0000-0000-000000000001'::uuid, + 5, 25, false, 20.00, NULL, + false, false, false, '{}'::jsonb, + jsonb_build_object('exercises_completed', 5, 'target', 25), + ARRAY[]::text[], + jsonb_build_object('demo_achievement', true, 'category', 'progress', 'status', 'in_progress'), + gamilit.now_mexico() - INTERVAL '8 days', + gamilit.now_mexico() - INTERVAL '8 days' +) ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 3: Mar�a Fernanda (5+ achievements - m�dulo 1 completado) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000003-0001-0000-0000-000000000003'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '15 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '15 days', gamilit.now_mexico() - INTERVAL '15 days'), +-- Primeros Pasos +('e0000003-0002-0000-0000-000000000003'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '90000001-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '14 days', true, true, true, jsonb_build_object('xp', 100, 'ml_coins', 50), jsonb_build_object('exercises_completed', 1), ARRAY['first_exercise'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '14 days', gamilit.now_mexico() - INTERVAL '14 days'), +-- Lector Principiante +('e0000003-0003-0000-0000-000000000003'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '90000001-0000-0000-0000-000000000002'::uuid, 25, 25, true, 100.00, gamilit.now_mexico() - INTERVAL '12 days', true, true, true, jsonb_build_object('xp', 200, 'ml_coins', 75), jsonb_build_object('exercises_completed', 25), ARRAY['milestone_10', 'milestone_20', 'milestone_25'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '13 days', gamilit.now_mexico() - INTERVAL '12 days'), +-- Racha de 7 D�as +('e0000003-0004-0000-0000-000000000003'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '90000002-0000-0000-0000-000000000002'::uuid, 7, 7, true, 100.00, gamilit.now_mexico() - INTERVAL '7 days', true, true, true, jsonb_build_object('xp', 300, 'ml_coins', 100), jsonb_build_object('streak_days', 7), ARRAY['day_3', 'day_5', 'day_7'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '10 days', gamilit.now_mexico() - INTERVAL '7 days'), +-- M�dulo 1 Completado +('e0000003-0005-0000-0000-000000000003'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '90000003-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '10 days', true, true, true, jsonb_build_object('xp', 500, 'ml_coins', 150, 'certificate_url', '/certificates/modules/modulo-1.pdf'), jsonb_build_object('module_completed', 'modulo-01', 'score', 88), ARRAY['module_1_completed'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '12 days', gamilit.now_mexico() - INTERVAL '10 days'), +-- Lector Experimentado (en progreso 40% por M�dulo 2) +('e0000003-0006-0000-0000-000000000003'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '90000001-0000-0000-0000-000000000003'::uuid, 35, 100, false, 35.00, NULL, false, false, false, '{}'::jsonb, jsonb_build_object('exercises_completed', 35, 'target', 100), ARRAY['milestone_25'], jsonb_build_object('demo_achievement', true, 'status', 'in_progress'), gamilit.now_mexico() - INTERVAL '12 days', gamilit.now_mexico() - INTERVAL '10 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 4: Luis Miguel (2 achievements) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000004-0001-0000-0000-000000000004'::uuid, '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '14 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '14 days', gamilit.now_mexico() - INTERVAL '14 days'), +-- Primeros Pasos +('e0000004-0002-0000-0000-000000000004'::uuid, '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, '90000001-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '13 days', true, true, true, jsonb_build_object('xp', 100, 'ml_coins', 50), jsonb_build_object('exercises_completed', 1), ARRAY['first_exercise'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '13 days', gamilit.now_mexico() - INTERVAL '13 days'), +-- Lector Principiante (en progreso 80%) +('e0000004-0003-0000-0000-000000000004'::uuid, '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, '90000001-0000-0000-0000-000000000002'::uuid, 20, 25, false, 80.00, NULL, false, false, false, '{}'::jsonb, jsonb_build_object('exercises_completed', 20, 'target', 25), ARRAY['milestone_10', 'milestone_20'], jsonb_build_object('demo_achievement', true, 'status', 'in_progress'), gamilit.now_mexico() - INTERVAL '12 days', gamilit.now_mexico() - INTERVAL '12 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 5: Sof�a Mart�nez (8+ achievements - 2 m�dulos completados, mastery) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000005-0001-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '20 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '20 days', gamilit.now_mexico() - INTERVAL '20 days'), +-- Primeros Pasos +('e0000005-0002-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000001-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '19 days', true, true, true, jsonb_build_object('xp', 100, 'ml_coins', 50), jsonb_build_object('exercises_completed', 1), ARRAY['first_exercise'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '19 days', gamilit.now_mexico() - INTERVAL '19 days'), +-- Lector Principiante +('e0000005-0003-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000001-0000-0000-0000-000000000002'::uuid, 25, 25, true, 100.00, gamilit.now_mexico() - INTERVAL '18 days', true, true, true, jsonb_build_object('xp', 200, 'ml_coins', 75), jsonb_build_object('exercises_completed', 25), ARRAY['milestone_10', 'milestone_20', 'milestone_25'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '19 days', gamilit.now_mexico() - INTERVAL '18 days'), +-- Lector Experimentado +('e0000005-0004-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000001-0000-0000-0000-000000000003'::uuid, 50, 100, true, 100.00, gamilit.now_mexico() - INTERVAL '15 days', true, true, true, jsonb_build_object('xp', 400, 'ml_coins', 125), jsonb_build_object('exercises_completed', 50), ARRAY['milestone_25', 'milestone_50', 'milestone_75', 'milestone_100'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '18 days', gamilit.now_mexico() - INTERVAL '15 days'), +-- Racha de 7 D�as +('e0000005-0005-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000002-0000-0000-0000-000000000002'::uuid, 7, 7, true, 100.00, gamilit.now_mexico() - INTERVAL '14 days', true, true, true, jsonb_build_object('xp', 300, 'ml_coins', 100), jsonb_build_object('streak_days', 7), ARRAY['day_3', 'day_5', 'day_7'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '16 days', gamilit.now_mexico() - INTERVAL '14 days'), +-- M�dulo 1 Completado +('e0000005-0006-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000003-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '16 days', true, true, true, jsonb_build_object('xp', 500, 'ml_coins', 150, 'certificate_url', '/certificates/modules/modulo-1.pdf'), jsonb_build_object('module_completed', 'modulo-01', 'score', 96), ARRAY['module_1_completed'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '18 days', gamilit.now_mexico() - INTERVAL '16 days'), +-- M�dulo 2 Completado +('e0000005-0007-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000003-0000-0000-0000-000000000002'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '12 days', true, true, true, jsonb_build_object('xp', 500, 'ml_coins', 150, 'certificate_url', '/certificates/modules/modulo-2.pdf'), jsonb_build_object('module_completed', 'modulo-02', 'score', 90), ARRAY['module_2_completed'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '14 days', gamilit.now_mexico() - INTERVAL '12 days'), +-- Perfeccionista +('e0000005-0008-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000004-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '16 days', true, true, true, jsonb_build_object('xp', 750, 'ml_coins', 250), jsonb_build_object('perfect_score_module', 'modulo-01'), ARRAY['perfect_module'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '16 days', gamilit.now_mexico() - INTERVAL '16 days'), +-- Explorador Curioso (completado) +('e0000005-0009-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000005-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '18 days', true, true, true, jsonb_build_object('xp', 100, 'ml_coins', 50), jsonb_build_object('modules_explored', 3), ARRAY['explore_module_1', 'explore_module_2', 'explore_module_3'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '18 days', gamilit.now_mexico() - INTERVAL '18 days'), +-- Lector Experto (en progreso 55%) +('e0000005-0010-0000-0000-000000000005'::uuid, 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid, '90000001-0000-0000-0000-000000000004'::uuid, 55, 100, false, 55.00, NULL, false, false, false, '{}'::jsonb, jsonb_build_object('exercises_completed', 55, 'target', 200), ARRAY['milestone_25', 'milestone_50'], jsonb_build_object('demo_achievement', true, 'status', 'in_progress'), gamilit.now_mexico() - INTERVAL '15 days', gamilit.now_mexico() - INTERVAL '12 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- PROFESOR 1: Juan P�rez (5 achievements de profesor) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000006-0001-0000-0000-000000000006'::uuid, 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '30 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '30 days', gamilit.now_mexico() - INTERVAL '30 days'), +-- Racha de 7 D�as +('e0000006-0002-0000-0000-000000000006'::uuid, 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, '90000002-0000-0000-0000-000000000002'::uuid, 7, 7, true, 100.00, gamilit.now_mexico() - INTERVAL '22 days', true, true, true, jsonb_build_object('xp', 300, 'ml_coins', 100), jsonb_build_object('streak_days', 7), ARRAY['day_3', 'day_5', 'day_7'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '25 days', gamilit.now_mexico() - INTERVAL '22 days'), +-- Racha de 30 D�as (en progreso 50%) +('e0000006-0003-0000-0000-000000000006'::uuid, 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, '90000002-0000-0000-0000-000000000003'::uuid, 15, 30, false, 50.00, NULL, false, false, false, '{}'::jsonb, jsonb_build_object('streak_days', 15, 'target', 30), ARRAY['day_7', 'day_14'], jsonb_build_object('demo_achievement', true, 'status', 'in_progress'), gamilit.now_mexico() - INTERVAL '30 days', gamilit.now_mexico() - INTERVAL '15 days'), +-- Compa�ero de Aula +('e0000006-0004-0000-0000-000000000006'::uuid, 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, '90000006-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '28 days', true, true, true, jsonb_build_object('xp', 200, 'ml_coins', 75), jsonb_build_object('classroom_joined', true), ARRAY['join_classroom'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '28 days', gamilit.now_mexico() - INTERVAL '28 days'), +-- Estudiante Colaborativo +('e0000006-0005-0000-0000-000000000006'::uuid, 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, '90000006-0000-0000-0000-000000000002'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '25 days', true, true, true, jsonb_build_object('xp', 300, 'ml_coins', 100), jsonb_build_object('collaborations', 10), ARRAY['collab_5', 'collab_10'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '26 days', gamilit.now_mexico() - INTERVAL '25 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- PROFESOR 2: Laura Mart�nez (5 achievements de profesora) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000007-0001-0000-0000-000000000007'::uuid, '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '28 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '28 days', gamilit.now_mexico() - INTERVAL '28 days'), +-- Racha de 7 D�as +('e0000007-0002-0000-0000-000000000007'::uuid, '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, '90000002-0000-0000-0000-000000000002'::uuid, 7, 7, true, 100.00, gamilit.now_mexico() - INTERVAL '20 days', true, true, true, jsonb_build_object('xp', 300, 'ml_coins', 100), jsonb_build_object('streak_days', 7), ARRAY['day_3', 'day_5', 'day_7'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '23 days', gamilit.now_mexico() - INTERVAL '20 days'), +-- Racha de 30 D�as (en progreso 40%) +('e0000007-0003-0000-0000-000000000007'::uuid, '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, '90000002-0000-0000-0000-000000000003'::uuid, 12, 30, false, 40.00, NULL, false, false, false, '{}'::jsonb, jsonb_build_object('streak_days', 12, 'target', 30), ARRAY['day_7'], jsonb_build_object('demo_achievement', true, 'status', 'in_progress'), gamilit.now_mexico() - INTERVAL '28 days', gamilit.now_mexico() - INTERVAL '16 days'), +-- Compa�ero de Aula +('e0000007-0004-0000-0000-000000000007'::uuid, '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, '90000006-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '26 days', true, true, true, jsonb_build_object('xp', 200, 'ml_coins', 75), jsonb_build_object('classroom_joined', true), ARRAY['join_classroom'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '26 days', gamilit.now_mexico() - INTERVAL '26 days'), +-- Estudiante Colaborativo +('e0000007-0005-0000-0000-000000000007'::uuid, '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, '90000006-0000-0000-0000-000000000002'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '23 days', true, true, true, jsonb_build_object('xp', 300, 'ml_coins', 100), jsonb_build_object('collaborations', 10), ARRAY['collab_5', 'collab_10'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '24 days', gamilit.now_mexico() - INTERVAL '23 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ADMIN: Sistema Admin (10+ achievements - usuario avanzado) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000008-0001-0000-0000-000000000008'::uuid, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '60 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '60 days', gamilit.now_mexico() - INTERVAL '60 days'), +-- Racha de 30 D�as +('e0000008-0002-0000-0000-000000000008'::uuid, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, '90000002-0000-0000-0000-000000000003'::uuid, 30, 30, true, 100.00, gamilit.now_mexico() - INTERVAL '30 days', true, true, true, jsonb_build_object('xp', 1000, 'ml_coins', 300), jsonb_build_object('streak_days', 30), ARRAY['day_7', 'day_14', 'day_21', 'day_30'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '60 days', gamilit.now_mexico() - INTERVAL '30 days'), +-- Maestro de la Lectura +('e0000008-0003-0000-0000-000000000008'::uuid, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, '90000001-0000-0000-0000-000000000005'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '35 days', true, true, true, jsonb_build_object('xp', 1500, 'ml_coins', 500, 'badge_url', '/badges/achievements/maestro-lectura.png'), jsonb_build_object('exercises_completed', 500), ARRAY['master_level'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '40 days', gamilit.now_mexico() - INTERVAL '35 days'), +-- Completista Total +('e0000008-0004-0000-0000-000000000008'::uuid, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, '90000003-0000-0000-0000-000000000004'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '32 days', true, true, true, jsonb_build_object('xp', 2000, 'ml_coins', 750, 'certificate_url', '/certificates/all-modules-completed.pdf'), jsonb_build_object('all_modules_completed', true), ARRAY['all_modules'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '35 days', gamilit.now_mexico() - INTERVAL '32 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- DIRECTOR: Roberto Director (6 achievements) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000009-0001-0000-0000-000000000009'::uuid, '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '45 days', true, true, true, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '45 days', gamilit.now_mexico() - INTERVAL '45 days'), +-- Racha de 30 D�as (en progreso 67%) +('e0000009-0002-0000-0000-000000000009'::uuid, '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, '90000002-0000-0000-0000-000000000003'::uuid, 20, 30, false, 66.67, NULL, false, false, false, '{}'::jsonb, jsonb_build_object('streak_days', 20, 'target', 30), ARRAY['day_7', 'day_14'], jsonb_build_object('demo_achievement', true, 'status', 'in_progress'), gamilit.now_mexico() - INTERVAL '45 days', gamilit.now_mexico() - INTERVAL '25 days'), +-- Compa�ero de Aula +('e0000009-0003-0000-0000-000000000009'::uuid, '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, '90000006-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '40 days', true, true, true, jsonb_build_object('xp', 200, 'ml_coins', 75), jsonb_build_object('classroom_joined', true), ARRAY['join_classroom'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '40 days', gamilit.now_mexico() - INTERVAL '40 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + progress = EXCLUDED.progress, + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- PADRE: Carmen Madre (1 achievement) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.user_achievements +(id, user_id, achievement_id, progress, max_progress, is_completed, completion_percentage, completed_at, notified, viewed, rewards_claimed, rewards_received, progress_data, milestones_reached, metadata, started_at, created_at) +VALUES +-- Primera Visita +('e0000010-0001-0000-0000-000000000010'::uuid, '5e738038-1743-4aa9-b222-30171300ea9d'::uuid, '90000007-0000-0000-0000-000000000001'::uuid, 1, 1, true, 100.00, gamilit.now_mexico() - INTERVAL '5 days', true, false, false, jsonb_build_object('xp', 50, 'ml_coins', 25), jsonb_build_object('first_login', true), ARRAY['first_login'], jsonb_build_object('demo_achievement', true), gamilit.now_mexico() - INTERVAL '5 days', gamilit.now_mexico() - INTERVAL '5 days') +ON CONFLICT (user_id, achievement_id) DO UPDATE SET + is_completed = EXCLUDED.is_completed, + completion_percentage = EXCLUDED.completion_percentage; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- VERIFICACI�N DE USER ACHIEVEMENTS +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +DO $$ +DECLARE + v_achievement_count INTEGER; + v_completed_count INTEGER; + v_in_progress_count INTEGER; +BEGIN + -- Contar user achievements insertados + SELECT COUNT(*) INTO v_achievement_count + FROM gamification_system.user_achievements + WHERE metadata->>'demo_achievement' = 'true'; + + -- Contar completados + SELECT COUNT(*) INTO v_completed_count + FROM gamification_system.user_achievements + WHERE metadata->>'demo_achievement' = 'true' + AND is_completed = true; + + -- Contar en progreso + SELECT COUNT(*) INTO v_in_progress_count + FROM gamification_system.user_achievements + WHERE metadata->>'demo_achievement' = 'true' + AND is_completed = false; + + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + RAISE NOTICE 'User Achievements - Verificaci�n de Seeds'; + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + RAISE NOTICE 'Total de user achievements insertados: %', v_achievement_count; + RAISE NOTICE 'Achievements completados: %', v_completed_count; + RAISE NOTICE 'Achievements en progreso: %', v_in_progress_count; + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + + -- Verificar que tenemos achievements + IF v_achievement_count = 0 THEN + RAISE WARNING 'No se insertaron user achievements demo'; + ELSIF v_achievement_count < 35 THEN + RAISE WARNING 'Se esperaban al menos 35 user achievements, se insertaron %', v_achievement_count; + ELSE + RAISE NOTICE ' Seeds de user achievements insertados correctamente'; + END IF; +END $$; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- LISTADO DE USER ACHIEVEMENTS INSERTADOS (para debugging) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +DO $$ +DECLARE + v_user_record RECORD; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Resumen de achievements por usuario:'; + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; + + FOR v_user_record IN ( + SELECT + u.email, + p.display_name, + COUNT(ua.id) as total_achievements, + COUNT(CASE WHEN ua.is_completed THEN 1 END) as completed, + COUNT(CASE WHEN NOT ua.is_completed THEN 1 END) as in_progress + FROM auth.users u + JOIN auth_management.profiles p ON p.user_id = u.id + LEFT JOIN gamification_system.user_achievements ua ON ua.user_id = p.id + WHERE ua.metadata->>'demo_achievement' = 'true' + GROUP BY u.email, p.display_name + ORDER BY u.email + ) LOOP + RAISE NOTICE '% (%): % total | % completados | % en progreso', + v_user_record.display_name, + v_user_record.email, + v_user_record.total_achievements, + v_user_record.completed, + v_user_record.in_progress; + END LOOP; + + RAISE NOTICE 'PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP'; +END $$; + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- FIN DEL SEED +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/09-comodines_inventory.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/09-comodines_inventory.sql new file mode 100644 index 0000000..2888b52 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/09-comodines_inventory.sql @@ -0,0 +1,112 @@ +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Seed: Comodines Inventory (Production Demo Data) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- Description: Inventarios de comodines (power-ups) para usuarios demo +-- Environment: production +-- Dependencies: +-- - auth.users (01-demo-users.sql) +-- - auth_management.profiles (04-profiles-complete.sql) +-- - gamification_system.user_stats (05-user_stats.sql) +-- Execution Order: 9 +-- Created: 2025-01-11 +-- Version: 1.1.0 +-- Updated: 2025-11-24 - Seed temporalmente deshabilitado (ISSUE-P2-002) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- +-- ⚠️ ISSUE-P2-002: Seed Temporalmente Deshabilitado +-- +-- PROBLEMA: +-- - Seed usa UUIDs hardcodeados que NO existen en tabla profiles +-- - 10 violaciones de FK constraint "comodines_inventory_user_id_fkey" +-- - UUIDs hardcodeados no coinciden con profiles creados en 04-profiles-complete.sql +-- +-- SOLUCIÓN TEMPORAL: +-- - Seed completamente comentado para permitir recreación exitosa de BD +-- - FK constraint funciona correctamente (el problema es data, no schema) +-- +-- SOLUCIÓN DEFINITIVA (TODO - Próximo Sprint): +-- - Reescribir seed usando queries dinámicas para obtener UUIDs reales +-- - Ejemplo: +-- WITH student_profiles AS ( +-- SELECT id, email FROM auth_management.profiles +-- WHERE role = 'student' AND email LIKE '%demo%' +-- ORDER BY email LIMIT 10 +-- ) +-- INSERT INTO gamification_system.comodines_inventory (user_id, ...) +-- SELECT id, ... FROM student_profiles; +-- +-- REFERENCIAS: +-- - orchestration/reportes/REPORTE-FINAL-RESOLUCION-ISSUES-2025-11-24.md (ISSUE-P2-002) +-- - orchestration/agentes/database/validacion-coherencia-2025-11-24/ (ISSUE-P2-001) +-- +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +SET search_path TO gamification_system, public; + +-- ===================================================== +-- SEED DESHABILITADO - Ver comentario ISSUE-P2-002 arriba +-- ===================================================== + +/* +-- ORIGINAL SEED COMMENTED OUT - REQUIRES REWRITE WITH VALID UUIDs + +-- Tipos de Comodines: +-- 1. Pistas Contextuales (15 ML Coins): Ayudas para resolver ejercicios +-- 2. Visión Lectora (25 ML Coins): Resalta información clave en textos +-- 3. Segunda Oportunidad (40 ML Coins): Permite reintentar ejercicios +-- +-- Fórmula: available = purchased_total - used_total + +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP +-- ESTUDIANTE 1: Ana García (usuario activo - uso moderado de comodines) +-- UUID HARDCODED: '01ac4f00-082e-4287-b899-2e169c49b05e' (NO EXISTE EN PROFILES) +-- PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + +INSERT INTO gamification_system.comodines_inventory ( + id, user_id, + pistas_available, vision_lectora_available, segunda_oportunidad_available, + pistas_purchased_total, vision_lectora_purchased_total, segunda_oportunidad_purchased_total, + pistas_used_total, vision_lectora_used_total, segunda_oportunidad_used_total, + pistas_cost, vision_lectora_cost, segunda_oportunidad_cost, + metadata, created_at, updated_at +) VALUES ( + 'f0000001-0000-0000-0000-000000000001'::uuid, + '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, -- ❌ UUID NO EXISTE + 2, 1, 0, -- available (2 pistas, 1 visión, 0 segunda) + 7, 4, 2, -- purchased_total + 5, 3, 2, -- used_total + 15, 25, 40, -- costs + jsonb_build_object( + 'demo_inventory', true, + 'last_purchase', gamilit.now_mexico() - INTERVAL '2 days', + 'favorite_comodin', 'pistas' + ), + gamilit.now_mexico() - INTERVAL '12 days', + gamilit.now_mexico() - INTERVAL '2 days' +) ON CONFLICT (user_id) DO UPDATE SET + pistas_available = EXCLUDED.pistas_available, + vision_lectora_available = EXCLUDED.vision_lectora_available, + segunda_oportunidad_available = EXCLUDED.segunda_oportunidad_available, + updated_at = EXCLUDED.updated_at; + +-- [9 more INSERT statements with hardcoded UUIDs that don't exist...] + +*/ + +-- ===================================================== +-- PLACEHOLDER: Seed será reescrito en próximo sprint +-- ===================================================== + +-- Por ahora, tabla comodines_inventory existe y funciona correctamente, +-- solo sin data de demo. Las aplicaciones pueden crear inventories +-- dinámicamente cuando usuarios compren comodines. + +DO $$ +BEGIN + RAISE NOTICE '======================================================================'; + RAISE NOTICE 'SEED 09-comodines_inventory.sql: TEMPORALMENTE DESHABILITADO'; + RAISE NOTICE 'Razón: UUIDs hardcodeados no existen en profiles (ISSUE-P2-002)'; + RAISE NOTICE 'Tabla comodines_inventory está creada y funcional'; + RAISE NOTICE 'Data de demo se agregará en próximo sprint con UUIDs válidos'; + RAISE NOTICE '======================================================================'; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/10-mission_templates.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/10-mission_templates.sql new file mode 100644 index 0000000..5cabc88 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/10-mission_templates.sql @@ -0,0 +1,346 @@ +-- ===================================================== +-- Seed: gamification_system.mission_templates +-- Description: Templates de misiones para generar misiones diarias/semanales/especiales +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed crea los templates base para el sistema de misiones. +-- Los templates se usan para generar misiones automáticamente. +-- +-- Tipos de misiones (ENUM mission_type): +-- - daily: Misiones diarias (reset cada día) +-- - weekly: Misiones semanales +-- - special: Misiones especiales/eventos +-- - classroom: Misiones de aula (asignadas por profesores) +-- +-- Tipos de objetivos (target_type): +-- - complete_exercises: Completar N ejercicios +-- - study_minutes: Estudiar N minutos +-- - earn_xp: Ganar N XP +-- - correct_streak: Racha de N respuestas correctas +-- - use_comodines: Usar N comodines +-- - perfect_scores: Obtener N puntuaciones perfectas +-- - daily_streak: Mantener racha de N días +-- - complete_modules: Completar N módulos +-- - explore_modules: Explorar N módulos diferentes +-- ===================================================== + +-- ===================================================== +-- MISIONES DIARIAS +-- ===================================================== +INSERT INTO gamification_system.mission_templates ( + id, + name, + description, + type, + category, + target_type, + target_value, + xp_reward, + ml_coins_reward, + difficulty, + is_active, + priority, + min_level, + icon, + color, + metadata +) VALUES +-- Misión diaria: Completar ejercicios +( + '20000001-0000-0000-0000-000000000001'::uuid, + 'Calentamiento Científico', + 'Completa 3 ejercicios para comenzar tu día de aprendizaje', + 'daily', + 'exercise', + 'complete_exercises', + 3, + 50, + 10, + 'easy', + true, + 100, + 1, + '🔬', + '#4CAF50', + '{"description_es": "Ejercicios completados", "reward_multiplier": 1.0}'::jsonb +), +-- Misión diaria: Racha de respuestas correctas +( + '20000001-0000-0000-0000-000000000002'::uuid, + 'Mente Brillante', + 'Consigue una racha de 5 respuestas correctas consecutivas', + 'daily', + 'streak', + 'correct_streak', + 5, + 75, + 15, + 'normal', + true, + 90, + 1, + '⚡', + '#FF9800', + '{"description_es": "Respuestas consecutivas correctas", "reward_multiplier": 1.0}'::jsonb +), +-- Misión diaria: Ganar XP +( + '20000001-0000-0000-0000-000000000003'::uuid, + 'Acumulador de Sabiduría', + 'Gana 100 puntos de experiencia durante el día', + 'daily', + 'progress', + 'earn_xp', + 100, + 30, + 5, + 'easy', + true, + 80, + 1, + '📈', + '#2196F3', + '{"description_es": "XP ganado", "reward_multiplier": 1.0}'::jsonb +), +-- Misión diaria: Puntuación perfecta +( + '20000001-0000-0000-0000-000000000004'::uuid, + 'Perfeccionista del Día', + 'Obtén al menos 1 puntuación perfecta en cualquier ejercicio', + 'daily', + 'mastery', + 'perfect_scores', + 1, + 100, + 25, + 'hard', + true, + 70, + 2, + '🌟', + '#9C27B0', + '{"description_es": "Puntuaciones perfectas", "reward_multiplier": 1.2}'::jsonb +) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + xp_reward = EXCLUDED.xp_reward, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- MISIONES SEMANALES +-- ===================================================== +INSERT INTO gamification_system.mission_templates ( + id, + name, + description, + type, + category, + target_type, + target_value, + xp_reward, + ml_coins_reward, + difficulty, + is_active, + priority, + min_level, + icon, + color, + metadata +) VALUES +-- Misión semanal: Completar ejercicios +( + '20000002-0000-0000-0000-000000000001'::uuid, + 'Maratón de Conocimiento', + 'Completa 15 ejercicios durante la semana', + 'weekly', + 'exercise', + 'complete_exercises', + 15, + 200, + 50, + 'normal', + true, + 100, + 1, + '🏃', + '#4CAF50', + '{"description_es": "Ejercicios completados esta semana", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: Racha diaria +( + '20000002-0000-0000-0000-000000000002'::uuid, + 'Constancia Científica', + 'Mantén una racha de estudio de 5 días consecutivos', + 'weekly', + 'streak', + 'daily_streak', + 5, + 300, + 75, + 'hard', + true, + 95, + 1, + '🔥', + '#FF5722', + '{"description_es": "Días de racha", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: XP total +( + '20000002-0000-0000-0000-000000000003'::uuid, + 'Ascenso Semanal', + 'Acumula 500 puntos de experiencia durante la semana', + 'weekly', + 'progress', + 'earn_xp', + 500, + 150, + 40, + 'normal', + true, + 85, + 1, + '📊', + '#00BCD4', + '{"description_es": "XP total semanal", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: Explorar módulos +( + '20000002-0000-0000-0000-000000000004'::uuid, + 'Explorador Curioso', + 'Realiza ejercicios de al menos 3 módulos diferentes', + 'weekly', + 'exploration', + 'explore_modules', + 3, + 175, + 45, + 'normal', + true, + 80, + 1, + '🗺️', + '#795548', + '{"description_es": "Módulos explorados", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: Puntuaciones perfectas +( + '20000002-0000-0000-0000-000000000005'::uuid, + 'Semana de Excelencia', + 'Consigue 5 puntuaciones perfectas durante la semana', + 'weekly', + 'mastery', + 'perfect_scores', + 5, + 400, + 100, + 'epic', + true, + 75, + 3, + '👑', + '#FFD700', + '{"description_es": "Puntuaciones perfectas semanales", "reward_multiplier": 2.0}'::jsonb +) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + xp_reward = EXCLUDED.xp_reward, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- MISIONES ESPECIALES +-- ===================================================== +INSERT INTO gamification_system.mission_templates ( + id, + name, + description, + type, + category, + target_type, + target_value, + xp_reward, + ml_coins_reward, + difficulty, + is_active, + priority, + min_level, + icon, + color, + metadata +) VALUES +-- Misión especial: Completar módulo +( + '20000003-0000-0000-0000-000000000001'::uuid, + 'Dominio del Módulo', + 'Completa todos los ejercicios de un módulo con al menos 80% de aciertos', + 'special', + 'completion', + 'complete_modules', + 1, + 500, + 150, + 'epic', + true, + 100, + 1, + '🎓', + '#E91E63', + '{"description_es": "Módulo completado con maestría", "min_score_percentage": 80, "reward_multiplier": 2.5}'::jsonb +), +-- Misión especial: Uso de comodines +( + '20000003-0000-0000-0000-000000000002'::uuid, + 'Estratega Sabio', + 'Usa 3 comodines estratégicamente durante tus ejercicios', + 'special', + 'strategy', + 'use_comodines', + 3, + 75, + 20, + 'normal', + true, + 60, + 2, + '🃏', + '#673AB7', + '{"description_es": "Comodines usados", "reward_multiplier": 1.0}'::jsonb +) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + xp_reward = EXCLUDED.xp_reward, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- VERIFICACIÓN +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM gamification_system.mission_templates; + + RAISE NOTICE ''; + RAISE NOTICE '=== MISSION TEMPLATES CREADOS ==='; + RAISE NOTICE 'Total templates: %', v_count; + RAISE NOTICE 'Daily missions: %', (SELECT COUNT(*) FROM gamification_system.mission_templates WHERE type = 'daily'); + RAISE NOTICE 'Weekly missions: %', (SELECT COUNT(*) FROM gamification_system.mission_templates WHERE type = 'weekly'); + RAISE NOTICE 'Special missions: %', (SELECT COUNT(*) FROM gamification_system.mission_templates WHERE type = 'special'); + + IF v_count < 10 THEN + RAISE WARNING '⚠️ Se esperaban al menos 10 mission_templates'; + ELSE + RAISE NOTICE '✅ Seed de mission_templates completado exitosamente'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/11-missions-production-users.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/11-missions-production-users.sql new file mode 100644 index 0000000..8ace68a --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/11-missions-production-users.sql @@ -0,0 +1,548 @@ +-- ===================================================== +-- Seed: gamification_system.missions (PROD - Production Users) +-- Description: Inicialización de misiones para usuarios de producción sin misiones +-- Environment: PRODUCTION +-- Dependencies: auth.users, auth_management.profiles, gamification_system.missions +-- Order: 11 +-- Created: 2025-11-24 +-- Version: 1.0 +-- ===================================================== +-- +-- PROPÓSITO: +-- - Crear misiones para usuarios de producción (backup) que NO tienen misiones +-- - No afectar a usuarios de test (@gamilit.com) que ya tienen misiones via seed 10 +-- - Script idempotente: puede ejecutarse múltiples veces sin crear duplicados +-- +-- MISIONES INCLUIDAS (8 por usuario): +-- - 3 misiones diarias: +-- * daily_complete_exercises: Completar 3 ejercicios (50 XP, 25 ML Coins) +-- * daily_earn_xp: Ganar 100 XP (30 XP, 15 ML Coins) +-- * daily_use_comodin: Usar un comodín (20 XP, 10 ML Coins) +-- - 5 misiones semanales: +-- * weekly_complete_module: Completar un módulo (200 XP, 100 ML Coins) +-- * weekly_daily_streak: Racha de 5 días (150 XP, 75 ML Coins) +-- * weekly_perfect_scores: 3 puntajes perfectos (180 XP, 90 ML Coins) +-- * weekly_explorer: Explorar 3 módulos (120 XP, 60 ML Coins) +-- * weekly_master_learner: Completar 15 ejercicios (250 XP, 125 ML Coins) +-- +-- CRITERIOS IDEMPOTENCIA: +-- - Solo procesa usuarios que NO tienen ninguna misión +-- - Usa ON CONFLICT DO NOTHING para evitar duplicados +-- - Verifica existencia de tabla gamification_system.missions +-- ===================================================== + +SET search_path TO gamification_system, auth_management, public; + +-- ===================================================== +-- Insert missions for production users without missions +-- ===================================================== + +DO $$ +DECLARE + v_user_record RECORD; + v_users_without_missions INTEGER := 0; + v_users_processed INTEGER := 0; + v_missions_created INTEGER := 0; + v_today_start TIMESTAMP; + v_today_end TIMESTAMP; + v_week_end TIMESTAMP; +BEGIN + -- Log inicio del proceso + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'INICIALIZANDO MISIONES PARA USUARIOS DE PRODUCCIÓN'; + RAISE NOTICE '========================================'; + RAISE NOTICE ''; + + -- Verificar que la tabla de misiones existe + IF NOT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = 'gamification_system' + AND table_name = 'missions' + ) THEN + RAISE WARNING '⚠️ Tabla gamification_system.missions no existe'; + RETURN; + END IF; + + -- Contar usuarios sin misiones (excluir usuarios de test @gamilit.com) + SELECT COUNT(DISTINCT p.id) INTO v_users_without_missions + FROM auth_management.profiles p + WHERE NOT EXISTS ( + SELECT 1 + FROM gamification_system.missions m + WHERE m.user_id = p.id + ) + AND p.email NOT LIKE '%@gamilit.com'; + + RAISE NOTICE '📊 Usuarios sin misiones encontrados: %', v_users_without_missions; + RAISE NOTICE ''; + + -- Si no hay usuarios sin misiones, terminar + IF v_users_without_missions = 0 THEN + RAISE NOTICE '✅ Todos los usuarios de producción ya tienen misiones'; + RAISE NOTICE ' (usuarios de test @gamilit.com se excluyen automáticamente)'; + RETURN; + END IF; + + -- Calcular rangos de fechas para misiones + v_today_start := gamilit.now_mexico()::date; + v_today_end := v_today_start + INTERVAL '23 hours 59 minutes'; + v_week_end := v_today_start + INTERVAL '7 days'; + + -- Procesar cada usuario sin misiones + FOR v_user_record IN + SELECT + p.id, + p.email, + p.display_name, + p.role + FROM auth_management.profiles p + WHERE NOT EXISTS ( + SELECT 1 + FROM gamification_system.missions m + WHERE m.user_id = p.id + ) + AND p.email NOT LIKE '%@gamilit.com' + ORDER BY p.created_at + LOOP + v_users_processed := v_users_processed + 1; + + RAISE NOTICE '🔄 Procesando usuario %/%: % (%)', + v_users_processed, + v_users_without_missions, + COALESCE(v_user_record.display_name, v_user_record.email), + v_user_record.role; + + -- ===================================================== + -- DAILY MISSIONS (3) + -- ===================================================== + + -- Daily Mission 1: Complete exercises + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'daily_complete_exercises', + 'Completar 3 ejercicios', + 'Completa 3 ejercicios hoy para ganar recompensas', + 'daily', + jsonb_build_object( + 'type', 'complete_exercises', + 'target', 3, + 'current', 0 + ), + jsonb_build_object( + 'xp', 50, + 'ml_coins', 25 + ), + 'active', + 0, + v_today_start, + v_today_end + ) + ON CONFLICT DO NOTHING; + + -- Daily Mission 2: Earn XP + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'daily_earn_xp', + 'Ganar 100 XP', + 'Acumula 100 puntos de experiencia hoy', + 'daily', + jsonb_build_object( + 'type', 'earn_xp', + 'target', 100, + 'current', 0 + ), + jsonb_build_object( + 'xp', 30, + 'ml_coins', 15 + ), + 'active', + 0, + v_today_start, + v_today_end + ) + ON CONFLICT DO NOTHING; + + -- Daily Mission 3: Use comodín + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'daily_use_comodin', + 'Usar un comodín', + 'Usa al menos un comodín en un ejercicio', + 'daily', + jsonb_build_object( + 'type', 'use_comodines', + 'target', 1, + 'current', 0 + ), + jsonb_build_object( + 'xp', 20, + 'ml_coins', 10 + ), + 'active', + 0, + v_today_start, + v_today_end + ) + ON CONFLICT DO NOTHING; + + -- ===================================================== + -- WEEKLY MISSIONS (5) + -- ===================================================== + + -- Weekly Mission 1: Complete a module + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'weekly_complete_module', + 'Completar un módulo', + 'Completa un módulo completo esta semana', + 'weekly', + jsonb_build_object( + 'type', 'complete_modules', + 'target', 1, + 'current', 0 + ), + jsonb_build_object( + 'xp', 200, + 'ml_coins', 100 + ), + 'active', + 0, + v_today_start, + v_week_end + ) + ON CONFLICT DO NOTHING; + + -- Weekly Mission 2: Daily streak + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'weekly_daily_streak', + 'Racha de 5 días', + 'Completa al menos un ejercicio durante 5 días seguidos', + 'weekly', + jsonb_build_object( + 'type', 'daily_streak', + 'target', 5, + 'current', 0 + ), + jsonb_build_object( + 'xp', 150, + 'ml_coins', 75 + ), + 'active', + 0, + v_today_start, + v_week_end + ) + ON CONFLICT DO NOTHING; + + -- Weekly Mission 3: Perfect scores + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'weekly_perfect_scores', + 'Perfección absoluta', + 'Obtén 3 puntajes perfectos (100%) en ejercicios', + 'weekly', + jsonb_build_object( + 'type', 'perfect_scores', + 'target', 3, + 'current', 0 + ), + jsonb_build_object( + 'xp', 180, + 'ml_coins', 90 + ), + 'active', + 0, + v_today_start, + v_week_end + ) + ON CONFLICT DO NOTHING; + + -- Weekly Mission 4: Explorer + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'weekly_explorer', + 'Explorador curioso', + 'Completa ejercicios de 3 módulos diferentes', + 'weekly', + jsonb_build_object( + 'type', 'explore_modules', + 'target', 3, + 'current', 0, + 'modules_visited', '[]'::jsonb + ), + jsonb_build_object( + 'xp', 120, + 'ml_coins', 60 + ), + 'active', + 0, + v_today_start, + v_week_end + ) + ON CONFLICT DO NOTHING; + + -- Weekly Mission 5: Master learner + INSERT INTO gamification_system.missions ( + user_id, + template_id, + title, + description, + mission_type, + objectives, + rewards, + status, + progress, + start_date, + end_date + ) VALUES ( + v_user_record.id, + 'weekly_master_learner', + 'Maestro del aprendizaje', + 'Completa 15 ejercicios esta semana', + 'weekly', + jsonb_build_object( + 'type', 'complete_exercises', + 'target', 15, + 'current', 0 + ), + jsonb_build_object( + 'xp', 250, + 'ml_coins', 125 + ), + 'active', + 0, + v_today_start, + v_week_end + ) + ON CONFLICT DO NOTHING; + + RAISE NOTICE ' ✅ 8 misiones creadas (3 diarias + 5 semanales)'; + + END LOOP; + + -- Contar misiones totales creadas en este script + SELECT COUNT(*) INTO v_missions_created + FROM gamification_system.missions m + WHERE m.created_at > (gamilit.now_mexico() - INTERVAL '1 minute'); + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'PROCESO COMPLETADO'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Usuarios procesados: %', v_users_processed; + RAISE NOTICE 'Misiones creadas: %', v_missions_created; + RAISE NOTICE 'Promedio por usuario: %', + CASE + WHEN v_users_processed > 0 THEN ROUND(v_missions_created::numeric / v_users_processed::numeric, 1) + ELSE 0 + END; + RAISE NOTICE '========================================'; + RAISE NOTICE ''; + +EXCEPTION + WHEN OTHERS THEN + RAISE WARNING '❌ Error inicializando misiones: %', SQLERRM; + RAISE WARNING 'SQLSTATE: %', SQLSTATE; + RAISE WARNING 'DETAIL: %', SQLERRM; +END $$; + +-- ===================================================== +-- Verification - Estado final de misiones +-- ===================================================== + +DO $$ +DECLARE + v_total_users INTEGER; + v_users_with_missions INTEGER; + v_users_without_missions INTEGER; + v_total_daily INTEGER; + v_total_weekly INTEGER; + v_total_missions INTEGER; + v_test_users_with_missions INTEGER; + v_prod_users_with_missions INTEGER; + v_user_record RECORD; +BEGIN + -- Contar usuarios totales + SELECT COUNT(*) INTO v_total_users + FROM auth_management.profiles; + + -- Contar usuarios con misiones (test) + SELECT COUNT(DISTINCT p.id) INTO v_test_users_with_missions + FROM auth_management.profiles p + WHERE EXISTS ( + SELECT 1 FROM gamification_system.missions m WHERE m.user_id = p.id + ) + AND p.email LIKE '%@gamilit.com'; + + -- Contar usuarios con misiones (producción) + SELECT COUNT(DISTINCT p.id) INTO v_prod_users_with_missions + FROM auth_management.profiles p + WHERE EXISTS ( + SELECT 1 FROM gamification_system.missions m WHERE m.user_id = p.id + ) + AND p.email NOT LIKE '%@gamilit.com'; + + -- Total usuarios con misiones + v_users_with_missions := v_test_users_with_missions + v_prod_users_with_missions; + + -- Usuarios sin misiones + SELECT COUNT(DISTINCT p.id) INTO v_users_without_missions + FROM auth_management.profiles p + WHERE NOT EXISTS ( + SELECT 1 FROM gamification_system.missions m WHERE m.user_id = p.id + ); + + -- Contar misiones por tipo + SELECT COUNT(*) INTO v_total_daily + FROM gamification_system.missions + WHERE mission_type = 'daily'; + + SELECT COUNT(*) INTO v_total_weekly + FROM gamification_system.missions + WHERE mission_type = 'weekly'; + + SELECT COUNT(*) INTO v_total_missions + FROM gamification_system.missions; + + -- Reporte final + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN FINAL - ESTADO DE MISIONES'; + RAISE NOTICE '========================================'; + RAISE NOTICE ''; + RAISE NOTICE '👥 USUARIOS:'; + RAISE NOTICE ' Total de usuarios: %', v_total_users; + RAISE NOTICE ' Usuarios con misiones (test): %', v_test_users_with_missions; + RAISE NOTICE ' Usuarios con misiones (prod): %', v_prod_users_with_missions; + RAISE NOTICE ' Usuarios SIN misiones: %', v_users_without_missions; + RAISE NOTICE ''; + RAISE NOTICE '📋 MISIONES:'; + RAISE NOTICE ' Misiones diarias: %', v_total_daily; + RAISE NOTICE ' Misiones semanales: %', v_total_weekly; + RAISE NOTICE ' Total de misiones: %', v_total_missions; + RAISE NOTICE ''; + + -- Validaciones + IF v_users_without_missions = 0 THEN + RAISE NOTICE '✅ ÉXITO: Todos los usuarios tienen misiones inicializadas'; + ELSE + RAISE WARNING '⚠️ ADVERTENCIA: % usuarios aún no tienen misiones', v_users_without_missions; + RAISE NOTICE ''; + RAISE NOTICE '📝 Usuarios sin misiones:'; + + -- Listar primeros 5 usuarios sin misiones + FOR v_user_record IN ( + SELECT + p.email, + p.display_name, + p.role, + p.created_at + FROM auth_management.profiles p + WHERE NOT EXISTS ( + SELECT 1 FROM gamification_system.missions m WHERE m.user_id = p.id + ) + ORDER BY p.created_at + LIMIT 5 + ) LOOP + RAISE NOTICE ' - % (%) - creado: %', + v_user_record.email, + v_user_record.role, + v_user_record.created_at::date; + END LOOP; + + IF v_users_without_missions > 5 THEN + RAISE NOTICE ' ... y % más', v_users_without_missions - 5; + END IF; + END IF; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE ''; + +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/12-shop_categories.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/12-shop_categories.sql new file mode 100644 index 0000000..8d652ee --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/12-shop_categories.sql @@ -0,0 +1,146 @@ +-- ===================================================== +-- Seed: gamification_system.shop_categories (PROD) +-- Description: Categorías de la tienda virtual +-- Environment: PRODUCTION +-- Dependencies: gamification_system.shop_categories table +-- Order: 12 +-- Created: 2025-11-29 +-- Version: 1.0 +-- ===================================================== +-- +-- CATEGORÍAS INCLUIDAS: +-- - cosmetics: Cosméticos visuales (avatares, marcos, fondos) +-- - profile: Items de perfil (títulos, badges) +-- - guild: Items de gremio (banderas, emblemas) +-- - social: Items sociales (emojis, stickers) +-- - consumable: Consumibles (boosts, power-ups) +-- +-- TOTAL: 5 categorías +-- +-- IMPORTANTE: Estas categorías estructuran la tienda virtual +-- y permiten organizar los items por tipo. +-- ===================================================== + +SET search_path TO gamification_system, public; + +-- ===================================================== +-- INSERT: Shop Categories +-- ===================================================== + +INSERT INTO gamification_system.shop_categories ( + name, + display_name, + description, + icon, + color, + display_order, + is_active +) VALUES +( + 'cosmetics', + 'Cosméticos', + 'Personaliza tu apariencia con avatares, marcos y fondos únicos', + 'palette', + 'from-pink-500 to-purple-500', + 1, + true +), +( + 'profile', + 'Perfil', + 'Mejora tu perfil con títulos y badges exclusivos', + 'user', + 'from-blue-500 to-cyan-500', + 2, + true +), +( + 'guild', + 'Gremio', + 'Items para personalizar y mejorar tu gremio', + 'crown', + 'from-yellow-500 to-orange-500', + 3, + true +), +( + 'social', + 'Social', + 'Emojis, stickers y efectos para interactuar con otros', + 'star', + 'from-green-500 to-emerald-500', + 4, + true +), +( + 'consumable', + 'Consumibles', + 'Boosts temporales y items de uso único', + 'zap', + 'from-orange-500 to-red-500', + 5, + true +) +ON CONFLICT (name) DO UPDATE SET + display_name = EXCLUDED.display_name, + description = EXCLUDED.description, + icon = EXCLUDED.icon, + color = EXCLUDED.color, + display_order = EXCLUDED.display_order, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + category_count INTEGER; +BEGIN + SELECT COUNT(*) INTO category_count + FROM gamification_system.shop_categories; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'SHOP CATEGORIES CREADAS EXITOSAMENTE'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total categorías: %', category_count; + RAISE NOTICE '========================================'; + + IF category_count = 5 THEN + RAISE NOTICE '✓ Todas las categorías fueron creadas correctamente'; + ELSE + RAISE WARNING '⚠ Se esperaban 5 categorías, se crearon %', category_count; + END IF; +END $$; + +-- ===================================================== +-- Listado de categorías +-- ===================================================== + +DO $$ +DECLARE + category_record RECORD; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Listado de categorías de tienda:'; + RAISE NOTICE '========================================'; + + FOR category_record IN + SELECT + display_name, + name, + icon, + display_order + FROM gamification_system.shop_categories + ORDER BY display_order + LOOP + RAISE NOTICE ' % - % [%] (orden: %)', + category_record.display_order, + category_record.display_name, + category_record.name, + category_record.icon; + END LOOP; + + RAISE NOTICE '========================================'; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/gamification_system/13-shop_items.sql b/projects/gamilit/apps/database/seeds/dev/gamification_system/13-shop_items.sql new file mode 100644 index 0000000..2f79d1d --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/gamification_system/13-shop_items.sql @@ -0,0 +1,671 @@ +-- ===================================================== +-- Seed: gamification_system.shop_items (PROD) +-- Description: Items de la tienda virtual (20+ items variados) +-- Environment: PRODUCTION +-- Dependencies: gamification_system.shop_items, shop_categories +-- Order: 13 +-- Created: 2025-11-29 +-- Version: 1.0 +-- ===================================================== +-- +-- ITEMS INCLUIDOS: +-- - COSMETICS (5 items): Avatares, marcos, fondos +-- - PROFILE (5 items): Títulos, badges exclusivos +-- - GUILD (4 items): Banderas, emblemas de gremio +-- - SOCIAL (4 items): Emojis, stickers, efectos +-- - CONSUMABLE (2 items): Boosts temporales +-- +-- TOTAL: 20 items +-- +-- IMPORTANTE: Estos items proveen valor y variedad +-- a la economía de ML Coins del sistema GAMILIT. +-- ===================================================== + +SET search_path TO gamification_system, auth_management, public; + +-- ===================================================== +-- INSERT: Shop Items +-- ===================================================== + +-- Get category IDs for reference +DO $$ +DECLARE + cat_cosmetics_id uuid; + cat_profile_id uuid; + cat_guild_id uuid; + cat_social_id uuid; + cat_consumable_id uuid; +BEGIN + SELECT id INTO cat_cosmetics_id FROM gamification_system.shop_categories WHERE name = 'cosmetics'; + SELECT id INTO cat_profile_id FROM gamification_system.shop_categories WHERE name = 'profile'; + SELECT id INTO cat_guild_id FROM gamification_system.shop_categories WHERE name = 'guild'; + SELECT id INTO cat_social_id FROM gamification_system.shop_categories WHERE name = 'social'; + SELECT id INTO cat_consumable_id FROM gamification_system.shop_categories WHERE name = 'consumable'; + + -- ===================================================== + -- CATEGORY: COSMETICS (5 items) + -- ===================================================== + + INSERT INTO gamification_system.shop_items ( + id, + tenant_id, + name, + description, + icon, + category_id, + category, + rarity, + tags, + price, + is_available, + max_per_user, + is_consumable, + effect_data, + metadata, + created_at, + updated_at + ) VALUES + ( + '80000001-0001-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Avatar Detective Dorado', + 'Avatar exclusivo de detective con acabado dorado. Demuestra tu nivel de maestría en comprensión lectora.', + 'user-circle', + cat_cosmetics_id, + 'cosmetics'::gamification_system.shop_item_category, + 'legendary', + ARRAY['avatar', 'detective', 'gold', 'exclusive'], + 500, + true, + 1, + false, + jsonb_build_object( + 'type', 'avatar', + 'asset_url', '/assets/avatars/detective-gold.png', + 'animated', false + ), + jsonb_build_object( + 'featured', true, + 'recommended_rank', 'K''uk''ulkan' + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000001-0001-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Marco Lector Experto', + 'Marco decorativo épico para tu perfil. Muestra que eres un lector experimentado.', + 'square', + cat_cosmetics_id, + 'cosmetics'::gamification_system.shop_item_category, + 'epic', + ARRAY['frame', 'border', 'expert'], + 300, + true, + 1, + false, + jsonb_build_object( + 'type', 'profile_frame', + 'asset_url', '/assets/frames/expert-reader.png', + 'border_color', '#8B5CF6' + ), + jsonb_build_object( + 'featured', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000001-0001-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Fondo Biblioteca Mágica', + 'Fondo animado de biblioteca con libros flotantes y efectos mágicos.', + 'image', + cat_cosmetics_id, + 'cosmetics'::gamification_system.shop_item_category, + 'rare', + ARRAY['background', 'library', 'animated'], + 150, + true, + 1, + false, + jsonb_build_object( + 'type', 'profile_background', + 'asset_url', '/assets/backgrounds/magic-library.gif', + 'animated', true + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000001-0001-0000-0000-000000000004'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Avatar Búho Sabio', + 'Avatar de búho con toga académica. Perfecto para estudiantes dedicados.', + 'user-circle', + cat_cosmetics_id, + 'cosmetics'::gamification_system.shop_item_category, + 'common', + ARRAY['avatar', 'owl', 'wisdom'], + 50, + true, + 1, + false, + jsonb_build_object( + 'type', 'avatar', + 'asset_url', '/assets/avatars/wise-owl.png', + 'animated', false + ), + jsonb_build_object( + 'starter_item', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000001-0001-0000-0000-000000000005'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Marco Estrellas', + 'Marco sencillo con estrellas doradas. Ideal para principiantes.', + 'square', + cat_cosmetics_id, + 'cosmetics'::gamification_system.shop_item_category, + 'common', + ARRAY['frame', 'stars', 'simple'], + 75, + true, + 1, + false, + jsonb_build_object( + 'type', 'profile_frame', + 'asset_url', '/assets/frames/stars.png', + 'border_color', '#FFD700' + ), + jsonb_build_object( + 'starter_item', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + + -- ===================================================== + -- CATEGORY: PROFILE (5 items) + -- ===================================================== + + ( + '80000002-0001-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Título "Maestro Lector"', + 'Título prestigioso que demuestra tu dominio absoluto de la comprensión lectora.', + 'award', + cat_profile_id, + 'profile'::gamification_system.shop_item_category, + 'legendary', + ARRAY['title', 'master', 'prestige'], + 400, + true, + 1, + false, + jsonb_build_object( + 'type', 'title', + 'display_text', 'Maestro Lector', + 'color', '#FFD700' + ), + jsonb_build_object( + 'featured', true, + 'achievement_showcase', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000002-0001-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Título "Explorador de Historias"', + 'Para aquellos que han explorado diversos tipos de textos y géneros literarios.', + 'compass', + cat_profile_id, + 'profile'::gamification_system.shop_item_category, + 'epic', + ARRAY['title', 'explorer', 'stories'], + 250, + true, + 1, + false, + jsonb_build_object( + 'type', 'title', + 'display_text', 'Explorador de Historias', + 'color', '#8B5CF6' + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000002-0001-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Badge Detective Élite', + 'Badge exclusivo para los mejores detectives textuales de GAMILIT.', + 'shield', + cat_profile_id, + 'profile'::gamification_system.shop_item_category, + 'rare', + ARRAY['badge', 'detective', 'elite'], + 200, + true, + 1, + false, + jsonb_build_object( + 'type', 'badge', + 'asset_url', '/assets/badges/detective-elite.png', + 'animated', true + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000002-0001-0000-0000-000000000004'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Título "Aprendiz Curioso"', + 'Título inicial para estudiantes que muestran curiosidad y ganas de aprender.', + 'book-open', + cat_profile_id, + 'profile'::gamification_system.shop_item_category, + 'common', + ARRAY['title', 'beginner', 'curious'], + 100, + true, + 1, + false, + jsonb_build_object( + 'type', 'title', + 'display_text', 'Aprendiz Curioso', + 'color', '#3B82F6' + ), + jsonb_build_object( + 'starter_item', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000002-0001-0000-0000-000000000005'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Badge Primer Logro', + 'Badge conmemorativo de tu primer logro en GAMILIT.', + 'award', + cat_profile_id, + 'profile'::gamification_system.shop_item_category, + 'common', + ARRAY['badge', 'first', 'achievement'], + 50, + true, + 1, + false, + jsonb_build_object( + 'type', 'badge', + 'asset_url', '/assets/badges/first-achievement.png', + 'animated', false + ), + jsonb_build_object( + 'starter_item', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + + -- ===================================================== + -- CATEGORY: GUILD (4 items) + -- ===================================================== + + ( + '80000003-0001-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Bandera Dorada de Gremio', + 'Bandera legendaria que ondea en la sede de tu gremio. Símbolo de excelencia.', + 'flag', + cat_guild_id, + 'guild'::gamification_system.shop_item_category, + 'legendary', + ARRAY['guild', 'banner', 'gold', 'prestige'], + 600, + true, + 1, + false, + jsonb_build_object( + 'type', 'guild_banner', + 'asset_url', '/assets/guild/golden-banner.png', + 'animated', true + ), + jsonb_build_object( + 'featured', true, + 'guild_level_required', 10 + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000003-0001-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Emblema Dragón Lector', + 'Emblema épico con diseño de dragón rodeado de libros antiguos.', + 'shield', + cat_guild_id, + 'guild'::gamification_system.shop_item_category, + 'epic', + ARRAY['guild', 'emblem', 'dragon'], + 350, + true, + 1, + false, + jsonb_build_object( + 'type', 'guild_emblem', + 'asset_url', '/assets/guild/dragon-emblem.png', + 'animated', false + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000003-0001-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Escudo del Conocimiento', + 'Escudo simbólico que representa la protección del saber.', + 'shield-check', + cat_guild_id, + 'guild'::gamification_system.shop_item_category, + 'rare', + ARRAY['guild', 'shield', 'knowledge'], + 200, + true, + 1, + false, + jsonb_build_object( + 'type', 'guild_shield', + 'asset_url', '/assets/guild/knowledge-shield.png', + 'animated', false + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000003-0001-0000-0000-000000000004'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Estandarte Básico', + 'Estandarte inicial para tu gremio. Funcional y elegante.', + 'flag', + cat_guild_id, + 'guild'::gamification_system.shop_item_category, + 'common', + ARRAY['guild', 'banner', 'basic'], + 100, + true, + 1, + false, + jsonb_build_object( + 'type', 'guild_banner', + 'asset_url', '/assets/guild/basic-banner.png', + 'animated', false + ), + jsonb_build_object( + 'starter_item', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + + -- ===================================================== + -- CATEGORY: SOCIAL (4 items) + -- ===================================================== + + ( + '80000004-0001-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Pack Emojis Premium', + 'Colección de 50 emojis exclusivos temáticos de lectura y aprendizaje.', + 'smile', + cat_social_id, + 'social'::gamification_system.shop_item_category, + 'epic', + ARRAY['emoji', 'pack', 'premium', 'exclusive'], + 200, + true, + 1, + false, + jsonb_build_object( + 'type', 'emoji_pack', + 'emoji_count', 50, + 'emojis', jsonb_build_array('📚', '✨', '🎯', '🏆', '💡', '🔥') + ), + jsonb_build_object( + 'featured', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000004-0001-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Sticker Celebración', + 'Sticker animado de celebración para compartir tus logros.', + 'sticker', + cat_social_id, + 'social'::gamification_system.shop_item_category, + 'rare', + ARRAY['sticker', 'celebration', 'animated'], + 100, + true, + null, + false, + jsonb_build_object( + 'type', 'sticker', + 'asset_url', '/assets/stickers/celebration.gif', + 'animated', true + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000004-0001-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Efecto Confeti', + 'Efecto de confeti para celebrar victorias en desafíos.', + 'sparkles', + cat_social_id, + 'social'::gamification_system.shop_item_category, + 'rare', + ARRAY['effect', 'confetti', 'celebration'], + 150, + true, + 1, + false, + jsonb_build_object( + 'type', 'chat_effect', + 'effect_name', 'confetti', + 'duration_seconds', 5 + ), + jsonb_build_object(), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000004-0001-0000-0000-000000000004'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Pack Emojis Básico', + 'Colección inicial de 20 emojis básicos para comunicarte.', + 'smile', + cat_social_id, + 'social'::gamification_system.shop_item_category, + 'common', + ARRAY['emoji', 'pack', 'basic'], + 50, + true, + 1, + false, + jsonb_build_object( + 'type', 'emoji_pack', + 'emoji_count', 20, + 'emojis', jsonb_build_array('😊', '👍', '❤️', '🎉', '💪', '🌟') + ), + jsonb_build_object( + 'starter_item', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + + -- ===================================================== + -- CATEGORY: CONSUMABLE (2 items) + -- ===================================================== + + ( + '80000005-0001-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Boost XP 2x (24h)', + 'Duplica tu ganancia de XP durante 24 horas. Perfecto para avanzar rápido.', + 'zap', + cat_consumable_id, + 'consumable'::gamification_system.shop_item_category, + 'rare', + ARRAY['boost', 'xp', 'temporary'], + 100, + true, + null, + true, + jsonb_build_object( + 'type', 'xp_boost', + 'multiplier', 2.0, + 'duration_hours', 24 + ), + jsonb_build_object( + 'stackable', false, + 'popular', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ), + ( + '80000005-0001-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Boost Coins 1.5x (12h)', + 'Aumenta 50% la ganancia de ML Coins durante 12 horas.', + 'coins', + cat_consumable_id, + 'consumable'::gamification_system.shop_item_category, + 'common', + ARRAY['boost', 'coins', 'temporary'], + 75, + true, + null, + true, + jsonb_build_object( + 'type', 'coins_boost', + 'multiplier', 1.5, + 'duration_hours', 12 + ), + jsonb_build_object( + 'stackable', false + ), + gamilit.now_mexico(), + gamilit.now_mexico() + ) + ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + icon = EXCLUDED.icon, + category_id = EXCLUDED.category_id, + category = EXCLUDED.category, + rarity = EXCLUDED.rarity, + tags = EXCLUDED.tags, + price = EXCLUDED.price, + is_available = EXCLUDED.is_available, + max_per_user = EXCLUDED.max_per_user, + is_consumable = EXCLUDED.is_consumable, + effect_data = EXCLUDED.effect_data, + metadata = EXCLUDED.metadata, + updated_at = gamilit.now_mexico(); +END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + items_count INTEGER; + cosmetics_count INTEGER; + profile_count INTEGER; + guild_count INTEGER; + social_count INTEGER; + consumable_count INTEGER; +BEGIN + SELECT COUNT(*) INTO items_count FROM gamification_system.shop_items; + SELECT COUNT(*) INTO cosmetics_count FROM gamification_system.shop_items WHERE category = 'cosmetics'; + SELECT COUNT(*) INTO profile_count FROM gamification_system.shop_items WHERE category = 'profile'; + SELECT COUNT(*) INTO guild_count FROM gamification_system.shop_items WHERE category = 'guild'; + SELECT COUNT(*) INTO social_count FROM gamification_system.shop_items WHERE category = 'social'; + SELECT COUNT(*) INTO consumable_count FROM gamification_system.shop_items WHERE category = 'consumable'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'SHOP ITEMS CREADOS EXITOSAMENTE'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total items: %', items_count; + RAISE NOTICE ' - Cosmetics: %', cosmetics_count; + RAISE NOTICE ' - Profile: %', profile_count; + RAISE NOTICE ' - Guild: %', guild_count; + RAISE NOTICE ' - Social: %', social_count; + RAISE NOTICE ' - Consumable: %', consumable_count; + RAISE NOTICE '========================================'; + + IF items_count >= 20 THEN + RAISE NOTICE '✓ Todos los items fueron creados correctamente'; + ELSE + RAISE WARNING '⚠ Se esperaban al menos 20 items, se crearon %', items_count; + END IF; +END $$; + +-- ===================================================== +-- Listado de items por categoría y rareza +-- ===================================================== + +DO $$ +DECLARE + item_record RECORD; + current_category TEXT := ''; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Listado de items de tienda por categoría:'; + RAISE NOTICE '========================================'; + + FOR item_record IN + SELECT + name, + category::text as category, + rarity, + price, + is_consumable + FROM gamification_system.shop_items + ORDER BY category, rarity DESC, price DESC + LOOP + IF current_category != item_record.category THEN + current_category := item_record.category; + RAISE NOTICE ''; + RAISE NOTICE '=== % ===', UPPER(current_category); + END IF; + + RAISE NOTICE ' - % [%] - % ML Coins %', + item_record.name, + item_record.rarity, + item_record.price, + CASE WHEN item_record.is_consumable THEN '(Consumible)' ELSE '' END; + END LOOP; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/social_features/00-schools-default.sql b/projects/gamilit/apps/database/seeds/dev/social_features/00-schools-default.sql new file mode 100644 index 0000000..3f38753 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/social_features/00-schools-default.sql @@ -0,0 +1,167 @@ +-- ===================================================== +-- Seed: social_features.schools - SCHOOL DEFAULT (PROD) +-- Description: Escuela del sistema para usuarios pendientes de asignación +-- Environment: PRODUCTION +-- Dependencies: auth_management.tenants +-- Order: 00 (debe ejecutarse ANTES de 01-schools.sql) +-- Created: 2025-12-15 +-- Version: 1.0 +-- ===================================================== +-- +-- PROPÓSITO: +-- Esta escuela es utilizada por el sistema para: +-- 1. Asignar automáticamente a usuarios admin nuevos +-- 2. Servir como pool de usuarios "por asignar" +-- 3. El classroom DEFAULT apunta a esta escuela +-- +-- UUID FIJO: 99999999-9999-9999-9999-999999999999 +-- CÓDIGO: SYSTEM-UNASSIGNED +-- +-- IMPORTANTE: Esta escuela NO debe eliminarse nunca. +-- ===================================================== + +SET search_path TO social_features, auth_management, public; + +-- ===================================================== +-- Obtener tenant_id para la escuela +-- ===================================================== + +DO $$ +DECLARE + v_tenant_id UUID; +BEGIN + -- Obtener el tenant principal de GAMILIT Platform + SELECT id INTO v_tenant_id + FROM auth_management.tenants + WHERE name = 'GAMILIT Platform' + LIMIT 1; + + IF v_tenant_id IS NULL THEN + RAISE EXCEPTION 'Tenant "GAMILIT Platform" no encontrado. Ejecutar primero seed de tenants.'; + END IF; + + RAISE NOTICE 'Usando tenant_id: %', v_tenant_id; + +-- ===================================================== +-- INSERT: Escuela Default del Sistema +-- ===================================================== + +INSERT INTO social_features.schools ( + id, + tenant_id, + name, + code, + short_name, + description, + address, + city, + region, + country, + postal_code, + phone, + email, + website, + principal_id, + administrative_contact_id, + academic_year, + semester_system, + grade_levels, + settings, + max_students, + max_teachers, + current_students_count, + current_teachers_count, + is_active, + is_verified, + metadata, + created_at, + updated_at +) VALUES ( + '99999999-9999-9999-9999-999999999999'::uuid, -- UUID fija para sistema + v_tenant_id, + 'Sistema - Por Asignar', + 'SYSTEM-UNASSIGNED', + 'Sistema', + 'Escuela del sistema para usuarios pendientes de asignación a sus instituciones finales. Los administradores y profesores nuevos se asignan aquí automáticamente.', + NULL, -- Sin dirección física + NULL, -- Sin ciudad + NULL, -- Sin región + 'México', + NULL, -- Sin código postal + NULL, -- Sin teléfono + 'sistema@gamilit.com', -- Email de sistema + NULL, -- Sin website + NULL, -- Sin principal_id + NULL, -- Sin administrative_contact_id + '2025', -- Año académico + false, -- No usa semestres + ARRAY['todos'], -- Todos los niveles + jsonb_build_object( + 'is_system', true, + 'is_default', true, + 'auto_assignment', true, + 'allow_reassignment', true, + 'allow_public_registration', false, + 'require_email_verification', false, + 'enable_gamification', true, + 'max_students_per_classroom', 999, + 'description', 'Configuración de sistema - no editar' + ), + 9999, -- max_students (sin límite efectivo) + 999, -- max_teachers (sin límite efectivo) + 0, -- current_students_count + 0, -- current_teachers_count + true, -- is_active + true, -- is_verified (sistema) + jsonb_build_object( + 'system_school', true, + 'is_default', true, + 'created_by', 'system', + 'purpose', 'Escuela de sistema para asignación pendiente', + 'policies', jsonb_build_object( + 'allow_student_reassignment', true, + 'allow_admin_reassignment', true, + 'require_approval', false, + 'auto_assign_new_admins', true + ), + 'description', 'Escuela automática del sistema para gestión de usuarios no asignados' + ), + gamilit.now_mexico(), + gamilit.now_mexico() +) +ON CONFLICT (code) DO UPDATE SET + name = EXCLUDED.name, + short_name = EXCLUDED.short_name, + description = EXCLUDED.description, + settings = EXCLUDED.settings, + metadata = EXCLUDED.metadata, + is_active = true, + updated_at = gamilit.now_mexico(); + +END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + v_school_id UUID; + v_school_name TEXT; +BEGIN + SELECT id, name INTO v_school_id, v_school_name + FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED'; + + IF v_school_id IS NOT NULL THEN + RAISE NOTICE '========================================'; + RAISE NOTICE 'ESCUELA DEFAULT DEL SISTEMA CREADA'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'ID: %', v_school_id; + RAISE NOTICE 'Nombre: %', v_school_name; + RAISE NOTICE 'Código: SYSTEM-UNASSIGNED'; + RAISE NOTICE '========================================'; + ELSE + RAISE WARNING 'ERROR: No se pudo crear la escuela default del sistema'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/social_features/01-schools.sql b/projects/gamilit/apps/database/seeds/dev/social_features/01-schools.sql index e82c0ed..72b2236 100644 --- a/projects/gamilit/apps/database/seeds/dev/social_features/01-schools.sql +++ b/projects/gamilit/apps/database/seeds/dev/social_features/01-schools.sql @@ -1,210 +1,58 @@ --- ===================================================================== --- Archivo: 01-schools.sql --- Schema: social_features --- Descripción: Seeds de escuelas demo para testing --- Dependencias: auth.users (instructores) --- Autor: SA-SEEDS-SOCIAL --- Fecha: 2025-11-02 --- ===================================================================== +-- ===================================================== +-- Seed: social_features.schools (DEV) +-- Description: NO demo schools - Solo escuela default +-- Environment: DEVELOPMENT +-- Dependencies: auth_management.tenants +-- Order: 01 +-- Created: 2025-01-11 +-- Updated: 2025-12-15 - Removidas escuelas demo +-- Version: 3.0 +-- ===================================================== +-- +-- NOTA: Este archivo está intencionalmente vacío de INSERTs. +-- +-- DECISIÓN DE DISEÑO: +-- - Solo existe la escuela default "Sistema - Por Asignar" (SYSTEM-UNASSIGNED) +-- - Creada en 00-schools-default.sql +-- - Todas las escuelas adicionales serán creadas por el admin desde la UI +-- +-- ESCUELAS DEMO REMOVIDAS (v3.0): +-- - Secundaria Federal No. 15 "Marie Curie" - REMOVIDA +-- - Secundaria Técnica No. 42 - REMOVIDA +-- - Colegio Científico "Albert Einstein" - REMOVIDA +-- +-- ===================================================== -SET search_path TO social_features, public; +SET search_path TO social_features, auth_management, public; --- ===================================================================== --- SCHOOLS: Escuelas de diferentes tipos y ubicaciones --- ===================================================================== +-- ===================================================== +-- Verification Query - Solo escuela default +-- ===================================================== -INSERT INTO social_features.schools ( - name, code, type, - address, city, state, country, postal_code, - phone, email, website, - principal_name, contact_name, email, - current_students_count, current_teachers_count, - is_active, - settings, metadata, - created_at, updated_at -) VALUES --- ===================================================================== --- Escuela 1: Secundaria Federal No. 15 "Marie Curie" (Ciudad de México) --- Tipo: Pública | Turno: Matutino | Capacidad: 450 estudiantes --- ===================================================================== -( - 'Secundaria Federal No. 15 "Marie Curie"', - 'SF-015-CDMX', - 'public', - 'Av. Insurgentes Sur 1234', - 'Ciudad de México', - 'CDMX', - 'México', - '03100', - '55-1234-5678', - 'secundaria15@sep.gob.mx', - 'https://sf15mariecurie.edu.mx', - 'Lic. Ana García Rodríguez', - 'Prof. Carlos Méndez', - 'contacto@sf15mariecurie.edu.mx', - 450, - 32, - 'active', - true, - '{ - "allow_public_registration": true, - "require_email_verification": true, - "max_students_per_classroom": 35, - "enable_parent_portal": true, - "academic_calendar": { - "start_date": "2025-08-15", - "end_date": "2026-07-15", - "vacation_periods": [ - {"name": "Navidad", "start": "2025-12-20", "end": "2026-01-06"}, - {"name": "Semana Santa", "start": "2026-04-02", "end": "2026-04-12"} - ] - } - }'::jsonb, - '{ - "year_founded": 1975, - "cct": "09DES0015K", - "shift": "matutino", - "grades": ["1", "2", "3"], - "recognition": "Escuela de Calidad 2024", - "infrastructure": { - "library": true, - "computer_lab": true, - "science_lab": true, - "sports_facilities": true - } - }'::jsonb, - NOW(), - NOW() -), - --- ===================================================================== --- Escuela 2: Secundaria Técnica No. 42 (Monterrey) --- Tipo: Pública Técnica | Turno: Vespertino | Especialidades técnicas --- ===================================================================== -( - 'Secundaria Técnica No. 42', - 'ST-042-NL', - 'public', - 'Av. Tecnológico 567', - 'Monterrey', - 'Nuevo León', - 'México', - '64700', - '81-8765-4321', - 'secundariatecnica42@sep.gob.mx', - 'https://st42.edu.mx', - 'Ing. Roberto Sánchez', - 'Prof. Laura Martínez', - 'contacto@st42.edu.mx', - 380, - 28, - 'active', - true, - '{ - "allow_public_registration": true, - "require_email_verification": true, - "max_students_per_classroom": 30, - "enable_parent_portal": true, - "technical_workshops": true, - "academic_calendar": { - "start_date": "2025-08-15", - "end_date": "2026-07-15" - } - }'::jsonb, - '{ - "year_founded": 1982, - "cct": "19DST0042L", - "shift": "vespertino", - "grades": ["1", "2", "3"], - "specialties": ["Computación", "Electrónica", "Diseño Gráfico"], - "certifications": ["SEP", "CONOCER"], - "infrastructure": { - "library": true, - "computer_lab": true, - "electronics_workshop": true, - "design_studio": true - } - }'::jsonb, - NOW(), - NOW() -), - --- ===================================================================== --- Escuela 3: Colegio Científico "Albert Einstein" (Guadalajara) --- Tipo: Privado | Enfoque: STEAM | Bilingüe --- ===================================================================== -( - 'Colegio Científico "Albert Einstein"', - 'CP-AE-JAL', - 'private', - 'Av. Chapultepec 890', - 'Guadalajara', - 'Jalisco', - 'México', - '44100', - '33-3456-7890', - 'info@colegioeinstein.edu.mx', - 'https://colegioeinstein.edu.mx', - 'Dra. Patricia Hernández', - 'Lic. Miguel Ángel Torres', - 'admisiones@colegioeinstein.edu.mx', - 280, - 24, - 'active', - true, - '{ - "allow_public_registration": false, - "require_email_verification": true, - "max_students_per_classroom": 25, - "enable_parent_portal": true, - "tuition_required": true, - "admission_process": { - "requires_interview": true, - "requires_exam": true, - "requires_documents": ["birth_certificate", "previous_grades", "recommendation_letters"] - }, - "academic_calendar": { - "start_date": "2025-08-15", - "end_date": "2026-06-30" - } - }'::jsonb, - '{ - "year_founded": 1995, - "accreditation": "SACS", - "bilingual": true, - "steam_focused": true, - "international_programs": ["Cambridge IGCSE"], - "partnerships": ["MIT", "Stanford Pre-Collegiate"], - "infrastructure": { - "library": true, - "computer_lab": true, - "science_lab": true, - "robotics_lab": true, - "innovation_hub": true, - "sports_complex": true, - "auditorium": true - }, - "extracurricular": ["Robotics Club", "Science Olympiad", "Model UN", "Debate Team"] - }'::jsonb, - NOW(), - NOW() -) -ON CONFLICT (code) DO UPDATE SET - name = EXCLUDED.name, - email = EXCLUDED.email, - current_students_count = EXCLUDED.current_students_count, - current_teachers_count = EXCLUDED.current_teachers_count, - settings = EXCLUDED.settings, - metadata = EXCLUDED.metadata, - updated_at = NOW(); - --- ===================================================================== --- Verificación de inserción --- ===================================================================== DO $$ DECLARE - inserted_count INTEGER; + school_count INTEGER; + default_school_exists BOOLEAN; BEGIN - SELECT COUNT(*) INTO inserted_count FROM social_features.schools; - RAISE NOTICE 'Total de escuelas en la base de datos: %', inserted_count; + SELECT COUNT(*) INTO school_count + FROM social_features.schools; + + SELECT EXISTS( + SELECT 1 FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED' AND is_active = true + ) INTO default_school_exists; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN DE ESCUELAS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total escuelas: %', school_count; + RAISE NOTICE 'Escuela default existe: %', default_school_exists; + RAISE NOTICE '========================================'; + + IF default_school_exists THEN + RAISE NOTICE '✓ Escuela default (SYSTEM-UNASSIGNED) configurada correctamente'; + RAISE NOTICE ' Las demás escuelas serán creadas por el admin desde la UI'; + ELSE + RAISE WARNING '⚠ Escuela default NO encontrada. Ejecutar 00-schools-default.sql primero'; + END IF; END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/social_features/02-classrooms.sql b/projects/gamilit/apps/database/seeds/dev/social_features/02-classrooms.sql index 05ea8fd..83d7da4 100644 --- a/projects/gamilit/apps/database/seeds/dev/social_features/02-classrooms.sql +++ b/projects/gamilit/apps/database/seeds/dev/social_features/02-classrooms.sql @@ -1,349 +1,239 @@ --- ===================================================================== --- Archivo: 02-classrooms.sql --- Schema: social_features --- Descripción: Seeds de aulas/grupos para las escuelas --- Dependencias: 01-schools.sql, auth.users --- Autor: SA-SEEDS-SOCIAL --- Fecha: 2025-11-02 --- ===================================================================== +-- ===================================================== +-- Seed: social_features.classrooms (DEV) +-- Description: SOLO classroom default para asignación automática +-- Environment: DEVELOPMENT +-- Dependencies: social_features.schools (00-schools-default.sql), auth_management.profiles +-- Order: 02 +-- Created: 2025-01-11 +-- Updated: 2025-12-15 - Simplificado a solo DEFAULT +-- Version: 3.0 +-- ===================================================== +-- +-- AULAS INCLUIDAS: +-- - Sin Asignar (DEFAULT - Sistema) - Única aula del sistema +-- +-- TOTAL: 1 aula (sistema) +-- +-- DECISIÓN DE DISEÑO: +-- - Solo existe el classroom default para asignación automática +-- - Todas las aulas adicionales serán creadas por el admin desde la UI +-- - Los estudiantes nuevos se asignan automáticamente aquí +-- +-- AULAS DEMO REMOVIDAS (v3.0): +-- - 5to A (Marie Curie) - REMOVIDA +-- - 5to B (Marie Curie) - REMOVIDA +-- - 6to A (Marie Curie) - REMOVIDA +-- - Aula de Pruebas (IEI) - REMOVIDA +-- - Demo Parent Portal (IEI) - REMOVIDA +-- +-- ===================================================== -SET search_path TO social_features, auth, public; +SET search_path TO social_features, auth_management, public; --- ===================================================================== --- CLASSROOMS: Aulas/grupos distribuidos por escuelas --- ===================================================================== +-- ===================================================== +-- Obtener tenant_id y validar dependencias +-- ===================================================== DO $$ DECLARE - school_sf15 UUID; - school_st42 UUID; - school_einstein UUID; - teacher_id UUID; + v_tenant_id UUID; + v_default_school_id UUID; + v_teacher_id UUID; BEGIN - -- ===================================================================== - -- Obtener school IDs dinámicamente - -- ===================================================================== - SELECT school_id INTO school_sf15 - FROM social_features.schools - WHERE school_code = 'SF-015-CDMX'; + -- Obtener el tenant principal + SELECT id INTO v_tenant_id + FROM auth_management.tenants + WHERE name = 'GAMILIT Platform' + LIMIT 1; - SELECT school_id INTO school_st42 - FROM social_features.schools - WHERE school_code = 'ST-042-NL'; - - SELECT school_id INTO school_einstein - FROM social_features.schools - WHERE school_code = 'CP-AE-JAL'; - - -- ===================================================================== - -- Obtener instructor demo - -- ===================================================================== - SELECT user_id INTO teacher_id - FROM auth.users - WHERE email = 'instructor@demo.glit.edu.mx'; - - -- Validar que exista el instructor - IF teacher_id IS NULL THEN - RAISE EXCEPTION 'No se encontró el instructor demo. Ejecutar seeds de auth primero.'; + IF v_tenant_id IS NULL THEN + RAISE EXCEPTION 'Tenant "GAMILIT Platform" no encontrado. Ejecutar primero seed de tenants.'; END IF; - -- ===================================================================== - -- AULAS PARA SECUNDARIA FEDERAL 15 (CDMX) - -- ===================================================================== + -- Obtener la escuela default + SELECT id INTO v_default_school_id + FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED' AND is_active = true + LIMIT 1; - -- Aula 1: 2° A - Comprensión Lectora - INSERT INTO social_features.classrooms ( - school_id, teacher_id, - name, code, grade_level, section, - subject, description, - capacity, current_students_count, - start_date, end_date, - schedule, is_active, is_active, - settings, created_at, updated_at - ) VALUES - ( - school_sf15, - teacher_id, - '2° A - Comprensión Lectora', - '2A-LECT-2025', - '2', - 'A', - 'Comprensión Lectora', - 'Grupo de segundo año, sección A. Enfoque en desarrollo de competencias lectoras con metodología GLIT. Estrategias de lectura crítica y análisis textual.', - 35, - 0, - '2025-08-15', - '2026-07-15', - '{ - "days": ["Lunes", "Miércoles", "Viernes"], - "time": "08:00-09:00", - "room": "Aula 201", - "weekly_hours": 3 - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "require_parental_consent": true, - "grading_system": "numerical", - "attendance_required": true, - "homework_policy": { - "frequency": "weekly", - "submission_platform": "glit" - } - }'::jsonb, - NOW(), - NOW() + IF v_default_school_id IS NULL THEN + RAISE EXCEPTION 'Escuela default (SYSTEM-UNASSIGNED) no encontrada. Ejecutar primero 00-schools-default.sql'; + END IF; + + -- Obtener el teacher default (teacher@gamilit.com) + SELECT id INTO v_teacher_id + FROM auth_management.profiles + WHERE email = 'teacher@gamilit.com' + LIMIT 1; + + IF v_teacher_id IS NULL THEN + -- Usar el UUID conocido como fallback + v_teacher_id := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid; + RAISE WARNING 'Teacher default no encontrado, usando UUID: %', v_teacher_id; + END IF; + + RAISE NOTICE 'Usando tenant_id: %', v_tenant_id; + RAISE NOTICE 'Usando school_id (default): %', v_default_school_id; + RAISE NOTICE 'Usando teacher_id: %', v_teacher_id; + +-- ===================================================== +-- INSERT: SOLO Classroom DEFAULT +-- ===================================================== + +INSERT INTO social_features.classrooms ( + id, + school_id, + tenant_id, + teacher_id, + name, + code, + grade_level, + section, + subject, + description, + capacity, + current_students_count, + start_date, + end_date, + schedule, + is_active, + settings, + metadata, + created_at, + updated_at +) VALUES +-- ===================================================== +-- CLASSROOM DEFAULT (Sistema - Sin Asignar) +-- IMPORTANTE: Este classroom es usado automáticamente para +-- asignar estudiantes nuevos que aún no tienen aula. +-- ===================================================== +( + '00000000-0000-0000-0000-000000000001'::uuid, -- UUID predecible para default + v_default_school_id, -- Escuela Sistema - Por Asignar (default) + v_tenant_id, + v_teacher_id, -- Teacher default (teacher@gamilit.com) + 'Sin Asignar - Aula Default', + 'DEFAULT', + 'todos', -- Todos los niveles + 'DEFAULT', + 'General', + 'Aula de sistema para estudiantes pendientes de asignación. Los administradores y profesores pueden reasignar estudiantes a aulas específicas.', + 999, -- Capacidad alta para no limitar + 0, + '2025-01-01'::date, + '2099-12-31'::date, -- Sin fecha de fin + jsonb_build_object( + 'days', jsonb_build_array('Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes'), + 'time', 'flexible', + 'room', 'Virtual', + 'weekly_hours', 0 ), - - -- Aula 2: 3° B - Lectura Digital - ( - school_sf15, - teacher_id, - '3° B - Lectura Digital', - '3B-DIGI-2025', - '3', - 'B', - 'Lectura Digital', - 'Grupo de tercer año, sección B. Especialización en alfabetización digital, fact-checking y análisis crítico de medios digitales.', - 35, - 0, - '2025-08-15', - '2026-07-15', - '{ - "days": ["Martes", "Jueves"], - "time": "10:00-11:30", - "room": "Laboratorio de Cómputo", - "weekly_hours": 3 - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "require_parental_consent": true, - "grading_system": "numerical", - "attendance_required": true, - "digital_literacy_focus": true, - "tools_used": ["GLIT", "Google Classroom", "Canva"] - }'::jsonb, - NOW(), - NOW() + true, + jsonb_build_object( + 'allow_student_self_enrollment', false, + 'enable_gamification', true, + 'require_parental_consent', false, + 'grading_system', 'none', + 'attendance_required', false, + 'is_system_classroom', true ), - - -- Aula 3: 1° C - Lectura Básica - ( - school_sf15, - teacher_id, - '1° C - Lectura Básica', - '1C-BASIC-2025', - '1', - 'C', - 'Lectura Básica', - 'Primer año, sección C. Fundamentos de comprensión lectora y desarrollo de habilidades básicas.', - 35, - 0, - '2025-08-15', - '2026-07-15', - '{ - "days": ["Lunes", "Miércoles", "Viernes"], - "time": "11:00-12:00", - "room": "Aula 105", - "weekly_hours": 3 - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "require_parental_consent": true, - "grading_system": "numerical", - "attendance_required": true, - "foundational_skills": true - }'::jsonb, - NOW(), - NOW() + jsonb_build_object( + 'is_default', true, + 'system_classroom', true, + 'auto_assignment', true, + 'description', 'Classroom para asignación automática de estudiantes nuevos' ), - - -- ===================================================================== - -- AULAS PARA SECUNDARIA TÉCNICA 42 (MONTERREY) - -- ===================================================================== - - -- Aula 4: 1° A - Introducción a la Lectura - ( - school_st42, - teacher_id, - '1° A - Introducción a la Lectura', - '1A-INTRO-2025', - '1', - 'A', - 'Introducción a la Lectura', - 'Primer año. Desarrollo de habilidades básicas de comprensión lectora con enfoque técnico y práctico.', - 30, - 0, - '2025-08-15', - '2026-07-15', - '{ - "days": ["Lunes", "Miércoles", "Viernes"], - "time": "14:00-15:00", - "room": "Aula 105", - "weekly_hours": 3, - "shift": "vespertino" - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "require_parental_consent": true, - "grading_system": "numerical", - "technical_reading_focus": true - }'::jsonb, - NOW(), - NOW() - ), - - -- Aula 5: 2° B - Lectura Técnica - ( - school_st42, - teacher_id, - '2° B - Lectura Técnica', - '2B-TECH-2025', - '2', - 'B', - 'Lectura Técnica', - 'Segundo año. Comprensión de textos técnicos, manuales y documentación especializada.', - 30, - 0, - '2025-08-15', - '2026-07-15', - '{ - "days": ["Martes", "Jueves"], - "time": "15:00-16:30", - "room": "Taller de Computación", - "weekly_hours": 3, - "shift": "vespertino" - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "technical_manuals": true, - "industry_standards": ["ISO", "IEEE"] - }'::jsonb, - NOW(), - NOW() - ), - - -- ===================================================================== - -- AULAS PARA COLEGIO EINSTEIN (GUADALAJARA) - -- ===================================================================== - - -- Aula 6: 2° STEAM - Literatura Científica - ( - school_einstein, - teacher_id, - '2° STEAM - Literatura Científica', - '2ST-LITC-2025', - '2', - 'STEAM', - 'Literatura Científica', - 'Grupo STEAM. Integración de literatura y ciencias mediante biografías de científicos, ensayos y divulgación científica.', - 25, - 0, - '2025-08-15', - '2026-06-30', - '{ - "days": ["Lunes", "Miércoles", "Viernes"], - "time": "09:00-10:30", - "room": "Aula STEAM 3", - "language": "Español/Inglés", - "weekly_hours": 4 - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "require_parental_consent": false, - "bilingual": true, - "grading_system": "cambridge", - "steam_integration": true, - "project_based_learning": true - }'::jsonb, - NOW(), - NOW() - ), - - -- Aula 7: 3° Advanced - Critical Reading (Bilingüe) - ( - school_einstein, - teacher_id, - '3° Advanced - Critical Reading', - '3ADV-CRIT-2025', - '3', - 'Advanced', - 'Critical Reading', - 'Tercer año avanzado. Lectura crítica en inglés y español con análisis de textos complejos y retórica.', - 25, - 0, - '2025-08-15', - '2026-06-30', - '{ - "days": ["Martes", "Jueves"], - "time": "11:00-12:30", - "room": "Innovation Hub", - "language": "English/Spanish", - "weekly_hours": 3 - }'::jsonb, - 'active', - true, - '{ - "allow_student_self_enrollment": false, - "enable_gamification": true, - "bilingual": true, - "grading_system": "cambridge", - "advanced_placement": true, - "college_prep": true, - "debate_integration": true - }'::jsonb, - NOW(), - NOW() - ) - ON CONFLICT (school_id, code) DO UPDATE SET - name = EXCLUDED.name, - current_students_count = EXCLUDED.current_students_count, - schedule = EXCLUDED.schedule, - settings = EXCLUDED.settings, - updated_at = NOW(); - - -- ===================================================================== - -- SYNC teacher_classrooms (many-to-many) - -- ===================================================================== - -- Asegurar que todos los classrooms tienen su entrada en teacher_classrooms - -- Esto es necesario porque algunos servicios usan classrooms.teacher_id - -- y otros usan la tabla teacher_classrooms - -- ===================================================================== - INSERT INTO social_features.teacher_classrooms (id, teacher_id, classroom_id, tenant_id, role, assigned_at, created_at) - SELECT - gen_random_uuid(), - c.teacher_id, - c.id, - c.tenant_id, - 'owner', - c.created_at, - NOW() - FROM social_features.classrooms c - WHERE c.teacher_id IS NOT NULL - ON CONFLICT DO NOTHING; - - -- ===================================================================== - -- Verificación de inserción - -- ===================================================================== - RAISE NOTICE 'Aulas creadas exitosamente para 3 escuelas'; - RAISE NOTICE 'SF-015-CDMX: 3 aulas | ST-042-NL: 2 aulas | CP-AE-JAL: 2 aulas'; - RAISE NOTICE 'Sincronización teacher_classrooms ejecutada'; + gamilit.now_mexico(), + gamilit.now_mexico() +) +ON CONFLICT (code) DO UPDATE SET + school_id = EXCLUDED.school_id, + name = EXCLUDED.name, + description = EXCLUDED.description, + capacity = EXCLUDED.capacity, + is_active = EXCLUDED.is_active, + settings = EXCLUDED.settings, + metadata = EXCLUDED.metadata, + updated_at = gamilit.now_mexico(); END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + classroom_count INTEGER; + default_classroom_exists BOOLEAN; + default_classroom RECORD; +BEGIN + SELECT COUNT(*) INTO classroom_count + FROM social_features.classrooms; + + SELECT EXISTS( + SELECT 1 FROM social_features.classrooms + WHERE code = 'DEFAULT' AND is_active = true + ) INTO default_classroom_exists; + + SELECT c.id, c.name, c.code, s.name as school_name + INTO default_classroom + FROM social_features.classrooms c + JOIN social_features.schools s ON c.school_id = s.id + WHERE c.code = 'DEFAULT'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN DE CLASSROOMS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total classrooms: %', classroom_count; + RAISE NOTICE 'Classroom default existe: %', default_classroom_exists; + + IF default_classroom_exists THEN + RAISE NOTICE '========================================'; + RAISE NOTICE 'CLASSROOM DEFAULT:'; + RAISE NOTICE ' ID: %', default_classroom.id; + RAISE NOTICE ' Nombre: %', default_classroom.name; + RAISE NOTICE ' Código: %', default_classroom.code; + RAISE NOTICE ' Escuela: %', default_classroom.school_name; + RAISE NOTICE '========================================'; + RAISE NOTICE '✓ Classroom default configurado correctamente'; + RAISE NOTICE ' Las demás aulas serán creadas por el admin desde la UI'; + ELSE + RAISE WARNING '⚠ Classroom default NO encontrado'; + END IF; +END $$; + +-- ===================================================== +-- SYNC teacher_classrooms (many-to-many) +-- ===================================================== +-- Asegurar que el classroom default tiene su entrada en teacher_classrooms +-- ===================================================== + +INSERT INTO social_features.teacher_classrooms (id, teacher_id, classroom_id, tenant_id, role, assigned_at, created_at) +SELECT + gen_random_uuid(), + c.teacher_id, + c.id, + c.tenant_id, + 'owner', + c.created_at, + NOW() +FROM social_features.classrooms c +WHERE c.teacher_id IS NOT NULL + AND c.code = 'DEFAULT' +ON CONFLICT DO NOTHING; + +-- Verificar sync +DO $$ +DECLARE + tc_count INTEGER; +BEGIN + SELECT COUNT(*) INTO tc_count + FROM social_features.teacher_classrooms tc + JOIN social_features.classrooms c ON tc.classroom_id = c.id + WHERE c.code = 'DEFAULT'; + + RAISE NOTICE ''; + RAISE NOTICE 'teacher_classrooms sincronizados para DEFAULT: %', tc_count; + RAISE NOTICE '========================================'; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/social_features/03-classroom-members.sql b/projects/gamilit/apps/database/seeds/dev/social_features/03-classroom-members.sql index 98febe0..5646db0 100644 --- a/projects/gamilit/apps/database/seeds/dev/social_features/03-classroom-members.sql +++ b/projects/gamilit/apps/database/seeds/dev/social_features/03-classroom-members.sql @@ -1,335 +1,196 @@ --- ===================================================================== --- Archivo: 03-classroom-members.sql --- Schema: social_features --- Descripción: Seeds de membresías de estudiantes a aulas --- Dependencias: 02-classrooms.sql, auth.users (estudiantes) --- Autor: SA-SEEDS-SOCIAL --- Fecha: 2025-11-02 --- ===================================================================== +-- ===================================================== +-- Seed: social_features.classroom_members (DEV) +-- Description: Asignar TODOS los estudiantes al classroom DEFAULT +-- Environment: DEVELOPMENT +-- Dependencies: social_features.classrooms (02-classrooms.sql), auth_management.profiles +-- Order: 03 +-- Created: 2025-01-11 +-- Updated: 2025-12-15 - Simplificado a solo DEFAULT +-- Version: 3.0 +-- ===================================================== +-- +-- DECISIÓN DE DISEÑO: +-- - Todos los estudiantes se asignan automáticamente al classroom DEFAULT +-- - El admin/teacher puede reasignarlos a otros classrooms desde la UI +-- - Esto facilita la gestión inicial de usuarios nuevos +-- +-- ASOCIACIONES DEMO REMOVIDAS (v3.0): +-- - Todas las asociaciones específicas - REMOVIDAS +-- +-- ===================================================== -SET search_path TO social_features, auth, public; +SET search_path TO social_features, auth_management, public; --- ===================================================================== --- CLASSROOM MEMBERS: Asignar estudiantes demo a aulas --- ===================================================================== +-- ===================================================== +-- Asignar TODOS los estudiantes al classroom DEFAULT +-- ===================================================== DO $$ DECLARE - classroom_2a UUID; - classroom_3b UUID; - classroom_1c UUID; - classroom_1a UUID; - classroom_2b UUID; - classroom_2st UUID; - classroom_3adv UUID; - student1_id UUID; - student2_id UUID; - student3_id UUID; - enrolled_count INTEGER; + v_default_classroom_id UUID; + v_student_count INTEGER; + v_assigned_count INTEGER; + rec RECORD; BEGIN - -- ===================================================================== - -- Obtener classroom IDs dinámicamente - -- ===================================================================== - SELECT classroom_id INTO classroom_2a + -- Obtener el classroom DEFAULT + SELECT id INTO v_default_classroom_id FROM social_features.classrooms - WHERE classroom_code = '2A-LECT-2025'; + WHERE code = 'DEFAULT' AND is_active = true + LIMIT 1; - SELECT classroom_id INTO classroom_3b - FROM social_features.classrooms - WHERE classroom_code = '3B-DIGI-2025'; - - SELECT classroom_id INTO classroom_1c - FROM social_features.classrooms - WHERE classroom_code = '1C-BASIC-2025'; - - SELECT classroom_id INTO classroom_1a - FROM social_features.classrooms - WHERE classroom_code = '1A-INTRO-2025'; - - SELECT classroom_id INTO classroom_2b - FROM social_features.classrooms - WHERE classroom_code = '2B-TECH-2025'; - - SELECT classroom_id INTO classroom_2st - FROM social_features.classrooms - WHERE classroom_code = '2ST-LITC-2025'; - - SELECT classroom_id INTO classroom_3adv - FROM social_features.classrooms - WHERE classroom_code = '3ADV-CRIT-2025'; - - -- ===================================================================== - -- Obtener student IDs - -- ===================================================================== - SELECT user_id INTO student1_id - FROM auth.users - WHERE email = 'estudiante1@demo.glit.edu.mx'; - - SELECT user_id INTO student2_id - FROM auth.users - WHERE email = 'estudiante2@demo.glit.edu.mx'; - - SELECT user_id INTO student3_id - FROM auth.users - WHERE email = 'estudiante3@demo.glit.edu.mx'; - - -- Validar que existan los estudiantes - IF student1_id IS NULL OR student2_id IS NULL OR student3_id IS NULL THEN - RAISE EXCEPTION 'No se encontraron todos los estudiantes demo. Ejecutar seeds de auth primero.'; + IF v_default_classroom_id IS NULL THEN + RAISE EXCEPTION 'Classroom DEFAULT no encontrado. Ejecutar primero 02-classrooms.sql'; END IF; - -- ===================================================================== - -- MEMBRESÍAS PARA ESTUDIANTE 1 - -- Perfil: Estudiante activo, inscrito en múltiples aulas - -- ===================================================================== + RAISE NOTICE 'Usando classroom DEFAULT: %', v_default_classroom_id; - -- Estudiante 1 → Aula 2° A (Secundaria Federal 15) + -- Contar estudiantes existentes + SELECT COUNT(*) INTO v_student_count + FROM auth_management.profiles + WHERE role = 'student'; + + RAISE NOTICE 'Estudiantes encontrados: %', v_student_count; + + -- Asignar cada estudiante al classroom DEFAULT INSERT INTO social_features.classroom_members ( - classroom_id, user_id, role, - joined_at, is_active, is_active, - settings, metadata, created_at, updated_at - ) VALUES - ( - classroom_2a, - student1_id, - 'student', - '2025-08-15', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "preferred_communication": "email" - }'::jsonb, - '{ - "enrollment_type": "regular", - "previous_performance": "excellent", - "special_needs": false - }'::jsonb, - NOW(), - NOW() - ), - - -- Estudiante 1 → Aula 3° B (Multi-enrollment) - ( - classroom_3b, - student1_id, - 'student', - '2025-08-20', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "preferred_communication": "email" - }'::jsonb, - '{ - "enrollment_type": "advanced_placement", - "reason": "Alto desempeño en lectura digital" - }'::jsonb, - NOW(), - NOW() - ), - - -- ===================================================================== - -- MEMBRESÍAS PARA ESTUDIANTE 2 - -- Perfil: Estudiante de secundaria técnica - -- ===================================================================== - - -- Estudiante 2 → Aula 3° B (Secundaria Federal 15) - ( - classroom_3b, - student2_id, - 'student', - '2025-08-15', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "preferred_communication": "sms" - }'::jsonb, - '{ - "enrollment_type": "regular", - "previous_performance": "good", - "special_needs": false - }'::jsonb, - NOW(), - NOW() - ), - - -- Estudiante 2 → Aula 2° B Técnica (Secundaria Técnica 42) - ( - classroom_2b, - student2_id, - 'student', - '2025-08-15', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "workshop_access": true - }'::jsonb, - '{ - "enrollment_type": "technical_specialty", - "specialty": "Computación", - "workshop_schedule": "Lunes y Miércoles 16:00-18:00" - }'::jsonb, - NOW(), - NOW() - ), - - -- ===================================================================== - -- MEMBRESÍAS PARA ESTUDIANTE 3 - -- Perfil: Estudiante de primer año - -- ===================================================================== - - -- Estudiante 3 → Aula 1° A (Secundaria Técnica 42) - ( - classroom_1a, - student3_id, - 'student', - '2025-08-15', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "parental_monitoring": true - }'::jsonb, - '{ - "enrollment_type": "regular", - "is_first_year": true, - "orientation_completed": true, - "parent_contact": "padre3@demo.glit.edu.mx" - }'::jsonb, - NOW(), - NOW() - ), - - -- Estudiante 3 → Aula 1° C Básica (Secundaria Federal 15) - ( - classroom_1c, - student3_id, - 'student', - '2025-08-18', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "parental_monitoring": true - }'::jsonb, - '{ - "enrollment_type": "regular", - "is_first_year": true, - "foundational_support": true - }'::jsonb, - NOW(), - NOW() - ), - - -- ===================================================================== - -- MEMBRESÍAS ADICIONALES PARA VARIEDAD - -- ===================================================================== - - -- Estudiante 1 → Aula STEAM (Colegio Einstein) - ( - classroom_2st, - student1_id, - 'student', - '2025-08-15', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "bilingual_mode": true - }'::jsonb, - '{ - "enrollment_type": "steam_program", - "scholarship": true, - "stem_projects_enrolled": ["Biografías Científicas", "Science Fair"] - }'::jsonb, - NOW(), - NOW() - ), - - -- Estudiante 2 → Aula Advanced (Colegio Einstein) - ( - classroom_3adv, - student2_id, - 'student', - '2025-08-15', - 'active', - true, - '{ - "attendance_tracking": true, - "grade_visibility": true, - "notifications_enabled": true, - "bilingual_mode": true, - "advanced_resources": true - }'::jsonb, - '{ - "enrollment_type": "advanced_placement", - "cambridge_candidate": true, - "debate_team": true - }'::jsonb, - NOW(), - NOW() + id, + classroom_id, + student_id, + enrollment_date, + enrollment_method, + status, + attendance_percentage, + metadata, + created_at, + updated_at ) - ON CONFLICT (classroom_id, user_id) DO UPDATE SET - status = EXCLUDED.is_active, - settings = EXCLUDED.settings, - metadata = EXCLUDED.metadata, - updated_at = NOW(); + SELECT + gen_random_uuid(), + v_default_classroom_id, + p.id, + gamilit.now_mexico(), + 'admin_add', -- Valid values: teacher_invite, self_enroll, admin_add, bulk_import + 'active', + 0.00, + jsonb_build_object( + 'enrollment_type', 'auto', + 'auto_assigned', true, + 'enrolled_by', 'seed_script', + 'assigned_to_default', true, + 'pending_reassignment', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + FROM auth_management.profiles p + WHERE p.role = 'student' + ON CONFLICT (classroom_id, student_id) DO UPDATE SET + status = 'active', + metadata = EXCLUDED.metadata || jsonb_build_object('updated_by_seed', true), + updated_at = gamilit.now_mexico(); - -- ===================================================================== - -- Actualizar enrollment counts en classrooms - -- ===================================================================== - UPDATE social_features.classrooms - SET current_enrollment = ( - SELECT COUNT(*) - FROM social_features.classroom_members - WHERE classroom_members.classroom_id = classrooms.classroom_id - AND status = 'active' - ) - WHERE classroom_id IN ( - classroom_2a, classroom_3b, classroom_1c, - classroom_1a, classroom_2b, - classroom_2st, classroom_3adv - ); + GET DIAGNOSTICS v_assigned_count = ROW_COUNT; - -- ===================================================================== - -- Verificación de inserción - -- ===================================================================== - SELECT COUNT(*) INTO enrolled_count - FROM social_features.classroom_members - WHERE status = 'active'; - - RAISE NOTICE 'Total de membresías activas creadas: %', enrolled_count; - RAISE NOTICE 'Estudiante 1: % aulas', ( - SELECT COUNT(*) - FROM social_features.classroom_members - WHERE user_id = student1_id AND status = 'active' - ); - RAISE NOTICE 'Estudiante 2: % aulas', ( - SELECT COUNT(*) - FROM social_features.classroom_members - WHERE user_id = student2_id AND status = 'active' - ); - RAISE NOTICE 'Estudiante 3: % aulas', ( - SELECT COUNT(*) - FROM social_features.classroom_members - WHERE user_id = student3_id AND status = 'active' - ); + RAISE NOTICE '========================================'; + RAISE NOTICE 'ASIGNACIÓN DE ESTUDIANTES A DEFAULT'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Classroom DEFAULT ID: %', v_default_classroom_id; + RAISE NOTICE 'Estudiantes asignados: %', v_assigned_count; + RAISE NOTICE '========================================'; END $$; + +-- ===================================================== +-- Actualizar current_students_count en classroom DEFAULT +-- ===================================================== + +UPDATE social_features.classrooms +SET current_students_count = ( + SELECT COUNT(*) + FROM social_features.classroom_members + WHERE classroom_id = classrooms.id + AND status = 'active' +) +WHERE code = 'DEFAULT'; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + member_count INTEGER; + default_count INTEGER; + classroom_students INTEGER; +BEGIN + -- Total membresías + SELECT COUNT(*) INTO member_count + FROM social_features.classroom_members; + + -- Membresías en classroom DEFAULT + SELECT COUNT(*) INTO default_count + FROM social_features.classroom_members cm + JOIN social_features.classrooms c ON c.id = cm.classroom_id + WHERE c.code = 'DEFAULT'; + + -- Count en el classroom + SELECT current_students_count INTO classroom_students + FROM social_features.classrooms + WHERE code = 'DEFAULT'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN DE CLASSROOM_MEMBERS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total membresías: %', member_count; + RAISE NOTICE 'Estudiantes en DEFAULT: %', default_count; + RAISE NOTICE 'current_students_count: %', classroom_students; + RAISE NOTICE '========================================'; + + IF default_count = member_count THEN + RAISE NOTICE '✓ Todos los estudiantes están en el classroom DEFAULT'; + RAISE NOTICE ' El admin puede reasignarlos desde la UI'; + ELSE + RAISE WARNING '⚠ Hay membresías en otros classrooms'; + END IF; +END $$; + +-- ===================================================== +-- Listado de estudiantes asignados +-- ===================================================== + +DO $$ +DECLARE + member_record RECORD; + total_students INTEGER := 0; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Estudiantes asignados al classroom DEFAULT:'; + RAISE NOTICE '========================================'; + + FOR member_record IN + SELECT + p.display_name, + p.email, + cm.enrollment_date, + cm.status + FROM social_features.classroom_members cm + JOIN auth_management.profiles p ON p.id = cm.student_id + JOIN social_features.classrooms c ON c.id = cm.classroom_id + WHERE c.code = 'DEFAULT' + ORDER BY p.display_name + LIMIT 20 -- Limitar output para no saturar + LOOP + RAISE NOTICE ' - % <%> [%]', + member_record.display_name, + member_record.email, + member_record.status; + total_students := total_students + 1; + END LOOP; + + IF total_students = 0 THEN + RAISE NOTICE ' (No hay estudiantes asignados aún)'; + ELSIF total_students = 20 THEN + RAISE NOTICE ' ... (mostrando primeros 20)'; + END IF; + + RAISE NOTICE '========================================'; +END $$; diff --git a/projects/gamilit/apps/database/seeds/dev/social_features/04-friendships.sql b/projects/gamilit/apps/database/seeds/dev/social_features/04-friendships.sql new file mode 100644 index 0000000..0b1db31 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/dev/social_features/04-friendships.sql @@ -0,0 +1,115 @@ +-- ===================================================== +-- Seed: social_features.friendships (PROD) - v1.2 +-- Description: Relaciones de amistad entre usuarios de producción +-- Environment: PRODUCTION +-- Dependencies: auth_management.profiles (usuarios de producción) +-- Order: 04 +-- Created: 2025-11-15 +-- Updated: 2025-12-14 (v1.2 - Ajustado al DDL actual, usa usuarios prod) +-- Version: 1.2 +-- ===================================================== +-- +-- CAMBIOS v1.2: +-- - Eliminadas columnas status y updated_at (no existen en DDL) +-- - Cambiado a usar UUIDs de usuarios de producción reales +-- - El DDL actual solo tiene: id, user_id, friend_id, created_at +-- +-- NOTA: En este modelo simplificado, una entrada en friendships +-- significa que la amistad está ACEPTADA. Las solicitudes pendientes +-- se manejarían en una tabla separada (friend_requests) si se necesita. +-- +-- FRIENDSHIPS INCLUIDOS: +-- - Relaciones bidireccionales entre usuarios de producción +-- +-- IMPORTANTE: Este seed habilita testing de: +-- - /friends page (FriendsPage.tsx) +-- - FriendsLeaderboard component +-- ===================================================== + +SET search_path TO social_features, auth_management, public; + +-- ===================================================== +-- INSERT: Friendships entre usuarios de producción +-- ===================================================== +-- Usamos los UUIDs reales de los usuarios de producción + +DO $$ +DECLARE + user_ids uuid[]; + i INTEGER; + j INTEGER; + friendship_count INTEGER := 0; +BEGIN + -- Obtener IDs de usuarios de producción (excluyendo testing @gamilit.com) + SELECT ARRAY_AGG(id ORDER BY created_at) + INTO user_ids + FROM auth_management.profiles + WHERE email NOT LIKE '%@gamilit.com' + LIMIT 10; + + -- Si hay al menos 2 usuarios, crear friendships + IF array_length(user_ids, 1) >= 2 THEN + -- Crear friendships bidireccionales entre los primeros usuarios + FOR i IN 1..LEAST(array_length(user_ids, 1) - 1, 5) LOOP + FOR j IN (i + 1)..LEAST(array_length(user_ids, 1), i + 2) LOOP + -- Insertar relación bidireccional + INSERT INTO social_features.friendships (user_id, friend_id, created_at) + VALUES (user_ids[i], user_ids[j], gamilit.now_mexico() - (random() * INTERVAL '30 days')) + ON CONFLICT (user_id, friend_id) DO NOTHING; + + INSERT INTO social_features.friendships (user_id, friend_id, created_at) + VALUES (user_ids[j], user_ids[i], gamilit.now_mexico() - (random() * INTERVAL '30 days')) + ON CONFLICT (user_id, friend_id) DO NOTHING; + + friendship_count := friendship_count + 2; + END LOOP; + END LOOP; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'FRIENDSHIPS CREADOS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Relaciones creadas: %', friendship_count; + RAISE NOTICE 'Usuarios disponibles: %', array_length(user_ids, 1); + RAISE NOTICE '========================================'; + ELSE + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'FRIENDSHIPS: Sin usuarios suficientes'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Se necesitan al menos 2 usuarios de producción'; + RAISE NOTICE 'Usuarios encontrados: %', COALESCE(array_length(user_ids, 1), 0); + RAISE NOTICE '========================================'; + END IF; +END $$; + +-- ===================================================== +-- Verification +-- ===================================================== + +DO $$ +DECLARE + total_count INTEGER; +BEGIN + SELECT COUNT(*) INTO total_count + FROM social_features.friendships; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN FRIENDSHIPS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total friendships en tabla: %', total_count; + RAISE NOTICE '========================================'; + + IF total_count > 0 THEN + RAISE NOTICE '✓ Friendships creados correctamente'; + RAISE NOTICE ''; + RAISE NOTICE 'Features habilitadas:'; + RAISE NOTICE ' - /friends page (FriendsPage.tsx)'; + RAISE NOTICE ' - FriendsLeaderboard component'; + ELSE + RAISE NOTICE '⚠ No hay friendships (normal si no hay usuarios prod)'; + END IF; + + RAISE NOTICE ''; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/auth/01-demo-users.sql b/projects/gamilit/apps/database/seeds/prod/auth/01-demo-users.sql index b7e3c8e..5945c5a 100644 --- a/projects/gamilit/apps/database/seeds/prod/auth/01-demo-users.sql +++ b/projects/gamilit/apps/database/seeds/prod/auth/01-demo-users.sql @@ -2,7 +2,7 @@ -- Seed: auth.users - Test Users (PRODUCTION CLEAN) -- Description: Solo usuarios de testing con dominio @gamilit.com -- Environment: PRODUCTION --- Dependencies: None (auth schema managed by Supabase) +-- Dependencies: None (auth schema base) -- Order: 01 -- Created: 2025-11-17 -- Version: 2.0 (CLEAN - Solo 3 usuarios @gamilit.com) diff --git a/projects/gamilit/apps/database/seeds/prod/auth/02-production-users.sql b/projects/gamilit/apps/database/seeds/prod/auth/02-production-users.sql index 4e4542c..06c0db9 100644 --- a/projects/gamilit/apps/database/seeds/prod/auth/02-production-users.sql +++ b/projects/gamilit/apps/database/seeds/prod/auth/02-production-users.sql @@ -5,20 +5,24 @@ -- Dependencies: 01-demo-users.sql -- Order: 02 -- Created: 2025-11-19 --- Version: 1.0 (Migrados desde servidor producción) +-- Updated: 2025-12-18 +-- Version: 2.0 (Actualizado con backup producción 2025-12-18) -- ===================================================== -- --- USUARIOS REALES REGISTRADOS (13): --- Usuarios que se registraron en el servidor de producción --- durante 2025-11-18 +-- USUARIOS REALES REGISTRADOS: +-- - Lote 1 (2025-11-18): 13 usuarios con nombres completos +-- - Lote 2 (2025-11-24/25): 29 usuarios (algunos sin nombres) +-- - Lote 3 (2025-12-08 y 2025-12-17): 2 usuarios -- --- TOTAL: 13 usuarios estudiantes +-- TOTAL: 44 usuarios estudiantes +-- EXCLUIDO: rckrdmrd@gmail.com (usuario de pruebas del owner) -- -- POLÍTICA DE CARGA LIMPIA: -- ✅ UUIDs originales del servidor preservados -- ✅ Passwords hasheados originales preservados -- ✅ instance_id corregido a UUID válido -- ✅ Metadata mínima agregada para compatibilidad +-- ✅ Triggers crearán profiles, user_stats, user_ranks automáticamente -- -- IMPORTANTE: Estos son usuarios reales de producción. -- No modificar sus UUIDs ni passwords hasheados. @@ -27,7 +31,7 @@ SET search_path TO auth, public; -- ===================================================== --- INSERT: Production Registered Users (13 usuarios) +-- INSERT: Production Registered Users (44 usuarios) -- ===================================================== INSERT INTO auth.users ( @@ -70,632 +74,700 @@ INSERT INTO auth.users ( ) VALUES -- ===================================================== --- USUARIO 1: Jose Aguirre +-- LOTE 1: USUARIOS 2025-11-18 (13 usuarios) -- ===================================================== + +-- USUARIO 1: Jose Aguirre ( 'b017b792-b327-40dd-aefb-a80312776952'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'joseal.guirre34@gmail.com', '$2b$10$kb9yCB4Y2WBr2.Gth.wC9e8q8bnkZJ6O2X6kFSn.O4VK8d76Cr/xO', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Jose', - 'last_name', 'Aguirre' - ), - false, - '2025-11-18 07:29:05.226874+00'::timestamptz, - '2025-11-18 07:29:05.226874+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Jose", "last_name": "Aguirre"}'::jsonb, + false, '2025-11-18 07:29:05.226874+00'::timestamptz, '2025-11-18 07:29:05.226874+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 2: Sergio Jimenez --- ===================================================== ( '06a24962-e83d-4e94-aad7-ff69f20a9119'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'sergiojimenezesteban63@gmail.com', '$2b$10$8oPdKN15ndCqCOIt12SEO.2yx4D29kQEQGPCC5rtUYWu8Qp5L7/zW', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Sergio', - 'last_name', 'Jimenez' - ), - false, - '2025-11-18 08:17:40.925857+00'::timestamptz, - '2025-11-18 08:17:40.925857+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Sergio", "last_name": "Jimenez"}'::jsonb, + false, '2025-11-18 08:17:40.925857+00'::timestamptz, '2025-11-18 08:17:40.925857+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 3: Hugo Gomez --- ===================================================== ( '24e8c563-8854-43d1-b3c9-2f83e91f5a1e'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'Gomezfornite92@gmail.com', '$2b$10$FuEfoSA0jxvBI2f6odMJqux9Gpgvt7Zjk.plRhRatvK0ykkIXxbI.', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Hugo', - 'last_name', 'Gomez' - ), - false, - '2025-11-18 08:18:04.240276+00'::timestamptz, - '2025-11-18 08:18:04.240276+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Hugo", "last_name": "Gomez"}'::jsonb, + false, '2025-11-18 08:18:04.240276+00'::timestamptz, '2025-11-18 08:18:04.240276+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 4: Hugo Aragón --- ===================================================== ( 'bf0d3e34-e077-43d1-9626-292f7fae2bd6'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'Aragon494gt54@icloud.com', '$2b$10$lE8M8qWUIsgYLwcHyRGvTOjxdykLVchRVifsMVqCRCZq3bEeXR.xG', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Hugo', - 'last_name', 'Aragón' - ), - false, - '2025-11-18 08:20:17.228812+00'::timestamptz, - '2025-11-18 08:20:17.228812+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Hugo", "last_name": "Aragón"}'::jsonb, + false, '2025-11-18 08:20:17.228812+00'::timestamptz, '2025-11-18 08:20:17.228812+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 5: Azul Valentina --- ===================================================== ( '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'blu3wt7@gmail.com', '$2b$10$gKRXQ.rmOePqsNKWdxABQuyIZike2oSsYpdfWpQdi5HHDWDUk.3u2', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Azul', - 'last_name', 'Valentina' - ), - false, - '2025-11-18 08:32:17.314233+00'::timestamptz, - '2025-11-18 08:32:17.314233+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Azul", "last_name": "Valentina"}'::jsonb, + false, '2025-11-18 08:32:17.314233+00'::timestamptz, '2025-11-18 08:32:17.314233+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 6: Ricardo Lugo --- ===================================================== ( '5e738038-1743-4aa9-b222-30171300ea9d'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'ricardolugo786@icloud.com', '$2b$10$YV1StKIdCPPED/Ft84zR2ONxj/VzzV7zOxjgwMSbDpd2hzvYOGtby', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Ricardo', - 'last_name', 'Lugo' - ), - false, - '2025-11-18 10:15:06.479774+00'::timestamptz, - '2025-11-18 10:15:06.479774+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Ricardo", "last_name": "Lugo"}'::jsonb, + false, '2025-11-18 10:15:06.479774+00'::timestamptz, '2025-11-18 10:15:06.479774+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 7: Carlos Marban --- ===================================================== ( '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'marbancarlos916@gmail.com', '$2b$10$PfsKOsEEXpGA6YB6eXNBPePo6OV6Am1glUN6Mkunl64bK/ji6uttW', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Carlos', - 'last_name', 'Marban' - ), - false, - '2025-11-18 10:29:05.23842+00'::timestamptz, - '2025-11-18 10:29:05.23842+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Carlos", "last_name": "Marban"}'::jsonb, + false, '2025-11-18 10:29:05.23842+00'::timestamptz, '2025-11-18 10:29:05.23842+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 8: Diego Colores --- ===================================================== ( '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'diego.colores09@gmail.com', '$2b$10$rFlH9alBbgPGVEZMYIV8p.AkeZ30yRCVd5acasFjIt7fpCZhE6RuO', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Diego', - 'last_name', 'Colores' - ), - false, - '2025-11-18 10:29:20.530359+00'::timestamptz, - '2025-11-18 10:29:20.530359+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Diego", "last_name": "Colores"}'::jsonb, + false, '2025-11-18 10:29:20.530359+00'::timestamptz, '2025-11-18 10:29:20.530359+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 9: Benjamin Hernandez --- ===================================================== ( '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'hernandezfonsecabenjamin7@gmail.com', '$2b$10$1E6gLqfMojNLYrSKIbatqOh0pHblZ3jWZwbcxTY/DCx7MGADToCVm', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Benjamin', - 'last_name', 'Hernandez' - ), - false, - '2025-11-18 10:37:06.919813+00'::timestamptz, - '2025-11-18 10:37:06.919813+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Benjamin", "last_name": "Hernandez"}'::jsonb, + false, '2025-11-18 10:37:06.919813+00'::timestamptz, '2025-11-18 10:37:06.919813+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 10: Josue Reyes --- ===================================================== ( 'ccd7135c-0fea-4488-9094-9da52df1c98c'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'jr7794315@gmail.com', '$2b$10$Ej/Gwx8mGCWg4TnQSjh1r.QZLw/GkUANqXmz4bEfVaNF9E527L02C', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Josue', - 'last_name', 'Reyes' - ), - false, - '2025-11-18 17:53:39.67958+00'::timestamptz, - '2025-11-18 17:53:39.67958+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Josue", "last_name": "Reyes"}'::jsonb, + false, '2025-11-18 17:53:39.67958+00'::timestamptz, '2025-11-18 17:53:39.67958+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 11: Fernando Barragan --- ===================================================== ( '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'barraganfer03@gmail.com', '$2b$10$VJ8bS.ksyKpa7oG575r5YOWQYcq8vwmwTa8jMBkCv0dwskF04SHn2', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Fernando', - 'last_name', 'Barragan' - ), - false, - '2025-11-18 20:39:27.408624+00'::timestamptz, - '2025-11-18 20:39:27.408624+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Fernando", "last_name": "Barragan"}'::jsonb, + false, '2025-11-18 20:39:27.408624+00'::timestamptz, '2025-11-18 20:39:27.408624+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 12: Marco Antonio Roman --- ===================================================== ( '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'roman.rebollar.marcoantonio1008@gmail.com', '$2b$10$l4eF8UoOB7D8LKDEzTigXOUO7EABhVdYCqknJ/lD6R4p8uF1R4I.W', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Marco Antonio', - 'last_name', 'Roman' - ), - false, - '2025-11-18 21:03:17.326679+00'::timestamptz, - '2025-11-18 21:03:17.326679+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Marco Antonio", "last_name": "Roman"}'::jsonb, + false, '2025-11-18 21:03:17.326679+00'::timestamptz, '2025-11-18 21:03:17.326679+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ), --- ===================================================== -- USUARIO 13: Rodrigo Guerrero --- ===================================================== ( 'ebe48628-5e44-4562-97b7-b4950b216247'::uuid, '00000000-0000-0000-0000-000000000000'::uuid, - 'authenticated', - NULL, + 'authenticated', NULL, 'rodrigoguerrero0914@gmail.com', '$2b$10$ihoy7HbOdlqU38zAddpTOuDO7Nqa8.Cr1dEQjCgMpdb30UwCIMhGW', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - jsonb_build_object( - 'provider', 'email', - 'providers', ARRAY['email'] - ), - jsonb_build_object( - 'first_name', 'Rodrigo', - 'last_name', 'Guerrero' - ), - false, - '2025-11-18 21:20:52.303128+00'::timestamptz, - '2025-11-18 21:20:52.303128+00'::timestamptz, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL, - NULL, - false, - NULL, - 'student'::auth_management.gamilit_role, - 'active'::auth_management.user_status + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Rodrigo", "last_name": "Guerrero"}'::jsonb, + false, '2025-11-18 21:20:52.303128+00'::timestamptz, '2025-11-18 21:20:52.303128+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- ===================================================== +-- LOTE 2: USUARIOS 2025-11-24 (23 usuarios) +-- ===================================================== + +-- USUARIO 14: santiagoferrara78 +( + 'd089b1af-462f-4d2c-b0f5-d2528cec8506'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'santiagoferrara78@gmail.com', + '$2b$10$Wjo3EENjiuddS9BwPMAW1OORZrZpU8ECP9zEXmd4Gvn7orwgjo8O2', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 09:21:04.898591+00'::timestamptz, '2025-11-24 09:21:04.898591+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 15: alexanserrv917 +( + 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'alexanserrv917@gmail.com', + '$2b$10$8sT/ObLZUNmiu6CpbceHhenfc7E8zZml8AvB1HUiyOddSLqchggZ2', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:26:51.934739+00'::timestamptz, '2025-11-24 10:26:51.934739+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 16: aarizmendi434 +( + 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'aarizmendi434@gmail.com', + '$2b$10$2BAG4EskBG0feGOIva6XyOCBtBJbKJE9h27GU6DmuBH3f.2iK6FoS', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:30:54.728262+00'::timestamptz, '2025-11-24 10:30:54.728262+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 17: ashernarcisobenitezpalomino +( + '26fbc469-10af-4fa3-bd65-e5498188cc4f'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'ashernarcisobenitezpalomino@gmail.com', + '$2b$10$Bv5vo0GDeseWUWTt.5xV0O9nN93TRVN.vHRigs4vF/ww7Hbnjylam', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:37:35.325342+00'::timestamptz, '2025-11-24 10:37:35.325342+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 18: ra.alejandrobm +( + '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'ra.alejandrobm@gmail.com', + '$2b$10$QZId3lZBIzBulD7AZCeEKOiL0LBJRekGlQTGiacC70IDwDo2wx7py', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:42:33.424367+00'::timestamptz, '2025-11-24 10:42:33.424367+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 19: abdallahxelhaneriavega +( + 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'abdallahxelhaneriavega@gmail.com', + '$2b$10$jQ4SquNUxIO70e7IBYqqLeUw1d.gSCleJ/cwinuWMVlW25a8.pRGG', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:45:19.984994+00'::timestamptz, '2025-11-24 10:45:19.984994+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 20: 09enriquecampos +( + '012adac4-8ffd-47bd-9248-f0c5851e981f'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + '09enriquecampos@gmail.com', + '$2b$10$95c9hOplonbo/46O5UlPqummq.AIaGVIZ7YgBstSuOWPbgGersKxy', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:51:54.731982+00'::timestamptz, '2025-11-24 10:51:54.731982+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 21: johhkk22 +( + '126b9257-7b0a-4bd6-9ab3-c505ee00e10a'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'johhkk22@gmail.com', + '$2b$10$Bt6IZ19zuBkly.6QmmPWBeF0kfyVN/O/c3/9bqyUGup3gPZu14DGa', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:53:47.029991+00'::timestamptz, '2025-11-24 10:53:47.029991+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 22: edangiel4532 +( + '9ac1746e-94a6-4efc-a961-951c015d416e'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'edangiel4532@gmail.com', + '$2b$10$eZap9LmAws7VtY9sHnS17.RJkhIte5SUobIWaWpuTxTPKjbKgzK.6', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 10:58:12.790316+00'::timestamptz, '2025-11-24 10:58:12.790316+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 23: erickfranco462 +( + '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'erickfranco462@gmail.com', + '$2b$10$lNzkSO7zbBHQcJJui0O76.a2artcsZHari4Mgkjo4btGww.Wy9/iC', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:00:11.800551+00'::timestamptz, '2025-11-24 11:00:11.800551+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 24: gallinainsana +( + 'aff5dcc6-32de-4769-9aaf-eda751fa0866'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'gallinainsana@gmail.com', + '$2b$10$6y/FVa4LqyliI4PXuBxKpepTRwIIRWybFN0NhcAqRM.Kl/cnvXDMq', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:03:17.536383+00'::timestamptz, '2025-11-24 11:03:17.536383+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 25: leile5257 +( + '0cda1645-83c5-445b-80b7-d0e4d436c00c'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'leile5257@gmail.com', + '$2b$10$ZZX0.z30VPm7BsLF8bNVweQpRZ2ca/1EPlxdIZy0xNaCFugoKL0ci', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:05:17.75852+00'::timestamptz, '2025-11-24 11:05:17.75852+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 26: maximiliano.mejia367 +( + '1364c463-88de-479b-a883-c0b7b362bcf8'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'maximiliano.mejia367@gmail.com', + '$2b$10$iTfIWKh2ISvPys2bkK2LOOPI24ua7I47oT8dFxHHYW7AuztoZreQa', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:08:58.232003+00'::timestamptz, '2025-11-24 11:08:58.232003+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 27: fl432025 +( + '547eb778-4782-4681-b198-c731bba36147'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'fl432025@gmail.com', + '$2b$10$aGKv6yhAWwHb07m3N2DxJOXIn5omkP3t2QeSYblhcDo52pB2ZiFQi', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:12:13.692614+00'::timestamptz, '2025-11-24 11:12:13.692614+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 28: 7341023901m +( + '5fc06693-e408-4eab-a9a3-fcd5f4e01296'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + '7341023901m@gmail.com', + '$2b$10$Z/HUBov20g..LZ6RDYax4.NcDuiFD/gn9Nrt7/OPCPBqCoTJUgr3C', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:15:18.276345+00'::timestamptz, '2025-11-24 11:15:18.276345+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 29: segurauriel235 +( + '5d1839f6-b03f-4e12-b236-eca43f4674f2'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'segurauriel235@gmail.com', + '$2b$10$IfdhPuUOModgrJT7bMfYkODZkXeTcaAReuCQf9BGpK1cT6GiP9UGu', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:17:46.846963+00'::timestamptz, '2025-11-24 11:17:46.846963+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 30: angelrabano11 +( + '1b310708-6f24-4c6a-88c9-a11f7a7f9763'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'angelrabano11@gmail.com', + '$2b$10$Sg6q4kErMvxRlZgWM9lCj.PfRg5sCQrwm763d7sfc3iaAUID7y436', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:47:53.790673+00'::timestamptz, '2025-11-24 11:47:53.790673+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 31: daliaayalareyes35 +( + '3c613b0e-66f9-4640-a599-c9426d8edffb'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'daliaayalareyes35@gmail.com', + '$2b$10$dd2SQeBqNIZpZWCGMIDu1O8U6MLpWnKF05w641MNOMzHDZ/U5glCe', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:55:08.708961+00'::timestamptz, '2025-11-24 11:55:08.708961+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 32: alexeimongam +( + '7ded133e-9b13-4467-9803-edb813f6a9a1'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'alexeimongam@gmail.com', + '$2b$10$jyQrHAIj6SsnReQ45FrFlOnDgpZtabskpxPuOYgB/h.YPLyZhuld.', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 11:55:11.906996+00'::timestamptz, '2025-11-24 11:55:11.906996+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 33: davidocampovenegas +( + '4cc04f54-7771-462d-98aa-a94448bb6ff5'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'davidocampovenegas@gmail.com', + '$2b$10$8COk10WE5.bXFJnAucEA0efcGQKU6KUXKV9N7n32ZX6aNKORs4McW', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 14:52:46.468737+00'::timestamptz, '2025-11-24 14:52:46.468737+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 34: zaid080809 +( + 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'zaid080809@gmail.com', + '$2b$10$kdaUWR1BUqPRY7H8YkR.xuuDbqtLcvP5yKW.B0ooPlb.I6b/UU192', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 16:25:03.689847+00'::timestamptz, '2025-11-24 16:25:03.689847+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 35: ruizcruzabrahamfrancisco +( + '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'ruizcruzabrahamfrancisco@gmail.com', + '$2b$10$DXHr682C4/VpesiHa7fRrOjKceiWSDUSx.1LZTbsvuxpqCdMNh/Ii', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 19:46:06.311558+00'::timestamptz, '2025-11-24 19:46:06.311558+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 36: vituschinchilla +( + '615adf6e-dbf3-480f-a907-3cfb3a64c6d2'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'vituschinchilla@gmail.com', + '$2b$10$dA8adTYlfhgqhZfACcQkFOCYjXdsmggXnIUluNDoh1zRFgQ6pq5O2', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-24 21:07:26.037867+00'::timestamptz, '2025-11-24 21:07:26.037867+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- ===================================================== +-- LOTE 3: USUARIOS 2025-11-25 (6 usuarios) +-- ===================================================== + +-- USUARIO 37: bryan@betanzos.com +( + 'bf445960-4c1f-4e29-8fb7-31667b183d7e'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'bryan@betanzos.com', + '$2b$10$Xdfuf4Tfog9QKd1FRLL.7eAaD6tr2cXgPx1/L8xqT1kLLzNHzSM26', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 06:13:30.263795+00'::timestamptz, '2025-11-25 06:13:30.263795+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 38: loganalexander816 +( + 'd5fa4905-a78a-4040-8ad8-23220881c6a6'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'loganalexander816@gmail.com', + '$2b$10$8zLduh/9L/priag.nujz5utuloO9RnNFFDGdKgI2UniFCOwocEPLq', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 07:37:04.953164+00'::timestamptz, '2025-11-25 07:37:04.953164+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 39: carlois1974 +( + '71734c15-cdaa-431b-90f5-97a57e0316a8'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'carlois1974@gmail.com', + '$2b$10$IfLfJ.q59DZgicR07ckSVOcrkkBJe42m1FECXxaoaodKYSo6uj5wW', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 07:41:38.025764+00'::timestamptz, '2025-11-25 07:41:38.025764+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 40: enriquecuevascbtis136 +( + '1efe491d-98ef-4c02-acd1-3135f7289072'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'enriquecuevascbtis136@gmail.com', + '$2b$10$9BX3OQMZmHruffBtN.3WPOFoyea6zgPd8i72DvhJ7vRAdqWKax6GS', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 08:16:33.977647+00'::timestamptz, '2025-11-25 08:16:33.977647+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 41: omarcitogonzalezzavaleta +( + '5ae21325-7450-4c37-82f1-3f9bcd7b6f45'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'omarcitogonzalezzavaleta@gmail.com', + '$2b$10$RRk3DAgQdiikxVImFIMqquqB.TNpKs3E.RNFtt1rwwTzO24uShri.', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 08:17:07.610076+00'::timestamptz, '2025-11-25 08:17:07.610076+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 42: gustavobm2024cbtis +( + 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'gustavobm2024cbtis@gmail.com', + '$2b$10$lg7KRUTPofcx4Rtyey8J7.XO0gmdBLCFIfK5uP08mqT0qUIl1aTJq', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 08:20:49.649184+00'::timestamptz, '2025-11-25 08:20:49.649184+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 43: marianaxsotoxt22 +( + '6e30164a-78b0-49b0-bd21-23d7c6c03349'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'marianaxsotoxt22@gmail.com', + '$2b$10$GQC9yTWiP2vP9GUp0gnhUeLjmw70EI4JQhfJBZbMOlCNXGXb/bt5O', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "", "last_name": ""}'::jsonb, + false, '2025-11-25 08:33:18.150784+00'::timestamptz, '2025-11-25 08:33:18.150784+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- ===================================================== +-- LOTE 4: USUARIOS RECIENTES (2 usuarios) +-- ===================================================== + +-- USUARIO 44: javiermar06 (2025-12-08) +( + '69681b09-5077-4f77-84cc-67606abd9755'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'javiermar06@hotmail.com', + '$2b$10$3RHyXnR4BG3NaxP8Ez82FuiGDMNCG7GhNaOsMFigy3BpIVOzCqHMW', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-14 03:51:04.122+00'::timestamptz, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Javier", "last_name": "Mar"}'::jsonb, + false, '2025-12-08 19:24:06.266895+00'::timestamptz, '2025-12-14 03:51:04.123886+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status +), + +-- USUARIO 45: ju188an (2025-12-17) +( + 'f929d6df-8c29-461f-88f5-264facd879e9'::uuid, + '00000000-0000-0000-0000-000000000000'::uuid, + 'authenticated', NULL, + 'ju188an@gmail.com', + '$2b$10$9vUERFnXApdfXuAI7DFve.aa8uDjI5bfm4CI75/EZ2cUre83RytKe', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2025-12-17 23:51:43.553+00'::timestamptz, + '{"provider": "email", "providers": ["email"]}'::jsonb, + '{"first_name": "Juan", "last_name": "pa"}'::jsonb, + false, '2025-12-17 17:51:43.530434+00'::timestamptz, '2025-12-17 23:51:43.55475+00'::timestamptz, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, false, NULL, + 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status ) ON CONFLICT (id) DO UPDATE SET encrypted_password = EXCLUDED.encrypted_password, raw_user_meta_data = EXCLUDED.raw_user_meta_data, + last_sign_in_at = EXCLUDED.last_sign_in_at, updated_at = EXCLUDED.updated_at; -- ===================================================== @@ -706,6 +778,10 @@ DO $$ DECLARE production_user_count INTEGER; total_user_count INTEGER; + lote1_count INTEGER; + lote2_count INTEGER; + lote3_count INTEGER; + lote4_count INTEGER; BEGIN -- Contar usuarios de producción (excluyendo @gamilit.com) SELECT COUNT(*) INTO production_user_count @@ -716,20 +792,50 @@ BEGIN SELECT COUNT(*) INTO total_user_count FROM auth.users; + -- Contar por lotes + SELECT COUNT(*) INTO lote1_count + FROM auth.users + WHERE created_at::date = '2025-11-18' + AND email NOT LIKE '%@gamilit.com'; + + SELECT COUNT(*) INTO lote2_count + FROM auth.users + WHERE created_at::date = '2025-11-24' + AND email NOT LIKE '%@gamilit.com'; + + SELECT COUNT(*) INTO lote3_count + FROM auth.users + WHERE created_at::date = '2025-11-25' + AND email NOT LIKE '%@gamilit.com'; + + SELECT COUNT(*) INTO lote4_count + FROM auth.users + WHERE created_at::date >= '2025-12-01' + AND email NOT LIKE '%@gamilit.com'; + RAISE NOTICE '========================================'; RAISE NOTICE 'USUARIOS DE PRODUCCIÓN REGISTRADOS'; RAISE NOTICE '========================================'; - RAISE NOTICE 'Usuarios de producción: %', production_user_count; - RAISE NOTICE 'Total usuarios (incluyendo testing): %', total_user_count; + RAISE NOTICE 'Total usuarios producción: %', production_user_count; + RAISE NOTICE 'Total usuarios (con testing): %', total_user_count; + RAISE NOTICE '----------------------------------------'; + RAISE NOTICE 'Por lotes:'; + RAISE NOTICE ' - Lote 1 (2025-11-18): %', lote1_count; + RAISE NOTICE ' - Lote 2 (2025-11-24): %', lote2_count; + RAISE NOTICE ' - Lote 3 (2025-11-25): %', lote3_count; + RAISE NOTICE ' - Lote 4 (2025-12+): %', lote4_count; RAISE NOTICE '========================================'; - IF production_user_count = 13 THEN - RAISE NOTICE '✓ Los 13 usuarios de producción fueron creados correctamente'; + IF production_user_count >= 44 THEN + RAISE NOTICE '✓ Los usuarios de producción fueron creados correctamente'; ELSE - RAISE WARNING '⚠ Se esperaban 13 usuarios de producción, se crearon %', production_user_count; + RAISE WARNING '⚠ Se esperaban 44+ usuarios de producción, se crearon %', production_user_count; END IF; RAISE NOTICE '========================================'; + RAISE NOTICE 'NOTA: Usuario rckrdmrd@gmail.com EXCLUIDO'; + RAISE NOTICE '(Usuario de pruebas del owner)'; + RAISE NOTICE '========================================'; END $$; -- ===================================================== @@ -748,10 +854,14 @@ END $$; -- ===================================================== -- CHANGELOG -- ===================================================== +-- v2.0 (2025-12-18): Actualización completa desde backup producción +-- - 44 usuarios totales (excluyendo rckrdmrd@gmail.com) +-- - Lote 1: 13 usuarios (2025-11-18) +-- - Lote 2: 23 usuarios (2025-11-24) +-- - Lote 3: 6 usuarios (2025-11-25) +-- - Lote 4: 2 usuarios (2025-12-08, 2025-12-17) +-- - UUIDs y passwords originales preservados +-- -- v1.0 (2025-11-19): Primera versión --- - 13 usuarios reales migrados desde servidor producción --- - Passwords hasheados originales preservados --- - UUIDs originales preservados --- - instance_id corregido a UUID válido --- - Metadata mínima agregada +-- - 13 usuarios del lote inicial -- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/prod/auth_management/06-profiles-production.sql b/projects/gamilit/apps/database/seeds/prod/auth_management/06-profiles-production.sql index b7d5dd3..3c48986 100644 --- a/projects/gamilit/apps/database/seeds/prod/auth_management/06-profiles-production.sql +++ b/projects/gamilit/apps/database/seeds/prod/auth_management/06-profiles-production.sql @@ -1,45 +1,40 @@ -- ===================================================== --- Seed: auth_management.profiles - Production Users (CORREGIDO) --- Description: Perfiles CORREGIDOS para usuarios reales registrados en producción +-- Seed: auth_management.profiles - Production Users (ACTUALIZADO) +-- Description: Perfiles CORREGIDOS para usuarios reales registrados en produccion -- Environment: PRODUCTION -- Dependencies: auth/02-production-users.sql, auth_management/01-tenants.sql -- Order: 06 -- Created: 2025-11-19 --- Version: 2.0 (CORRECCIÓN: profiles.id = auth.users.id) +-- Version: 3.0 (Actualizado con backup produccion 2025-12-18) -- ===================================================== -- -- CORRECCIONES APLICADAS: --- ❌ ANTES: profiles.id generado con gen_random_uuid() (diferente de auth.users.id) --- ✅ AHORA: profiles.id = auth.users.id (consistente con seeds de testing) +-- ✅ profiles.id = auth.users.id (consistente para TODOS) +-- ✅ tenant_id apunta al tenant principal (GAMILIT Platform) -- --- ❌ ANTES: tenant_id apuntaba a tenants personales --- ✅ AHORA: tenant_id apunta al tenant principal (GAMILIT Platform) +-- TOTAL: 45 perfiles de estudiantes de produccion +-- EXCLUIDO: rckrdmrd@gmail.com (usuario de pruebas del owner) -- --- JUSTIFICACIÓN: --- 1. Todos los usuarios de testing tienen profiles.id = auth.users.id --- 2. Backend busca user_stats con profiles.id, pero user_stats usa auth.users.id --- 3. Resultado: Error 404 al enviar respuestas de ejercicios --- 4. Solución: Unificar IDs (1 usuario = 1 ID único) +-- ESTRUCTURA DE LOTES: +-- - LOTE 1 (2025-11-18): 13 usuarios +-- - LOTE 2 (2025-11-24): 23 usuarios +-- - LOTE 3 (2025-11-25): 6 usuarios +-- - LOTE 4 (2025-12-08/17): 3 usuarios -- --- IMPACTO: --- - ✅ Usuarios de producción funcionan igual que usuarios de testing --- - ✅ No más errores 404 al enviar respuestas --- - ✅ Gamificación funciona correctamente --- - ✅ Trigger initialize_user_stats() usa el ID correcto --- --- TOTAL: 13 perfiles de estudiantes (CORREGIDOS) +-- NOTA: profiles.id = user_id para TODOS los usuarios +-- Esto asegura que el trigger initialize_user_stats funcione correctamente -- ===================================================== SET search_path TO auth_management, public; -- ===================================================== --- INSERT: Production User Profiles (13 perfiles CORREGIDOS) +-- INSERT: Production User Profiles (44 perfiles) -- ===================================================== INSERT INTO auth_management.profiles ( - id, -- ✅ AHORA: auth.users.id (NO gen_random_uuid()) - tenant_id, -- ✅ AHORA: Tenant principal (NO personal) - user_id, -- ✅ auth.users.id (sin cambios) + id, -- ✅ auth.users.id + tenant_id, -- ✅ Tenant principal + user_id, -- ✅ auth.users.id email, display_name, full_name, @@ -63,462 +58,788 @@ INSERT INTO auth_management.profiles ( ) VALUES -- ===================================================== --- PROFILE 1: Jose Aguirre (CORREGIDO) +-- LOTE 1: Registros 2025-11-18 (13 usuarios) -- ===================================================== + +-- PROFILE 1: Jose Aguirre ( - 'b017b792-b327-40dd-aefb-a80312776952'::uuid, -- ✅ id = user_id (auth.users.id) - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, -- ✅ Tenant principal - 'b017b792-b327-40dd-aefb-a80312776952'::uuid, -- user_id + 'b017b792-b327-40dd-aefb-a80312776952'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'b017b792-b327-40dd-aefb-a80312776952'::uuid, 'joseal.guirre34@gmail.com', - 'Jose Aguirre', - 'Jose Aguirre', - 'Jose', - 'Aguirre', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Jose Aguirre', 'Jose Aguirre', 'Jose', 'Aguirre', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 07:29:05.229254+00'::timestamptz, '2025-11-18 07:29:05.229254+00'::timestamptz ), --- ===================================================== --- PROFILE 2: Sergio Jimenez (CORREGIDO) --- ===================================================== +-- PROFILE 2: Sergio Jimenez ( '06a24962-e83d-4e94-aad7-ff69f20a9119'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '06a24962-e83d-4e94-aad7-ff69f20a9119'::uuid, 'sergiojimenezesteban63@gmail.com', - 'Sergio Jimenez', - 'Sergio Jimenez', - 'Sergio', - 'Jimenez', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Sergio Jimenez', 'Sergio Jimenez', 'Sergio', 'Jimenez', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 08:17:40.928077+00'::timestamptz, '2025-11-18 08:17:40.928077+00'::timestamptz ), --- ===================================================== --- PROFILE 3: Hugo Gomez (CORREGIDO) --- ===================================================== +-- PROFILE 3: Hugo Gomez ( '24e8c563-8854-43d1-b3c9-2f83e91f5a1e'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '24e8c563-8854-43d1-b3c9-2f83e91f5a1e'::uuid, 'Gomezfornite92@gmail.com', - 'Hugo Gomez', - 'Hugo Gomez', - 'Hugo', - 'Gomez', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Hugo Gomez', 'Hugo Gomez', 'Hugo', 'Gomez', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 08:18:04.242047+00'::timestamptz, '2025-11-18 08:18:04.242047+00'::timestamptz ), --- ===================================================== --- PROFILE 4: Hugo Aragón (CORREGIDO) --- ===================================================== +-- PROFILE 4: Hugo Aragon ( 'bf0d3e34-e077-43d1-9626-292f7fae2bd6'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'bf0d3e34-e077-43d1-9626-292f7fae2bd6'::uuid, 'Aragon494gt54@icloud.com', - 'Hugo Aragón', - 'Hugo Aragón', - 'Hugo', - 'Aragón', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Hugo Aragon', 'Hugo Aragon', 'Hugo', 'Aragon', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 08:20:17.230714+00'::timestamptz, '2025-11-18 08:20:17.230714+00'::timestamptz ), --- ===================================================== --- PROFILE 5: Azul Valentina (CORREGIDO) --- ===================================================== +-- PROFILE 5: Azul Valentina ( '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, 'blu3wt7@gmail.com', - 'Azul Valentina', - 'Azul Valentina', - 'Azul', - 'Valentina', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Azul Valentina', 'Azul Valentina', 'Azul', 'Valentina', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 08:32:17.315932+00'::timestamptz, '2025-11-18 08:32:17.315932+00'::timestamptz ), --- ===================================================== --- PROFILE 6: Ricardo Lugo (CORREGIDO) --- ===================================================== +-- PROFILE 6: Ricardo Lugo ( '5e738038-1743-4aa9-b222-30171300ea9d'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '5e738038-1743-4aa9-b222-30171300ea9d'::uuid, 'ricardolugo786@icloud.com', - 'Ricardo Lugo', - 'Ricardo Lugo', - 'Ricardo', - 'Lugo', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Ricardo Lugo', 'Ricardo Lugo', 'Ricardo', 'Lugo', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 10:15:06.481498+00'::timestamptz, '2025-11-18 10:15:06.481498+00'::timestamptz ), --- ===================================================== --- PROFILE 7: Carlos Marban (CORREGIDO) --- ===================================================== +-- PROFILE 7: Carlos Marban ( '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, 'marbancarlos916@gmail.com', - 'Carlos Marban', - 'Carlos Marban', - 'Carlos', - 'Marban', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Carlos Marban', 'Carlos Marban', 'Carlos', 'Marban', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 10:29:05.240413+00'::timestamptz, '2025-11-18 10:29:05.240413+00'::timestamptz ), --- ===================================================== --- PROFILE 8: Diego Colores (CORREGIDO) --- ===================================================== +-- PROFILE 8: Diego Colores ( '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, 'diego.colores09@gmail.com', - 'Diego Colores', - 'Diego Colores', - 'Diego', - 'Colores', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Diego Colores', 'Diego Colores', 'Diego', 'Colores', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 10:29:20.531883+00'::timestamptz, '2025-11-18 10:29:20.531883+00'::timestamptz ), --- ===================================================== --- PROFILE 9: Benjamin Hernandez (CORREGIDO) --- ===================================================== +-- PROFILE 9: Benjamin Hernandez ( '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, 'hernandezfonsecabenjamin7@gmail.com', - 'Benjamin Hernandez', - 'Benjamin Hernandez', - 'Benjamin', - 'Hernandez', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Benjamin Hernandez', 'Benjamin Hernandez', 'Benjamin', 'Hernandez', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 10:37:06.9215+00'::timestamptz, '2025-11-18 10:37:06.9215+00'::timestamptz ), --- ===================================================== --- PROFILE 10: Josue Reyes (CORREGIDO) --- ===================================================== +-- PROFILE 10: Josue Reyes ( 'ccd7135c-0fea-4488-9094-9da52df1c98c'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'ccd7135c-0fea-4488-9094-9da52df1c98c'::uuid, 'jr7794315@gmail.com', - 'Josue Reyes', - 'Josue Reyes', - 'Josue', - 'Reyes', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Josue Reyes', 'Josue Reyes', 'Josue', 'Reyes', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 17:53:39.681271+00'::timestamptz, '2025-11-18 17:53:39.681271+00'::timestamptz ), --- ===================================================== --- PROFILE 11: Fernando Barragan (CORREGIDO) --- ===================================================== +-- PROFILE 11: Fernando Barragan ( '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, 'barraganfer03@gmail.com', - 'Fernando Barragan', - 'Fernando Barragan', - 'Fernando', - 'Barragan', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Fernando Barragan', 'Fernando Barragan', 'Fernando', 'Barragan', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 20:39:27.410436+00'::timestamptz, '2025-11-18 20:39:27.410436+00'::timestamptz ), --- ===================================================== --- PROFILE 12: Marco Antonio Roman (CORREGIDO) --- ===================================================== +-- PROFILE 12: Marco Antonio Roman ( '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, '735235f5-260a-4c9b-913c-14a1efd083ea'::uuid, 'roman.rebollar.marcoantonio1008@gmail.com', - 'Marco Antonio Roman', - 'Marco Antonio Roman', - 'Marco Antonio', - 'Roman', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Marco Antonio Roman', 'Marco Antonio Roman', 'Marco Antonio', 'Roman', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 21:03:17.328254+00'::timestamptz, '2025-11-18 21:03:17.328254+00'::timestamptz ), --- ===================================================== --- PROFILE 13: Rodrigo Guerrero (CORREGIDO) --- ===================================================== +-- PROFILE 13: Rodrigo Guerrero ( 'ebe48628-5e44-4562-97b7-b4950b216247'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'ebe48628-5e44-4562-97b7-b4950b216247'::uuid, 'rodrigoguerrero0914@gmail.com', - 'Rodrigo Guerrero', - 'Rodrigo Guerrero', - 'Rodrigo', - 'Guerrero', - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + 'Rodrigo Guerrero', 'Rodrigo Guerrero', 'Rodrigo', 'Guerrero', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'student'::auth_management.gamilit_role, 'active'::auth_management.user_status, - false, - false, - jsonb_build_object( - 'theme', 'detective', - 'language', 'es', - 'timezone', 'America/Mexico_City', - 'sound_enabled', true, - 'notifications_enabled', true - ), + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, '{}'::jsonb, '2025-11-18 21:20:52.304488+00'::timestamptz, '2025-11-18 21:20:52.304488+00'::timestamptz +), + +-- ===================================================== +-- LOTE 2: Registros 2025-11-24 (23 usuarios) +-- ===================================================== + +-- PROFILE 14 +( + '5fc06693-e408-4eab-a9a3-fcd5f4e01296'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '5fc06693-e408-4eab-a9a3-fcd5f4e01296'::uuid, + '7341023901m@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 15 +( + '615adf6e-dbf3-480f-a907-3cfb3a64c6d2'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '615adf6e-dbf3-480f-a907-3cfb3a64c6d2'::uuid, + 'vituschinchilla@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 16 +( + '7ded133e-9b13-4467-9803-edb813f6a9a1'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '7ded133e-9b13-4467-9803-edb813f6a9a1'::uuid, + 'alexeimongam@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 17 +( + '1b310708-6f24-4c6a-88c9-a11f7a7f9763'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '1b310708-6f24-4c6a-88c9-a11f7a7f9763'::uuid, + 'angelrabano11@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 18 +( + 'd5fa4905-a78a-4040-8ad8-23220881c6a6'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'd5fa4905-a78a-4040-8ad8-23220881c6a6'::uuid, + 'loganalexander816@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 19 +( + '126b9257-7b0a-4bd6-9ab3-c505ee00e10a'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '126b9257-7b0a-4bd6-9ab3-c505ee00e10a'::uuid, + 'johhkk22@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 20 +( + '9ac1746e-94a6-4efc-a961-951c015d416e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '9ac1746e-94a6-4efc-a961-951c015d416e'::uuid, + 'edangiel4532@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 21 +( + 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'af4d8788-f8a8-4971-bb0d-2f48c150dfc2'::uuid, + 'aarizmendi434@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 22 +( + 'd089b1af-462f-4d2c-b0f5-d2528cec8506'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'd089b1af-462f-4d2c-b0f5-d2528cec8506'::uuid, + 'santiagoferrara78@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 23 +( + '012adac4-8ffd-47bd-9248-f0c5851e981f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '012adac4-8ffd-47bd-9248-f0c5851e981f'::uuid, + '09enriquecampos@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 24 +( + '1364c463-88de-479b-a883-c0b7b362bcf8'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '1364c463-88de-479b-a883-c0b7b362bcf8'::uuid, + 'maximiliano.mejia367@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 25 +( + '5d1839f6-b03f-4e12-b236-eca43f4674f2'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '5d1839f6-b03f-4e12-b236-eca43f4674f2'::uuid, + 'segurauriel235@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 26 +( + '5ae21325-7450-4c37-82f1-3f9bcd7b6f45'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '5ae21325-7450-4c37-82f1-3f9bcd7b6f45'::uuid, + 'omarcitogonzalezzavaleta@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 27 +( + '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '2d9f05d4-44dd-42cd-97aa-d57bd06fecd0'::uuid, + 'erickfranco462@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 28 +( + 'bf445960-4c1f-4e29-8fb7-31667b183d7e'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'bf445960-4c1f-4e29-8fb7-31667b183d7e'::uuid, + 'bryan@betanzos.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 29 +( + 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'b1cadf36-1f07-46b2-b63d-da72d9b54dc6'::uuid, + 'alexanserrv917@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 30 +( + '71734c15-cdaa-431b-90f5-97a57e0316a8'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '71734c15-cdaa-431b-90f5-97a57e0316a8'::uuid, + 'carlois1974@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 31 +( + 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'a4d27774-8a51-4660-ad2f-81d0dfd3a5a7'::uuid, + 'gustavobm2024cbtis@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 32 +( + 'aff5dcc6-32de-4769-9aaf-eda751fa0866'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'aff5dcc6-32de-4769-9aaf-eda751fa0866'::uuid, + 'gallinainsana@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 33 +( + 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'fbbe7d19-048c-45e4-8a9c-cf86d2098c35'::uuid, + 'zaid080809@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 34 +( + '4cc04f54-7771-462d-98aa-a94448bb6ff5'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '4cc04f54-7771-462d-98aa-a94448bb6ff5'::uuid, + 'davidocampovenegas@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 35 +( + '6e30164a-78b0-49b0-bd21-23d7c6c03349'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '6e30164a-78b0-49b0-bd21-23d7c6c03349'::uuid, + 'marianaxsotoxt22@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- PROFILE 36 +( + '0cda1645-83c5-445b-80b7-d0e4d436c00c'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '0cda1645-83c5-445b-80b7-d0e4d436c00c'::uuid, + 'leile5257@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-24 00:00:00+00'::timestamptz, + '2025-11-24 00:00:00+00'::timestamptz +), + +-- ===================================================== +-- LOTE 3: Registros 2025-11-25 (6 usuarios) +-- ===================================================== + +-- PROFILE 37 +( + '26fbc469-10af-4fa3-bd65-e5498188cc4f'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '26fbc469-10af-4fa3-bd65-e5498188cc4f'::uuid, + 'ashernarcisobenitezpalomino@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- PROFILE 38 +( + '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '5b3d74e8-fd1a-4c80-96d2-24c54bfe90c4'::uuid, + 'ruizcruzabrahamfrancisco@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- PROFILE 39 +( + '3c613b0e-66f9-4640-a599-c9426d8edffb'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '3c613b0e-66f9-4640-a599-c9426d8edffb'::uuid, + 'daliaayalareyes35@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- PROFILE 40 +( + '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '74ed8c97-ec36-43aa-a1cc-b0c99e4be4e8'::uuid, + 'ra.alejandrobm@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- PROFILE 41 +( + '1efe491d-98ef-4c02-acd1-3135f7289072'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '1efe491d-98ef-4c02-acd1-3135f7289072'::uuid, + 'enriquecuevascbtis136@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- PROFILE 42 +( + '547eb778-4782-4681-b198-c731bba36147'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '547eb778-4782-4681-b198-c731bba36147'::uuid, + 'fl432025@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- ===================================================== +-- LOTE 4: Registros 2025-12-08/17 (2 usuarios) +-- ===================================================== + +-- PROFILE 43 +( + 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'f4c46f46-3fb9-40bf-a52b-a8ad2e6a92e1'::uuid, + 'abdallahxelhaneriavega@gmail.com', + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-11-25 00:00:00+00'::timestamptz, + '2025-11-25 00:00:00+00'::timestamptz +), + +-- PROFILE 44: Javier Mar +( + '69681b09-5077-4f77-84cc-67606abd9755'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + '69681b09-5077-4f77-84cc-67606abd9755'::uuid, + 'javiermar06@hotmail.com', + 'Javier Mar', 'Javier Mar', 'Javier', 'Mar', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-12-08 19:24:06.272257+00'::timestamptz, + '2025-12-08 19:24:06.272257+00'::timestamptz +), + +-- PROFILE 45: Juan Pa +( + 'f929d6df-8c29-461f-88f5-264facd879e9'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'f929d6df-8c29-461f-88f5-264facd879e9'::uuid, + 'ju188an@gmail.com', + 'Juan Pa', 'Juan Pa', 'Juan', 'Pa', + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 'student'::auth_management.gamilit_role, + 'active'::auth_management.user_status, + false, false, + '{"theme": "detective", "language": "es", "timezone": "America/Mexico_City", "sound_enabled": true, "notifications_enabled": true}'::jsonb, + '{}'::jsonb, + '2025-12-17 17:51:43.536295+00'::timestamptz, + '2025-12-17 17:51:43.536295+00'::timestamptz ) ON CONFLICT (id) DO UPDATE SET - tenant_id = EXCLUDED.tenant_id, -- ✅ Actualizar tenant al principal + tenant_id = EXCLUDED.tenant_id, display_name = EXCLUDED.display_name, full_name = EXCLUDED.full_name, first_name = EXCLUDED.first_name, @@ -535,7 +856,7 @@ DECLARE corrected_ids_count INTEGER; corrected_tenants_count INTEGER; BEGIN - -- Contar perfiles de producción + -- Contar perfiles de produccion SELECT COUNT(*) INTO production_profile_count FROM auth_management.profiles WHERE email NOT LIKE '%@gamilit.com'; @@ -553,22 +874,22 @@ BEGIN AND tenant_id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'; RAISE NOTICE '========================================'; - RAISE NOTICE 'PERFILES DE PRODUCCIÓN (CORREGIDOS)'; + RAISE NOTICE 'PERFILES DE PRODUCCION'; RAISE NOTICE '========================================'; - RAISE NOTICE 'Total perfiles de producción: %', production_profile_count; + RAISE NOTICE 'Total perfiles de produccion: %', production_profile_count; RAISE NOTICE 'Perfiles con profiles.id = auth.users.id: %', corrected_ids_count; RAISE NOTICE 'Perfiles con tenant principal: %', corrected_tenants_count; RAISE NOTICE '========================================'; - IF production_profile_count = 13 AND corrected_ids_count = 13 AND corrected_tenants_count = 13 THEN - RAISE NOTICE '✅ Los 13 perfiles de producción fueron CORREGIDOS correctamente'; - RAISE NOTICE '✅ profiles.id = auth.users.id para TODOS los usuarios'; - RAISE NOTICE '✅ tenant_id = GAMILIT Platform para TODOS los usuarios'; + IF production_profile_count >= 45 AND corrected_ids_count >= 45 AND corrected_tenants_count >= 45 THEN + RAISE NOTICE '✓ Los 45 perfiles de produccion fueron creados correctamente'; + RAISE NOTICE '✓ profiles.id = auth.users.id para TODOS los usuarios'; + RAISE NOTICE '✓ tenant_id = GAMILIT Platform para TODOS los usuarios'; ELSE - RAISE WARNING '⚠ Corrección incompleta:'; - RAISE WARNING ' - Esperados: 13 perfiles'; - RAISE WARNING ' - IDs corregidos: %', corrected_ids_count; - RAISE WARNING ' - Tenants corregidos: %', corrected_tenants_count; + RAISE WARNING '! Verificacion incompleta:'; + RAISE WARNING ' - Esperados: 45 perfiles'; + RAISE WARNING ' - IDs correctos: %', corrected_ids_count; + RAISE WARNING ' - Tenants correctos: %', corrected_tenants_count; END IF; RAISE NOTICE '========================================'; @@ -577,13 +898,15 @@ END $$; -- ===================================================== -- CHANGELOG -- ===================================================== --- v2.0 (2025-11-19): Corrección de IDs y tenants --- - ✅ profiles.id = auth.users.id (era diferente) --- - ✅ tenant_id = Tenant principal (era personal) --- - ✅ Consistente con usuarios de testing --- - ✅ Elimina error 404 al enviar respuestas +-- v3.0 (2025-12-18): Actualizacion con backup produccion +-- - ✓ Actualizado de 13 a 45 perfiles de produccion +-- - ✓ Excluido rckrdmrd@gmail.com (usuario de pruebas owner) +-- - ✓ profiles.id = auth.users.id para TODOS +-- - ✓ tenant_id = Tenant principal para TODOS -- --- v1.0 (2025-11-19): Primera versión (DEPRECADA) --- - ❌ profiles.id generado con gen_random_uuid() --- - ❌ tenant_id apuntaba a tenants personales +-- v2.0 (2025-11-19): Correccion de IDs y tenants +-- - ✓ profiles.id = auth.users.id (era diferente) +-- - ✓ tenant_id = Tenant principal (era personal) +-- +-- v1.0 (2025-11-19): Primera version (DEPRECADA) -- ===================================================== diff --git a/projects/gamilit/apps/database/seeds/prod/auth_management/07-user_roles.sql b/projects/gamilit/apps/database/seeds/prod/auth_management/07-user_roles.sql new file mode 100644 index 0000000..a7d6995 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/auth_management/07-user_roles.sql @@ -0,0 +1,260 @@ +-- ===================================================== +-- Seed: auth_management.user_roles +-- Description: Asignaciones de roles a usuarios de prueba y demo +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed asigna roles a los usuarios existentes en profiles. +-- Los roles definen los permisos y accesos del sistema. +-- +-- Roles disponibles (ENUM gamilit_role): +-- - super_admin: Administrador global del sistema +-- - admin_teacher: Profesor con permisos administrativos +-- - teacher: Profesor estándar +-- - student: Estudiante +-- - parent: Padre de familia +-- ===================================================== + +DO $$ +DECLARE + v_tenant_id UUID; + v_admin_id UUID; + v_teacher_id UUID; + v_student_id UUID; +BEGIN + -- Obtener tenant principal (puede ser 'GAMILIT Platform' o 'GAMILIT Principal') + SELECT id INTO v_tenant_id FROM auth_management.tenants + WHERE name LIKE 'GAMILIT%' OR id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid + LIMIT 1; + + IF v_tenant_id IS NULL THEN + -- Usar UUID por defecto si no se encuentra + v_tenant_id := 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid; + RAISE NOTICE 'Usando tenant por defecto: %', v_tenant_id; + END IF; + + -- Obtener IDs de usuarios de prueba (creados en 04-profiles-complete.sql) + v_admin_id := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid; + v_teacher_id := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid; + v_student_id := 'cccccccc-cccc-cccc-cccc-cccccccccccc'::uuid; + + RAISE NOTICE 'Asignando roles a usuarios de prueba...'; + + -- ===================================================== + -- 1. ROL SUPER_ADMIN para admin@gamilit.com + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, + user_id, + tenant_id, + role, + permissions, + assigned_by, + is_active, + metadata + ) VALUES ( + '10000001-0000-0000-0000-000000000001'::uuid, + v_admin_id, + v_tenant_id, + 'super_admin', + '{ + "read": true, + "write": true, + "admin": true, + "analytics": true, + "manage_users": true, + "manage_content": true, + "manage_gamification": true, + "system_config": true + }'::jsonb, + v_admin_id, + true, + '{"assigned_reason": "Usuario administrador del sistema", "seed_version": "1.0.0"}'::jsonb + ) + ON CONFLICT (user_id, tenant_id, role) DO UPDATE SET + permissions = EXCLUDED.permissions, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Rol super_admin asignado a admin@gamilit.com'; + + -- ===================================================== + -- 2. ROL ADMIN_TEACHER para teacher@gamilit.com + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, + user_id, + tenant_id, + role, + permissions, + assigned_by, + is_active, + metadata + ) VALUES ( + '10000002-0000-0000-0000-000000000001'::uuid, + v_teacher_id, + v_tenant_id, + 'admin_teacher', + '{ + "read": true, + "write": true, + "admin": false, + "analytics": true, + "manage_students": true, + "manage_classrooms": true, + "create_content": true, + "grade_assignments": true + }'::jsonb, + v_admin_id, + true, + '{"assigned_reason": "Profesor de prueba con permisos administrativos", "seed_version": "1.0.0"}'::jsonb + ) + ON CONFLICT (user_id, tenant_id, role) DO UPDATE SET + permissions = EXCLUDED.permissions, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Rol admin_teacher asignado a teacher@gamilit.com'; + + -- ===================================================== + -- 3. ROL STUDENT para student@gamilit.com + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, + user_id, + tenant_id, + role, + permissions, + assigned_by, + is_active, + metadata + ) VALUES ( + '10000003-0000-0000-0000-000000000001'::uuid, + v_student_id, + v_tenant_id, + 'student', + '{ + "read": true, + "write": false, + "admin": false, + "analytics": false, + "view_own_progress": true, + "submit_exercises": true, + "participate_classrooms": true, + "earn_achievements": true + }'::jsonb, + v_admin_id, + true, + '{"assigned_reason": "Estudiante de prueba", "seed_version": "1.0.0"}'::jsonb + ) + ON CONFLICT (user_id, tenant_id, role) DO UPDATE SET + permissions = EXCLUDED.permissions, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Rol student asignado a student@gamilit.com'; + + -- ===================================================== + -- 4. ROLES PARA ESTUDIANTES DEMO (Ana, Carlos, María, Luis) + -- ===================================================== + + -- Ana García (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000004-0000-0000-0000-000000000001'::uuid, + '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + -- Carlos Ramírez (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000005-0000-0000-0000-000000000001'::uuid, + '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + -- María Fernanda (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000006-0000-0000-0000-000000000001'::uuid, + '00c742d9-e5f7-4666-9597-5a8ca54d5478'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + -- Luis Miguel (student) + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000007-0000-0000-0000-000000000001'::uuid, + '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, + v_tenant_id, + 'student', + '{"read": true, "submit_exercises": true, "view_own_progress": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + RAISE NOTICE '✅ Roles student asignados a estudiantes demo'; + + -- ===================================================== + -- 5. ROL TEACHER para Laura Martínez (profesora demo) + -- ===================================================== + INSERT INTO auth_management.user_roles ( + id, user_id, tenant_id, role, permissions, assigned_by, is_active + ) VALUES ( + '10000008-0000-0000-0000-000000000001'::uuid, + '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, + v_tenant_id, + 'admin_teacher', + '{"read": true, "write": true, "analytics": true, "manage_students": true}'::jsonb, + v_admin_id, + true + ) + ON CONFLICT (user_id, tenant_id, role) DO NOTHING; + + RAISE NOTICE '✅ Rol admin_teacher asignado a Laura Martínez'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== RESUMEN DE ROLES ASIGNADOS ==='; + RAISE NOTICE 'Total roles insertados: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE tenant_id = v_tenant_id); + RAISE NOTICE 'Super Admins: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE role = 'super_admin' AND is_active = true); + RAISE NOTICE 'Admin Teachers: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE role = 'admin_teacher' AND is_active = true); + RAISE NOTICE 'Students: %', (SELECT COUNT(*) FROM auth_management.user_roles WHERE role = 'student' AND is_active = true); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +BEGIN + IF (SELECT COUNT(*) FROM auth_management.user_roles) < 3 THEN + RAISE WARNING '⚠️ Se esperaban al menos 3 registros en user_roles'; + ELSE + RAISE NOTICE '✅ Seed de user_roles completado exitosamente'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/auth_management/08-assign-admin-schools.sql b/projects/gamilit/apps/database/seeds/prod/auth_management/08-assign-admin-schools.sql new file mode 100644 index 0000000..974d00f --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/auth_management/08-assign-admin-schools.sql @@ -0,0 +1,149 @@ +-- ===================================================== +-- Seed: auth_management.profiles - Assign School to ALL Users (PROD) +-- Description: Asigna la escuela default a TODOS los usuarios sin escuela +-- Environment: PRODUCTION +-- Dependencies: +-- - social_features.schools (00-schools-default.sql) +-- - auth_management.profiles (04-profiles-complete.sql) +-- Order: 08 (debe ejecutarse DESPUÉS de profiles y schools) +-- Created: 2025-12-15 +-- Updated: 2025-12-15 - Expandido a TODOS los usuarios +-- Version: 2.0 +-- ===================================================== +-- +-- PROPÓSITO: +-- Asegurar que TODOS los usuarios tengan una escuela asignada +-- (school_id NOT NULL) apuntando a la escuela default. +-- +-- USUARIOS AFECTADOS: +-- - Todos los roles: super_admin, admin_teacher, student, parent +-- - Cualquier usuario nuevo sin school_id +-- +-- DECISIÓN DE DISEÑO (v2.0): +-- - TODOS los usuarios van a la escuela default +-- - El admin puede reasignarlos desde la UI a otras escuelas +-- - Esto garantiza integridad referencial +-- +-- IMPORTANTE: Este seed es idempotente - solo actualiza si school_id IS NULL +-- ===================================================== + +SET search_path TO auth_management, social_features, public; + +-- ===================================================== +-- Asignar escuela default a TODOS los usuarios sin escuela +-- ===================================================== + +DO $$ +DECLARE + v_default_school_id UUID; + v_total_users INTEGER; + v_users_without_school INTEGER; + v_affected_count INTEGER; + rec RECORD; +BEGIN + -- Obtener la escuela default del sistema + SELECT id INTO v_default_school_id + FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED' + AND is_active = true + LIMIT 1; + + IF v_default_school_id IS NULL THEN + RAISE EXCEPTION 'Escuela default (SYSTEM-UNASSIGNED) no encontrada. Ejecutar primero 00-schools-default.sql'; + END IF; + + RAISE NOTICE 'Usando escuela default: %', v_default_school_id; + + -- Contar usuarios totales y sin escuela + SELECT COUNT(*) INTO v_total_users + FROM auth_management.profiles; + + SELECT COUNT(*) INTO v_users_without_school + FROM auth_management.profiles + WHERE school_id IS NULL; + + RAISE NOTICE 'Total usuarios: %', v_total_users; + RAISE NOTICE 'Usuarios sin escuela: %', v_users_without_school; + + -- Actualizar TODOS los usuarios sin escuela asignada + UPDATE auth_management.profiles + SET + school_id = v_default_school_id, + updated_at = gamilit.now_mexico() + WHERE school_id IS NULL; + + GET DIAGNOSTICS v_affected_count = ROW_COUNT; + + -- Reportar resultados + RAISE NOTICE '========================================'; + RAISE NOTICE 'ASIGNACIÓN DE ESCUELA A USUARIOS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Escuela default ID: %', v_default_school_id; + RAISE NOTICE 'Usuarios actualizados: %', v_affected_count; + RAISE NOTICE '========================================'; + + IF v_affected_count > 0 THEN + RAISE NOTICE 'Usuarios ahora tienen escuela asignada por rol:'; + + -- Mostrar conteo por rol + FOR rec IN + SELECT role, COUNT(*) as count + FROM auth_management.profiles + WHERE school_id = v_default_school_id + GROUP BY role + ORDER BY role + LOOP + RAISE NOTICE ' - %: % usuarios', rec.role, rec.count; + END LOOP; + ELSE + RAISE NOTICE 'No hay usuarios sin escuela asignada (todos ya tienen school_id)'; + END IF; + +END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + v_users_without_school INTEGER; + v_total_in_default INTEGER; + rec RECORD; +BEGIN + SELECT COUNT(*) INTO v_users_without_school + FROM auth_management.profiles + WHERE school_id IS NULL; + + SELECT COUNT(*) INTO v_total_in_default + FROM auth_management.profiles p + JOIN social_features.schools s ON p.school_id = s.id + WHERE s.code = 'SYSTEM-UNASSIGNED'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN DE ASIGNACIÓN DE ESCUELAS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Usuarios sin escuela: %', v_users_without_school; + RAISE NOTICE 'Usuarios en escuela default: %', v_total_in_default; + RAISE NOTICE '========================================'; + + IF v_users_without_school = 0 THEN + RAISE NOTICE '✓ Todos los usuarios tienen escuela asignada'; + + -- Mostrar distribución por rol + RAISE NOTICE ''; + RAISE NOTICE 'Distribución por rol en escuela default:'; + FOR rec IN + SELECT p.role, COUNT(*) as count + FROM auth_management.profiles p + JOIN social_features.schools s ON p.school_id = s.id + WHERE s.code = 'SYSTEM-UNASSIGNED' + GROUP BY p.role + ORDER BY p.role + LOOP + RAISE NOTICE ' - %: %', rec.role, rec.count; + END LOOP; + ELSE + RAISE WARNING '⚠ ADVERTENCIA: Hay % usuarios sin escuela asignada', v_users_without_school; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/auth_management/05-profiles-demo.sql b/projects/gamilit/apps/database/seeds/prod/auth_management/_deprecated/05-profiles-demo.sql similarity index 100% rename from projects/gamilit/apps/database/seeds/prod/auth_management/05-profiles-demo.sql rename to projects/gamilit/apps/database/seeds/prod/auth_management/_deprecated/05-profiles-demo.sql diff --git a/projects/gamilit/apps/database/seeds/prod/content_management/02-marie_curie_content.sql b/projects/gamilit/apps/database/seeds/prod/content_management/02-marie_curie_content.sql new file mode 100644 index 0000000..39f8b5a --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/content_management/02-marie_curie_content.sql @@ -0,0 +1,483 @@ +-- ===================================================== +-- Seed: content_management.marie_curie_content +-- Description: Contenido curado sobre Marie Curie - biografía, descubrimientos, legado +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed crea el contenido educativo principal de GAMILIT +-- basado en la vida y obra de Marie Curie. +-- +-- Categorías de contenido (CHECK constraint): +-- - biography: Datos biográficos +-- - discoveries: Descubrimientos científicos +-- - historical_context: Contexto histórico +-- - scientific_method: Metodología científica +-- - radioactivity: Radiactividad +-- - nobel_prizes: Premios Nobel +-- - women_in_science: Mujeres en la ciencia +-- - modern_physics: Física moderna +-- - legacy: Legado +-- ===================================================== + +DO $$ +DECLARE + v_tenant_id UUID; + v_admin_id UUID; +BEGIN + -- Obtener tenant principal (puede ser 'GAMILIT Platform' o 'GAMILIT Principal') + SELECT id INTO v_tenant_id FROM auth_management.tenants + WHERE name LIKE 'GAMILIT%' OR id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid + LIMIT 1; + + -- Admin para created_by + v_admin_id := 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid; + + IF v_tenant_id IS NULL THEN + -- Usar UUID por defecto si no se encuentra + v_tenant_id := 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid; + RAISE NOTICE 'Usando tenant por defecto: %', v_tenant_id; + END IF; + + RAISE NOTICE 'Creando contenido de Marie Curie...'; + + -- ===================================================== + -- 1. BIOGRAFÍA - PRIMEROS AÑOS + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000001'::uuid, + v_tenant_id, + 'Marie Curie: Primeros Años en Polonia', + 'El nacimiento de una mente brillante', + 'Explora la infancia y juventud de Maria Sklodowska en Varsovia, Polonia, su temprano amor por el aprendizaje y los desafíos que enfrentó como mujer en busca de educación en el siglo XIX.', + 'biography', + '{ + "introduction": "Maria Salomea Sklodowska nació el 7 de noviembre de 1867 en Varsovia, Polonia, cuando el país estaba bajo el dominio del Imperio Ruso.", + "main_content": "Desde muy joven, Maria demostró una extraordinaria capacidad intelectual. Era la menor de cinco hermanos en una familia de educadores. Su padre, Wladyslaw, era profesor de matemáticas y física, y su madre, Bronislawa, dirigía una prestigiosa escuela para niñas. La educación era profundamente valorada en la familia Sklodowski.", + "key_points": [ + "Nació el 7 de noviembre de 1867 en Varsovia, Polonia", + "Era la menor de cinco hermanos", + "Su padre era profesor de matemáticas y física", + "Desde niña mostró memoria excepcional y amor por el aprendizaje", + "Aprendió a leer a los 4 años" + ], + "timeline": [ + {"year": 1867, "event": "Nacimiento en Varsovia"}, + {"year": 1876, "event": "Muerte de su hermana Zofia por tifus"}, + {"year": 1878, "event": "Muerte de su madre por tuberculosis"}, + {"year": 1883, "event": "Graduación con medalla de oro"} + ], + "quotes": [ + {"text": "Nada en la vida debe ser temido, solo debe ser entendido.", "context": "Sobre el conocimiento científico"} + ] + }'::jsonb, + ARRAY['6', '7', '8'], + 'beginner', + ARRAY['Conocer los orígenes de Marie Curie', 'Comprender el contexto histórico de Polonia', 'Identificar influencias familiares en su educación'], + ARRAY['Polonia', 'Varsovia', 'Imperio Ruso', 'educación', 'familia Sklodowski'], + '1867-1891', + 'Biography', + 'published', + true, + ARRAY['marie curie', 'biografía', 'polonia', 'infancia', 'educación'], + ARRAY['#marie-curie', '#biografia', '#primeros-años', '#polonia'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Biografía: Primeros Años en Polonia'; + + -- ===================================================== + -- 2. BIOGRAFÍA - LLEGADA A PARÍS + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000002'::uuid, + v_tenant_id, + 'El Sueño de París: Marie en la Sorbona', + 'Persiguiendo la educación contra todo pronóstico', + 'La determinación de Marie para estudiar en la Universidad de París y sus primeros años como estudiante de física y matemáticas en la Sorbona.', + 'biography', + '{ + "introduction": "En 1891, a los 24 años, Maria Sklodowska finalmente llegó a París para cumplir su sueño de estudiar ciencias en la prestigiosa Universidad de la Sorbona.", + "main_content": "Marie vivía en condiciones muy humildes en el Barrio Latino. Su pequeña habitación en el sexto piso no tenía calefacción ni agua corriente. A menudo se olvidaba de comer, tan absorta estaba en sus estudios. A pesar de las dificultades económicas y el frío parisino, Marie se graduó primera de su promoción en física en 1893, y segunda en matemáticas en 1894.", + "key_points": [ + "Llegó a París en noviembre de 1891", + "Se inscribió en la Facultad de Ciencias de la Sorbona", + "Cambió su nombre a Marie", + "Primera de su promoción en física (1893)", + "Segunda en matemáticas (1894)" + ], + "timeline": [ + {"year": 1891, "event": "Llegada a París e inscripción en la Sorbona"}, + {"year": 1893, "event": "Licenciatura en Física (1ª de su promoción)"}, + {"year": 1894, "event": "Licenciatura en Matemáticas"}, + {"year": 1894, "event": "Conoce a Pierre Curie"} + ], + "quotes": [ + {"text": "La vida no es fácil para ninguno de nosotros. Pero... ¡qué importa! Debemos tener perseverancia.", "context": "Sobre su tiempo en París"} + ] + }'::jsonb, + ARRAY['6', '7', '8'], + 'beginner', + ARRAY['Valorar la perseverancia académica', 'Comprender las barreras para mujeres en educación', 'Conocer la vida universitaria del siglo XIX'], + ARRAY['Sorbona', 'París', 'universidad', 'Barrio Latino', 'física', 'matemáticas'], + '1891-1894', + 'Biography', + 'published', + true, + ARRAY['marie curie', 'sorbona', 'paris', 'universidad', 'educación'], + ARRAY['#marie-curie', '#sorbona', '#paris', '#universidad'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Biografía: Llegada a París'; + + -- ===================================================== + -- 3. DESCUBRIMIENTOS - RADIACTIVIDAD + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000003'::uuid, + v_tenant_id, + 'El Descubrimiento de la Radiactividad', + 'Una nueva era en la física', + 'El revolucionario trabajo de Marie Curie que llevó al descubrimiento del polonio y el radio, y la acuñación del término "radiactividad".', + 'discoveries', + '{ + "introduction": "En 1898, Marie Curie descubrió dos nuevos elementos químicos y acuñó el término radiactividad para describir la emisión espontánea de radiación por ciertos materiales.", + "main_content": "Marie eligió estudiar los misteriosos rayos de uranio descubiertos por Henri Becquerel para su tesis doctoral. Trabajando en un cobertizo frío y húmedo en la Escuela de Física, desarrolló técnicas innovadoras para medir la intensidad de la radiación. Descubrió que el mineral pechblenda era más radiactivo que el uranio puro, lo que sugería la presencia de elementos desconocidos.", + "key_points": [ + "Acuñó el término ''radiactividad'' en 1898", + "Descubrió el polonio (nombrado en honor a Polonia)", + "Descubrió el radio (del latín radius, rayo)", + "Trabajó en condiciones extremadamente difíciles", + "Procesó toneladas de pechblenda para aislar el radio" + ], + "timeline": [ + {"year": 1897, "event": "Inicio de investigación sobre rayos de uranio"}, + {"year": 1898, "event": "Descubrimiento del polonio (julio)"}, + {"year": 1898, "event": "Descubrimiento del radio (diciembre)"}, + {"year": 1902, "event": "Aislamiento de radio puro"}, + {"year": 1903, "event": "Tesis doctoral sobre sustancias radiactivas"} + ], + "quotes": [ + {"text": "Uno nunca nota lo que se ha hecho; uno solo puede ver lo que queda por hacer.", "context": "Sobre el trabajo científico"} + ] + }'::jsonb, + ARRAY['7', '8', '9'], + 'intermediate', + ARRAY['Comprender el concepto de radiactividad', 'Conocer el proceso del descubrimiento científico', 'Valorar la metodología de investigación'], + ARRAY['radiactividad', 'polonio', 'radio', 'pechblenda', 'uranio', 'elementos químicos'], + '1897-1903', + 'Physics', + 'published', + true, + ARRAY['radiactividad', 'polonio', 'radio', 'descubrimiento', 'física'], + ARRAY['#radiactividad', '#polonio', '#radio', '#descubrimiento'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Descubrimientos: Radiactividad'; + + -- ===================================================== + -- 4. PREMIOS NOBEL + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000004'::uuid, + v_tenant_id, + 'Dos Premios Nobel: Un Logro Histórico', + 'La primera persona en ganar dos Premios Nobel', + 'Marie Curie hizo historia al ser la primera mujer en ganar un Premio Nobel y la primera persona en ganar dos en diferentes disciplinas científicas.', + 'nobel_prizes', + '{ + "introduction": "Marie Curie es la única persona en la historia en recibir Premios Nobel en dos ciencias diferentes: Física (1903) y Química (1911).", + "main_content": "En 1903, Marie compartió el Premio Nobel de Física con su esposo Pierre y Henri Becquerel por sus investigaciones sobre radiactividad. Inicialmente, el comité solo había nominado a Pierre y Becquerel, pero Pierre insistió en que Marie fuera incluida. En 1911, recibió el Premio Nobel de Química en solitario por el descubrimiento del polonio y el radio.", + "key_points": [ + "Primera mujer en ganar un Premio Nobel (1903)", + "Premio Nobel de Física 1903 (compartido)", + "Premio Nobel de Química 1911 (individual)", + "Única persona con Nobel en dos ciencias diferentes", + "Primera mujer en ser profesora en la Sorbona" + ], + "timeline": [ + {"year": 1903, "event": "Premio Nobel de Física (con Pierre y Becquerel)"}, + {"year": 1906, "event": "Sucede a Pierre como profesora en la Sorbona"}, + {"year": 1911, "event": "Premio Nobel de Química (individual)"} + ], + "quotes": [ + {"text": "Soy de las que piensan que la ciencia tiene una gran belleza.", "context": "Sobre su pasión por la ciencia"} + ] + }'::jsonb, + ARRAY['7', '8', '9'], + 'intermediate', + ARRAY['Conocer los logros históricos de Marie Curie', 'Comprender la importancia de los Premios Nobel', 'Valorar el reconocimiento científico'], + ARRAY['Premio Nobel', 'Física', 'Química', 'Estocolmo', 'reconocimiento científico'], + '1903-1911', + 'Physics', + 'published', + true, + ARRAY['premio nobel', 'física', 'química', 'historia', 'logros'], + ARRAY['#nobel', '#premio', '#historia', '#logros'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Premios Nobel'; + + -- ===================================================== + -- 5. MUJERES EN LA CIENCIA + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000005'::uuid, + v_tenant_id, + 'Rompiendo Barreras: Marie Curie y las Mujeres en la Ciencia', + 'Un legado de inspiración', + 'El impacto de Marie Curie como pionera para las mujeres en la ciencia y su lucha contra los prejuicios de género en la academia.', + 'women_in_science', + '{ + "introduction": "Marie Curie no solo hizo contribuciones monumentales a la ciencia, sino que abrió puertas para las mujeres en campos tradicionalmente dominados por hombres.", + "main_content": "En una época donde las mujeres tenían acceso limitado a la educación superior, Marie tuvo que superar innumerables obstáculos. Fue la primera mujer en obtener un doctorado en ciencias en Francia, la primera profesora en la Sorbona, y la primera mujer en ser enterrada en el Panteón de París por méritos propios. Su hija Irène continuó su legado y también ganó el Premio Nobel.", + "key_points": [ + "Primera mujer con doctorado en ciencias en Francia", + "Primera profesora de la Universidad de París", + "Primera mujer enterrada en el Panteón por méritos propios", + "Su hija Irène también ganó el Premio Nobel (1935)", + "Inspiración para generaciones de científicas" + ], + "timeline": [ + {"year": 1903, "event": "Primera mujer en ganar un Premio Nobel"}, + {"year": 1906, "event": "Primera profesora en la Sorbona"}, + {"year": 1995, "event": "Primera mujer enterrada en el Panteón por méritos propios"} + ], + "quotes": [ + {"text": "Sé menos curioso sobre las personas y más curioso sobre las ideas.", "context": "Consejo a jóvenes científicos"} + ] + }'::jsonb, + ARRAY['6', '7', '8', '9'], + 'beginner', + ARRAY['Valorar la lucha por la igualdad de género', 'Conocer barreras históricas para mujeres', 'Inspirarse en el legado de Marie Curie'], + ARRAY['igualdad', 'mujeres', 'ciencia', 'pionera', 'Irène Joliot-Curie'], + '1867-presente', + 'History of Science', + 'published', + true, + ARRAY['mujeres', 'ciencia', 'igualdad', 'pionera', 'inspiración'], + ARRAY['#mujeres-en-ciencia', '#igualdad', '#pionera', '#inspiración'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Mujeres en la Ciencia'; + + -- ===================================================== + -- 6. LEGADO + -- ===================================================== + INSERT INTO content_management.marie_curie_content ( + id, + tenant_id, + title, + subtitle, + description, + category, + content, + target_grade_levels, + difficulty_level, + learning_objectives, + key_vocabulary, + historical_period, + scientific_field, + status, + is_featured, + keywords, + search_tags, + created_by + ) VALUES ( + '50000001-0000-0000-0000-000000000006'::uuid, + v_tenant_id, + 'El Legado Eterno de Marie Curie', + 'Una vida dedicada a la ciencia y la humanidad', + 'El impacto duradero de Marie Curie en la ciencia, la medicina y la sociedad, desde el tratamiento del cáncer hasta la inspiración de futuras generaciones.', + 'legacy', + '{ + "introduction": "El legado de Marie Curie trasciende sus descubrimientos científicos: sus contribuciones salvaron millones de vidas y continúan inspirando a científicos de todo el mundo.", + "main_content": "Durante la Primera Guerra Mundial, Marie organizó unidades móviles de rayos X (''petites Curies'') para ayudar a los médicos del frente a localizar balas y metralla en los cuerpos de los soldados. Ella misma condujo estas unidades y entrenó a otras mujeres para operarlas. El Instituto del Radio que fundó en París (hoy Instituto Curie) sigue siendo un centro líder en investigación del cáncer.", + "key_points": [ + "Fundó el Instituto del Radio (hoy Instituto Curie)", + "Creó unidades móviles de rayos X en la WWI", + "Sus investigaciones llevaron a tratamientos contra el cáncer", + "El elemento 96 (Curio) lleva su nombre", + "Inspiración para generaciones de científicos" + ], + "timeline": [ + {"year": 1914, "event": "Organización de unidades de rayos X en WWI"}, + {"year": 1921, "event": "Visita a EE.UU. - Recibe 1g de radio"}, + {"year": 1934, "event": "Fallecimiento por anemia aplásica"}, + {"year": 1944, "event": "Elemento 96 (Curio) nombrado en su honor"}, + {"year": 1995, "event": "Traslado al Panteón de París"} + ], + "quotes": [ + {"text": "En la vida no hay cosas que temer, solo cosas que comprender.", "context": "Su filosofía de vida"} + ] + }'::jsonb, + ARRAY['7', '8', '9'], + 'intermediate', + ARRAY['Comprender el impacto de la ciencia en la sociedad', 'Valorar las contribuciones humanitarias', 'Conocer aplicaciones médicas de la radiactividad'], + ARRAY['legado', 'Instituto Curie', 'rayos X', 'medicina', 'cáncer', 'Primera Guerra Mundial'], + '1914-presente', + 'Medicine', + 'published', + true, + ARRAY['legado', 'instituto curie', 'medicina', 'rayos x', 'humanidad'], + ARRAY['#legado', '#instituto-curie', '#medicina', '#humanidad'], + v_admin_id + ) + ON CONFLICT (id) DO UPDATE SET + title = EXCLUDED.title, + content = EXCLUDED.content, + status = 'published', + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Legado'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== CONTENIDO MARIE CURIE CREADO ==='; + RAISE NOTICE 'Total artículos: %', (SELECT COUNT(*) FROM content_management.marie_curie_content); + RAISE NOTICE 'Publicados: %', (SELECT COUNT(*) FROM content_management.marie_curie_content WHERE status = 'published'); + RAISE NOTICE 'Destacados: %', (SELECT COUNT(*) FROM content_management.marie_curie_content WHERE is_featured = true); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM content_management.marie_curie_content; + + IF v_count < 5 THEN + RAISE WARNING '⚠️ Se esperaban al menos 5 artículos de Marie Curie'; + ELSE + RAISE NOTICE '✅ Seed de marie_curie_content completado exitosamente'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/educational_content/05-exercises-module4.sql b/projects/gamilit/apps/database/seeds/prod/educational_content/05-exercises-module4.sql index 5c374c3..8dfeeb3 100644 --- a/projects/gamilit/apps/database/seeds/prod/educational_content/05-exercises-module4.sql +++ b/projects/gamilit/apps/database/seeds/prod/educational_content/05-exercises-module4.sql @@ -1,17 +1,23 @@ -- ===================================================== -- Seed Data: Exercises Module 4 - Lectura Digital y Multimodal (PRODUCTION) -- ===================================================== --- Description: 5 ejercicios inactivos del Módulo 4 (visibles pero muestran "En Construcción") +-- Description: 5 ejercicios oficiales del Módulo 4 (según DocumentoDeDiseño v6.4) -- Module: MOD-04-DIGITAL --- Exercises: Verificador Fake News, Infografía Interactiva, Quiz TikTok, Navegación Hipertextual, Análisis Memes --- Reference: DocumentoDeDiseño_Mecanicas_GAMILIT_v6_1.md líneas 768-947 --- Date: 2025-11-23 --- Status: PRODUCTION (INACTIVOS - is_active = false) +-- Exercises: +-- 4.1 Verificador Fake News +-- 4.2 Infografía Interactiva +-- 4.3 Quiz TikTok +-- 4.4 Navegación Hipertextual +-- 4.5 Análisis Memes +-- Reference: DocumentoDeDiseño_Mecanicas_GAMILIT_v6_1.md líneas 782-965 +-- Date: 2025-12-18 (Limpieza: eliminados 4 ejercicios no oficiales) +-- Status: PRODUCTION (ACTIVOS - is_active = true) +-- NOTA: Ejercicios 4.6-4.9 (resena_critica, chat_literario, email_formal, +-- ensayo_argumentativo) fueron eliminados por no estar en el documento de diseño -- ===================================================== SET search_path TO educational_content, public; --- Obtener module_id dinámicamente DO $$ DECLARE mod_id UUID; @@ -19,393 +25,415 @@ BEGIN SELECT id INTO mod_id FROM educational_content.modules WHERE module_code = 'MOD-04-DIGITAL'; IF mod_id IS NULL THEN - RAISE EXCEPTION 'Módulo MOD-04-DIGITAL no encontrado. Ejecutar primero 01-modules.sql'; + RAISE EXCEPTION 'Módulo MOD-04-DIGITAL no encontrado. Ejecutar 01-modules.sql primero'; END IF; -- ======================================================================== -- EXERCISE 4.1: VERIFICADOR DE FAKE NEWS -- Referencia: DocumentoDeDiseño v6.4 líneas 778-818 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, max_attempts, + hints, xp_reward, ml_coins_reward, + is_active ) VALUES ( mod_id, 'Verificador de Fake News', - 'Identifica Noticias Falsas sobre Marie Curie', - 'Identifica noticias falsas sobre Marie Curie usando herramientas de verificación digital.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Identificar noticias falsas sobre Marie Curie usando herramientas de verificación digital. Desarrollar habilidades para detectar elementos sospechosos en titulares sensacionalistas, fechas imposibles, citas sin fuente e imágenes manipuladas.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 778-818). El estudiante aprenderá a identificar red flags comunes en noticias falsas: titulares sensacionalistas, anacronismos históricos, gramática deficiente y uso de imágenes fuera de contexto. Implementación pendiente.', - 'verificador_fake_news', - 1, - '{}'::jsonb, + 'Distingue Hechos de Ficción', + 'Analiza artículos sobre Marie Curie publicados en internet. Identifica afirmaciones falsas y verifica información con fuentes confiables', + 'Lee cada artículo. Selecciona las afirmaciones que te parecen sospechosas. Usa las herramientas de verificación para comprobar los hechos.', + 'verificador_fake_news', 1, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "factCheckTools": true, + "sourceVerification": true, + "claimExtraction": true, + "confidenceScoring": true }'::jsonb, - '{}'::jsonb, - 'intermediate', - 100, - 70, - 20, - 30, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "articles": [ + { + "id": "art1", + "title": "Marie Curie: La científica que ganó 3 Premios Nobel", + "source": "Blog de ciencia popular", + "claims": [ + { + "text": "Marie Curie ganó 3 Premios Nobel", + "verdict": "false", + "truth": "Ganó 2 Premios Nobel (Física 1903, Química 1911)", + "sources": ["Nobel Prize official website", "Biografías académicas"] + }, + { + "text": "Descubrió el radio y el polonio", + "verdict": "true", + "sources": ["Publicaciones científicas de 1898"] + }, + { + "text": "Fue la primera mujer en enseñar en la Sorbona", + "verdict": "true", + "sources": ["Registros de la Universidad de París"] + } + ] + } + ], + "verificationTools": [ + "Wikipedia (verificar consenso científico)", + "Sitio oficial Premio Nobel", + "Google Scholar (publicaciones académicas)", + "Snopes (verificador de hechos)" + ] }'::jsonb, - 100, - 20, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{"claimsVerified": 3, "accuracyRate": 0.9}'::jsonb, + 'intermediate', 100, 70, + 20, 3, + ARRAY[ + 'Verifica cifras específicas con fuentes oficiales', + 'Las afirmaciones extraordinarias requieren evidencia extraordinaria', + 'Compara múltiples fuentes confiables' + ], + 100, 20, + true ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET title = EXCLUDED.title, + subtitle = EXCLUDED.subtitle, + description = EXCLUDED.description, + instructions = EXCLUDED.instructions, + config = EXCLUDED.config, + content = EXCLUDED.content, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, is_active = EXCLUDED.is_active, updated_at = gamilit.now_mexico(); -- ======================================================================== - -- EXERCISE 4.2: CREACIÓN DE INFOGRAFÍA INTERACTIVA + -- EXERCISE 4.2: INFOGRAFÍA INTERACTIVA -- Referencia: DocumentoDeDiseño v6.4 líneas 820-867 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, max_attempts, + hints, xp_reward, ml_coins_reward, + is_active ) VALUES ( mod_id, - 'Creación de Infografía Interactiva', - 'Diseña una infografía digital sobre Marie Curie', - 'Diseña una infografía digital sobre los logros de Marie Curie usando herramientas interactivas.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Diseñar una infografía digital sobre los logros de Marie Curie. Organizar información en una estructura visual clara con título principal, 5 datos clave, línea de tiempo, 2 gráficos/estadísticas y 3 imágenes. Aplicar principios de diseño: jerarquía visual, paleta de colores consistente y fuentes legibles.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 820-867). El estudiante aprenderá principios de diseño de información: menos es más, jerarquía visual clara, uso de colores consistente. Incluye elementos interactivos como tooltips, enlaces a sitios oficiales y animaciones simples. Implementación pendiente.', - 'infografia_interactiva', - 2, - '{}'::jsonb, + 'Infografía Interactiva: Descubrimientos de Marie Curie', + 'Extrae Información Visual', + 'Explora una infografía interactiva sobre los descubrimientos de Marie Curie. Responde preguntas basándote en la información visual', + 'Haz clic en las diferentes secciones de la infografía. Examina gráficos, iconos y datos. Responde las preguntas de comprensión.', + 'infografia_interactiva', 2, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "interactiveElements": true, + "dataVisualization": true, + "clickableRegions": true }'::jsonb, - '{}'::jsonb, - 'intermediate', - 100, - 70, - 30, - 45, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "infographic": { + "title": "Marie Curie: 150 Años de Legado Científico", + "sections": [ + { + "id": "timeline", + "type": "visual timeline", + "data": "1867-1934: Principales hitos de su vida" + }, + { + "id": "discoveries", + "type": "icon grid", + "data": "Radio, Polonio, Radioactividad" + }, + { + "id": "impact", + "type": "flowchart", + "data": "Sus descubrimientos → Medicina nuclear → Tratamientos de cáncer" + } + ], + "questions": [ + { + "q": "¿Cuántos años vivió Marie Curie?", + "location": "timeline", + "answer": "67 años" + }, + { + "q": "¿Qué aplicación médica surgió de sus descubrimientos?", + "location": "impact", + "answer": "Tratamientos de cáncer / Radioterapia" + } + ] + } }'::jsonb, - 100, - 20, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{"questionsAnswered": 2, "sectionsExplored": 3}'::jsonb, + 'intermediate', 100, 70, + 15, 3, + ARRAY[ + 'Explora cada sección de la infografía antes de responder', + 'Los íconos y colores tienen significado', + 'Lee las leyendas y etiquetas cuidadosamente' + ], + 100, 20, + true ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET title = EXCLUDED.title, + subtitle = EXCLUDED.subtitle, + description = EXCLUDED.description, + instructions = EXCLUDED.instructions, + config = EXCLUDED.config, + content = EXCLUDED.content, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, is_active = EXCLUDED.is_active, updated_at = gamilit.now_mexico(); -- ======================================================================== -- EXERCISE 4.3: QUIZ ESTILO TIKTOK -- Referencia: DocumentoDeDiseño v6.4 líneas 869-892 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, max_attempts, + hints, xp_reward, ml_coins_reward, + is_active ) VALUES ( mod_id, - 'Quiz Estilo TikTok', - 'Preguntas Rápidas en Formato Vertical', - 'Responde 10 preguntas rápidas en formato vertical en 60 segundos.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Responder 10 preguntas rápidas sobre Marie Curie en formato vertical en 60 segundos (~6 segundos por pregunta). Desarrollar habilidad de lectura rápida y retención de información en formatos de contenido breve propios de redes sociales.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 869-892). Formato inspirado en TikTok: preguntas en formato vertical con efectos visuales, cuenta regresiva y transiciones rápidas. No se puede retroceder a preguntas anteriores. Desarrolla competencias de lectura en formatos de contenido breve típicos de redes sociales. Implementación pendiente.', - 'quiz_tiktok', - 3, - '{}'::jsonb, + 'Quiz TikTok: Datos Rápidos de Marie Curie', + 'Responde en 10 Segundos', + 'Preguntas rápidas estilo TikTok sobre Marie Curie. Tienes 10 segundos por pregunta. ¡Piensa rápido!', + 'Lee la pregunta y las opciones. Tienes 10 segundos para responder. Desliza para la siguiente pregunta.', + 'quiz_tiktok', 3, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "timeLimit": 10, + "swipeInterface": true, + "quickFeedback": true, + "sharable": true }'::jsonb, - '{}'::jsonb, - 'intermediate', - 100, - 70, - 10, - 15, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "questions": [ + { + "id": "q1", + "text": "¿En qué ciudad nació Marie Curie?", + "options": ["París", "Varsovia", "Berlín", "Londres"], + "correct": 1, + "timeLimit": 10, + "visual": "Map of Europe" + }, + { + "id": "q2", + "text": "¿Cuántos Premios Nobel ganó Marie Curie?", + "options": ["1", "2", "3", "4"], + "correct": 1, + "timeLimit": 10, + "visual": "Nobel medal icons" + }, + { + "id": "q3", + "text": "¿Qué elemento químico nombró por su país?", + "options": ["Radio", "Curio", "Polonio", "Francio"], + "correct": 2, + "timeLimit": 10, + "visual": "Periodic table" + } + ] }'::jsonb, - 100, - 20, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{"correctAnswers": [1, 1, 2], "totalQuestions": 3}'::jsonb, + 'elementary', 100, 70, + 5, 5, + ARRAY[ + 'Marie Curie era polaca', + 'Fue la primera persona en ganar dos Nobeles', + 'Polonia se llama "Polska" en polaco' + ], + 100, 20, + true ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET title = EXCLUDED.title, + subtitle = EXCLUDED.subtitle, + description = EXCLUDED.description, + instructions = EXCLUDED.instructions, + config = EXCLUDED.config, + content = EXCLUDED.content, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, is_active = EXCLUDED.is_active, updated_at = gamilit.now_mexico(); -- ======================================================================== -- EXERCISE 4.4: NAVEGACIÓN HIPERTEXTUAL -- Referencia: DocumentoDeDiseño v6.4 líneas 894-917 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") + -- Estructura: nodes[] con id, title, content, links[{targetId, label}] -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, max_attempts, + hints, xp_reward, ml_coins_reward, + is_active ) VALUES ( mod_id, - 'Navegación Hipertextual', - 'Encuentra Tesoros de Información', - 'Encuentra 5 "tesoros" de información navegando entre páginas enlazadas.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Encontrar 5 "tesoros" de información navegando entre páginas enlazadas sobre Marie Curie. Desarrollar habilidades de navegación hipertextual, uso de breadcrumbs (migas de pan) y estrategias de búsqueda eficiente en contenidos digitales interconectados.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 894-917). El estudiante debe encontrar: primer elemento descubierto, fecha de llegada a París, nombre del mentor, término "radioactividad" y dirección del laboratorio. Desarrolla competencias de navegación en entornos hipertextuales propios de la web. Implementación pendiente.', - 'navegacion_hipertextual', - 4, - '{}'::jsonb, + 'Navegación Hipertextual: Explora la Red de Conocimiento', + 'Sigue los Enlaces Relevantes', + 'Navega a través de un artículo web sobre Marie Curie. Sigue los hipervínculos correctos para encontrar información específica', + 'Lee la pregunta de investigación. Navega por el artículo haciendo clic en los enlaces relevantes. Encuentra la información solicitada.', + 'navegacion_hipertextual', 4, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "hyperlinks": true, + "pathTracking": true, + "informationSynthesis": true }'::jsonb, - '{}'::jsonb, - 'intermediate', - 100, - 70, - 20, - 30, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "researchQuestion": "¿Qué experimentos realizó Marie Curie para aislar el radio?", + "nodes": [ + { + "id": "main-article", + "title": "Marie Curie: Pionera de la Radiactividad", + "content": "Marie Curie (1867-1934) fue una científica polaca-francesa que revolucionó nuestra comprensión de la física y la química. Junto con su esposo Pierre, realizó investigaciones pioneras sobre los fenómenos radiactivos, un término que ella misma acuñó.\n\nSus descubrimientos en radiactividad cambiaron para siempre el campo de la física nuclear. Trabajó intensamente durante años en el aislamiento de elementos radiactivos, un proceso que requirió una dedicación extraordinaria.", + "links": [ + { "targetId": "radiactividad", "label": "descubrimientos en radiactividad" }, + { "targetId": "aislamiento", "label": "aislamiento de elementos radiactivos" }, + { "targetId": "premios", "label": "reconocimientos y premios" } + ] + }, + { + "id": "radiactividad", + "title": "Historia de la Radiactividad", + "content": "El término ''radiactividad'' fue acuñado por Marie Curie en 1898. Henri Becquerel había descubierto en 1896 que las sales de uranio emitían rayos que podían impresionar placas fotográficas.\n\nMarie Curie decidió estudiar este fenómeno como tema de su tesis doctoral. Descubrió que la radiactividad era una propiedad atómica, no molecular, lo que fue revolucionario para la física de la época.", + "links": [ + { "targetId": "main-article", "label": "volver al artículo principal" }, + { "targetId": "aislamiento", "label": "proceso de aislamiento" } + ] + }, + { + "id": "aislamiento", + "title": "El Proceso de Aislamiento del Radio", + "content": "El aislamiento del radio fue uno de los logros más impresionantes de Marie Curie. Trabajando en condiciones precarias en un cobertizo sin calefacción, procesó toneladas de pechblenda para obtener pequeñas cantidades de radio puro.\n\nEl proceso requirió:\n• Trituración de toneladas de mineral de pechblenda\n• Disolución en ácidos y precipitación química\n• Cristalización fraccionada repetida durante años\n• Mediciones precisas de radiactividad\n\nEn 1902, logró aislar 0.1 gramos de cloruro de radio puro.", + "links": [ + { "targetId": "main-article", "label": "volver al artículo principal" }, + { "targetId": "experimentos", "label": "experimentos específicos" }, + { "targetId": "radiactividad", "label": "qué es la radiactividad" } + ] + }, + { + "id": "experimentos", + "title": "Experimentos de Marie Curie con el Radio", + "content": "Los experimentos de Marie Curie para aislar el radio fueron meticulosos y agotadores:\n\n1. **Análisis de la pechblenda**: Descubrió que era más radiactiva de lo esperado, lo que sugería la presencia de elementos desconocidos.\n\n2. **Separación química**: Usó técnicas de precipitación selectiva para separar diferentes fracciones del mineral.\n\n3. **Cristalización fraccionada**: El proceso más largo. Disolvía cloruros y los cristalizaba repetidamente, separando el radio del bario por sus diferentes solubilidades.\n\n4. **Medición con electrómetro**: Usó un electrómetro piezoeléctrico diseñado por Pierre para medir la radiactividad y seguir el rastro del radio.\n\n¡Este es el objetivo de tu investigación! Has encontrado la información sobre los experimentos.", + "links": [ + { "targetId": "aislamiento", "label": "volver a aislamiento" }, + { "targetId": "premios", "label": "premios recibidos" } + ] + }, + { + "id": "premios", + "title": "Premios y Reconocimientos", + "content": "Marie Curie recibió numerosos reconocimientos por su trabajo:\n\n• **Premio Nobel de Física (1903)**: Compartido con Pierre Curie y Henri Becquerel por sus investigaciones sobre radiación.\n\n• **Premio Nobel de Química (1911)**: Por el descubrimiento del radio y polonio, y por el aislamiento del radio.\n\nFue la primera persona en ganar dos Premios Nobel en diferentes ciencias.", + "links": [ + { "targetId": "main-article", "label": "volver al artículo principal" }, + { "targetId": "aislamiento", "label": "proceso de aislamiento" } + ] + } + ], + "startNodeId": "main-article", + "targetNodeId": "experimentos", + "optimalPath": ["main-article", "aislamiento", "experimentos"] }'::jsonb, - 100, - 20, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{"informationFound": true, "pathEfficiency": 0.8, "relevantLinks": 3}'::jsonb, + 'intermediate', 100, 70, + 15, 3, + ARRAY[ + 'Lee la pregunta antes de empezar a navegar', + 'No todos los enlaces son igualmente relevantes', + 'Sintetiza información de múltiples páginas' + ], + 100, 20, + true ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET title = EXCLUDED.title, + subtitle = EXCLUDED.subtitle, + description = EXCLUDED.description, + instructions = EXCLUDED.instructions, + config = EXCLUDED.config, + content = EXCLUDED.content, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, is_active = EXCLUDED.is_active, updated_at = gamilit.now_mexico(); -- ======================================================================== -- EXERCISE 4.5: ANÁLISIS DE MEMES EDUCATIVOS -- Referencia: DocumentoDeDiseño v6.4 líneas 919-947 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, max_attempts, + hints, xp_reward, ml_coins_reward, + is_active ) VALUES ( mod_id, - 'Análisis de Memes Educativos', - 'Evalúa Memes sobre Marie Curie', - 'Evalúa la precisión y valor educativo de memes sobre Marie Curie o radioactividad.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Evaluar la precisión histórica y valor educativo de memes sobre Marie Curie o radioactividad. Identificar mensaje principal, humor e información implícita. Calificar en precisión histórica, valor educativo y creatividad. Crear un meme propio corrigiendo errores comunes.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 919-947). El estudiante aprenderá que el humor no justifica información falsa y desarrollará pensamiento crítico sobre contenido viral. Evaluación en 3 dimensiones: precisión histórica, valor educativo y creatividad. Implementación pendiente.', - 'analisis_memes', - 5, - '{}'::jsonb, + 'Análisis de Memes: Comprensión Visual-Textual', + 'Decodifica el Mensaje del Meme', + 'Analiza memes sobre Marie Curie. Identifica el mensaje, referencias culturales y humor implícito', + 'Observa cada meme cuidadosamente. Identifica: el formato utilizado, el mensaje principal, referencias culturales y por qué es gracioso.', + 'analisis_memes', 5, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "visualAnalysis": true, + "culturalReferences": true, + "humorDecoding": true }'::jsonb, - '{}'::jsonb, - 'intermediate', - 100, - 70, - 15, - 25, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "memes": [ + { + "id": "meme1", + "imageUrl": "/memes/marie-curie-glowing.jpg", + "format": "Drake Hotline Bling", + "topText": "Protección contra radiación", + "bottomText": "Seguir experimentando sin protección", + "analysis": { + "mainMessage": "Marie Curie no usaba protección contra radiación", + "humorType": "Ironía histórica", + "culturalReference": "Formato de meme popular Drake", + "historicalAccuracy": "Alta - realmente no usaban protección adecuada", + "implication": "Contraste entre conocimiento actual y pasado" + } + } + ], + "questions": [ + "¿Cuál es el mensaje principal del meme?", + "¿Qué formato de meme se utiliza?", + "¿Es históricamente exacto?", + "¿Por qué es gracioso/irónico?" + ] }'::jsonb, - 100, - 20, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{"messagesIdentified": 1, "referencesRecognized": 1, "accuracyEvaluated": true}'::jsonb, + 'intermediate', 100, 70, + 12, 3, + ARRAY[ + 'Los memes combinan imagen y texto para crear significado', + 'El humor a menudo viene de la ironía o el contraste', + 'Conocer el contexto histórico ayuda a entender el meme' + ], + 100, 20, + true ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET title = EXCLUDED.title, + subtitle = EXCLUDED.subtitle, + description = EXCLUDED.description, + instructions = EXCLUDED.instructions, + config = EXCLUDED.config, + content = EXCLUDED.content, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, is_active = EXCLUDED.is_active, updated_at = gamilit.now_mexico(); - RAISE NOTICE '✓ 5 ejercicios del Módulo 4 insertados (INACTIVOS - muestran "En Construcción")'; + RAISE NOTICE '✓ 5 ejercicios del Módulo 4 insertados (según DocumentoDeDiseño v6.4)'; END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/educational_content/06-exercises-module5.sql b/projects/gamilit/apps/database/seeds/prod/educational_content/06-exercises-module5.sql index 83f894d..68c65ef 100644 --- a/projects/gamilit/apps/database/seeds/prod/educational_content/06-exercises-module5.sql +++ b/projects/gamilit/apps/database/seeds/prod/educational_content/06-exercises-module5.sql @@ -1,18 +1,17 @@ -- ===================================================== --- Seed Data: Exercises Module 5 - Producción y Expresión Lectora (PRODUCTION) +-- Seed Data: Exercises Module 5 - Producción Creativa (PRODUCTION) -- ===================================================== --- Description: 3 opciones de ejercicios inactivos del Módulo 5 (visibles pero muestran "En Construcción") +-- Description: 3 ejercicios creativos del Módulo 5 COMPLETOS -- Module: MOD-05-PRODUCCION --- Exercises: Diario Interactivo, Cómic Digital, Cápsula del Tiempo --- Reference: DocumentoDeDiseño_Mecanicas_GAMILIT_v6_1.md líneas 950-1097 --- Date: 2025-11-23 --- Status: PRODUCTION (INACTIVOS - is_active = false) --- Note: El estudiante debe elegir SOLO UNO de los 3 ejercicios disponibles +-- Exercises: Diario Multimedia, Cómic Digital, Video-Carta +-- Created by: SA-SEEDS-EDUCATIONAL +-- Date: 2025-11-11 +-- Status: PRODUCTION +-- Updated: 2025-12-15 (Sincronizado con DEV + requires_manual_grading) -- ===================================================== SET search_path TO educational_content, public; --- Obtener module_id dinámicamente DO $$ DECLARE mod_id UUID; @@ -24,236 +23,602 @@ BEGIN END IF; -- ======================================================================== - -- EXERCISE 5.1 (OPCIÓN A): DIARIO INTERACTIVO DE MARIE - -- Referencia: DocumentoDeDiseño v6.4 líneas 958-991 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") + -- EXERCISE 5.1: DIARIO MULTIMEDIA DE MARIE CURIE + -- Requiere evaluación manual por docente -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, time_limit_minutes, max_attempts, + hints, enable_hints, hint_cost_ml_coins, + comodines_allowed, comodines_config, + xp_reward, ml_coins_reward, + is_active, version, + requires_manual_grading ) VALUES ( mod_id, 'Diario Interactivo de Marie', - 'Escribe desde la perspectiva de Marie Curie', - 'Escribe 5 entradas de diario desde la perspectiva de Marie Curie en momentos clave de su vida.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Escribir 5 entradas de diario desde la perspectiva de Marie Curie en momentos clave de su vida. Cada entrada debe incluir: fecha específica, saludo personal, descripción del evento, sentimientos y reflexiones, esperanzas o temores, y despedida. Usar lenguaje acorde a finales del siglo XIX / principios del XX. Mínimo 150 palabras por entrada.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 958-991). El estudiante debe escribir sobre 5 momentos sugeridos: llegada a París (1891), descubrimiento del Radio (1898), primer Nobel (1903), muerte de Pierre (1906), segundo Nobel (1911). Desarrolla habilidades de escritura creativa, empatía histórica y comprensión profunda del contexto biográfico. Implementación pendiente.', - 'diario_multimedia', - 1, - '{}'::jsonb, + 'Imagina su Vida Cotidiana en 1898', + 'Crea un diario multimedia desde la perspectiva de Marie Curie durante el descubrimiento del radio. Incluye entradas de texto, reflexiones, y elementos multimedia que capturen sus emociones, desafíos y triunfos.', + 'Escribe al menos 3 entradas de diario desde la perspectiva de Marie Curie. Cada entrada debe incluir: fecha histórica, contexto del día, estado emocional, reflexión personal, y opcionalmente elementos multimedia (imagen, audio, o boceto). Usa tu creatividad pero mantén precisión histórica.', + 'diario_multimedia', 1, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "allowMultimedia": true, + "minEntries": 3, + "maxEntries": 5, + "formats": ["text", "image", "audio", "video"], + "minWordsPerEntry": 150, + "maxWordsPerEntry": 400, + "requireDates": true, + "historicalAccuracyRequired": true, + "multimediaOptional": true, + "layouts": ["simple", "journal", "notebook", "letter"], + "fonts": ["handwriting", "typewriter", "modern"], + "templates": [ + { + "id": "template_classic", + "name": "Diario Clásico", + "style": "vintage", + "features": ["date_header", "mood_icon", "weather", "location"] + }, + { + "id": "template_scientific", + "name": "Cuaderno Científico", + "style": "lab_notebook", + "features": ["date_header", "observations", "calculations", "sketches"] + }, + { + "id": "template_letter", + "name": "Carta Personal", + "style": "letter", + "features": ["recipient", "signature", "postscript"] + } + ], + "autoSave": true, + "saveInterval": 30, + "characterLimit": 2000 }'::jsonb, - '{}'::jsonb, - 'advanced', - 500, - 70, - 60, - 90, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "prompts": [ + { + "id": "entry1", + "date": "1898-12-15", + "title": "El Día del Descubrimiento", + "context": "Marie y Pierre acaban de aislar el radio por primera vez. El mineral brilla en la oscuridad del laboratorio.", + "mood": "excitement", + "weather": "Frío invernal en París", + "location": "Laboratorio en Rue Lhomond", + "guidingQuestions": [ + "¿Cómo te sentiste al ver el radio brillar por primera vez?", + "¿Qué significó este momento para tu investigación?", + "¿Cuáles fueron las dificultades para llegar aquí?", + "¿Qué esperanzas tienes para este descubrimiento?" + ], + "historicalContext": "Después de 4 años de procesar toneladas de pechblenda, Marie y Pierre finalmente aislaron 0.1 gramos de radio puro. El elemento brillaba con luz azul-verde en la oscuridad.", + "suggestedElements": ["emoción", "perseverancia", "colaboración", "visión científica"] + }, + { + "id": "entry2", + "date": "1898-12-20", + "title": "Reflexiones sobre Dificultades", + "context": "Cinco días después del descubrimiento. Marie reflexiona sobre los años de trabajo en condiciones precarias.", + "mood": "determination", + "weather": "Nieve ligera", + "location": "Apartamento en París", + "guidingQuestions": [ + "¿Qué obstáculos enfrentaste en estos 4 años?", + "¿Hubo momentos donde quisiste rendirte?", + "¿Cómo el apoyo de Pierre fue crucial?", + "¿Qué sacrificios personales hiciste por la ciencia?" + ], + "historicalContext": "Marie trabajó en un hangar abandonado sin calefacción, procesando manualmente toneladas de mineral. Vivían en pobreza, priorizando investigación sobre comodidad.", + "suggestedElements": ["sacrificio", "pareja", "pobreza", "dedicación"] + }, + { + "id": "entry3", + "date": "1898-12-26", + "title": "Sueños para el Futuro", + "context": "Navidad 1898. Marie imagina cómo el radio podría cambiar la medicina y la ciencia.", + "mood": "hope", + "weather": "Noche clara y fría", + "location": "Junto a la ventana del apartamento", + "guidingQuestions": [ + "¿Cómo podría el radio ayudar a la humanidad?", + "¿Qué otros descubrimientos te gustaría hacer?", + "¿Qué significa ser una mujer científica en 1898?", + "¿Qué le dirías a las futuras generaciones de científicas?" + ], + "historicalContext": "Marie ya visualizaba aplicaciones médicas del radio (radioterapia). También reflexionaba sobre su rol como mujer en ciencia, un campo dominado por hombres.", + "suggestedElements": ["esperanza", "medicina", "igualdad", "legado"] + }, + { + "id": "entry4", + "date": "1899-01-05", + "title": "Primera Aplicación Médica", + "context": "Un médico visitó el laboratorio interesado en usar radio para tratar tumores.", + "mood": "anticipation", + "weather": "Helada matinal", + "location": "Laboratorio", + "guidingQuestions": [ + "¿Cómo te sientes al saber que tu descubrimiento salvará vidas?", + "¿Qué responsabilidades sientes ahora?", + "¿Preocupa el uso potencialmente peligroso de la radiación?" + ], + "historicalContext": "En 1899, los primeros médicos comenzaron a experimentar con radio para tratar cáncer de piel, iniciando la era de la radioterapia.", + "suggestedElements": ["medicina", "responsabilidad", "peligro", "esperanza"] + }, + { + "id": "entry5", + "date": "1899-02-14", + "title": "Amor y Ciencia", + "context": "Día de San Valentín. Marie reflexiona sobre su relación con Pierre y cómo la ciencia los une.", + "mood": "love", + "weather": "Primeros signos de primavera", + "location": "Caminata por el Sena", + "guidingQuestions": [ + "¿Cómo es trabajar con tu pareja en ciencia?", + "¿Qué admiras más de Pierre?", + "¿Cómo balanceas vida personal y científica?", + "¿Qué significa el amor en tu vida?" + ], + "historicalContext": "Marie y Pierre tenían una relación única: socios científicos y románticos. Su luna de miel fue un viaje en bicicleta donde discutían física.", + "suggestedElements": ["amor", "pareja", "colaboración", "balance"] + } + ], + "rubricDetails": { + "creativity": { + "weight": 30, + "criteria": [ + "Originalidad en la expresión", + "Uso creativo de metáforas y lenguaje", + "Perspectiva única y personal", + "Elementos visuales o multimedia creativos" + ] + }, + "historicalAccuracy": { + "weight": 30, + "criteria": [ + "Fechas y eventos históricos correctos", + "Contexto científico preciso", + "Detalles biográficos auténticos", + "Vocabulario y tono de época apropiado" + ] + }, + "multimedia": { + "weight": 20, + "criteria": [ + "Uso efectivo de imágenes/bocetos", + "Integración coherente de elementos multimedia", + "Calidad de presentación visual", + "Relevancia de elementos multimedia al contenido" + ] + }, + "expression": { + "weight": 20, + "criteria": [ + "Claridad y coherencia narrativa", + "Profundidad emocional", + "Voz auténtica del personaje", + "Gramática y ortografía correcta" + ] + } + }, + "exampleEntry": { + "date": "1898-12-15", + "title": "¡El Radio Brilla!", + "content": "Querido diario,\\n\\nHoy, 15 de diciembre de 1898, es un día que nunca olvidaré. Después de cuatro años de trabajo incansable, Pierre y yo finalmente lo logramos. En la oscuridad de nuestro laboratorio, el radio brilló con una luz azul-verde etérea que parecía mágica.\\n\\nMis manos están agrietadas por el frío y el trabajo con ácidos. He procesado toneladas de pechblenda en ese hangar helado. Hubo días donde dudé, donde el cansancio era insoportable. Pero hoy, todo cobra sentido.\\n\\nEl radio pesa apenas 0.1 gramos, pero representa años de fe, perseverancia y amor por la ciencia. Pierre me abrazó cuando vimos la luminiscencia. No dijimos nada; no hacían falta palabras.\\n\\nEste descubrimiento abrirá nuevas puertas en física y, espero, en medicina. Imagino un futuro donde la radiactividad ayude a curar enfermedades. Pero por ahora, simplemente contemplo este pequeño milagro brillante.\\n\\nCon emoción y gratitud,\\nMarie", + "mood": "excitement", + "multimedia": null, + "wordCount": 156 + }, + "assessmentGuidelines": "El diario será evaluado por: (1) Creatividad en expresión y perspectiva (30%), (2) Precisión histórica y científica (30%), (3) Uso efectivo de multimedia cuando aplique (20%), (4) Claridad, profundidad emocional y voz auténtica (20%). Se valorará especialmente la capacidad de ponerse en los zapatos de Marie Curie y transmitir sus emociones, desafíos y visión científica de manera auténtica y conmovedora." }'::jsonb, - 500, - 50, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{ + "rubric": { + "creativity": 30, + "historicalAccuracy": 30, + "multimedia": 20, + "expression": 20 + }, + "sampleEvaluation": { + "excellentEntry": { + "score": 95, + "feedback": "Excelente trabajo. Tu diario captura magistralmente la voz de Marie Curie, combinando precisión histórica con profundidad emocional. Los detalles sobre el laboratorio frío, las manos agrietadas, y la luminiscencia del radio demuestran investigación cuidadosa. Tu expresión es auténtica y conmovedora." + }, + "goodEntry": { + "score": 80, + "feedback": "Buen trabajo. Tu diario muestra comprensión del contexto histórico y captura aspectos emocionales de Marie. Para mejorar, incluye más detalles específicos sobre el proceso científico y profundiza en las reflexiones personales." + }, + "averageEntry": { + "score": 70, + "feedback": "Trabajo adecuado. Has cumplido con los requisitos básicos, pero la entrada se siente genérica. Investiga más sobre la vida de Marie y usa detalles específicos para hacer tu diario más auténtico y personal." + } + } + }'::jsonb, + 'intermediate', 100, 70, + 40, 60, 3, + ARRAY[ + 'Investiga sobre la vida diaria de Marie en 1898: dónde vivía, cómo era su laboratorio', + 'Lee cartas reales de Marie Curie para capturar su voz y tono', + 'Piensa en las emociones: frustración del trabajo tedioso, emoción del descubrimiento', + 'Incluye detalles sensoriales: frío del laboratorio, brillo del radio, olor de químicos', + 'No olvides el contexto histórico: ser mujer científica en 1898 era revolucionario' + ], + true, 5, + ARRAY['pistas', 'vision_lectora']::gamification_system.comodin_type[], + '{ + "pistas": {"cost": 15, "enabled": true, "description": "Revela contexto histórico adicional"}, + "vision_lectora": {"cost": 25, "enabled": true, "description": "Muestra ejemplo de entrada de diario"} + }'::jsonb, + 500, 100, + true, 1, + true -- requires_manual_grading ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET - title = EXCLUDED.title, - is_active = EXCLUDED.is_active, + content = EXCLUDED.content, + config = EXCLUDED.config, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, + requires_manual_grading = EXCLUDED.requires_manual_grading, updated_at = gamilit.now_mexico(); -- ======================================================================== - -- EXERCISE 5.2 (OPCIÓN B): CÓMIC DIGITAL DE MARIE CURIE - -- Referencia: DocumentoDeDiseño v6.4 líneas 993-1036 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") + -- EXERCISE 5.2: CÓMIC DIGITAL - EL DESCUBRIMIENTO DEL RADIO + -- Requiere evaluación manual por docente -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, time_limit_minutes, max_attempts, + hints, enable_hints, hint_cost_ml_coins, + comodines_allowed, comodines_config, + xp_reward, ml_coins_reward, + is_active, version, + requires_manual_grading ) VALUES ( mod_id, - 'Cómic Digital de Marie Curie', - 'Crea un cómic de 6 viñetas', - 'Crea un cómic de 6 viñetas resumiendo la vida de Marie Curie con dibujos y diálogos.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Crear un cómic de 6 viñetas resumiendo la vida de Marie Curie. Estructura sugerida: (1) Infancia en Polonia, (2) Viaje a París, (3) Encuentro con Pierre, (4) Descubrimientos, (5) Premios Nobel, (6) Legado. Cada viñeta debe incluir dibujo o imagen, globos de diálogo, recuadros de narrador y onomatopeyas si procede. Mantener coherencia visual y narrativa.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 993-1036). El estudiante aprenderá principios de narrativa visual: uso de diferentes planos (general, medio, primer plano), colores para representar emociones, balance texto-imagen. Desarrolla competencias de síntesis, expresión visual y secuenciación narrativa. Implementación pendiente.', - 'comic_digital', - 2, - '{}'::jsonb, + 'Resumen Visual Progresivo (Cómic Digital)', + 'Narrativa Visual Científica', + 'Crea un cómic digital de 4-6 viñetas narrando el descubrimiento del radio por Marie Curie. Usa narrativa visual para contar esta historia científica de manera atractiva y educativa.', + 'Diseña un cómic de 4-6 viñetas (panels) que cuente la historia del descubrimiento del radio. Cada viñeta debe incluir: ilustración/boceto, diálogo de personajes, narración contextual, y elementos visuales que refuercen la historia. Usa la herramienta de creación de cómics o dibuja manualmente y sube imágenes.', + 'comic_digital', 2, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "minPanels": 4, + "maxPanels": 6, + "requireDialogue": true, + "requireNarration": true, + "requireCaption": true, + "allowSketches": true, + "allowDigitalDrawing": true, + "allowPhotoComposition": true, + "panelLayouts": [ + {"id": "classic_4", "name": "4 Viñetas Clásicas", "grid": "2x2"}, + {"id": "vertical_strip", "name": "Tira Vertical", "grid": "1xN"}, + {"id": "horizontal_strip", "name": "Tira Horizontal", "grid": "Nx1"}, + {"id": "dynamic", "name": "Layout Dinámico", "grid": "variable"} + ], + "visualStyles": [ + {"id": "realistic", "name": "Realista"}, + {"id": "cartoon", "name": "Caricatura"}, + {"id": "manga", "name": "Manga/Anime"}, + {"id": "sketch", "name": "Boceto"}, + {"id": "minimalist", "name": "Minimalista"} + ], + "colorOptions": ["black_white", "grayscale", "full_color", "sepia", "two_tone"], + "speechBubbleTypes": ["round", "square", "thought", "shout", "whisper", "narration"], + "drawingTools": { + "pencil": true, + "pen": true, + "brush": true, + "eraser": true, + "colorPicker": true, + "shapes": ["circle", "rectangle", "line", "arrow"], + "textTool": true, + "layers": true, + "undo_redo": true + }, + "characters": [ + {"name": "Marie Curie", "description": "Mujer de ~31 años, cabello oscuro recogido, bata de laboratorio"}, + {"name": "Pierre Curie", "description": "Hombre de ~39 años, barba, bata de laboratorio"}, + {"name": "Narrador", "description": "Voz omnisciente que provee contexto"} + ], + "settings": [ + {"name": "Laboratorio", "description": "Hangar frío, mesas con equipo, pechblenda"}, + {"name": "Noche oscura", "description": "Laboratorio a oscuras, radio brillando"}, + {"name": "Universidad", "description": "Sorbonne, aulas"}, + {"name": "Hogar Curie", "description": "Apartamento modesto"} + ] }'::jsonb, - '{}'::jsonb, - 'advanced', - 500, - 70, - 90, - 120, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "storyStructure": { + "act1_setup": { + "panels": [1], + "purpose": "Establecer el contexto y el desafío", + "description": "Introducción a Marie y Pierre trabajando en el laboratorio con pechblenda" + }, + "act2_rising_action": { + "panels": [2, 3], + "purpose": "Mostrar el proceso y las dificultades", + "description": "El trabajo tedioso de procesar toneladas de mineral, años de esfuerzo" + }, + "act3_climax": { + "panels": [4], + "purpose": "El momento del descubrimiento", + "description": "El radio brilla en la oscuridad - momento culminante" + }, + "act4_resolution": { + "panels": [5, 6], + "purpose": "Impacto y legado (opcional)", + "description": "Aplicaciones médicas y el legado de Marie" + } + }, + "storyBeats": [ + { + "panel": 1, + "title": "El Laboratorio Humilde", + "scene": "Marie y Pierre en laboratorio con pechblenda", + "visualDescription": "Laboratorio frío y destartalado. Marie y Pierre con batas manchadas examinando mineral oscuro.", + "suggestedDialogue": { + "Marie": "Este mineral contiene algo extraordinario, Pierre.", + "Pierre": "Entonces debemos aislarlo, por muy difícil que sea." + }, + "narration": "1898. Marie Curie y su esposo Pierre investigan un mineral llamado pechblenda.", + "mood": "determination" + }, + { + "panel": 2, + "title": "La Anomalía", + "scene": "Marie descubre anomalía en mediciones", + "visualDescription": "Close-up de Marie mirando electroscopio con expresión de sorpresa.", + "suggestedDialogue": { + "Marie": "¡Mira estos números! La radiación es cuatro veces más intensa.", + "Pierre": "Debe haber un nuevo elemento..." + }, + "narration": "Las mediciones revelan algo inesperado.", + "mood": "excitement" + }, + { + "panel": 3, + "title": "Años de Trabajo", + "scene": "Montaje de Marie trabajando duro", + "visualDescription": "Panel dividido mostrando paso del tiempo.", + "suggestedDialogue": { + "Marie": "No puedo rendirme. El secreto está ahí." + }, + "narration": "Cuatro años. Ocho toneladas de pechblenda. Trabajo manual extenuante.", + "mood": "perseverance" + }, + { + "panel": 4, + "title": "¡Brilla en la Oscuridad!", + "scene": "El radio aislado brilla con luz azul-verde", + "visualDescription": "Laboratorio oscuro. Radio brillando intensamente. Caras iluminadas.", + "suggestedDialogue": { + "Pierre": "Es... hermoso. Como pequeñas luces de hadas.", + "Marie": "Lo logramos, Pierre. Aislamos el radio." + }, + "narration": "15 de diciembre de 1898. Marie y Pierre aislan 0.1 gramos de radio puro.", + "mood": "triumph" + }, + { + "panel": 5, + "title": "Medicina del Futuro", + "scene": "Aplicación de radio en medicina", + "visualDescription": "Marie presentando radio a médicos.", + "suggestedDialogue": { + "Médico": "Con este radio podemos atacar tumores.", + "Marie": "La ciencia debe servir a la humanidad." + }, + "narration": "El descubrimiento revoluciona la medicina.", + "mood": "hope" + }, + { + "panel": 6, + "title": "Legado Inmortal", + "scene": "Marie mayor, su legado perdura", + "visualDescription": "Marie mayor en su oficina del Instituto Curie.", + "suggestedDialogue": { + "Marie": "Pierre, lo que comenzamos juntos cambió el mundo." + }, + "narration": "Marie Curie: primera mujer en ganar Nobel, única en ganar dos.", + "mood": "bittersweet" + } + ], + "rubricDetails": { + "narrative": {"weight": 25, "criteria": ["Historia clara", "Secuencia lógica", "Transiciones efectivas"]}, + "visual": {"weight": 25, "criteria": ["Composición efectiva", "Expresiones claras", "Consistencia visual"]}, + "accuracy": {"weight": 25, "criteria": ["Detalles históricos correctos", "Proceso científico preciso"]}, + "creativity": {"weight": 25, "criteria": ["Originalidad visual", "Técnicas de cómic creativas"]} + } }'::jsonb, - 500, - 50, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{ + "rubric": {"narrative": 25, "visual": 25, "accuracy": 25, "creativity": 25}, + "sampleEvaluation": { + "excellent": {"score": 95, "feedback": "Excelente cómic. Narrativa visual clara y emotiva."}, + "good": {"score": 80, "feedback": "Buen cómic. Historia se sigue claramente."}, + "average": {"score": 70, "feedback": "Cómic adecuado. Cumples requisitos básicos."} + } + }'::jsonb, + 'intermediate', 100, 70, + 50, 75, 3, + ARRAY[ + 'Usa el panel del radio brillando como clímax visual', + 'Las expresiones faciales comunican más que el diálogo', + 'Piensa en el flujo visual: el ojo debe moverse naturalmente', + 'Menos texto, más visual: cuenta la historia con imágenes' + ], + true, 5, + ARRAY['pistas', 'vision_lectora']::gamification_system.comodin_type[], + '{ + "pistas": {"cost": 15, "enabled": true, "description": "Revela técnicas visuales"}, + "vision_lectora": {"cost": 25, "enabled": true, "description": "Muestra ejemplo de panel"} + }'::jsonb, + 500, 100, + true, 1, + true -- requires_manual_grading ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET - title = EXCLUDED.title, - is_active = EXCLUDED.is_active, + content = EXCLUDED.content, + config = EXCLUDED.config, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, + requires_manual_grading = EXCLUDED.requires_manual_grading, updated_at = gamilit.now_mexico(); -- ======================================================================== - -- EXERCISE 5.3 (OPCIÓN C): CÁPSULA DEL TIEMPO DIGITAL - -- Referencia: DocumentoDeDiseño v6.4 líneas 1038-1097 - -- Estado: INACTIVO (visible en UI pero muestra página "En Construcción") + -- EXERCISE 5.3: VIDEO-CARTA - MENSAJE DE MARIE AL FUTURO + -- Requiere evaluación manual por docente -- ======================================================================== INSERT INTO educational_content.exercises ( - module_id, - title, - subtitle, - description, - instructions, - objective, - how_to_solve, - recommended_strategy, - pedagogical_notes, - exercise_type, - order_index, - config, - content, - solution, - difficulty_level, - max_points, - passing_score, - estimated_time_minutes, - time_limit_minutes, - max_attempts, - hints, - enable_hints, - hint_cost_ml_coins, - comodines_allowed, - comodines_config, - xp_reward, - ml_coins_reward, - is_active, - version + module_id, title, subtitle, description, instructions, + exercise_type, order_index, + config, content, solution, + difficulty_level, max_points, passing_score, + estimated_time_minutes, time_limit_minutes, max_attempts, + hints, enable_hints, hint_cost_ml_coins, + comodines_allowed, comodines_config, + xp_reward, ml_coins_reward, + is_active, version, + requires_manual_grading ) VALUES ( mod_id, 'Cápsula del Tiempo Digital', - 'Video mensaje de Marie al futuro', - 'Crea un video de 2-3 minutos como si Marie Curie dejara un mensaje para el futuro.', - 'Este ejercicio estará disponible próximamente. Actualmente se encuentra en desarrollo.', - 'Crear un video de 2-3 minutos como si Marie Curie dejara un mensaje para el futuro. Estructura del guion: (1) Introducción (30 seg): saludo y presentación personal con contexto, (2) Mensaje Principal (90 seg): logros, desafíos y reflexiones sobre la ciencia, (3) Reflexiones y Advertencias (45 seg): peligros de la radiación y ética científica, (4) Cierre (15 seg): esperanzas para el futuro y despedida. Grabar frente a la cámara o usando avatar con caracterización de época.', - 'Este ejercicio estará disponible próximamente.', - 'Este ejercicio estará disponible próximamente.', - 'Ejercicio planificado según DocumentoDeDiseño v6.4 (líneas 1038-1097). El estudiante debe hablar en primera persona con tono de época, mantener contacto visual con la cámara, hablar pausado y claro, usar gestos apropiados y transmitir emoción. Elementos de producción opcionales: vestuario de época, fondo de laboratorio, props científicos, efectos visuales. Desarrolla habilidades de expresión oral, caracterización y producción multimedia. Implementación pendiente.', - 'video_carta', - 3, - '{}'::jsonb, + 'Comunicación a Través del Tiempo', + 'Graba un video (o escribe guión detallado) como Marie Curie en 1925 enviando un mensaje inspirador y reflexivo a las generaciones del siglo XXI. Captura su sabiduría, esperanzas y advertencias.', + 'Imagina que eres Marie Curie en 1925, a los 58 años, con dos premios Nobel y décadas de experiencia. Graba un video de 2-5 minutos (o escribe guión de 400-600 palabras) dirigido a los jóvenes del siglo XXI. Habla desde tu perspectiva sobre educación, ciencia, igualdad, responsabilidad, y tu legado. Sé auténtica, inspiradora y reflexiva.', + 'video_carta', 3, '{ - "placeholder": true, - "message": "Este ejercicio está en desarrollo. El contenido interactivo estará disponible próximamente." + "videoRequired": false, + "scriptAlternative": true, + "videoOptional": true, + "minDuration": 120, + "maxDuration": 300, + "minWords": 400, + "maxWords": 600, + "allowedFormats": ["mp4", "webm", "mov", "script"], + "recordingOptions": { + "webcam": true, + "screenRecord": false, + "audioOnly": true, + "scriptOnly": true + }, + "deliveryGuidelines": { + "pace": "moderate (120-150 words per minute)", + "tone": "warm, wise, inspirational", + "eyeContact": "look at camera", + "posture": "seated or standing, confident" + } }'::jsonb, - '{}'::jsonb, - 'advanced', - 500, - 70, - 120, - 180, - 3, - ARRAY['Este ejercicio estará disponible próximamente']::text[], - true, - 15, - ARRAY['pistas', 'vision_lectora', 'segunda_oportunidad']::gamification_system.comodin_type[], '{ - "pistas": {"costo": 15, "penalizacion_xp": 10}, - "vision_lectora": {"costo": 25, "penalizacion_xp": 20}, - "segunda_oportunidad": {"costo": 40, "penalizacion_xp": 30} + "context": { + "year": 1925, + "marieAge": 58, + "location": "Instituto Curie, París", + "achievements": [ + "Primera mujer en ganar Nobel (Física, 1903)", + "Primera persona en ganar dos Nobel (Química, 1911)", + "Fundadora del Instituto Curie", + "Pionera en radioterapia" + ], + "challenges": [ + "Discriminación como mujer en ciencia", + "Pobreza extrema durante investigación", + "Muerte de Pierre en 1906", + "Salud deteriorada por radiación" + ] + }, + "themes": [ + { + "theme": "Educación para Mujeres", + "message": "La educación es liberación. Las mujeres deben tener las mismas oportunidades." + }, + { + "theme": "Ética Científica", + "message": "Los científicos no deben buscar sólo fama. La ciencia debe servir al bien común." + }, + { + "theme": "Perseverancia", + "message": "Los obstáculos son inevitables. La perseverancia fue mi verdadera fortaleza." + }, + { + "theme": "Legado", + "message": "Mi vida fue dedicada a la ciencia. Espero inspirar a otras mujeres científicas." + } + ], + "scriptStructure": { + "introduction": {"duration": "30 seconds", "purpose": "Establecer quién es y por qué habla"}, + "body": {"duration": "3-4 minutes", "purpose": "Desarrollar 2-3 temas principales"}, + "conclusion": {"duration": "30 seconds", "purpose": "Mensaje final inspirador"} + }, + "sampleScript": { + "title": "Mensaje de Marie Curie al Siglo XXI", + "wordCount": 487, + "content": "Buenos días, jóvenes del siglo XXI. Soy Marie Curie, y les hablo desde el año 1925..." + }, + "rubricDetails": { + "authenticity": {"weight": 25, "criteria": ["Voz auténtica de Marie", "Detalles precisos", "Tono apropiado"]}, + "message": {"weight": 25, "criteria": ["Mensaje claro e inspirador", "Temas relevantes"]}, + "presentation": {"weight": 25, "criteria": ["Claridad en entrega", "Estructura lógica"]}, + "emotion": {"weight": 25, "criteria": ["Conexión emocional", "Balance vulnerabilidad/fortaleza"]} + } }'::jsonb, - 500, - 50, - false, -- ← INACTIVO (muestra página "En Construcción") - 1 + '{ + "rubric": {"authenticity": 25, "message": 25, "presentation": 25, "emotion": 25}, + "sampleEvaluation": { + "excellent": {"score": 95, "feedback": "Video-carta excepcional. Voz auténtica de Marie."}, + "good": {"score": 80, "feedback": "Buen trabajo. Mensaje inspirador y auténtico."}, + "average": {"score": 70, "feedback": "Video-carta adecuada. Cumples requisitos básicos."} + } + }'::jsonb, + 'advanced', 100, 70, + 60, 90, 3, + ARRAY[ + 'Lee cartas reales de Marie Curie para capturar su voz', + 'Investiga su biografía: logros, tragedias, valores', + 'Muestra vulnerabilidad además de fortaleza', + 'Practica tu entrega antes de grabar' + ], + true, 5, + ARRAY['pistas', 'vision_lectora']::gamification_system.comodin_type[], + '{ + "pistas": {"cost": 15, "enabled": true, "description": "Revela citas reales de Marie"}, + "vision_lectora": {"cost": 25, "enabled": true, "description": "Muestra guión de ejemplo"} + }'::jsonb, + 500, 100, + true, 1, + true -- requires_manual_grading ) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET - title = EXCLUDED.title, - is_active = EXCLUDED.is_active, + content = EXCLUDED.content, + config = EXCLUDED.config, + solution = EXCLUDED.solution, + hints = EXCLUDED.hints, + requires_manual_grading = EXCLUDED.requires_manual_grading, updated_at = gamilit.now_mexico(); - RAISE NOTICE '✓ 3 opciones de ejercicios del Módulo 5 insertadas (INACTIVAS - muestran "En Construcción")'; - RAISE NOTICE ' Nota: El estudiante debe completar SOLO UNO de los 3 ejercicios disponibles'; + -- ======================================================================== + -- UPDATE MODULE METADATA + -- ======================================================================== + UPDATE educational_content.modules + SET + total_exercises = 3, + metadata = jsonb_set( + jsonb_set( + COALESCE(metadata, '{}'::jsonb), + '{exercises_loaded}', + 'true'::jsonb + ), + '{last_seed_update}', + to_jsonb(gamilit.now_mexico()) + ), + updated_at = gamilit.now_mexico() + WHERE id = mod_id; + + RAISE NOTICE '✅ Módulo 5 (MOD-05-PRODUCCION): 3 ejercicios COMPLETOS cargados exitosamente'; + RAISE NOTICE ' - Diario Multimedia: Templates completos, 5 prompts detallados'; + RAISE NOTICE ' - Cómic Digital: 6 story beats, guías visuales'; + RAISE NOTICE ' - Video-Carta: Guión completo, 4 temas, tips de entrega'; + RAISE NOTICE '✅ Todos los ejercicios configurados con requires_manual_grading = true'; + END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/educational_content/11-module_dependencies.sql b/projects/gamilit/apps/database/seeds/prod/educational_content/11-module_dependencies.sql new file mode 100644 index 0000000..e29de32 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/educational_content/11-module_dependencies.sql @@ -0,0 +1,262 @@ +-- ===================================================== +-- Seed: educational_content.module_dependencies +-- Description: Dependencias entre módulos educativos +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed define las dependencias/prerrequisitos entre módulos. +-- La progresión del estudiante se valida usando estas dependencias. +-- +-- Estructura de módulos GAMILIT: +-- - MOD-01-LITERAL: Comprensión Literal (sin prerrequisitos) +-- - MOD-02-INFERENCIAL: Comprensión Inferencial (requiere MOD-01) +-- - MOD-03-CRITICA: Comprensión Crítica (requiere MOD-02) +-- - MOD-04-DIGITAL: Lectura Digital (requiere MOD-01, recomendado MOD-02) +-- - MOD-05-PRODUCCION: Producción Lectora (requiere MOD-03) +-- +-- Tipos de dependencia: +-- - required: Debe completarse antes (bloqueante) +-- - recommended: Se recomienda completar antes +-- - optional: Complementario, no bloqueante +-- ===================================================== + +DO $$ +DECLARE + v_mod1_id UUID; + v_mod2_id UUID; + v_mod3_id UUID; + v_mod4_id UUID; + v_mod5_id UUID; +BEGIN + -- Obtener IDs de módulos por su código + SELECT id INTO v_mod1_id FROM educational_content.modules WHERE module_code = 'MOD-01-LITERAL'; + SELECT id INTO v_mod2_id FROM educational_content.modules WHERE module_code = 'MOD-02-INFERENCIAL'; + SELECT id INTO v_mod3_id FROM educational_content.modules WHERE module_code = 'MOD-03-CRITICA'; + SELECT id INTO v_mod4_id FROM educational_content.modules WHERE module_code = 'MOD-04-DIGITAL'; + SELECT id INTO v_mod5_id FROM educational_content.modules WHERE module_code = 'MOD-05-PRODUCCION'; + + -- Verificar que todos los módulos existen + IF v_mod1_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-01-LITERAL no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod2_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-02-INFERENCIAL no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod3_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-03-CRITICA no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod4_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-04-DIGITAL no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + IF v_mod5_id IS NULL THEN + RAISE EXCEPTION 'Módulo MOD-05-PRODUCCION no encontrado. Ejecutar primero 01-modules.sql'; + END IF; + + RAISE NOTICE 'Todos los módulos encontrados. Creando dependencias...'; + + -- ===================================================== + -- DEPENDENCIA 1: MOD-02 requiere MOD-01 (100%) + -- Comprensión Inferencial requiere Comprensión Literal + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000001'::uuid, + v_mod2_id, + v_mod1_id, + 'required', + 80 -- 80% del módulo 1 debe completarse + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-02 → MOD-01 (required, 80%%)'; + + -- ===================================================== + -- DEPENDENCIA 2: MOD-03 requiere MOD-02 (100%) + -- Comprensión Crítica requiere Comprensión Inferencial + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000002'::uuid, + v_mod3_id, + v_mod2_id, + 'required', + 80 -- 80% del módulo 2 debe completarse + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-03 → MOD-02 (required, 80%%)'; + + -- ===================================================== + -- DEPENDENCIA 3: MOD-04 requiere MOD-01 (50%) + -- Lectura Digital requiere base de Comprensión Literal + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000003'::uuid, + v_mod4_id, + v_mod1_id, + 'required', + 50 -- 50% del módulo 1 (más flexible para ruta paralela) + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-04 → MOD-01 (required, 50%%)'; + + -- ===================================================== + -- DEPENDENCIA 4: MOD-04 recomienda MOD-02 + -- Se beneficia de habilidades inferenciales pero no es bloqueante + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000004'::uuid, + v_mod4_id, + v_mod2_id, + 'recommended', + 60 -- 60% recomendado pero no obligatorio + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-04 → MOD-02 (recommended, 60%%)'; + + -- ===================================================== + -- DEPENDENCIA 5: MOD-05 requiere MOD-03 (100%) + -- Producción Lectora requiere maestría en Comprensión Crítica + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000005'::uuid, + v_mod5_id, + v_mod3_id, + 'required', + 80 -- 80% del módulo 3 (alto requisito para producción) + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-05 → MOD-03 (required, 80%%)'; + + -- ===================================================== + -- DEPENDENCIA 6: MOD-05 recomienda MOD-04 + -- Habilidades digitales complementan la producción + -- ===================================================== + INSERT INTO educational_content.module_dependencies ( + id, + module_id, + prerequisite_module_id, + dependency_type, + minimum_completion_percentage + ) VALUES ( + '30000001-0000-0000-0000-000000000006'::uuid, + v_mod5_id, + v_mod4_id, + 'recommended', + 50 -- 50% recomendado para producción digital + ) + ON CONFLICT (module_id, prerequisite_module_id) DO UPDATE SET + dependency_type = EXCLUDED.dependency_type, + minimum_completion_percentage = EXCLUDED.minimum_completion_percentage; + + RAISE NOTICE '✅ MOD-05 → MOD-04 (recommended, 50%%)'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== DEPENDENCIAS DE MÓDULOS CREADAS ==='; + RAISE NOTICE 'Total dependencias: %', (SELECT COUNT(*) FROM educational_content.module_dependencies); + RAISE NOTICE 'Required: %', (SELECT COUNT(*) FROM educational_content.module_dependencies WHERE dependency_type = 'required'); + RAISE NOTICE 'Recommended: %', (SELECT COUNT(*) FROM educational_content.module_dependencies WHERE dependency_type = 'recommended'); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM educational_content.module_dependencies; + + IF v_count < 5 THEN + RAISE WARNING '⚠️ Se esperaban al menos 5 dependencias de módulos'; + ELSE + RAISE NOTICE '✅ Seed de module_dependencies completado exitosamente'; + END IF; +END $$; + +-- ===================================================== +-- MAPA DE DEPENDENCIAS (COMENTARIO) +-- ===================================================== +/* + RUTA DE PROGRESIÓN GAMILIT: + + ┌─────────────────┐ + │ MOD-01-LITERAL │ ← Punto de entrada (sin prerrequisitos) + │ (Comprensión │ + │ Literal) │ + └────────┬────────┘ + │ + │ required (80%) + ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ MOD-02-INFEREN. │ │ MOD-04-DIGITAL │ + │ (Comprensión │ │ (Lectura │ ← Ruta paralela (50% MOD-01) + │ Inferencial) │ ········>│ Digital) │ recommended MOD-02 + └────────┬────────┘ └────────┬────────┘ + │ │ + │ required (80%) │ recommended (50%) + ▼ │ + ┌─────────────────┐ │ + │ MOD-03-CRITICA │ │ + │ (Comprensión │ │ + │ Crítica) │ │ + └────────┬────────┘ │ + │ │ + │ required (80%) │ + ▼ │ + ┌─────────────────┐◀──────────────────┘ + │ MOD-05-PRODUC. │ + │ (Producción │ ← Requiere MOD-03, recomienda MOD-04 + │ Lectora) │ + └─────────────────┘ +*/ diff --git a/projects/gamilit/apps/database/seeds/prod/educational_content/12-taxonomies.sql b/projects/gamilit/apps/database/seeds/prod/educational_content/12-taxonomies.sql new file mode 100644 index 0000000..4eded11 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/educational_content/12-taxonomies.sql @@ -0,0 +1,158 @@ +-- ===================================================== +-- Seed: educational_content.taxonomies +-- Description: Taxonomías educativas para clasificación de ejercicios +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed completa las taxonomías educativas del sistema. +-- La taxonomía de Bloom ya existe en el DDL, aquí agregamos: +-- - SOLO Taxonomy (Structure of Observed Learning Outcomes) +-- - Webb's DOK (Depth of Knowledge) +-- - Taxonomía GAMILIT (personalizada para lectura) +-- +-- Tipos de taxonomía (CHECK constraint): +-- - bloom: Taxonomía de Bloom (cognitiva) +-- - solo: SOLO Taxonomy (estructural) +-- - webb: Webb's Depth of Knowledge +-- - custom: Taxonomías personalizadas +-- ===================================================== + +DO $$ +BEGIN + -- ===================================================== + -- 1. TAXONOMÍA DE BLOOM (ACTUALIZACIÓN/INSERCIÓN) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000001'::uuid, + 'Taxonomía de Bloom', + 'Taxonomía cognitiva de Benjamin Bloom para clasificar objetivos educativos', + 'bloom', + '[ + {"level": 1, "name": "Recordar", "description": "Recuperar conocimiento de la memoria", "verbs": ["definir", "identificar", "listar", "nombrar", "recordar"]}, + {"level": 2, "name": "Comprender", "description": "Construir significado a partir de mensajes", "verbs": ["explicar", "interpretar", "resumir", "clasificar", "comparar"]}, + {"level": 3, "name": "Aplicar", "description": "Usar procedimientos en situaciones dadas", "verbs": ["aplicar", "demostrar", "ejecutar", "implementar", "resolver"]}, + {"level": 4, "name": "Analizar", "description": "Descomponer en partes e identificar relaciones", "verbs": ["analizar", "diferenciar", "organizar", "atribuir", "deconstruir"]}, + {"level": 5, "name": "Evaluar", "description": "Hacer juicios basados en criterios", "verbs": ["evaluar", "criticar", "juzgar", "justificar", "argumentar"]}, + {"level": 6, "name": "Crear", "description": "Reorganizar elementos en nuevo patrón", "verbs": ["crear", "diseñar", "construir", "producir", "inventar"]} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Taxonomía de Bloom creada/actualizada'; + + -- ===================================================== + -- 2. TAXONOMÍA SOLO (Structure of Observed Learning Outcomes) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000002'::uuid, + 'Taxonomía SOLO', + 'Structure of Observed Learning Outcomes - Biggs & Collis para evaluar calidad de respuestas', + 'solo', + '[ + {"level": 1, "name": "Preestructural", "description": "El estudiante no entiende la tarea", "indicator": "Respuesta irrelevante o sin relación"}, + {"level": 2, "name": "Uniestructural", "description": "El estudiante enfoca un aspecto relevante", "indicator": "Un punto relevante identificado"}, + {"level": 3, "name": "Multiestructural", "description": "El estudiante enfoca varios aspectos relevantes independientes", "indicator": "Varios puntos sin conexión"}, + {"level": 4, "name": "Relacional", "description": "El estudiante integra aspectos en una estructura coherente", "indicator": "Puntos conectados y relacionados"}, + {"level": 5, "name": "Abstracto Extendido", "description": "El estudiante generaliza más allá de la tarea", "indicator": "Aplicación a nuevos dominios"} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Taxonomía SOLO creada/actualizada'; + + -- ===================================================== + -- 3. WEBB'S DEPTH OF KNOWLEDGE (DOK) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000003'::uuid, + 'Webb DOK', + 'Depth of Knowledge de Norman Webb para alinear estándares con evaluaciones', + 'webb', + '[ + {"level": 1, "name": "Recordar y Reproducir", "description": "Recuerdo de hechos, definiciones, términos", "examples": ["Identificar", "Definir", "Reconocer", "Localizar"]}, + {"level": 2, "name": "Habilidades y Conceptos", "description": "Usar información, aplicar conceptos", "examples": ["Resumir", "Interpretar", "Organizar", "Clasificar"]}, + {"level": 3, "name": "Pensamiento Estratégico", "description": "Razonamiento complejo, múltiples pasos", "examples": ["Analizar", "Evaluar", "Formular", "Investigar"]}, + {"level": 4, "name": "Pensamiento Extendido", "description": "Pensamiento complejo a largo plazo", "examples": ["Diseñar", "Crear", "Sintetizar", "Aplicar conceptos"]} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Webb DOK creada/actualizada'; + + -- ===================================================== + -- 4. TAXONOMÍA GAMILIT (Personalizada para Comprensión Lectora) + -- ===================================================== + INSERT INTO educational_content.taxonomies ( + id, name, description, taxonomy_type, levels, is_active + ) VALUES ( + '40000001-0000-0000-0000-000000000004'::uuid, + 'Taxonomía GAMILIT', + 'Taxonomía personalizada de GAMILIT para comprensión lectora basada en Marie Curie', + 'custom', + '[ + {"level": 1, "name": "Comprensión Literal", "description": "Identificar información explícita en el texto", "module": "MOD-01-LITERAL", "skills": ["Identificar hechos", "Localizar información", "Reconocer secuencias"]}, + {"level": 2, "name": "Comprensión Inferencial", "description": "Deducir información no explícita del texto", "module": "MOD-02-INFERENCIAL", "skills": ["Inferir causas", "Predecir consecuencias", "Interpretar significados"]}, + {"level": 3, "name": "Comprensión Crítica", "description": "Evaluar y juzgar el contenido del texto", "module": "MOD-03-CRITICA", "skills": ["Evaluar argumentos", "Detectar sesgos", "Contrastar fuentes"]}, + {"level": 4, "name": "Lectura Digital", "description": "Navegar y evaluar información en medios digitales", "module": "MOD-04-DIGITAL", "skills": ["Verificar fuentes", "Navegar hipertexto", "Evaluar credibilidad"]}, + {"level": 5, "name": "Producción Lectora", "description": "Crear textos basados en comprensión profunda", "module": "MOD-05-PRODUCCION", "skills": ["Sintetizar información", "Argumentar posiciones", "Crear contenido"]} + ]'::jsonb, + true + ) + ON CONFLICT (name) DO UPDATE SET + description = EXCLUDED.description, + levels = EXCLUDED.levels, + is_active = true, + updated_at = gamilit.now_mexico(); + + RAISE NOTICE '✅ Taxonomía GAMILIT creada/actualizada'; + + -- ===================================================== + -- VERIFICACIÓN + -- ===================================================== + RAISE NOTICE ''; + RAISE NOTICE '=== TAXONOMÍAS EDUCATIVAS ==='; + RAISE NOTICE 'Total taxonomías activas: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE is_active = true); + RAISE NOTICE 'Bloom: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'bloom'); + RAISE NOTICE 'SOLO: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'solo'); + RAISE NOTICE 'Webb DOK: %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'webb'); + RAISE NOTICE 'Custom (GAMILIT): %', (SELECT COUNT(*) FROM educational_content.taxonomies WHERE taxonomy_type = 'custom'); + +END $$; + +-- ===================================================== +-- VERIFICACIÓN FINAL +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM educational_content.taxonomies WHERE is_active = true; + + IF v_count < 3 THEN + RAISE WARNING '⚠️ Se esperaban al menos 3 taxonomías'; + ELSE + RAISE NOTICE '✅ Seed de taxonomies completado exitosamente (%s taxonomías)', v_count; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/gamification_system/04-achievements.sql b/projects/gamilit/apps/database/seeds/prod/gamification_system/04-achievements.sql index da50f88..671ce4e 100644 --- a/projects/gamilit/apps/database/seeds/prod/gamification_system/04-achievements.sql +++ b/projects/gamilit/apps/database/seeds/prod/gamification_system/04-achievements.sql @@ -17,7 +17,7 @@ -- - Social (2): Interacci�n social -- - Special (1): Logro especial -- --- TOTAL: 30 achievements demo (20 originales + 10 nuevos shop/engagement) +-- TOTAL: 20 achievements demo -- -- IMPORTANTE: Estos achievements cubren casos de uso comunes -- del sistema educativo de comprensi�n lectora GAMILIT. @@ -63,8 +63,8 @@ INSERT INTO gamification_system.achievements ( '90000001-0000-0000-0000-000000000001'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, -- Tenant principal 'Primeros Pasos', - 'Completa tu primer ejercicio de comprensi�n lectora', - '<�', + 'Completa tu primer ejercicio de comprensión lectora', + 'footprints', 'progress'::gamification_system.achievement_category, 'common', 'beginner'::educational_content.difficulty_level, @@ -105,8 +105,8 @@ INSERT INTO gamification_system.achievements ( '90000001-0000-0000-0000-000000000002'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Lector Principiante', - 'Completa 10 ejercicios de comprensi�n lectora', - '=�', + 'Completa 10 ejercicios de comprensión lectora', + 'book-open', 'progress'::gamification_system.achievement_category, 'common', 'elementary'::educational_content.difficulty_level, @@ -146,8 +146,8 @@ INSERT INTO gamification_system.achievements ( '90000001-0000-0000-0000-000000000003'::uuid, 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Lector Experimentado', - 'Completa 50 ejercicios de comprensi�n lectora', - '=�', + 'Completa 50 ejercicios de comprensión lectora', + 'book-open', 'progress'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -188,7 +188,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Lector Experto', 'Completa 100 ejercicios de comprensi�n lectora', - '<�', + 'footprints', 'progress'::gamification_system.achievement_category, 'epic', 'upper_intermediate'::educational_content.difficulty_level, @@ -229,7 +229,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Maestro de la Lectura', 'Completa 200 ejercicios de comprensi�n lectora', - '=Q', + 'graduation-cap', 'progress'::gamification_system.achievement_category, 'legendary', 'proficient'::educational_content.difficulty_level, @@ -274,7 +274,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Racha de 3 D�as', 'Mant�n una racha de 3 d�as consecutivos practicando', - '=%', + 'flame', 'streak'::gamification_system.achievement_category, 'common', 'elementary'::educational_content.difficulty_level, @@ -315,7 +315,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Racha de 7 D�as', 'Mant�n una racha de 7 d�as consecutivos practicando', - '=%=%', + 'flame', 'streak'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -356,7 +356,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Racha de 30 D�as', 'Mant�n una racha de 30 d�as consecutivos practicando', - '=%=%=%', + 'flame', 'streak'::gamification_system.achievement_category, 'epic', 'proficient'::educational_content.difficulty_level, @@ -401,7 +401,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Comprensi�n Literal Dominada', 'Completa todos los ejercicios del M�dulo 1: Comprensi�n Literal', - '', + 'brain', 'completion'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -443,7 +443,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Comprensi�n Inferencial Dominada', 'Completa todos los ejercicios del M�dulo 2: Comprensi�n Inferencial', - '', + 'brain', 'completion'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -485,7 +485,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Comprensi�n Cr�tica Dominada', 'Completa todos los ejercicios del M�dulo 3: Comprensi�n Cr�tica', - '', + 'brain', 'completion'::gamification_system.achievement_category, 'epic', 'upper_intermediate'::educational_content.difficulty_level, @@ -527,7 +527,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Completista Total', 'Completa todos los m�dulos del sistema', - '<�', + 'trophy', 'completion'::gamification_system.achievement_category, 'legendary', 'proficient'::educational_content.difficulty_level, @@ -573,7 +573,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Perfeccionista', 'Obt�n 100% de aciertos en 10 ejercicios', - 'P', + 'target', 'mastery'::gamification_system.achievement_category, 'rare', 'upper_intermediate'::educational_content.difficulty_level, @@ -615,7 +615,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Experto en Inferencias', 'Completa 20 ejercicios de inferencia con 90% o m�s de aciertos', - '>�', + 'brain', 'mastery'::gamification_system.achievement_category, 'epic', 'upper_intermediate'::educational_content.difficulty_level, @@ -659,7 +659,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Cr�tico Avanzado', 'Completa 20 ejercicios de pensamiento cr�tico con 90% o m�s', - '<�', + 'footprints', 'mastery'::gamification_system.achievement_category, 'epic', 'proficient'::educational_content.difficulty_level, @@ -707,7 +707,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Explorador Curioso', 'Explora al menos 3 m�dulos diferentes', - '= ', + 'compass', 'exploration'::gamification_system.achievement_category, 'common', 'elementary'::educational_content.difficulty_level, @@ -749,7 +749,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Aventurero del Conocimiento', 'Completa ejercicios de todos los niveles de dificultad', - '=�', + 'compass', 'exploration'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -795,7 +795,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Compa�ero de Aula', '�nete a tu primera aula virtual', - '=e', + 'users', 'social'::gamification_system.achievement_category, 'common', 'beginner'::educational_content.difficulty_level, @@ -836,7 +836,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Estudiante Colaborativo', 'Participa en 5 actividades sociales (aulas, desaf�os, etc.)', - '>', + 'handshake', 'social'::gamification_system.achievement_category, 'rare', 'pre_intermediate'::educational_content.difficulty_level, @@ -881,7 +881,7 @@ INSERT INTO gamification_system.achievements ( 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 'Primera Visita', 'Inicia sesi�n por primera vez en GAMILIT', - '<�', + 'footprints', 'special'::gamification_system.achievement_category, 'common', 'beginner'::educational_content.difficulty_level, @@ -915,448 +915,6 @@ INSERT INTO gamification_system.achievements ( ), gamilit.now_mexico(), gamilit.now_mexico() -), - --- ===================================================== --- NUEVOS ACHIEVEMENTS - TIENDA Y ENGAGEMENT (10 adicionales) --- Agregados: 2025-11-29 --- ===================================================== - --- ===================================================== --- CATEGORY: SOCIAL (3 nuevos - shop y engagement) --- ===================================================== - --- 21. Primera Compra -( - '90000008-0001-0000-0000-000000000001'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Primera Compra', - 'Realiza tu primera compra en la tienda virtual', - '🛒', - 'social'::gamification_system.achievement_category, - 'common', - 'beginner'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'shop_purchase', - 'requirements', jsonb_build_object( - 'purchases_count', 1 - ) - ), - jsonb_build_object( - 'xp', 50, - 'ml_coins', 25, - 'badge', 'first_purchase' - ), - 25, - false, - true, - false, - 52, - 50, - '¡Felicidades! Has hecho tu primera compra en la tienda GAMILIT.', - 'Compra cualquier item en la tienda virtual.', - ARRAY[ - 'Los ML Coins se ganan completando ejercicios', - 'Explora diferentes categorías de items' - ], - jsonb_build_object( - 'social_type', 'shop_engagement', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- 22. Coleccionista Novato -( - '90000008-0001-0000-0000-000000000002'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Coleccionista Novato', - 'Adquiere 5 items diferentes de la tienda', - '📦', - 'social'::gamification_system.achievement_category, - 'rare', - 'elementary'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'collection', - 'requirements', jsonb_build_object( - 'unique_items', 5 - ) - ), - jsonb_build_object( - 'xp', 150, - 'ml_coins', 75, - 'badge', 'novice_collector' - ), - 75, - false, - true, - false, - 53, - 150, - '¡Excelente! Estás comenzando una colección interesante.', - 'Compra 5 items diferentes en la tienda.', - ARRAY[ - 'Varía entre categorías de items', - 'Busca items que complementen tu estilo' - ], - jsonb_build_object( - 'social_type', 'collection', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- 23. Comunicador Social -( - '90000008-0001-0000-0000-000000000003'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Comunicador Social', - 'Usa 10 stickers o emojis en conversaciones', - '💬', - 'social'::gamification_system.achievement_category, - 'common', - 'beginner'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'social', - 'requirements', jsonb_build_object( - 'stickers_used', 10 - ) - ), - jsonb_build_object( - 'xp', 75, - 'ml_coins', 30, - 'badge', 'social_communicator' - ), - 30, - false, - true, - false, - 54, - 75, - '¡Genial! Te comunicas activamente con la comunidad GAMILIT.', - 'Usa 10 stickers o emojis en chats o comentarios.', - ARRAY[ - 'Los emojis enriquecen la comunicación', - 'Expresa tus emociones de forma creativa' - ], - jsonb_build_object( - 'social_type', 'communication', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- CATEGORY: STREAK (1 nuevo) --- ===================================================== - --- 24. Racha de Fuego -( - '90000008-0002-0000-0000-000000000001'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Racha de Fuego', - 'Mantén una racha de 14 días consecutivos practicando', - '🔥', - 'streak'::gamification_system.achievement_category, - 'epic', - 'upper_intermediate'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'streak', - 'requirements', jsonb_build_object( - 'consecutive_days', 14 - ) - ), - jsonb_build_object( - 'xp', 300, - 'ml_coins', 100, - 'badge', 'streak_14' - ), - 100, - false, - true, - false, - 13, - 300, - '¡Épico! Dos semanas de racha sin parar. Tu dedicación es extraordinaria.', - 'Practica al menos un ejercicio durante 14 días consecutivos.', - ARRAY[ - 'Estás cerca del hábito de 21 días', - 'Tu constancia es inspiradora' - ], - jsonb_build_object( - 'streak_milestone', 14, - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- CATEGORY: MASTERY (2 nuevos) --- ===================================================== - --- 25. Velocista Lector -( - '90000008-0003-0000-0000-000000000001'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Velocista Lector', - 'Completa un ejercicio en menos de 2 minutos con 100% de aciertos', - '⚡', - 'mastery'::gamification_system.achievement_category, - 'rare', - 'pre_intermediate'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'speed_mastery', - 'requirements', jsonb_build_object( - 'max_time_seconds', 120, - 'min_score', 100 - ) - ), - jsonb_build_object( - 'xp', 200, - 'ml_coins', 100, - 'badge', 'speed_reader' - ), - 100, - false, - true, - false, - 33, - 200, - '¡Impresionante! Velocidad y precisión combinadas perfectamente.', - 'Completa un ejercicio en menos de 2 minutos con puntaje perfecto.', - ARRAY[ - 'La práctica mejora tu velocidad de lectura', - 'Lee rápido pero sin sacrificar comprensión' - ], - jsonb_build_object( - 'mastery_type', 'speed', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- 26. Perfeccionista Total -( - '90000008-0003-0000-0000-000000000002'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Perfeccionista Total', - 'Obtén 100% de aciertos en 10 ejercicios consecutivos', - '🎯', - 'mastery'::gamification_system.achievement_category, - 'legendary', - 'proficient'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'perfect_streak', - 'requirements', jsonb_build_object( - 'perfect_exercises_consecutive', 10, - 'score_required', 100 - ) - ), - jsonb_build_object( - 'xp', 750, - 'ml_coins', 300, - 'badge', 'total_perfectionist' - ), - 300, - false, - true, - false, - 34, - 750, - '¡LEGENDARIO! 10 ejercicios perfectos seguidos. Tu maestría es absoluta.', - 'Obtén 100% de aciertos en 10 ejercicios consecutivos.', - ARRAY[ - 'Mantén la concentración en cada ejercicio', - 'Tu consistencia es excepcional' - ], - jsonb_build_object( - 'mastery_type', 'perfect_streak', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- CATEGORY: EXPLORATION (1 nuevo) --- ===================================================== - --- 27. Explorador Completo -( - '90000008-0004-0000-0000-000000000001'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Explorador Completo', - 'Visita todas las secciones de la plataforma GAMILIT', - '🗺️', - 'exploration'::gamification_system.achievement_category, - 'legendary', - 'upper_intermediate'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'exploration', - 'requirements', jsonb_build_object( - 'sections_visited', jsonb_build_array('modules', 'shop', 'leaderboard', 'profile', 'guild', 'achievements') - ) - ), - jsonb_build_object( - 'xp', 500, - 'ml_coins', 200, - 'badge', 'complete_explorer' - ), - 200, - false, - true, - false, - 42, - 500, - '¡Legendario! Has explorado cada rincón de GAMILIT. Conoces la plataforma completa.', - 'Visita todas las secciones principales: módulos, tienda, leaderboard, perfil, gremio y logros.', - ARRAY[ - 'Cada sección tiene características únicas', - 'Conocer la plataforma maximiza tu experiencia' - ], - jsonb_build_object( - 'exploration_type', 'complete_platform', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- CATEGORY: SPECIAL (2 nuevos - gremio y shop) --- ===================================================== - --- 28. Mecenas del Gremio -( - '90000008-0005-0000-0000-000000000001'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Mecenas del Gremio', - 'Compra 5 items de categoría Gremio en la tienda', - '👑', - 'special'::gamification_system.achievement_category, - 'epic', - 'pre_intermediate'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'shop_category', - 'requirements', jsonb_build_object( - 'category', 'guild', - 'items_purchased', 5 - ) - ), - jsonb_build_object( - 'xp', 300, - 'ml_coins', 150, - 'badge', 'guild_patron' - ), - 150, - false, - true, - false, - 61, - 300, - '¡Épico! Tu gremio luce increíble gracias a tu inversión.', - 'Compra 5 items diferentes de la categoría Gremio.', - ARRAY[ - 'Los items de gremio mejoran el prestigio colectivo', - 'Contribuye al éxito de tu equipo' - ], - jsonb_build_object( - 'special_type', 'guild_shop', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- 29. Fashionista -( - '90000008-0005-0000-0000-000000000002'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Fashionista', - 'Equipa 3 cosméticos diferentes al mismo tiempo', - '✨', - 'special'::gamification_system.achievement_category, - 'rare', - 'elementary'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'cosmetics_equipped', - 'requirements', jsonb_build_object( - 'equipped_cosmetics', 3 - ) - ), - jsonb_build_object( - 'xp', 100, - 'ml_coins', 50, - 'badge', 'fashionista' - ), - 50, - false, - true, - false, - 62, - 100, - '¡Genial! Tu estilo es único y llamativo.', - 'Equipa 3 cosméticos simultáneamente (avatar, marco, fondo).', - ARRAY[ - 'Combina cosméticos para crear tu estilo', - 'La personalización te hace destacar' - ], - jsonb_build_object( - 'special_type', 'cosmetics_style', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- CATEGORY: COMPLETION (1 nuevo - coleccionista) --- ===================================================== - --- 30. Coleccionista Experto -( - '90000008-0006-0000-0000-000000000001'::uuid, - 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, - 'Coleccionista Experto', - 'Adquiere 20 items diferentes de la tienda', - '🏆', - 'completion'::gamification_system.achievement_category, - 'epic', - 'upper_intermediate'::educational_content.difficulty_level, - jsonb_build_object( - 'type', 'collection', - 'requirements', jsonb_build_object( - 'unique_items', 20 - ) - ), - jsonb_build_object( - 'xp', 400, - 'ml_coins', 200, - 'badge', 'expert_collector' - ), - 200, - false, - true, - false, - 24, - 400, - '¡Épico! Tu colección es impresionante y variada.', - 'Compra 20 items diferentes en la tienda.', - ARRAY[ - 'Explora todas las categorías de la tienda', - 'Una colección completa demuestra dedicación' - ], - jsonb_build_object( - 'achievement_tier', 'collector', - 'demo_achievement', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() ) ON CONFLICT (id) DO UPDATE SET @@ -1440,10 +998,10 @@ BEGIN RAISE NOTICE ' - Special: %', special_count; RAISE NOTICE '========================================'; - IF achievement_count = 30 THEN + IF achievement_count = 20 THEN RAISE NOTICE ' Todos los achievements demo fueron creados correctamente'; ELSE - RAISE WARNING '� Se esperaban 30 achievements, se crearon %', achievement_count; + RAISE WARNING '� Se esperaban 20 achievements, se crearon %', achievement_count; END IF; END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/gamification_system/10-mission_templates.sql b/projects/gamilit/apps/database/seeds/prod/gamification_system/10-mission_templates.sql new file mode 100644 index 0000000..5cabc88 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/gamification_system/10-mission_templates.sql @@ -0,0 +1,346 @@ +-- ===================================================== +-- Seed: gamification_system.mission_templates +-- Description: Templates de misiones para generar misiones diarias/semanales/especiales +-- Priority: P0 - CRÍTICO (Auditoría AUDIT-DB-001) +-- Created: 2025-12-14 +-- ===================================================== +-- +-- Este seed crea los templates base para el sistema de misiones. +-- Los templates se usan para generar misiones automáticamente. +-- +-- Tipos de misiones (ENUM mission_type): +-- - daily: Misiones diarias (reset cada día) +-- - weekly: Misiones semanales +-- - special: Misiones especiales/eventos +-- - classroom: Misiones de aula (asignadas por profesores) +-- +-- Tipos de objetivos (target_type): +-- - complete_exercises: Completar N ejercicios +-- - study_minutes: Estudiar N minutos +-- - earn_xp: Ganar N XP +-- - correct_streak: Racha de N respuestas correctas +-- - use_comodines: Usar N comodines +-- - perfect_scores: Obtener N puntuaciones perfectas +-- - daily_streak: Mantener racha de N días +-- - complete_modules: Completar N módulos +-- - explore_modules: Explorar N módulos diferentes +-- ===================================================== + +-- ===================================================== +-- MISIONES DIARIAS +-- ===================================================== +INSERT INTO gamification_system.mission_templates ( + id, + name, + description, + type, + category, + target_type, + target_value, + xp_reward, + ml_coins_reward, + difficulty, + is_active, + priority, + min_level, + icon, + color, + metadata +) VALUES +-- Misión diaria: Completar ejercicios +( + '20000001-0000-0000-0000-000000000001'::uuid, + 'Calentamiento Científico', + 'Completa 3 ejercicios para comenzar tu día de aprendizaje', + 'daily', + 'exercise', + 'complete_exercises', + 3, + 50, + 10, + 'easy', + true, + 100, + 1, + '🔬', + '#4CAF50', + '{"description_es": "Ejercicios completados", "reward_multiplier": 1.0}'::jsonb +), +-- Misión diaria: Racha de respuestas correctas +( + '20000001-0000-0000-0000-000000000002'::uuid, + 'Mente Brillante', + 'Consigue una racha de 5 respuestas correctas consecutivas', + 'daily', + 'streak', + 'correct_streak', + 5, + 75, + 15, + 'normal', + true, + 90, + 1, + '⚡', + '#FF9800', + '{"description_es": "Respuestas consecutivas correctas", "reward_multiplier": 1.0}'::jsonb +), +-- Misión diaria: Ganar XP +( + '20000001-0000-0000-0000-000000000003'::uuid, + 'Acumulador de Sabiduría', + 'Gana 100 puntos de experiencia durante el día', + 'daily', + 'progress', + 'earn_xp', + 100, + 30, + 5, + 'easy', + true, + 80, + 1, + '📈', + '#2196F3', + '{"description_es": "XP ganado", "reward_multiplier": 1.0}'::jsonb +), +-- Misión diaria: Puntuación perfecta +( + '20000001-0000-0000-0000-000000000004'::uuid, + 'Perfeccionista del Día', + 'Obtén al menos 1 puntuación perfecta en cualquier ejercicio', + 'daily', + 'mastery', + 'perfect_scores', + 1, + 100, + 25, + 'hard', + true, + 70, + 2, + '🌟', + '#9C27B0', + '{"description_es": "Puntuaciones perfectas", "reward_multiplier": 1.2}'::jsonb +) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + xp_reward = EXCLUDED.xp_reward, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- MISIONES SEMANALES +-- ===================================================== +INSERT INTO gamification_system.mission_templates ( + id, + name, + description, + type, + category, + target_type, + target_value, + xp_reward, + ml_coins_reward, + difficulty, + is_active, + priority, + min_level, + icon, + color, + metadata +) VALUES +-- Misión semanal: Completar ejercicios +( + '20000002-0000-0000-0000-000000000001'::uuid, + 'Maratón de Conocimiento', + 'Completa 15 ejercicios durante la semana', + 'weekly', + 'exercise', + 'complete_exercises', + 15, + 200, + 50, + 'normal', + true, + 100, + 1, + '🏃', + '#4CAF50', + '{"description_es": "Ejercicios completados esta semana", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: Racha diaria +( + '20000002-0000-0000-0000-000000000002'::uuid, + 'Constancia Científica', + 'Mantén una racha de estudio de 5 días consecutivos', + 'weekly', + 'streak', + 'daily_streak', + 5, + 300, + 75, + 'hard', + true, + 95, + 1, + '🔥', + '#FF5722', + '{"description_es": "Días de racha", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: XP total +( + '20000002-0000-0000-0000-000000000003'::uuid, + 'Ascenso Semanal', + 'Acumula 500 puntos de experiencia durante la semana', + 'weekly', + 'progress', + 'earn_xp', + 500, + 150, + 40, + 'normal', + true, + 85, + 1, + '📊', + '#00BCD4', + '{"description_es": "XP total semanal", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: Explorar módulos +( + '20000002-0000-0000-0000-000000000004'::uuid, + 'Explorador Curioso', + 'Realiza ejercicios de al menos 3 módulos diferentes', + 'weekly', + 'exploration', + 'explore_modules', + 3, + 175, + 45, + 'normal', + true, + 80, + 1, + '🗺️', + '#795548', + '{"description_es": "Módulos explorados", "reward_multiplier": 1.5}'::jsonb +), +-- Misión semanal: Puntuaciones perfectas +( + '20000002-0000-0000-0000-000000000005'::uuid, + 'Semana de Excelencia', + 'Consigue 5 puntuaciones perfectas durante la semana', + 'weekly', + 'mastery', + 'perfect_scores', + 5, + 400, + 100, + 'epic', + true, + 75, + 3, + '👑', + '#FFD700', + '{"description_es": "Puntuaciones perfectas semanales", "reward_multiplier": 2.0}'::jsonb +) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + xp_reward = EXCLUDED.xp_reward, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- MISIONES ESPECIALES +-- ===================================================== +INSERT INTO gamification_system.mission_templates ( + id, + name, + description, + type, + category, + target_type, + target_value, + xp_reward, + ml_coins_reward, + difficulty, + is_active, + priority, + min_level, + icon, + color, + metadata +) VALUES +-- Misión especial: Completar módulo +( + '20000003-0000-0000-0000-000000000001'::uuid, + 'Dominio del Módulo', + 'Completa todos los ejercicios de un módulo con al menos 80% de aciertos', + 'special', + 'completion', + 'complete_modules', + 1, + 500, + 150, + 'epic', + true, + 100, + 1, + '🎓', + '#E91E63', + '{"description_es": "Módulo completado con maestría", "min_score_percentage": 80, "reward_multiplier": 2.5}'::jsonb +), +-- Misión especial: Uso de comodines +( + '20000003-0000-0000-0000-000000000002'::uuid, + 'Estratega Sabio', + 'Usa 3 comodines estratégicamente durante tus ejercicios', + 'special', + 'strategy', + 'use_comodines', + 3, + 75, + 20, + 'normal', + true, + 60, + 2, + '🃏', + '#673AB7', + '{"description_es": "Comodines usados", "reward_multiplier": 1.0}'::jsonb +) +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + xp_reward = EXCLUDED.xp_reward, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_active = EXCLUDED.is_active, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- VERIFICACIÓN +-- ===================================================== +DO $$ +DECLARE + v_count INTEGER; +BEGIN + SELECT COUNT(*) INTO v_count FROM gamification_system.mission_templates; + + RAISE NOTICE ''; + RAISE NOTICE '=== MISSION TEMPLATES CREADOS ==='; + RAISE NOTICE 'Total templates: %', v_count; + RAISE NOTICE 'Daily missions: %', (SELECT COUNT(*) FROM gamification_system.mission_templates WHERE type = 'daily'); + RAISE NOTICE 'Weekly missions: %', (SELECT COUNT(*) FROM gamification_system.mission_templates WHERE type = 'weekly'); + RAISE NOTICE 'Special missions: %', (SELECT COUNT(*) FROM gamification_system.mission_templates WHERE type = 'special'); + + IF v_count < 10 THEN + RAISE WARNING '⚠️ Se esperaban al menos 10 mission_templates'; + ELSE + RAISE NOTICE '✅ Seed de mission_templates completado exitosamente'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/social_features/00-schools-default.sql b/projects/gamilit/apps/database/seeds/prod/social_features/00-schools-default.sql new file mode 100644 index 0000000..3f38753 --- /dev/null +++ b/projects/gamilit/apps/database/seeds/prod/social_features/00-schools-default.sql @@ -0,0 +1,167 @@ +-- ===================================================== +-- Seed: social_features.schools - SCHOOL DEFAULT (PROD) +-- Description: Escuela del sistema para usuarios pendientes de asignación +-- Environment: PRODUCTION +-- Dependencies: auth_management.tenants +-- Order: 00 (debe ejecutarse ANTES de 01-schools.sql) +-- Created: 2025-12-15 +-- Version: 1.0 +-- ===================================================== +-- +-- PROPÓSITO: +-- Esta escuela es utilizada por el sistema para: +-- 1. Asignar automáticamente a usuarios admin nuevos +-- 2. Servir como pool de usuarios "por asignar" +-- 3. El classroom DEFAULT apunta a esta escuela +-- +-- UUID FIJO: 99999999-9999-9999-9999-999999999999 +-- CÓDIGO: SYSTEM-UNASSIGNED +-- +-- IMPORTANTE: Esta escuela NO debe eliminarse nunca. +-- ===================================================== + +SET search_path TO social_features, auth_management, public; + +-- ===================================================== +-- Obtener tenant_id para la escuela +-- ===================================================== + +DO $$ +DECLARE + v_tenant_id UUID; +BEGIN + -- Obtener el tenant principal de GAMILIT Platform + SELECT id INTO v_tenant_id + FROM auth_management.tenants + WHERE name = 'GAMILIT Platform' + LIMIT 1; + + IF v_tenant_id IS NULL THEN + RAISE EXCEPTION 'Tenant "GAMILIT Platform" no encontrado. Ejecutar primero seed de tenants.'; + END IF; + + RAISE NOTICE 'Usando tenant_id: %', v_tenant_id; + +-- ===================================================== +-- INSERT: Escuela Default del Sistema +-- ===================================================== + +INSERT INTO social_features.schools ( + id, + tenant_id, + name, + code, + short_name, + description, + address, + city, + region, + country, + postal_code, + phone, + email, + website, + principal_id, + administrative_contact_id, + academic_year, + semester_system, + grade_levels, + settings, + max_students, + max_teachers, + current_students_count, + current_teachers_count, + is_active, + is_verified, + metadata, + created_at, + updated_at +) VALUES ( + '99999999-9999-9999-9999-999999999999'::uuid, -- UUID fija para sistema + v_tenant_id, + 'Sistema - Por Asignar', + 'SYSTEM-UNASSIGNED', + 'Sistema', + 'Escuela del sistema para usuarios pendientes de asignación a sus instituciones finales. Los administradores y profesores nuevos se asignan aquí automáticamente.', + NULL, -- Sin dirección física + NULL, -- Sin ciudad + NULL, -- Sin región + 'México', + NULL, -- Sin código postal + NULL, -- Sin teléfono + 'sistema@gamilit.com', -- Email de sistema + NULL, -- Sin website + NULL, -- Sin principal_id + NULL, -- Sin administrative_contact_id + '2025', -- Año académico + false, -- No usa semestres + ARRAY['todos'], -- Todos los niveles + jsonb_build_object( + 'is_system', true, + 'is_default', true, + 'auto_assignment', true, + 'allow_reassignment', true, + 'allow_public_registration', false, + 'require_email_verification', false, + 'enable_gamification', true, + 'max_students_per_classroom', 999, + 'description', 'Configuración de sistema - no editar' + ), + 9999, -- max_students (sin límite efectivo) + 999, -- max_teachers (sin límite efectivo) + 0, -- current_students_count + 0, -- current_teachers_count + true, -- is_active + true, -- is_verified (sistema) + jsonb_build_object( + 'system_school', true, + 'is_default', true, + 'created_by', 'system', + 'purpose', 'Escuela de sistema para asignación pendiente', + 'policies', jsonb_build_object( + 'allow_student_reassignment', true, + 'allow_admin_reassignment', true, + 'require_approval', false, + 'auto_assign_new_admins', true + ), + 'description', 'Escuela automática del sistema para gestión de usuarios no asignados' + ), + gamilit.now_mexico(), + gamilit.now_mexico() +) +ON CONFLICT (code) DO UPDATE SET + name = EXCLUDED.name, + short_name = EXCLUDED.short_name, + description = EXCLUDED.description, + settings = EXCLUDED.settings, + metadata = EXCLUDED.metadata, + is_active = true, + updated_at = gamilit.now_mexico(); + +END $$; + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + v_school_id UUID; + v_school_name TEXT; +BEGIN + SELECT id, name INTO v_school_id, v_school_name + FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED'; + + IF v_school_id IS NOT NULL THEN + RAISE NOTICE '========================================'; + RAISE NOTICE 'ESCUELA DEFAULT DEL SISTEMA CREADA'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'ID: %', v_school_id; + RAISE NOTICE 'Nombre: %', v_school_name; + RAISE NOTICE 'Código: SYSTEM-UNASSIGNED'; + RAISE NOTICE '========================================'; + ELSE + RAISE WARNING 'ERROR: No se pudo crear la escuela default del sistema'; + END IF; +END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/social_features/01-schools.sql b/projects/gamilit/apps/database/seeds/prod/social_features/01-schools.sql index 6c6c98b..37ef47c 100644 --- a/projects/gamilit/apps/database/seeds/prod/social_features/01-schools.sql +++ b/projects/gamilit/apps/database/seeds/prod/social_features/01-schools.sql @@ -1,313 +1,57 @@ -- ===================================================== -- Seed: social_features.schools (PROD) --- Description: Escuelas demo para testing y demostraciones +-- Description: NO demo schools - Solo escuela default -- Environment: PRODUCTION -- Dependencies: auth_management.tenants -- Order: 01 -- Created: 2025-01-11 --- Version: 2.0 (Actualizado para alineación con DDL) +-- Updated: 2025-12-15 - Removidas escuelas demo +-- Version: 3.0 -- ===================================================== -- --- ESCUELAS DEMO INCLUIDAS: --- - Escuela Primaria Marie Curie (CDMX) --- - Instituto de Educación Integral (Guadalajara) +-- NOTA: Este archivo está intencionalmente vacío de INSERTs. -- --- TOTAL: 2 escuelas demo +-- DECISIÓN DE DISEÑO: +-- - Solo existe la escuela default "Sistema - Por Asignar" (SYSTEM-UNASSIGNED) +-- - Creada en 00-schools-default.sql +-- - Todas las escuelas adicionales serán creadas por el admin desde la UI -- --- IMPORTANTE: Estas escuelas son para testing y demos. +-- ESCUELAS DEMO REMOVIDAS (v3.0): +-- - Escuela Primaria Marie Curie (CDMX) - REMOVIDA +-- - Instituto de Educación Integral (Guadalajara) - REMOVIDA -- --- CAMBIOS v2.0: --- - Agregado tenant_id (requerido) --- - Removido type (columna legacy) --- - Cambiado state → region (nuevo nombre de columna) --- - Removido principal_name, contact_name, contact_email (legacy) --- - Removido status (columna legacy) --- - Agregado short_name, description --- - Actualizados queries de verificación -- ===================================================== SET search_path TO social_features, auth_management, public; -- ===================================================== --- Obtener tenant_id para las escuelas --- ===================================================== - -DO $$ -DECLARE - v_tenant_id UUID; -BEGIN - -- Obtener el tenant principal de GAMILIT Platform - SELECT id INTO v_tenant_id - FROM auth_management.tenants - WHERE name = 'GAMILIT Platform' - LIMIT 1; - - IF v_tenant_id IS NULL THEN - RAISE EXCEPTION 'Tenant "GAMILIT Platform" no encontrado. Ejecutar primero seed de tenants.'; - END IF; - - RAISE NOTICE 'Usando tenant_id: %', v_tenant_id; - --- ===================================================== --- INSERT: Escuelas Demo --- ===================================================== - -INSERT INTO social_features.schools ( - id, - tenant_id, - name, - code, - short_name, - description, - address, - city, - region, - country, - postal_code, - phone, - email, - website, - current_students_count, - current_teachers_count, - is_active, - settings, - metadata, - created_at, - updated_at -) VALUES - --- ===================================================== --- Escuela 1: Escuela Primaria Marie Curie (CDMX) --- ===================================================== -( - '50000000-0000-0000-0000-000000000001'::uuid, - v_tenant_id, - 'Escuela Primaria Marie Curie', - 'EP-MC-CDMX', - 'EP Marie Curie', - 'Escuela primaria pública enfocada en ciencias y educación integral, inspirada en la vida de Marie Curie.', - 'Av. Insurgentes Sur 1234', - 'Ciudad de México', - 'CDMX', - 'México', - '03100', - '55-1234-5678', - 'contacto@mariecurie.edu.mx', - 'https://mariecurie.edu.mx', - 450, - 32, - true, - jsonb_build_object( - 'allow_public_registration', true, - 'require_email_verification', true, - 'max_students_per_classroom', 35, - 'enable_parent_portal', true, - 'academic_calendar', jsonb_build_object( - 'start_date', '2025-08-15', - 'end_date', '2026-07-15', - 'vacation_periods', jsonb_build_array( - jsonb_build_object('name', 'Navidad', 'start', '2025-12-20', 'end', '2026-01-06'), - jsonb_build_object('name', 'Semana Santa', 'start', '2026-04-02', 'end', '2026-04-12') - ) - ), - 'features', jsonb_build_object( - 'gamification', true, - 'assessments', true, - 'parent_portal', true, - 'analytics', true - ) - ), - jsonb_build_object( - 'year_founded', 2010, - 'type', 'public', - 'cct', '09DPR0123K', - 'shift', 'matutino', - 'grades', jsonb_build_array('1', '2', '3', '4', '5', '6'), - 'recognition', 'Escuela de Calidad 2024', - 'principal_name', 'Lic. Ana María Rodríguez', - 'contact_name', 'Prof. Carlos Méndez', - 'contact_email', 'admin@mariecurie.edu.mx', - 'infrastructure', jsonb_build_object( - 'library', true, - 'computer_lab', true, - 'science_lab', true, - 'sports_facilities', true, - 'cafeteria', true - ), - 'programs', jsonb_build_array( - 'Programa de Lectura Marie Curie', - 'Club de Ciencias', - 'Deportes vespertinos' - ), - 'demo_school', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- Escuela 2: Instituto de Educación Integral (Guadalajara) --- ===================================================== -( - '50000000-0000-0000-0000-000000000002'::uuid, - v_tenant_id, - 'Instituto de Educación Integral', - 'IEI-GDL', - 'IEI Guadalajara', - 'Instituto privado de educación integral con enfoque STEAM y programas bilingües.', - 'Av. Chapultepec 890', - 'Guadalajara', - 'Jalisco', - 'México', - '44100', - '33-3456-7890', - 'contacto@iei.edu.mx', - 'https://iei.edu.mx', - 280, - 24, - true, - jsonb_build_object( - 'allow_public_registration', false, - 'require_email_verification', true, - 'max_students_per_classroom', 25, - 'enable_parent_portal', true, - 'tuition_required', true, - 'admission_process', jsonb_build_object( - 'requires_interview', true, - 'requires_exam', true, - 'requires_documents', jsonb_build_array( - 'birth_certificate', - 'previous_grades', - 'recommendation_letters' - ) - ), - 'academic_calendar', jsonb_build_object( - 'start_date', '2025-08-15', - 'end_date', '2026-06-30', - 'vacation_periods', jsonb_build_array( - jsonb_build_object('name', 'Navidad', 'start', '2025-12-18', 'end', '2026-01-08'), - jsonb_build_object('name', 'Semana Santa', 'start', '2026-04-01', 'end', '2026-04-13') - ) - ), - 'features', jsonb_build_object( - 'gamification', true, - 'assessments', true, - 'parent_portal', true, - 'analytics', true, - 'bilingual_program', true - ) - ), - jsonb_build_object( - 'year_founded', 1995, - 'type', 'private', - 'accreditation', 'SEP', - 'bilingual', true, - 'steam_focused', true, - 'international_programs', jsonb_build_array('Cambridge Primary'), - 'partnerships', jsonb_build_array('Universidad de Guadalajara'), - 'principal_name', 'Dra. Patricia Hernández', - 'contact_name', 'Lic. Miguel Ángel Torres', - 'contact_email', 'admisiones@iei.edu.mx', - 'infrastructure', jsonb_build_object( - 'library', true, - 'computer_lab', true, - 'science_lab', true, - 'robotics_lab', true, - 'innovation_hub', true, - 'sports_complex', true, - 'auditorium', true, - 'art_studio', true - ), - 'extracurricular', jsonb_build_array( - 'Robótica', - 'Ajedrez', - 'Música', - 'Artes Plásticas', - 'Deportes' - ), - 'certifications', jsonb_build_array('SEP', 'Cambridge'), - 'demo_school', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -) - -ON CONFLICT (code) DO UPDATE SET - name = EXCLUDED.name, - short_name = EXCLUDED.short_name, - description = EXCLUDED.description, - email = EXCLUDED.email, - current_students_count = EXCLUDED.current_students_count, - current_teachers_count = EXCLUDED.current_teachers_count, - is_active = EXCLUDED.is_active, - settings = EXCLUDED.settings, - metadata = EXCLUDED.metadata, - updated_at = gamilit.now_mexico(); - -END $$; - --- ===================================================== --- Verification Query +-- Verification Query - Solo escuela default -- ===================================================== DO $$ DECLARE school_count INTEGER; - public_schools INTEGER; - private_schools INTEGER; + default_school_exists BOOLEAN; BEGIN SELECT COUNT(*) INTO school_count - FROM social_features.schools - WHERE metadata->>'demo_school' = 'true'; + FROM social_features.schools; - SELECT COUNT(*) INTO public_schools - FROM social_features.schools - WHERE metadata->>'type' = 'public' AND metadata->>'demo_school' = 'true'; - - SELECT COUNT(*) INTO private_schools - FROM social_features.schools - WHERE metadata->>'type' = 'private' AND metadata->>'demo_school' = 'true'; + SELECT EXISTS( + SELECT 1 FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED' AND is_active = true + ) INTO default_school_exists; RAISE NOTICE '========================================'; - RAISE NOTICE 'ESCUELAS DEMO CREADAS EXITOSAMENTE'; + RAISE NOTICE 'VERIFICACIÓN DE ESCUELAS'; RAISE NOTICE '========================================'; RAISE NOTICE 'Total escuelas: %', school_count; - RAISE NOTICE ' - Públicas: %', public_schools; - RAISE NOTICE ' - Privadas: %', private_schools; + RAISE NOTICE 'Escuela default existe: %', default_school_exists; RAISE NOTICE '========================================'; - IF school_count = 2 THEN - RAISE NOTICE '✓ Todas las escuelas demo fueron creadas correctamente'; + IF default_school_exists THEN + RAISE NOTICE '✓ Escuela default (SYSTEM-UNASSIGNED) configurada correctamente'; + RAISE NOTICE ' Las demás escuelas serán creadas por el admin desde la UI'; ELSE - RAISE WARNING '⚠ Se esperaban 2 escuelas, se crearon %', school_count; + RAISE WARNING '⚠ Escuela default NO encontrada. Ejecutar 00-schools-default.sql primero'; END IF; END $$; - --- ===================================================== --- Listado de escuelas --- ===================================================== - -DO $$ -DECLARE - school_record RECORD; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE 'Listado de escuelas demo:'; - RAISE NOTICE '========================================'; - - FOR school_record IN - SELECT name, code, city, region, current_students_count - FROM social_features.schools - WHERE metadata->>'demo_school' = 'true' - ORDER BY name - LOOP - RAISE NOTICE ' - % (%) - %, %', - school_record.name, - school_record.code, - school_record.city, - school_record.region; - RAISE NOTICE ' Estudiantes: %', school_record.current_students_count; - END LOOP; - - RAISE NOTICE '========================================'; -END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/social_features/02-classrooms.sql b/projects/gamilit/apps/database/seeds/prod/social_features/02-classrooms.sql index ef50fac..2147d12 100644 --- a/projects/gamilit/apps/database/seeds/prod/social_features/02-classrooms.sql +++ b/projects/gamilit/apps/database/seeds/prod/social_features/02-classrooms.sql @@ -1,29 +1,31 @@ -- ===================================================== -- Seed: social_features.classrooms (PROD) --- Description: Aulas demo para testing y demostraciones +-- Description: SOLO classroom default para asignación automática -- Environment: PRODUCTION --- Dependencies: social_features.schools, auth_management.profiles +-- Dependencies: social_features.schools (00-schools-default.sql), auth_management.profiles -- Order: 02 -- Created: 2025-01-11 --- Version: 2.0 (Actualizado para alineación con DDL) +-- Updated: 2025-12-15 - Simplificado a solo DEFAULT +-- Version: 3.0 -- ===================================================== -- -- AULAS INCLUIDAS: --- - Sin Asignar (DEFAULT - Sistema) ← NUEVO: Para asignación automática --- - 5to A (Escuela Marie Curie, Profesor 1) --- - 5to B (Escuela Marie Curie, Profesor 2) --- - 6to A (Escuela Marie Curie, Profesor 1) --- - Aula de Pruebas (IEI, Director) --- - Aula Demo Parent Portal (IEI, Profesor 2) +-- - Sin Asignar (DEFAULT - Sistema) - Única aula del sistema -- --- TOTAL: 6 aulas (1 sistema + 5 demo) +-- TOTAL: 1 aula (sistema) -- --- IMPORTANTE: Estas aulas están asociadas a las escuelas y profesores demo. +-- DECISIÓN DE DISEÑO: +-- - Solo existe el classroom default para asignación automática +-- - Todas las aulas adicionales serán creadas por el admin desde la UI +-- - Los estudiantes nuevos se asignan automáticamente aquí +-- +-- AULAS DEMO REMOVIDAS (v3.0): +-- - 5to A (Marie Curie) - REMOVIDA +-- - 5to B (Marie Curie) - REMOVIDA +-- - 6to A (Marie Curie) - REMOVIDA +-- - Aula de Pruebas (IEI) - REMOVIDA +-- - Demo Parent Portal (IEI) - REMOVIDA -- --- CAMBIOS v2.0: --- - Agregado tenant_id (requerido) --- - Removido status (columna legacy) --- - Actualizada estructura de bloques DO $$ -- ===================================================== SET search_path TO social_features, auth_management, public; @@ -35,8 +37,8 @@ SET search_path TO social_features, auth_management, public; DO $$ DECLARE v_tenant_id UUID; - v_school_count INTEGER; - v_teacher_count INTEGER; + v_default_school_id UUID; + v_teacher_id UUID; BEGIN -- Obtener el tenant principal SELECT id INTO v_tenant_id @@ -48,29 +50,34 @@ BEGIN RAISE EXCEPTION 'Tenant "GAMILIT Platform" no encontrado. Ejecutar primero seed de tenants.'; END IF; - -- Validar que existan escuelas - SELECT COUNT(*) INTO v_school_count - FROM social_features.schools; + -- Obtener la escuela default + SELECT id INTO v_default_school_id + FROM social_features.schools + WHERE code = 'SYSTEM-UNASSIGNED' AND is_active = true + LIMIT 1; - IF v_school_count = 0 THEN - RAISE EXCEPTION 'No hay escuelas. Ejecutar primero seed de schools.'; + IF v_default_school_id IS NULL THEN + RAISE EXCEPTION 'Escuela default (SYSTEM-UNASSIGNED) no encontrada. Ejecutar primero 00-schools-default.sql'; END IF; - -- Validar que existan profesores - SELECT COUNT(*) INTO v_teacher_count + -- Obtener el teacher default (teacher@gamilit.com) + SELECT id INTO v_teacher_id FROM auth_management.profiles - WHERE email IN ('teacher@gamilit.com', 'teacher2@gamilit.com', 'director@gamilit.com'); + WHERE email = 'teacher@gamilit.com' + LIMIT 1; - IF v_teacher_count = 0 THEN - RAISE WARNING 'No se encontraron profesores demo. Las aulas se crearán pero pueden tener profesores inválidos.'; + IF v_teacher_id IS NULL THEN + -- Usar el UUID conocido como fallback + v_teacher_id := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid; + RAISE WARNING 'Teacher default no encontrado, usando UUID: %', v_teacher_id; END IF; RAISE NOTICE 'Usando tenant_id: %', v_tenant_id; - RAISE NOTICE 'Escuelas disponibles: %', v_school_count; - RAISE NOTICE 'Profesores demo disponibles: %', v_teacher_count; + RAISE NOTICE 'Usando school_id (default): %', v_default_school_id; + RAISE NOTICE 'Usando teacher_id: %', v_teacher_id; -- ===================================================== --- INSERT: Aulas Demo +-- INSERT: SOLO Classroom DEFAULT -- ===================================================== INSERT INTO social_features.classrooms ( @@ -95,17 +102,16 @@ INSERT INTO social_features.classrooms ( created_at, updated_at ) VALUES - -- ===================================================== --- Aula 0: CLASSROOM DEFAULT (Sistema - Sin Asignar) +-- CLASSROOM DEFAULT (Sistema - Sin Asignar) -- IMPORTANTE: Este classroom es usado automáticamente para -- asignar estudiantes nuevos que aún no tienen aula. -- ===================================================== ( '00000000-0000-0000-0000-000000000001'::uuid, -- UUID predecible para default - '50000000-0000-0000-0000-000000000001'::uuid, -- Escuela Marie Curie (default) + v_default_school_id, -- Escuela Sistema - Por Asignar (default) v_tenant_id, - 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- Teacher default (teacher@gamilit.com) + v_teacher_id, -- Teacher default (teacher@gamilit.com) 'Sin Asignar - Aula Default', 'DEFAULT', 'todos', -- Todos los niveles @@ -139,235 +145,12 @@ INSERT INTO social_features.classrooms ( ), gamilit.now_mexico(), gamilit.now_mexico() -), - --- ===================================================== --- Aula 1: 5to A - Escuela Marie Curie (Profesor 1) --- ===================================================== -( - '60000000-0000-0000-0000-000000000001'::uuid, - '50000000-0000-0000-0000-000000000001'::uuid, -- Escuela Marie Curie - v_tenant_id, - 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- Profesor 1 (teacher@gamilit.com) - '5to A - Comprensión Lectora', - '5A-COMP-2025', - '5', - 'A', - 'Comprensión Lectora', - 'Grupo de 5to grado, sección A. Enfoque en comprensión literal e inferencial.', - 35, - 2, -- 2 estudiantes inicialmente - '2025-08-15'::date, - '2026-07-15'::date, - jsonb_build_object( - 'days', jsonb_build_array('Lunes', 'Miércoles', 'Viernes'), - 'time', '08:00-09:30', - 'room', 'Aula 501', - 'weekly_hours', 4.5 - ), - true, - jsonb_build_object( - 'allow_student_self_enrollment', false, - 'enable_gamification', true, - 'require_parental_consent', true, - 'grading_system', 'numerical', - 'attendance_required', true, - 'homework_policy', jsonb_build_object( - 'frequency', 'weekly', - 'submission_platform', 'gamilit', - 'late_penalty', 10 - ) - ), - jsonb_build_object( - 'academic_year', '2025-2026', - 'demo_classroom', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- Aula 2: 5to B - Escuela Marie Curie (Profesor 2) --- ===================================================== -( - '60000000-0000-0000-0000-000000000002'::uuid, - '50000000-0000-0000-0000-000000000001'::uuid, -- Escuela Marie Curie - v_tenant_id, - 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- Profesor 2 (teacher2@gamilit.com) - '5to B - Lectura Digital', - '5B-DIGI-2025', - '5', - 'B', - 'Lectura Digital', - 'Grupo de 5to grado, sección B. Enfoque en competencias digitales y multimedia.', - 35, - 1, - '2025-08-15'::date, - '2026-07-15'::date, - jsonb_build_object( - 'days', jsonb_build_array('Martes', 'Jueves'), - 'time', '10:00-11:30', - 'room', 'Lab Cómputo 1', - 'weekly_hours', 3 - ), - true, - jsonb_build_object( - 'allow_student_self_enrollment', false, - 'enable_gamification', true, - 'require_parental_consent', true, - 'grading_system', 'numerical', - 'attendance_required', true, - 'technology_requirements', jsonb_build_object( - 'devices', 'tablets', - 'software', jsonb_build_array('GAMILIT Platform', 'Browser') - ) - ), - jsonb_build_object( - 'academic_year', '2025-2026', - 'demo_classroom', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- Aula 3: 6to A - Escuela Marie Curie (Profesor 1) --- ===================================================== -( - '60000000-0000-0000-0000-000000000003'::uuid, - '50000000-0000-0000-0000-000000000001'::uuid, -- Escuela Marie Curie - v_tenant_id, - 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- Profesor 1 - '6to A - Producción de Textos', - '6A-PROD-2025', - '6', - 'A', - 'Producción de Textos', - 'Grupo de 6to grado, sección A. Enfoque en escritura creativa y argumentativa.', - 35, - 1, - '2025-08-15'::date, - '2026-07-15'::date, - jsonb_build_object( - 'days', jsonb_build_array('Lunes', 'Miércoles', 'Viernes'), - 'time', '09:45-11:15', - 'room', 'Aula 601', - 'weekly_hours', 4.5 - ), - true, - jsonb_build_object( - 'allow_student_self_enrollment', false, - 'enable_gamification', true, - 'require_parental_consent', true, - 'grading_system', 'numerical', - 'attendance_required', true, - 'writing_portfolio', true - ), - jsonb_build_object( - 'academic_year', '2025-2026', - 'demo_classroom', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- Aula 4: Aula de Pruebas - IEI (Director) --- ===================================================== -( - '60000000-0000-0000-0000-000000000004'::uuid, - '50000000-0000-0000-0000-000000000002'::uuid, -- Instituto IEI - v_tenant_id, - 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- Director (director@gamilit.com) - 'Aula de Pruebas - Todos los Niveles', - 'TEST-ALL-2025', - 'variable', - 'TEST', - 'Testing y Demos', - 'Aula para pruebas técnicas y demostraciones del sistema GAMILIT.', - 50, - 0, - '2025-01-01'::date, - '2025-12-31'::date, - jsonb_build_object( - 'days', jsonb_build_array('Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes'), - 'time', 'flexible', - 'room', 'Virtual', - 'weekly_hours', 10 - ), - true, - jsonb_build_object( - 'allow_student_self_enrollment', true, - 'enable_gamification', true, - 'require_parental_consent', false, - 'grading_system', 'pass_fail', - 'attendance_required', false, - 'testing_environment', true - ), - jsonb_build_object( - 'academic_year', '2025', - 'demo_classroom', true, - 'environment', 'testing' - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- Aula 5: Parent Portal Demo - IEI (Profesor 2) --- ===================================================== -( - '60000000-0000-0000-0000-000000000005'::uuid, - '50000000-0000-0000-0000-000000000002'::uuid, -- Instituto IEI - v_tenant_id, - 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid, -- Profesor 2 - 'Demo Parent Portal - 4to A', - '4A-PARENT-2025', - '4', - 'A', - 'Comprensión General', - 'Aula demo para mostrar funcionalidad de Parent Portal con comunicación padre-maestro.', - 30, - 0, - '2025-08-15'::date, - '2026-06-30'::date, - jsonb_build_object( - 'days', jsonb_build_array('Lunes', 'Miércoles', 'Viernes'), - 'time', '08:00-09:30', - 'room', 'Aula 401', - 'weekly_hours', 4.5 - ), - true, - jsonb_build_object( - 'allow_student_self_enrollment', false, - 'enable_gamification', true, - 'require_parental_consent', true, - 'grading_system', 'numerical', - 'attendance_required', true, - 'parent_portal_features', jsonb_build_object( - 'notifications', true, - 'progress_reports', true, - 'messaging', true, - 'calendar', true - ) - ), - jsonb_build_object( - 'academic_year', '2025-2026', - 'demo_classroom', true, - 'parent_portal_demo', true - ), - gamilit.now_mexico(), - gamilit.now_mexico() ) - ON CONFLICT (code) DO UPDATE SET + school_id = EXCLUDED.school_id, name = EXCLUDED.name, description = EXCLUDED.description, capacity = EXCLUDED.capacity, - current_students_count = EXCLUDED.current_students_count, - start_date = EXCLUDED.start_date, - end_date = EXCLUDED.end_date, - schedule = EXCLUDED.schedule, is_active = EXCLUDED.is_active, settings = EXCLUDED.settings, metadata = EXCLUDED.metadata, @@ -382,44 +165,48 @@ END $$; DO $$ DECLARE classroom_count INTEGER; - marie_curie_count INTEGER; - iei_count INTEGER; + default_classroom_exists BOOLEAN; + default_classroom RECORD; BEGIN SELECT COUNT(*) INTO classroom_count - FROM social_features.classrooms - WHERE metadata->>'demo_classroom' = 'true'; + FROM social_features.classrooms; - SELECT COUNT(*) INTO marie_curie_count + SELECT EXISTS( + SELECT 1 FROM social_features.classrooms + WHERE code = 'DEFAULT' AND is_active = true + ) INTO default_classroom_exists; + + SELECT c.id, c.name, c.code, s.name as school_name + INTO default_classroom FROM social_features.classrooms c JOIN social_features.schools s ON c.school_id = s.id - WHERE s.code = 'EP-MC-CDMX' AND c.metadata->>'demo_classroom' = 'true'; - - SELECT COUNT(*) INTO iei_count - FROM social_features.classrooms c - JOIN social_features.schools s ON c.school_id = s.id - WHERE s.code = 'IEI-GDL' AND c.metadata->>'demo_classroom' = 'true'; + WHERE c.code = 'DEFAULT'; RAISE NOTICE '========================================'; - RAISE NOTICE 'AULAS DEMO CREADAS EXITOSAMENTE'; - RAISE NOTICE '========================================'; - RAISE NOTICE 'Total aulas: %', classroom_count; - RAISE NOTICE ' - Marie Curie: %', marie_curie_count; - RAISE NOTICE ' - IEI: %', iei_count; + RAISE NOTICE 'VERIFICACIÓN DE CLASSROOMS'; RAISE NOTICE '========================================'; + RAISE NOTICE 'Total classrooms: %', classroom_count; + RAISE NOTICE 'Classroom default existe: %', default_classroom_exists; - IF classroom_count >= 5 THEN - RAISE NOTICE '✓ Todas las aulas fueron creadas correctamente (incluyendo DEFAULT)'; + IF default_classroom_exists THEN + RAISE NOTICE '========================================'; + RAISE NOTICE 'CLASSROOM DEFAULT:'; + RAISE NOTICE ' ID: %', default_classroom.id; + RAISE NOTICE ' Nombre: %', default_classroom.name; + RAISE NOTICE ' Código: %', default_classroom.code; + RAISE NOTICE ' Escuela: %', default_classroom.school_name; + RAISE NOTICE '========================================'; + RAISE NOTICE '✓ Classroom default configurado correctamente'; + RAISE NOTICE ' Las demás aulas serán creadas por el admin desde la UI'; ELSE - RAISE WARNING '⚠ Se esperaban al menos 5 aulas, se crearon %', classroom_count; + RAISE WARNING '⚠ Classroom default NO encontrado'; END IF; END $$; -- ===================================================== -- SYNC teacher_classrooms (many-to-many) -- ===================================================== --- Asegurar que todos los classrooms tienen su entrada en teacher_classrooms --- Esto es necesario porque algunos servicios usan classrooms.teacher_id --- y otros usan la tabla teacher_classrooms +-- Asegurar que el classroom default tiene su entrada en teacher_classrooms -- ===================================================== INSERT INTO social_features.teacher_classrooms (id, teacher_id, classroom_id, tenant_id, role, assigned_at, created_at) @@ -433,39 +220,20 @@ SELECT NOW() FROM social_features.classrooms c WHERE c.teacher_id IS NOT NULL + AND c.code = 'DEFAULT' ON CONFLICT DO NOTHING; --- ===================================================== --- Listado de aulas --- ===================================================== - +-- Verificar sync DO $$ DECLARE - classroom_record RECORD; tc_count INTEGER; BEGIN - -- Contar teacher_classrooms sincronizados - SELECT COUNT(*) INTO tc_count FROM social_features.teacher_classrooms; + SELECT COUNT(*) INTO tc_count + FROM social_features.teacher_classrooms tc + JOIN social_features.classrooms c ON tc.classroom_id = c.id + WHERE c.code = 'DEFAULT'; RAISE NOTICE ''; - RAISE NOTICE 'Listado de aulas demo:'; + RAISE NOTICE 'teacher_classrooms sincronizados para DEFAULT: %', tc_count; RAISE NOTICE '========================================'; - - FOR classroom_record IN - SELECT c.name, c.code, s.name as school_name, c.grade_level, c.section - FROM social_features.classrooms c - JOIN social_features.schools s ON c.school_id = s.id - WHERE c.metadata->>'demo_classroom' = 'true' - ORDER BY s.name, c.grade_level, c.section - LOOP - RAISE NOTICE ' - % (%) - % %° %', - classroom_record.name, - classroom_record.code, - classroom_record.school_name, - classroom_record.grade_level, - classroom_record.section; - END LOOP; - - RAISE NOTICE '========================================'; - RAISE NOTICE 'teacher_classrooms sincronizados: %', tc_count; END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/social_features/03-classroom-members.sql b/projects/gamilit/apps/database/seeds/prod/social_features/03-classroom-members.sql index e067813..df60e66 100644 --- a/projects/gamilit/apps/database/seeds/prod/social_features/03-classroom-members.sql +++ b/projects/gamilit/apps/database/seeds/prod/social_features/03-classroom-members.sql @@ -1,229 +1,109 @@ -- ===================================================== -- Seed: social_features.classroom_members (PROD) --- Description: Asociaciones estudiantes-aulas para testing y demos +-- Description: Asignar TODOS los estudiantes al classroom DEFAULT -- Environment: PRODUCTION --- Dependencies: social_features.classrooms, auth_management.profiles +-- Dependencies: social_features.classrooms (02-classrooms.sql), auth_management.profiles -- Order: 03 -- Created: 2025-01-11 --- Version: 1.0 +-- Updated: 2025-12-15 - Simplificado a solo DEFAULT +-- Version: 3.0 -- ===================================================== -- --- ASOCIACIONES INCLUIDAS: --- - 5to A: estudiante1, estudiante2 --- - 5to B: estudiante3, estudiante4 --- - 6to A: estudiante5 +-- DECISIÓN DE DISEÑO: +-- - Todos los estudiantes se asignan automáticamente al classroom DEFAULT +-- - El admin/teacher puede reasignarlos a otros classrooms desde la UI +-- - Esto facilita la gestión inicial de usuarios nuevos -- --- TOTAL: 5 asociaciones estudiante-aula +-- ASOCIACIONES DEMO REMOVIDAS (v3.0): +-- - 5to A (Marie Curie) - REMOVIDA +-- - 5to B (Marie Curie) - REMOVIDA +-- - 6to A (Marie Curie) - REMOVIDA +-- - Todas las asociaciones específicas - REMOVIDAS -- --- IMPORTANTE: Estas asociaciones conectan estudiantes demo con aulas demo. -- ===================================================== SET search_path TO social_features, auth_management, public; -- ===================================================== --- INSERT: Asociaciones Estudiante-Aula --- ===================================================== - -INSERT INTO social_features.classroom_members ( - id, - classroom_id, - student_id, - enrollment_date, - enrollment_method, - status, - attendance_percentage, - metadata, - created_at, - updated_at -) VALUES - --- ===================================================== --- 5to A - Estudiante 1 (Azul Valentina) --- ===================================================== -( - '70000000-0000-0000-0000-000000000001'::uuid, - '60000000-0000-0000-0000-000000000001'::uuid, -- 5to A - '2f5a9846-3393-40b2-9e87-0f29238c383f'::uuid, -- Azul Valentina (real profile ID) - gamilit.now_mexico(), - 'admin_add', - 'active', - 0.00, -- Sin attendance aún - jsonb_build_object( - 'enrollment_type', 'demo', - 'demo_member', true, - 'enrolled_by', 'seed_script' - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- 5to A - Estudiante 2 (Benjamin Hernandez) --- ===================================================== -( - '70000000-0000-0000-0000-000000000002'::uuid, - '60000000-0000-0000-0000-000000000001'::uuid, -- 5to A - '7a6a973e-83f7-4374-a9fc-54258138115f'::uuid, -- Benjamin Hernandez (real profile ID) - gamilit.now_mexico(), - 'admin_add', - 'active', - 0.00, - jsonb_build_object( - 'enrollment_type', 'demo', - 'demo_member', true, - 'enrolled_by', 'seed_script' - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- 5to B - Estudiante 3 (Diego Colores) --- ===================================================== -( - '70000000-0000-0000-0000-000000000003'::uuid, - '60000000-0000-0000-0000-000000000002'::uuid, -- 5to B - '33306a65-a3b1-41d5-a49d-47989957b822'::uuid, -- Diego Colores (real profile ID) - gamilit.now_mexico(), - 'admin_add', - 'active', - 0.00, - jsonb_build_object( - 'enrollment_type', 'demo', - 'demo_member', true, - 'enrolled_by', 'seed_script' - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- 5to B - Estudiante 4 (Fernando Barragan) --- ===================================================== -( - '70000000-0000-0000-0000-000000000004'::uuid, - '60000000-0000-0000-0000-000000000002'::uuid, -- 5to B - '9951ad75-e9cb-47b3-b478-6bb860ee2530'::uuid, -- Fernando Barragan (real profile ID) - gamilit.now_mexico(), - 'admin_add', - 'active', - 0.00, - jsonb_build_object( - 'enrollment_type', 'demo', - 'demo_member', true, - 'enrolled_by', 'seed_script' - ), - gamilit.now_mexico(), - gamilit.now_mexico() -), - --- ===================================================== --- 6to A - Estudiante 5 (Hugo Aragón) --- ===================================================== -( - '70000000-0000-0000-0000-000000000005'::uuid, - '60000000-0000-0000-0000-000000000003'::uuid, -- 6to A - 'bf0d3e34-e077-43d1-9626-292f7fae2bd6'::uuid, -- Hugo Aragón (real profile ID) - gamilit.now_mexico(), - 'admin_add', - 'active', - 0.00, - jsonb_build_object( - 'enrollment_type', 'demo', - 'demo_member', true, - 'enrolled_by', 'seed_script' - ), - gamilit.now_mexico(), - gamilit.now_mexico() -) - -ON CONFLICT (classroom_id, student_id) DO UPDATE SET - status = EXCLUDED.status, - metadata = EXCLUDED.metadata, - updated_at = gamilit.now_mexico(); - --- ===================================================== --- Verification Query +-- Asignar TODOS los estudiantes al classroom DEFAULT -- ===================================================== DO $$ DECLARE - member_count INTEGER; - classroom_5a_count INTEGER; - classroom_5b_count INTEGER; - classroom_6a_count INTEGER; + v_default_classroom_id UUID; + v_student_count INTEGER; + v_assigned_count INTEGER; + rec RECORD; BEGIN - SELECT COUNT(*) INTO member_count - FROM social_features.classroom_members - WHERE metadata->>'demo_member' = 'true'; + -- Obtener el classroom DEFAULT + SELECT id INTO v_default_classroom_id + FROM social_features.classrooms + WHERE code = 'DEFAULT' AND is_active = true + LIMIT 1; - SELECT COUNT(*) INTO classroom_5a_count - FROM social_features.classroom_members - WHERE classroom_id = '60000000-0000-0000-0000-000000000001'::uuid; - - SELECT COUNT(*) INTO classroom_5b_count - FROM social_features.classroom_members - WHERE classroom_id = '60000000-0000-0000-0000-000000000002'::uuid; - - SELECT COUNT(*) INTO classroom_6a_count - FROM social_features.classroom_members - WHERE classroom_id = '60000000-0000-0000-0000-000000000003'::uuid; - - RAISE NOTICE '========================================'; - RAISE NOTICE 'ASOCIACIONES AULA-ESTUDIANTE CREADAS'; - RAISE NOTICE '========================================'; - RAISE NOTICE 'Total asociaciones: %', member_count; - RAISE NOTICE ' - 5to A: % estudiantes', classroom_5a_count; - RAISE NOTICE ' - 5to B: % estudiantes', classroom_5b_count; - RAISE NOTICE ' - 6to A: % estudiantes', classroom_6a_count; - RAISE NOTICE '========================================'; - - IF member_count = 5 THEN - RAISE NOTICE '✓ Todas las asociaciones fueron creadas correctamente'; - ELSE - RAISE WARNING '⚠ Se esperaban 5 asociaciones, se crearon %', member_count; + IF v_default_classroom_id IS NULL THEN + RAISE EXCEPTION 'Classroom DEFAULT no encontrado. Ejecutar primero 02-classrooms.sql'; END IF; + + RAISE NOTICE 'Usando classroom DEFAULT: %', v_default_classroom_id; + + -- Contar estudiantes existentes + SELECT COUNT(*) INTO v_student_count + FROM auth_management.profiles + WHERE role = 'student'; + + RAISE NOTICE 'Estudiantes encontrados: %', v_student_count; + + -- Asignar cada estudiante al classroom DEFAULT + INSERT INTO social_features.classroom_members ( + id, + classroom_id, + student_id, + enrollment_date, + enrollment_method, + status, + attendance_percentage, + metadata, + created_at, + updated_at + ) + SELECT + gen_random_uuid(), + v_default_classroom_id, + p.id, + gamilit.now_mexico(), + 'admin_add', -- Valid values: teacher_invite, self_enroll, admin_add, bulk_import + 'active', + 0.00, + jsonb_build_object( + 'enrollment_type', 'auto', + 'auto_assigned', true, + 'enrolled_by', 'seed_script', + 'assigned_to_default', true, + 'pending_reassignment', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() + FROM auth_management.profiles p + WHERE p.role = 'student' + ON CONFLICT (classroom_id, student_id) DO UPDATE SET + status = 'active', + metadata = EXCLUDED.metadata || jsonb_build_object('updated_by_seed', true), + updated_at = gamilit.now_mexico(); + + GET DIAGNOSTICS v_assigned_count = ROW_COUNT; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'ASIGNACIÓN DE ESTUDIANTES A DEFAULT'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Classroom DEFAULT ID: %', v_default_classroom_id; + RAISE NOTICE 'Estudiantes asignados: %', v_assigned_count; + RAISE NOTICE '========================================'; + END $$; -- ===================================================== --- Listado de asociaciones --- ===================================================== - -DO $$ -DECLARE - member_record RECORD; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE 'Listado de estudiantes por aula:'; - RAISE NOTICE '========================================'; - - FOR member_record IN - SELECT - c.name as classroom_name, - c.code as classroom_code, - p.display_name as student_name, - p.email as student_email, - cm.status - FROM social_features.classroom_members cm - JOIN social_features.classrooms c ON c.id = cm.classroom_id - JOIN auth_management.profiles p ON p.id = cm.student_id - WHERE cm.metadata->>'demo_member' = 'true' - ORDER BY c.name, p.display_name - LOOP - RAISE NOTICE ' [%] % (%)', - member_record.classroom_code, - member_record.classroom_name, - member_record.status; - RAISE NOTICE ' └─ % <%>', - member_record.student_name, - member_record.student_email; - END LOOP; - - RAISE NOTICE '========================================'; -END $$; - --- ===================================================== --- Actualizar counts en classrooms +-- Actualizar current_students_count en classroom DEFAULT -- ===================================================== UPDATE social_features.classrooms @@ -233,34 +113,87 @@ SET current_students_count = ( WHERE classroom_id = classrooms.id AND status = 'active' ) -WHERE id IN ( - '60000000-0000-0000-0000-000000000001'::uuid, -- 5to A - '60000000-0000-0000-0000-000000000002'::uuid, -- 5to B - '60000000-0000-0000-0000-000000000003'::uuid -- 6to A -); +WHERE code = 'DEFAULT'; + +-- ===================================================== +-- Verification Query +-- ===================================================== --- Verificar counts actualizados DO $$ DECLARE - classroom_record RECORD; + member_count INTEGER; + default_count INTEGER; + classroom_students INTEGER; +BEGIN + -- Total membresías + SELECT COUNT(*) INTO member_count + FROM social_features.classroom_members; + + -- Membresías en classroom DEFAULT + SELECT COUNT(*) INTO default_count + FROM social_features.classroom_members cm + JOIN social_features.classrooms c ON c.id = cm.classroom_id + WHERE c.code = 'DEFAULT'; + + -- Count en el classroom + SELECT current_students_count INTO classroom_students + FROM social_features.classrooms + WHERE code = 'DEFAULT'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'VERIFICACIÓN DE CLASSROOM_MEMBERS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total membresías: %', member_count; + RAISE NOTICE 'Estudiantes en DEFAULT: %', default_count; + RAISE NOTICE 'current_students_count: %', classroom_students; + RAISE NOTICE '========================================'; + + IF default_count = member_count THEN + RAISE NOTICE '✓ Todos los estudiantes están en el classroom DEFAULT'; + RAISE NOTICE ' El admin puede reasignarlos desde la UI'; + ELSE + RAISE WARNING '⚠ Hay membresías en otros classrooms'; + END IF; +END $$; + +-- ===================================================== +-- Listado de estudiantes asignados +-- ===================================================== + +DO $$ +DECLARE + member_record RECORD; + total_students INTEGER := 0; BEGIN RAISE NOTICE ''; - RAISE NOTICE 'Verificación de counts actualizados:'; + RAISE NOTICE 'Estudiantes asignados al classroom DEFAULT:'; RAISE NOTICE '========================================'; - FOR classroom_record IN - SELECT name, code, current_students_count - FROM social_features.classrooms - WHERE metadata->>'demo_classroom' = 'true' - AND current_students_count > 0 - ORDER BY name + FOR member_record IN + SELECT + p.display_name, + p.email, + cm.enrollment_date, + cm.status + FROM social_features.classroom_members cm + JOIN auth_management.profiles p ON p.id = cm.student_id + JOIN social_features.classrooms c ON c.id = cm.classroom_id + WHERE c.code = 'DEFAULT' + ORDER BY p.display_name + LIMIT 20 -- Limitar output para no saturar LOOP - RAISE NOTICE ' % (%): % estudiantes', - classroom_record.name, - classroom_record.code, - classroom_record.current_students_count; + RAISE NOTICE ' - % <%> [%]', + member_record.display_name, + member_record.email, + member_record.status; + total_students := total_students + 1; END LOOP; + IF total_students = 0 THEN + RAISE NOTICE ' (No hay estudiantes asignados aún)'; + ELSIF total_students = 20 THEN + RAISE NOTICE ' ... (mostrando primeros 20)'; + END IF; + RAISE NOTICE '========================================'; - RAISE NOTICE '✓ Counts actualizados correctamente'; END $$; diff --git a/projects/gamilit/apps/database/seeds/prod/social_features/04-friendships.sql b/projects/gamilit/apps/database/seeds/prod/social_features/04-friendships.sql index 3e0dc4b..0b1db31 100644 --- a/projects/gamilit/apps/database/seeds/prod/social_features/04-friendships.sql +++ b/projects/gamilit/apps/database/seeds/prod/social_features/04-friendships.sql @@ -1,84 +1,87 @@ -- ===================================================== --- Seed: social_features.friendships (PROD) - v1.1 --- Description: Relaciones de amistad entre estudiantes demo +-- Seed: social_features.friendships (PROD) - v1.2 +-- Description: Relaciones de amistad entre usuarios de producción -- Environment: PRODUCTION --- Dependencies: auth_management.profiles +-- Dependencies: auth_management.profiles (usuarios de producción) -- Order: 04 -- Created: 2025-11-15 --- Updated: 2025-11-15 (v1.1 - Corregido para schema actual) --- Version: 1.1 +-- Updated: 2025-12-14 (v1.2 - Ajustado al DDL actual, usa usuarios prod) +-- Version: 1.2 -- ===================================================== -- --- CAMBIOS v1.1: --- - Eliminada columna accepted_at (no existe en DDL actual) --- - Ajustado para usar solo columnas disponibles +-- CAMBIOS v1.2: +-- - Eliminadas columnas status y updated_at (no existen en DDL) +-- - Cambiado a usar UUIDs de usuarios de producción reales +-- - El DDL actual solo tiene: id, user_id, friend_id, created_at +-- +-- NOTA: En este modelo simplificado, una entrada en friendships +-- significa que la amistad está ACEPTADA. Las solicitudes pendientes +-- se manejarían en una tabla separada (friend_requests) si se necesita. -- -- FRIENDSHIPS INCLUIDOS: --- - 10 relaciones bidireccionales entre estudiantes --- - 3 friend_requests pendientes +-- - Relaciones bidireccionales entre usuarios de producción -- --- TOTAL: 10 friendships + 3 pending requests --- --- IMPORTANTE: Este seed habilita testing completo de: +-- IMPORTANTE: Este seed habilita testing de: -- - /friends page (FriendsPage.tsx) -- - FriendsLeaderboard component --- - Friend requests feature -- ===================================================== SET search_path TO social_features, auth_management, public; -- ===================================================== --- INSERT: Friendships (accepted) +-- INSERT: Friendships entre usuarios de producción -- ===================================================== +-- Usamos los UUIDs reales de los usuarios de producción -INSERT INTO social_features.friendships ( - user_id, - friend_id, - status, - created_at, - updated_at -) VALUES - -- Ana García ↔ María Fernanda (mejores amigas) - ('01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, '03cd6000-282e-6487-d899-40369e49d070'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '15 days', gamilit.now_mexico() - INTERVAL '15 days'), - ('03cd6000-282e-6487-d899-40369e49d070'::uuid, '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '15 days', gamilit.now_mexico() - INTERVAL '15 days'), +DO $$ +DECLARE + user_ids uuid[]; + i INTEGER; + j INTEGER; + friendship_count INTEGER := 0; +BEGIN + -- Obtener IDs de usuarios de producción (excluyendo testing @gamilit.com) + SELECT ARRAY_AGG(id ORDER BY created_at) + INTO user_ids + FROM auth_management.profiles + WHERE email NOT LIKE '%@gamilit.com' + LIMIT 10; - -- Carlos Ramírez ↔ Luis Miguel - ('02bc5f00-182e-5387-c899-3f269d49c06f'::uuid, '04de7000-382e-7587-e899-51469f49e081'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '10 days', gamilit.now_mexico() - INTERVAL '10 days'), - ('04de7000-382e-7587-e899-51469f49e081'::uuid, '02bc5f00-182e-5387-c899-3f269d49c06f'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '10 days', gamilit.now_mexico() - INTERVAL '10 days'), + -- Si hay al menos 2 usuarios, crear friendships + IF array_length(user_ids, 1) >= 2 THEN + -- Crear friendships bidireccionales entre los primeros usuarios + FOR i IN 1..LEAST(array_length(user_ids, 1) - 1, 5) LOOP + FOR j IN (i + 1)..LEAST(array_length(user_ids, 1), i + 2) LOOP + -- Insertar relación bidireccional + INSERT INTO social_features.friendships (user_id, friend_id, created_at) + VALUES (user_ids[i], user_ids[j], gamilit.now_mexico() - (random() * INTERVAL '30 days')) + ON CONFLICT (user_id, friend_id) DO NOTHING; - -- Sofía Martínez ↔ María Fernanda (compañeras avanzadas) - ('05ef8000-482e-8687-f899-62569049f092'::uuid, '03cd6000-282e-6487-d899-40369e49d070'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '8 days', gamilit.now_mexico() - INTERVAL '8 days'), - ('03cd6000-282e-6487-d899-40369e49d070'::uuid, '05ef8000-482e-8687-f899-62569049f092'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '8 days', gamilit.now_mexico() - INTERVAL '8 days'), + INSERT INTO social_features.friendships (user_id, friend_id, created_at) + VALUES (user_ids[j], user_ids[i], gamilit.now_mexico() - (random() * INTERVAL '30 days')) + ON CONFLICT (user_id, friend_id) DO NOTHING; - -- Ana García ↔ Diego Rodríguez - ('01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, '06f09000-582e-9787-0899-73679149010d'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '5 days', gamilit.now_mexico() - INTERVAL '5 days'), - ('06f09000-582e-9787-0899-73679149010d'::uuid, '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '5 days', gamilit.now_mexico() - INTERVAL '5 days'), + friendship_count := friendship_count + 2; + END LOOP; + END LOOP; - -- Valentina Cruz ↔ Isabella Romero - ('07010000-682e-0887-1999-847802491e14'::uuid, '09232000-882e-2087-3119-0a90244931a3'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '12 days', gamilit.now_mexico() - INTERVAL '12 days'), - ('09232000-882e-2087-3119-0a90244931a3'::uuid, '07010000-682e-0887-1999-847802491e14'::uuid, 'accepted', gamilit.now_mexico() - INTERVAL '12 days', gamilit.now_mexico() - INTERVAL '12 days') -ON CONFLICT (user_id, friend_id) DO NOTHING; - --- ===================================================== --- INSERT: Friend Requests (pending) --- ===================================================== - -INSERT INTO social_features.friendships ( - user_id, - friend_id, - status, - created_at, - updated_at -) VALUES - -- Mateo Flores → Sofía Martínez (pending) - ('08121000-782e-1987-2009-9f891349212f'::uuid, '05ef8000-482e-8687-f899-62569049f092'::uuid, 'pending', gamilit.now_mexico() - INTERVAL '2 days', gamilit.now_mexico() - INTERVAL '2 days'), - - -- Sebastián Vargas → Ana García (pending) - ('10343000-982e-3187-4229-1b01354941b4'::uuid, '01ac4f00-082e-4287-b899-2e169c49b05e'::uuid, 'pending', gamilit.now_mexico() - INTERVAL '1 day', gamilit.now_mexico() - INTERVAL '1 day'), - - -- Camila Ortiz → María Fernanda (pending) - ('11454000-092e-4287-5339-2c12464951c5'::uuid, '03cd6000-282e-6487-d899-40369e49d070'::uuid, 'pending', gamilit.now_mexico() - INTERVAL '3 hours', gamilit.now_mexico() - INTERVAL '3 hours') -ON CONFLICT (user_id, friend_id) DO NOTHING; + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'FRIENDSHIPS CREADOS'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Relaciones creadas: %', friendship_count; + RAISE NOTICE 'Usuarios disponibles: %', array_length(user_ids, 1); + RAISE NOTICE '========================================'; + ELSE + RAISE NOTICE ''; + RAISE NOTICE '========================================'; + RAISE NOTICE 'FRIENDSHIPS: Sin usuarios suficientes'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Se necesitan al menos 2 usuarios de producción'; + RAISE NOTICE 'Usuarios encontrados: %', COALESCE(array_length(user_ids, 1), 0); + RAISE NOTICE '========================================'; + END IF; +END $$; -- ===================================================== -- Verification @@ -86,39 +89,26 @@ ON CONFLICT (user_id, friend_id) DO NOTHING; DO $$ DECLARE - accepted_count INTEGER; - pending_count INTEGER; total_count INTEGER; BEGIN - SELECT COUNT(*) INTO accepted_count - FROM social_features.friendships - WHERE status = 'accepted'; - - SELECT COUNT(*) INTO pending_count - FROM social_features.friendships - WHERE status = 'pending'; - - total_count := accepted_count + pending_count; + SELECT COUNT(*) INTO total_count + FROM social_features.friendships; RAISE NOTICE ''; RAISE NOTICE '========================================'; - RAISE NOTICE 'FRIENDSHIPS CREADOS'; + RAISE NOTICE 'VERIFICACIÓN FRIENDSHIPS'; RAISE NOTICE '========================================'; - RAISE NOTICE 'Friendships aceptados: %', accepted_count; - RAISE NOTICE 'Friend requests pendientes: %', pending_count; - RAISE NOTICE 'Total: %', total_count; + RAISE NOTICE 'Total friendships en tabla: %', total_count; RAISE NOTICE '========================================'; - IF accepted_count >= 10 AND pending_count >= 3 THEN + IF total_count > 0 THEN RAISE NOTICE '✓ Friendships creados correctamente'; RAISE NOTICE ''; RAISE NOTICE 'Features habilitadas:'; RAISE NOTICE ' - /friends page (FriendsPage.tsx)'; RAISE NOTICE ' - FriendsLeaderboard component'; - RAISE NOTICE ' - Friend requests system'; ELSE - RAISE WARNING '⚠ Se esperaban al menos 10 accepted y 3 pending'; - RAISE WARNING 'Actual: % accepted, % pending', accepted_count, pending_count; + RAISE NOTICE '⚠ No hay friendships (normal si no hay usuarios prod)'; END IF; RAISE NOTICE ''; diff --git a/projects/gamilit/apps/database/seeds/staging/gamification_system/02-achievements.sql b/projects/gamilit/apps/database/seeds/staging/gamification_system/02-achievements.sql index d5ec53b..671ce4e 100644 --- a/projects/gamilit/apps/database/seeds/staging/gamification_system/02-achievements.sql +++ b/projects/gamilit/apps/database/seeds/staging/gamification_system/02-achievements.sql @@ -1,20 +1,36 @@ -- ===================================================== --- Seed Data: Achievements (STAGING) +-- Seed: gamification_system.achievements (PROD) +-- Description: Logros y achievements demo para testing y producci�n +-- Environment: PRODUCTION +-- Dependencies: gamification_system.achievement_categories +-- Order: 04 +-- Created: 2025-01-11 +-- Version: 1.0 -- ===================================================== --- Description: Logros esenciales para demostración --- Environment: STAGING (configuración + demo data) --- Records: 15 (solo logros esenciales) --- Date: 2025-11-02 --- Migrated by: SA-SEEDS-GAM-01 +-- +-- ACHIEVEMENTS INCLUIDOS: +-- - Progress (5): Primeros pasos, ejercicios completados, progreso +-- - Streak (3): Rachas de d�as consecutivos +-- - Completion (4): Completaci�n de m�dulos +-- - Mastery (3): Dominio y maestr�a +-- - Exploration (2): Exploraci�n de contenido +-- - Social (2): Interacci�n social +-- - Special (1): Logro especial +-- +-- TOTAL: 20 achievements demo +-- +-- IMPORTANTE: Estos achievements cubren casos de uso comunes +-- del sistema educativo de comprensi�n lectora GAMILIT. -- ===================================================== -SET search_path TO gamification_system, public; +SET search_path TO gamification_system, educational_content, public; -- ===================================================== --- LOGROS ESENCIALES (15 registros) +-- INSERT: Achievements Demo -- ===================================================== INSERT INTO gamification_system.achievements ( + id, tenant_id, name, description, @@ -24,61 +40,1011 @@ INSERT INTO gamification_system.achievements ( difficulty_level, conditions, rewards, + ml_coins_reward, is_secret, is_active, is_repeatable, order_index, points_value, - ml_coins_reward, + unlock_message, + instructions, + tips, + metadata, created_at, updated_at ) VALUES --- Progreso básico -(NULL, 'Primer Paso', 'Complete tu primer ejercicio', '🎯', 'progress', 'common', 'beginner', '{"type": "exercise_completed", "requirements": {"exercises_count": 1}}', '{"xp": 10, "ml_coins": 50}', false, true, false, 1, 0, 50, NOW(), NOW()), -(NULL, 'Practicante', 'Completa 10 ejercicios', 'clipboard-check', 'progress', 'common', 'beginner', '{"type": "exercise_completed", "requirements": {"exercises_count": 10}}', '{"xp": 30, "ml_coins": 100}', false, true, false, 2, 0, 100, NOW(), NOW()), -(NULL, 'Aprendiz', 'Alcanza 100 XP totales', 'trending-up', 'progress', 'common', 'beginner', '{"type": "xp_milestone", "requirements": {"total_xp": 100}}', '{"xp": 20, "ml_coins": 50}', false, true, false, 3, 0, 50, NOW(), NOW()), -(NULL, 'Sabio', 'Alcanza 1000 XP totales', 'trending-up', 'progress', 'rare', 'intermediate', '{"type": "xp_milestone", "requirements": {"total_xp": 1000}}', '{"xp": 100, "ml_coins": 200}', false, true, false, 4, 0, 200, NOW(), NOW()), - --- Racha -(NULL, 'Racha Inicial', 'Mantén una racha de 3 días consecutivos', 'zap', 'streak', 'common', 'beginner', '{"type": "streak", "requirements": {"days": 3}}', '{"xp": 30, "ml_coins": 75}', false, true, false, 10, 0, 75, NOW(), NOW()), -(NULL, 'Persistente', 'Mantén una racha de 7 días consecutivos', 'zap', 'streak', 'rare', 'beginner', '{"type": "streak", "requirements": {"days": 7}}', '{"xp": 75, "ml_coins": 150}', false, true, false, 11, 0, 150, NOW(), NOW()), - --- Completación -(NULL, 'Detective Novato', 'Completa tu primer módulo completo', '🔍', 'completion', 'common', 'beginner', '{"type": "module_completed", "requirements": {"modules_count": 1}}', '{"xp": 50, "ml_coins": 100}', false, true, false, 20, 0, 100, NOW(), NOW()), -(NULL, 'Perfeccionista Novato', 'Obtén 5 calificaciones perfectas (100%)', 'target', 'completion', 'rare', 'intermediate', '{"type": "perfect_score", "requirements": {"perfect_count": 5}}', '{"xp": 50, "ml_coins": 100}', false, true, false, 21, 0, 100, NOW(), NOW()), - --- Maestría -(NULL, 'Ascenso Maya: BATAB', 'Alcanza el rango BATAB', '🏛️', 'mastery', 'rare', 'beginner', '{"type": "rank_achieved", "requirements": {"rank": "batab"}}', '{"xp": 50, "ml_coins": 100}', false, true, false, 30, 0, 100, NOW(), NOW()), -(NULL, 'Líder HOLCATTE', 'Alcanza el rango HOLCATTE', '🛡️', 'mastery', 'epic', 'beginner', '{"type": "rank_achieved", "requirements": {"rank": "holcatte"}}', '{"xp": 100, "ml_coins": 200}', false, true, false, 31, 0, 200, NOW(), NOW()), - --- Exploración -(NULL, 'Explorador Curioso', 'Completa 10 ejercicios diferentes', '🗺️', 'exploration', 'common', 'beginner', '{"type": "exercise_variety", "requirements": {"unique_exercises": 10}}', '{"xp": 30, "ml_coins": 75}', false, true, false, 40, 0, 75, NOW(), NOW()), - --- Social -(NULL, 'Líder de Equipo', 'Crea un equipo y recluta 5 miembros', '👥', 'social', 'common', 'beginner', '{"type": "team_leader", "requirements": {"team_members": 5}}', '{"xp": 50, "ml_coins": 100}', false, true, false, 50, 0, 100, NOW(), NOW()), - --- Especial -(NULL, 'Madrugador', 'Completa un ejercicio antes de las 6 AM', '🌅', 'special', 'rare', 'beginner', '{"type": "time_based", "requirements": {"hour_before": 6}}', '{"xp": 50, "ml_coins": 100}', false, true, false, 60, 0, 100, NOW(), NOW()), -(NULL, 'Noctámbulo', 'Completa un ejercicio después de las 11 PM', '🌙', 'special', 'rare', 'beginner', '{"type": "time_based", "requirements": {"hour_after": 23}}', '{"xp": 50, "ml_coins": 100}', false, true, false, 61, 0, 100, NOW(), NOW()), -(NULL, 'Coleccionista', 'Desbloquea 10 logros diferentes', '🎖️', 'special', 'rare', 'beginner', '{"type": "achievement_count", "requirements": {"achievements": 10}}', '{"xp": 125, "ml_coins": 250}', false, true, false, 62, 0, 250, NOW(), NOW()); -- ===================================================== --- VERIFICACIÓN +-- CATEGORY: PROGRESS (5 achievements) -- ===================================================== -SELECT - 'Achievements (Staging)' AS seed_name, - COUNT(*) AS total_achievements -FROM gamification_system.achievements; +-- 1. Primeros Pasos +( + '90000001-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, -- Tenant principal + 'Primeros Pasos', + 'Completa tu primer ejercicio de comprensión lectora', + 'footprints', + 'progress'::gamification_system.achievement_category, + 'common', + 'beginner'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exercise_completion', + 'requirements', jsonb_build_object( + 'exercises_completed', 1 + ) + ), + jsonb_build_object( + 'xp', 50, + 'ml_coins', 10, + 'badge', 'first_steps' + ), + 10, + false, + true, + false, + 1, + 50, + '�Felicidades! Has dado tus primeros pasos en tu viaje de aprendizaje.', + 'Completa cualquier ejercicio de comprensi�n lectora para desbloquear este logro.', + ARRAY[ + 'Lee el texto con atenci�n antes de responder', + 'No tengas miedo de equivocarte, es parte del aprendizaje' + ], + jsonb_build_object( + 'achievement_tier', 'starter', + 'demo_achievement', true, + 'module_required', null + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 2. Lector Principiante +( + '90000001-0000-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Lector Principiante', + 'Completa 10 ejercicios de comprensión lectora', + 'book-open', + 'progress'::gamification_system.achievement_category, + 'common', + 'elementary'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exercise_completion', + 'requirements', jsonb_build_object( + 'exercises_completed', 10 + ) + ), + jsonb_build_object( + 'xp', 100, + 'ml_coins', 25, + 'badge', 'beginner_reader' + ), + 25, + false, + true, + false, + 2, + 100, + '�Excelente! Ya eres un lector principiante. �Sigue as�!', + 'Completa 10 ejercicios de comprensi�n lectora en cualquier m�dulo.', + ARRAY[ + 'Practica diferentes tipos de textos', + 'Lee con atenci�n los detalles' + ], + jsonb_build_object( + 'achievement_tier', 'bronze', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 3. Lector Experimentado +( + '90000001-0000-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Lector Experimentado', + 'Completa 50 ejercicios de comprensión lectora', + 'book-open', + 'progress'::gamification_system.achievement_category, + 'rare', + 'pre_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exercise_completion', + 'requirements', jsonb_build_object( + 'exercises_completed', 50 + ) + ), + jsonb_build_object( + 'xp', 250, + 'ml_coins', 75, + 'badge', 'experienced_reader' + ), + 75, + false, + true, + false, + 3, + 250, + '�Impresionante! Tu experiencia como lector est� creciendo enormemente.', + 'Completa 50 ejercicios de comprensi�n lectora.', + ARRAY[ + 'Var�a los tipos de ejercicios', + 'Intenta ejercicios m�s dif�ciles' + ], + jsonb_build_object( + 'achievement_tier', 'silver', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 4. Lector Experto +( + '90000001-0000-0000-0000-000000000004'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Lector Experto', + 'Completa 100 ejercicios de comprensi�n lectora', + 'footprints', + 'progress'::gamification_system.achievement_category, + 'epic', + 'upper_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exercise_completion', + 'requirements', jsonb_build_object( + 'exercises_completed', 100 + ) + ), + jsonb_build_object( + 'xp', 500, + 'ml_coins', 150, + 'badge', 'expert_reader' + ), + 150, + false, + true, + false, + 4, + 500, + '�Extraordinario! Has alcanzado el nivel de lector experto.', + 'Completa 100 ejercicios de comprensi�n lectora.', + ARRAY[ + 'Mant�n tu constancia', + 'Ayuda a otros estudiantes' + ], + jsonb_build_object( + 'achievement_tier', 'gold', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 5. Maestro de la Lectura +( + '90000001-0000-0000-0000-000000000005'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Maestro de la Lectura', + 'Completa 200 ejercicios de comprensi�n lectora', + 'graduation-cap', + 'progress'::gamification_system.achievement_category, + 'legendary', + 'proficient'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exercise_completion', + 'requirements', jsonb_build_object( + 'exercises_completed', 200 + ) + ), + jsonb_build_object( + 'xp', 1000, + 'ml_coins', 300, + 'badge', 'reading_master' + ), + 300, + false, + true, + false, + 5, + 1000, + '�LEGENDARIO! Te has convertido en un verdadero Maestro de la Lectura.', + 'Completa 200 ejercicios de comprensi�n lectora.', + ARRAY[ + 'Eres un ejemplo para todos', + 'Tu dedicaci�n es inspiradora' + ], + jsonb_build_object( + 'achievement_tier', 'legendary', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), -- ===================================================== --- MIGRATION NOTES +-- CATEGORY: STREAK (3 achievements) -- ===================================================== --- ENVIRONMENT: STAGING --- CORRECCIONES APLICADAS: --- 1. UUIDs autogenerados (gen_random_uuid) en lugar de hardcodeados --- 2. Fechas dinámicas (NOW()) en lugar de hardcodeadas --- 3. Solo 15 logros esenciales (vs 37 en dev) --- 4. Sin IDs explícitos (permite autogeneración) --- 5. Sin ON CONFLICT (permite inserción limpia) + +-- 6. Racha de 3 D�as +( + '90000002-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Racha de 3 D�as', + 'Mant�n una racha de 3 d�as consecutivos practicando', + 'flame', + 'streak'::gamification_system.achievement_category, + 'common', + 'elementary'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'streak', + 'requirements', jsonb_build_object( + 'consecutive_days', 3 + ) + ), + jsonb_build_object( + 'xp', 75, + 'ml_coins', 20, + 'badge', 'streak_3' + ), + 20, + false, + true, + false, + 10, + 75, + '�Genial! Has mantenido tu racha por 3 d�as. �La constancia es clave!', + 'Practica al menos un ejercicio durante 3 d�as consecutivos.', + ARRAY[ + 'Establece un horario diario para practicar', + 'Aunque sea un ejercicio corto, mant�n la racha' + ], + jsonb_build_object( + 'streak_milestone', 3, + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 7. Racha de 7 D�as +( + '90000002-0000-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Racha de 7 D�as', + 'Mant�n una racha de 7 d�as consecutivos practicando', + 'flame', + 'streak'::gamification_system.achievement_category, + 'rare', + 'pre_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'streak', + 'requirements', jsonb_build_object( + 'consecutive_days', 7 + ) + ), + jsonb_build_object( + 'xp', 150, + 'ml_coins', 50, + 'badge', 'streak_7' + ), + 50, + false, + true, + false, + 11, + 150, + '�Incre�ble! Una semana completa de pr�ctica. �Tu dedicaci�n es admirable!', + 'Practica al menos un ejercicio durante 7 d�as consecutivos.', + ARRAY[ + 'Ya has creado un h�bito s�lido', + 'Sigue as� para alcanzar rachas m�s largas' + ], + jsonb_build_object( + 'streak_milestone', 7, + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 8. Racha de 30 D�as +( + '90000002-0000-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Racha de 30 D�as', + 'Mant�n una racha de 30 d�as consecutivos practicando', + 'flame', + 'streak'::gamification_system.achievement_category, + 'epic', + 'proficient'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'streak', + 'requirements', jsonb_build_object( + 'consecutive_days', 30 + ) + ), + jsonb_build_object( + 'xp', 500, + 'ml_coins', 200, + 'badge', 'streak_30' + ), + 200, + false, + true, + false, + 12, + 500, + '��PICO! 30 d�as de racha. Tu compromiso con el aprendizaje es extraordinario.', + 'Practica al menos un ejercicio durante 30 d�as consecutivos.', + ARRAY[ + 'Has desarrollado un h�bito excepcional', + 'Eres un modelo de constancia' + ], + jsonb_build_object( + 'streak_milestone', 30, + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + -- ===================================================== +-- CATEGORY: COMPLETION (4 achievements) +-- ===================================================== + +-- 9. M�dulo 1 Completado +( + '90000003-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Comprensi�n Literal Dominada', + 'Completa todos los ejercicios del M�dulo 1: Comprensi�n Literal', + 'brain', + 'completion'::gamification_system.achievement_category, + 'rare', + 'pre_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'module_completion', + 'requirements', jsonb_build_object( + 'module_id', 'modulo-01-comprension-literal', + 'completion_percentage', 100 + ) + ), + jsonb_build_object( + 'xp', 200, + 'ml_coins', 100, + 'badge', 'module_1_complete' + ), + 100, + false, + true, + false, + 20, + 200, + '�Felicidades! Has dominado la Comprensi�n Literal. �Sigue adelante!', + 'Completa todos los ejercicios del M�dulo 1 con al menos 60% de aciertos.', + ARRAY[ + 'Identifica informaci�n expl�cita en los textos', + 'Presta atenci�n a los detalles' + ], + jsonb_build_object( + 'module', 'M�DULO 1: Comprensi�n Literal', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 10. M�dulo 2 Completado +( + '90000003-0000-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Comprensi�n Inferencial Dominada', + 'Completa todos los ejercicios del M�dulo 2: Comprensi�n Inferencial', + 'brain', + 'completion'::gamification_system.achievement_category, + 'rare', + 'pre_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'module_completion', + 'requirements', jsonb_build_object( + 'module_id', 'modulo-02-comprension-inferencial', + 'completion_percentage', 100 + ) + ), + jsonb_build_object( + 'xp', 250, + 'ml_coins', 125, + 'badge', 'module_2_complete' + ), + 125, + false, + true, + false, + 21, + 250, + '�Excelente! Has dominado la Comprensi�n Inferencial. Tu habilidad crece.', + 'Completa todos los ejercicios del M�dulo 2 con al menos 60% de aciertos.', + ARRAY[ + 'Lee entre l�neas para encontrar significados impl�citos', + 'Usa tu conocimiento previo para hacer inferencias' + ], + jsonb_build_object( + 'module', 'M�DULO 2: Comprensi�n Inferencial', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 11. M�dulo 3 Completado +( + '90000003-0000-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Comprensi�n Cr�tica Dominada', + 'Completa todos los ejercicios del M�dulo 3: Comprensi�n Cr�tica', + 'brain', + 'completion'::gamification_system.achievement_category, + 'epic', + 'upper_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'module_completion', + 'requirements', jsonb_build_object( + 'module_id', 'modulo-03-comprension-critica', + 'completion_percentage', 100 + ) + ), + jsonb_build_object( + 'xp', 300, + 'ml_coins', 150, + 'badge', 'module_3_complete' + ), + 150, + false, + true, + false, + 22, + 300, + '�Impresionante! Has dominado la Comprensi�n Cr�tica. Tu pensamiento es agudo.', + 'Completa todos los ejercicios del M�dulo 3 con al menos 60% de aciertos.', + ARRAY[ + 'Eval�a la calidad y veracidad de la informaci�n', + 'Desarrolla tu pensamiento cr�tico' + ], + jsonb_build_object( + 'module', 'M�DULO 3: Comprensi�n Cr�tica', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 12. Todos los M�dulos Completados +( + '90000003-0000-0000-0000-000000000004'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Completista Total', + 'Completa todos los m�dulos del sistema', + 'trophy', + 'completion'::gamification_system.achievement_category, + 'legendary', + 'proficient'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'all_modules_completion', + 'requirements', jsonb_build_object( + 'modules_completed', 5, + 'min_score_average', 70 + ) + ), + jsonb_build_object( + 'xp', 1000, + 'ml_coins', 500, + 'badge', 'completionist' + ), + 500, + false, + true, + false, + 23, + 1000, + '�LEGENDARIO! Has completado todos los m�dulos. Eres un verdadero completista.', + 'Completa los 5 m�dulos del sistema con promedio de 70% o superior.', + ARRAY[ + 'Tu dedicaci�n es ejemplar', + 'Has alcanzado el nivel m�s alto' + ], + jsonb_build_object( + 'achievement_tier', 'ultimate', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- ===================================================== +-- CATEGORY: MASTERY (3 achievements) +-- ===================================================== + +-- 13. Perfeccionista +( + '90000004-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Perfeccionista', + 'Obt�n 100% de aciertos en 10 ejercicios', + 'target', + 'mastery'::gamification_system.achievement_category, + 'rare', + 'upper_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'perfect_score', + 'requirements', jsonb_build_object( + 'perfect_exercises', 10, + 'score_required', 100 + ) + ), + jsonb_build_object( + 'xp', 300, + 'ml_coins', 150, + 'badge', 'perfectionist' + ), + 150, + false, + true, + false, + 30, + 300, + '�Perfecto! Tu precisi�n es admirable. 10 ejercicios perfectos.', + 'Obt�n 100% de aciertos en 10 ejercicios diferentes.', + ARRAY[ + 'Lee cuidadosamente antes de responder', + 'Revisa tus respuestas antes de enviar' + ], + jsonb_build_object( + 'mastery_type', 'perfect_score', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 14. Experto en Inferencias +( + '90000004-0000-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Experto en Inferencias', + 'Completa 20 ejercicios de inferencia con 90% o m�s de aciertos', + 'brain', + 'mastery'::gamification_system.achievement_category, + 'epic', + 'upper_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'skill_mastery', + 'requirements', jsonb_build_object( + 'skill_type', 'inferencial', + 'exercises_completed', 20, + 'min_score', 90 + ) + ), + jsonb_build_object( + 'xp', 400, + 'ml_coins', 200, + 'badge', 'inference_expert' + ), + 200, + false, + true, + false, + 31, + 400, + '�Extraordinario! Eres un experto en hacer inferencias. Tu comprensi�n es profunda.', + 'Completa 20 ejercicios de comprensi�n inferencial con 90% o m�s.', + ARRAY[ + 'Conecta la informaci�n del texto con tu conocimiento', + 'Busca pistas en el contexto' + ], + jsonb_build_object( + 'mastery_type', 'skill_expert', + 'skill', 'inferencial', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 15. Cr�tico Avanzado +( + '90000004-0000-0000-0000-000000000003'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Cr�tico Avanzado', + 'Completa 20 ejercicios de pensamiento cr�tico con 90% o m�s', + 'footprints', + 'mastery'::gamification_system.achievement_category, + 'epic', + 'proficient'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'skill_mastery', + 'requirements', jsonb_build_object( + 'skill_type', 'critico', + 'exercises_completed', 20, + 'min_score', 90 + ) + ), + jsonb_build_object( + 'xp', 500, + 'ml_coins', 250, + 'badge', 'critical_thinker' + ), + 250, + false, + true, + false, + 32, + 500, + '��PICO! Tu pensamiento cr�tico es de nivel avanzado. Sobresaliente.', + 'Completa 20 ejercicios de comprensi�n cr�tica con 90% o m�s.', + ARRAY[ + 'Eval�a argumentos y evidencias', + 'Cuestiona y analiza la informaci�n' + ], + jsonb_build_object( + 'mastery_type', 'skill_expert', + 'skill', 'critico', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- ===================================================== +-- CATEGORY: EXPLORATION (2 achievements) +-- ===================================================== + +-- 16. Explorador Curioso +( + '90000005-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Explorador Curioso', + 'Explora al menos 3 m�dulos diferentes', + 'compass', + 'exploration'::gamification_system.achievement_category, + 'common', + 'elementary'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exploration', + 'requirements', jsonb_build_object( + 'different_modules', 3, + 'min_exercises_per_module', 1 + ) + ), + jsonb_build_object( + 'xp', 100, + 'ml_coins', 50, + 'badge', 'curious_explorer' + ), + 50, + false, + true, + false, + 40, + 100, + '�Genial! Tu curiosidad te ha llevado a explorar diferentes m�dulos.', + 'Completa al menos un ejercicio en 3 m�dulos diferentes.', + ARRAY[ + 'Var�a tus actividades de aprendizaje', + 'Descubre nuevos tipos de textos' + ], + jsonb_build_object( + 'exploration_type', 'module_variety', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 17. Aventurero del Conocimiento +( + '90000005-0000-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Aventurero del Conocimiento', + 'Completa ejercicios de todos los niveles de dificultad', + 'compass', + 'exploration'::gamification_system.achievement_category, + 'rare', + 'pre_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'exploration', + 'requirements', jsonb_build_object( + 'difficulty_levels', jsonb_build_array('beginner', 'elementary', 'pre_intermediate', 'upper_intermediate'), + 'min_exercises_per_level', 2 + ) + ), + jsonb_build_object( + 'xp', 200, + 'ml_coins', 100, + 'badge', 'knowledge_adventurer' + ), + 100, + false, + true, + false, + 41, + 200, + '�Incre�ble! Has explorado todos los niveles de dificultad. Eres un verdadero aventurero.', + 'Completa al menos 2 ejercicios de cada nivel de dificultad.', + ARRAY[ + 'Reta tus l�mites con ejercicios dif�ciles', + 'La variedad enriquece tu aprendizaje' + ], + jsonb_build_object( + 'exploration_type', 'difficulty_variety', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- ===================================================== +-- CATEGORY: SOCIAL (2 achievements) +-- ===================================================== + +-- 18. Compa�ero de Aula +( + '90000006-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Compa�ero de Aula', + '�nete a tu primera aula virtual', + 'users', + 'social'::gamification_system.achievement_category, + 'common', + 'beginner'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'social', + 'requirements', jsonb_build_object( + 'classrooms_joined', 1 + ) + ), + jsonb_build_object( + 'xp', 50, + 'ml_coins', 25, + 'badge', 'classroom_member' + ), + 25, + false, + true, + false, + 50, + 50, + '�Bienvenido! Te has unido a tu primera aula. El aprendizaje colaborativo comienza.', + '�nete a un aula virtual para desbloquear este logro.', + ARRAY[ + 'Colabora con tus compa�eros', + 'Aprende de las experiencias de otros' + ], + jsonb_build_object( + 'social_type', 'classroom_join', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- 19. Estudiante Colaborativo +( + '90000006-0000-0000-0000-000000000002'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Estudiante Colaborativo', + 'Participa en 5 actividades sociales (aulas, desaf�os, etc.)', + 'handshake', + 'social'::gamification_system.achievement_category, + 'rare', + 'pre_intermediate'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'social', + 'requirements', jsonb_build_object( + 'social_activities', 5 + ) + ), + jsonb_build_object( + 'xp', 150, + 'ml_coins', 75, + 'badge', 'collaborative_student' + ), + 75, + false, + true, + false, + 51, + 150, + '�Excelente! Tu participaci�n social es notable. Sigues creciendo con otros.', + 'Participa en 5 actividades sociales (unirte a aulas, aceptar desaf�os, etc.).', + ARRAY[ + 'El aprendizaje es m�s rico cuando es social', + 'Comparte tus logros con otros' + ], + jsonb_build_object( + 'social_type', 'social_participation', + 'demo_achievement', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +), + +-- ===================================================== +-- CATEGORY: SPECIAL (1 achievement) +-- ===================================================== + +-- 20. Primera Visita +( + '90000007-0000-0000-0000-000000000001'::uuid, + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, + 'Primera Visita', + 'Inicia sesi�n por primera vez en GAMILIT', + 'footprints', + 'special'::gamification_system.achievement_category, + 'common', + 'beginner'::educational_content.difficulty_level, + jsonb_build_object( + 'type', 'special', + 'requirements', jsonb_build_object( + 'first_login', true + ) + ), + jsonb_build_object( + 'xp', 25, + 'ml_coins', 10, + 'badge', 'first_visit' + ), + 10, + false, + true, + false, + 60, + 25, + '�Bienvenido a GAMILIT! Este es el comienzo de tu aventura de aprendizaje.', + 'Este logro se desbloquea autom�ticamente al iniciar sesi�n por primera vez.', + ARRAY[ + 'Explora la plataforma', + 'Comienza con ejercicios f�ciles' + ], + jsonb_build_object( + 'special_type', 'welcome', + 'demo_achievement', true, + 'auto_unlock', true + ), + gamilit.now_mexico(), + gamilit.now_mexico() +) + +ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + icon = EXCLUDED.icon, + category = EXCLUDED.category, + rarity = EXCLUDED.rarity, + difficulty_level = EXCLUDED.difficulty_level, + conditions = EXCLUDED.conditions, + rewards = EXCLUDED.rewards, + ml_coins_reward = EXCLUDED.ml_coins_reward, + is_secret = EXCLUDED.is_secret, + is_active = EXCLUDED.is_active, + is_repeatable = EXCLUDED.is_repeatable, + order_index = EXCLUDED.order_index, + points_value = EXCLUDED.points_value, + unlock_message = EXCLUDED.unlock_message, + instructions = EXCLUDED.instructions, + tips = EXCLUDED.tips, + metadata = EXCLUDED.metadata, + updated_at = gamilit.now_mexico(); + +-- ===================================================== +-- Verification Query +-- ===================================================== + +DO $$ +DECLARE + achievement_count INTEGER; + progress_count INTEGER; + streak_count INTEGER; + completion_count INTEGER; + mastery_count INTEGER; + exploration_count INTEGER; + social_count INTEGER; + special_count INTEGER; +BEGIN + SELECT COUNT(*) INTO achievement_count + FROM gamification_system.achievements + WHERE metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO progress_count + FROM gamification_system.achievements + WHERE category = 'progress' AND metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO streak_count + FROM gamification_system.achievements + WHERE category = 'streak' AND metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO completion_count + FROM gamification_system.achievements + WHERE category = 'completion' AND metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO mastery_count + FROM gamification_system.achievements + WHERE category = 'mastery' AND metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO exploration_count + FROM gamification_system.achievements + WHERE category = 'exploration' AND metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO social_count + FROM gamification_system.achievements + WHERE category = 'social' AND metadata->>'demo_achievement' = 'true'; + + SELECT COUNT(*) INTO special_count + FROM gamification_system.achievements + WHERE category = 'special' AND metadata->>'demo_achievement' = 'true'; + + RAISE NOTICE '========================================'; + RAISE NOTICE 'ACHIEVEMENTS DEMO CREADOS EXITOSAMENTE'; + RAISE NOTICE '========================================'; + RAISE NOTICE 'Total achievements: %', achievement_count; + RAISE NOTICE ' - Progress: %', progress_count; + RAISE NOTICE ' - Streak: %', streak_count; + RAISE NOTICE ' - Completion: %', completion_count; + RAISE NOTICE ' - Mastery: %', mastery_count; + RAISE NOTICE ' - Exploration: %', exploration_count; + RAISE NOTICE ' - Social: %', social_count; + RAISE NOTICE ' - Special: %', special_count; + RAISE NOTICE '========================================'; + + IF achievement_count = 20 THEN + RAISE NOTICE ' Todos los achievements demo fueron creados correctamente'; + ELSE + RAISE WARNING '� Se esperaban 20 achievements, se crearon %', achievement_count; + END IF; +END $$; + +-- ===================================================== +-- Listado de achievements por categor�a +-- ===================================================== + +DO $$ +DECLARE + achievement_record RECORD; + current_category TEXT := ''; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Listado de achievements demo:'; + RAISE NOTICE '========================================'; + + FOR achievement_record IN + SELECT + name, + category, + rarity, + difficulty_level, + points_value, + ml_coins_reward + FROM gamification_system.achievements + WHERE metadata->>'demo_achievement' = 'true' + ORDER BY category, order_index + LOOP + IF current_category != achievement_record.category THEN + current_category := achievement_record.category; + RAISE NOTICE ''; + RAISE NOTICE '=== % ===', UPPER(current_category); + END IF; + + RAISE NOTICE ' - % [%/%]', + achievement_record.name, + achievement_record.rarity, + achievement_record.difficulty_level; + RAISE NOTICE ' Puntos: % | ML Coins: %', + achievement_record.points_value, + achievement_record.ml_coins_reward; + END LOOP; + + RAISE NOTICE ''; + RAISE NOTICE '========================================'; +END $$; diff --git a/projects/gamilit/apps/frontend/src/App.tsx b/projects/gamilit/apps/frontend/src/App.tsx index 4e9b2da..1f6d272 100644 --- a/projects/gamilit/apps/frontend/src/App.tsx +++ b/projects/gamilit/apps/frontend/src/App.tsx @@ -38,7 +38,8 @@ import TeacherGamificationPage from '@/apps/teacher/pages/TeacherGamificationPag import TeacherMonitoringPage from '@/apps/teacher/pages/TeacherMonitoringPage'; import TeacherProgressPage from '@/apps/teacher/pages/TeacherProgressPage'; import TeacherReportsPage from '@/apps/teacher/pages/TeacherReportsPage'; -import TeacherResourcesPage from '@/apps/teacher/pages/TeacherResourcesPage'; +// FASE 6A: TeacherResourcesPage removido - ruta redirigida a dashboard +// import TeacherResourcesPage from '@/apps/teacher/pages/TeacherResourcesPage'; import TeacherClassesPage from '@/apps/teacher/pages/TeacherClassesPage'; import TeacherStudentsPage from '@/apps/teacher/pages/TeacherStudentsPage'; import TeacherExerciseResponsesPage from '@/apps/teacher/pages/TeacherExerciseResponsesPage'; @@ -221,13 +222,10 @@ function App() { } /> + {/* FASE 6A: /teacher/resources redirige a dashboard (placeholder sin funcionalidad) */} - - - } + element={} /> = ({ onResolve, onSuppress, }) => { - const getSeverityColor = (severity: SystemAlertSeverity): string => { - const colors: Record = { - critical: 'bg-red-500 text-white', - high: 'bg-orange-500 text-white', - medium: 'bg-yellow-500 text-gray-900', - low: 'bg-blue-500 text-white', - }; - return colors[severity]; - }; - - const getStatusColor = (status: SystemAlertStatus): string => { - const colors: Record = { - open: 'bg-red-500/20 text-red-400 border-red-500/50', - acknowledged: 'bg-orange-500/20 text-orange-400 border-orange-500/50', - resolved: 'bg-green-500/20 text-green-400 border-green-500/50', - suppressed: 'bg-gray-500/20 text-gray-400 border-gray-500/50', - }; - return colors[status]; - }; - - const getSeverityLabel = (severity: SystemAlertSeverity): string => { - const labels: Record = { - critical: 'Crítica', - high: 'Alta', - medium: 'Media', - low: 'Baja', - }; - return labels[severity]; - }; - - const getStatusLabel = (status: SystemAlertStatus): string => { - const labels: Record = { - open: 'Abierto', - acknowledged: 'Reconocido', - resolved: 'Resuelto', - suppressed: 'Suprimido', - }; - return labels[status]; - }; - - const formatDate = (dateString: string): string => { - const date = new Date(dateString); - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); - const diffMins = Math.floor(diffMs / 60000); - const diffHours = Math.floor(diffMs / 3600000); - const diffDays = Math.floor(diffMs / 86400000); - - if (diffMins < 60) { - return `Hace ${diffMins} min`; - } else if (diffHours < 24) { - return `Hace ${diffHours} horas`; - } else if (diffDays < 7) { - return `Hace ${diffDays} días`; - } else { - return date.toLocaleDateString('es-ES', { - day: '2-digit', - month: 'short', - year: 'numeric', - }); - } - }; - + // Utility functions imported from alertUtils.ts const canAcknowledge = alert.status === 'open'; const canResolve = alert.status === 'open' || alert.status === 'acknowledged'; const canSuppress = alert.status !== 'suppressed' && alert.status !== 'resolved'; @@ -143,7 +84,7 @@ export const AlertCard: React.FC = ({
- {formatDate(alert.triggered_at)} + {formatAlertTimestampDetailed(alert.triggered_at)}
diff --git a/projects/gamilit/apps/frontend/src/apps/admin/components/alerts/alertUtils.ts b/projects/gamilit/apps/frontend/src/apps/admin/components/alerts/alertUtils.ts new file mode 100644 index 0000000..ae7288d --- /dev/null +++ b/projects/gamilit/apps/frontend/src/apps/admin/components/alerts/alertUtils.ts @@ -0,0 +1,143 @@ +/** + * Alert Utilities + * + * Shared utility functions for alert components. + * Used by AlertCard and AlertasTab to ensure consistency. + * + * @module alertUtils + * @date 2025-12-15 + */ + +import type { SystemAlertSeverity, SystemAlertStatus } from '@/services/api/adminTypes'; + +/** + * Get severity badge color classes (solid background + text) + * Used by AlertCard for prominent severity badges + */ +export function getSeverityColor(severity: SystemAlertSeverity): string { + const colors: Record = { + critical: 'bg-red-500 text-white', + high: 'bg-orange-500 text-white', + medium: 'bg-yellow-500 text-gray-900', + low: 'bg-blue-500 text-white', + }; + return colors[severity]; +} + +/** + * Get severity color with transparent background and border + * Used by AlertasTab for more subtle severity badges + */ +export function getSeverityColorWithBorder(severity: SystemAlertSeverity): string { + const colors: Record = { + critical: 'bg-red-500/20 text-red-400 border-red-500/50', + high: 'bg-orange-500/20 text-orange-400 border-orange-500/50', + medium: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50', + low: 'bg-blue-500/20 text-blue-400 border-blue-500/50', + }; + return colors[severity]; +} + +/** + * Get status color classes (transparent bg with border) + * Used by AlertCard for status badges + */ +export function getStatusColor(status: SystemAlertStatus): string { + const colors: Record = { + open: 'bg-red-500/20 text-red-400 border-red-500/50', + acknowledged: 'bg-orange-500/20 text-orange-400 border-orange-500/50', + resolved: 'bg-green-500/20 text-green-400 border-green-500/50', + suppressed: 'bg-gray-500/20 text-gray-400 border-gray-500/50', + }; + return colors[status]; +} + +/** + * Get status text color only (for inline text without background) + * Used by AlertasTab for status labels + */ +export function getStatusTextColor(status: SystemAlertStatus): string { + const colors: Record = { + open: 'text-red-400', + acknowledged: 'text-yellow-400', + resolved: 'text-green-400', + suppressed: 'text-gray-400', + }; + return colors[status]; +} + +/** + * Get severity label in Spanish + */ +export function getSeverityLabel(severity: SystemAlertSeverity): string { + const labels: Record = { + critical: 'Critica', + high: 'Alta', + medium: 'Media', + low: 'Baja', + }; + return labels[severity]; +} + +/** + * Get status label in Spanish + */ +export function getStatusLabel(status: SystemAlertStatus): string { + const labels: Record = { + open: 'Abierta', + acknowledged: 'Reconocida', + resolved: 'Resuelta', + suppressed: 'Suprimida', + }; + return labels[status]; +} + +/** + * Format timestamp for compact relative display + * Used by AlertasTab (e.g., "Hace 5m", "Hace 2h") + */ +export function formatAlertTimestamp(timestamp: string): string { + const date = new Date(timestamp); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const minutes = Math.floor(diff / (1000 * 60)); + const hours = Math.floor(diff / (1000 * 60 * 60)); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + + if (minutes < 60) { + return `Hace ${minutes}m`; + } else if (hours < 24) { + return `Hace ${hours}h`; + } else if (days < 7) { + return `Hace ${days}d`; + } else { + return date.toLocaleDateString('es-ES', { month: 'short', day: 'numeric' }); + } +} + +/** + * Format timestamp with more detail + * Used by AlertCard (e.g., "Hace 5 min", "Hace 2 horas", "Hace 3 dias") + */ +export function formatAlertTimestampDetailed(timestamp: string): string { + const date = new Date(timestamp); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMs / 3600000); + const diffDays = Math.floor(diffMs / 86400000); + + if (diffMins < 60) { + return `Hace ${diffMins} min`; + } else if (diffHours < 24) { + return `Hace ${diffHours} horas`; + } else if (diffDays < 7) { + return `Hace ${diffDays} dias`; + } else { + return date.toLocaleDateString('es-ES', { + day: '2-digit', + month: 'short', + year: 'numeric', + }); + } +} diff --git a/projects/gamilit/apps/frontend/src/apps/admin/components/monitoring/AlertasTab.tsx b/projects/gamilit/apps/frontend/src/apps/admin/components/monitoring/AlertasTab.tsx index b421b61..9fac3d4 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/components/monitoring/AlertasTab.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/components/monitoring/AlertasTab.tsx @@ -19,72 +19,26 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { DetectiveCard } from '@shared/components/base/DetectiveCard'; import { DetectiveButton } from '@shared/components/base/DetectiveButton'; -import { AlertTriangle, CheckCircle, ExternalLink, Clock, Shield } from 'lucide-react'; -import type { SystemAlert, AlertsStats, SystemAlertSeverity } from '@/services/api/adminTypes'; +import { AlertTriangle, CheckCircle, ExternalLink, Shield } from 'lucide-react'; +import { AlertsStats } from '../alerts/AlertsStats'; +import { + getSeverityColorWithBorder, + getStatusTextColor, + getStatusLabel, + formatAlertTimestamp, +} from '../alerts/alertUtils'; +import type { SystemAlert, AlertsStats as AlertsStatsType, SystemAlertSeverity } from '@/services/api/adminTypes'; interface AlertasTabProps { alerts: SystemAlert[]; - stats: AlertsStats | null; + stats: AlertsStatsType | null; isLoading: boolean; onRefresh: () => Promise; onAcknowledge: (id: string, note?: string) => Promise; onResolve: (id: string, note: string) => Promise; } -/** - * Get severity color - */ -function getSeverityColor(severity: SystemAlertSeverity): string { - switch (severity) { - case 'critical': - return 'bg-red-500/20 text-red-400 border-red-500/50'; - case 'high': - return 'bg-orange-500/20 text-orange-400 border-orange-500/50'; - case 'medium': - return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/50'; - case 'low': - return 'bg-blue-500/20 text-blue-400 border-blue-500/50'; - default: - return 'bg-gray-500/20 text-gray-400 border-gray-500/50'; - } -} - -/** - * Get status color - */ -function getStatusColor(status: string): string { - switch (status) { - case 'open': - return 'text-red-400'; - case 'acknowledged': - return 'text-yellow-400'; - case 'resolved': - return 'text-green-400'; - case 'suppressed': - return 'text-gray-400'; - default: - return 'text-gray-400'; - } -} - -/** - * Format timestamp - */ -function formatTimestamp(timestamp: string): string { - const date = new Date(timestamp); - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const hours = Math.floor(diff / (1000 * 60 * 60)); - const minutes = Math.floor(diff / (1000 * 60)); - - if (minutes < 60) { - return `Hace ${minutes}m`; - } else if (hours < 24) { - return `Hace ${hours}h`; - } else { - return date.toLocaleDateString('es-ES', { month: 'short', day: 'numeric' }); - } -} +// Utility functions imported from alertUtils.ts /** * Alertas Tab Component @@ -92,6 +46,7 @@ function formatTimestamp(timestamp: string): string { export const AlertasTab: React.FC = ({ alerts, stats, + isLoading, onAcknowledge, onResolve, }) => { @@ -151,62 +106,8 @@ export const AlertasTab: React.FC = ({ - {/* Statistics Cards */} - {stats && ( -
- {/* Open Alerts */} - -
-
-
Alertas Abiertas
-
{stats.open_alerts}
-
- -
-
Requieren atención
-
- - {/* Acknowledged Alerts */} - -
-
-
Reconocidas
-
- {stats.acknowledged_alerts} -
-
- -
-
En proceso
-
- - {/* Resolved Alerts */} - -
-
-
Resueltas
-
{stats.resolved_alerts}
-
- -
-
Completadas
-
- - {/* Average Resolution Time */} - -
-
-
Tiempo Promedio
-
- {stats.avg_resolution_time_hours.toFixed(1)}h -
-
- -
-
De resolución
-
-
- )} + {/* Statistics Cards - Reutilizando AlertsStats component */} + {/* Recent Alerts */} @@ -248,22 +149,19 @@ export const AlertasTab: React.FC = ({
{alert.severity.toUpperCase()} - - {alert.status === 'open' && 'ABIERTA'} - {alert.status === 'acknowledged' && 'RECONOCIDA'} - {alert.status === 'resolved' && 'RESUELTA'} - {alert.status === 'suppressed' && 'SUPRIMIDA'} + + {getStatusLabel(alert.status).toUpperCase()} - {formatTimestamp(alert.triggered_at)} + {formatAlertTimestamp(alert.triggered_at)} {alert.alert_type && ( diff --git a/projects/gamilit/apps/frontend/src/apps/admin/hooks/index.ts b/projects/gamilit/apps/frontend/src/apps/admin/hooks/index.ts index f3ca470..13b8d6e 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/hooks/index.ts +++ b/projects/gamilit/apps/frontend/src/apps/admin/hooks/index.ts @@ -16,3 +16,4 @@ export { useAlerts } from './useAlerts'; export { useProgress } from './useProgress'; export { useMonitoring } from './useMonitoring'; export { useFeatureFlags } from './useFeatureFlags'; +export { useClassroomsList } from './useClassroomsList'; diff --git a/projects/gamilit/apps/frontend/src/apps/admin/hooks/useClassroomsList.ts b/projects/gamilit/apps/frontend/src/apps/admin/hooks/useClassroomsList.ts new file mode 100644 index 0000000..18c61a3 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/apps/admin/hooks/useClassroomsList.ts @@ -0,0 +1,71 @@ +/** + * useClassroomsList Hook + * + * Fetches list of classrooms for admin progress page selectors. + * Replaces MOCK_CLASSROOMS with real data from API. + * + * @module apps/admin/hooks/useClassroomsList + * @created 2025-12-14 + */ + +import { useQuery } from '@tanstack/react-query'; +import { adminAPI } from '@/services/api/adminAPI'; +import type { ClassroomBasic } from '@/services/api/adminTypes'; + +export interface UseClassroomsListParams { + schoolId?: string; + enabled?: boolean; +} + +export interface UseClassroomsListReturn { + classrooms: ClassroomBasic[]; + isLoading: boolean; + error: Error | null; + refetch: () => void; +} + +/** + * Hook to fetch list of classrooms for admin selectors + * + * @param params - Optional parameters for filtering + * @returns Classrooms list with loading and error states + * + * @example + * ```tsx + * const { classrooms, isLoading, error } = useClassroomsList(); + * + * if (isLoading) return ; + * + * return ( + * + * ); + * ``` + */ +export function useClassroomsList(params: UseClassroomsListParams = {}): UseClassroomsListReturn { + const { schoolId, enabled = true } = params; + + const { + data: classrooms = [], + isLoading, + error, + refetch, + } = useQuery({ + queryKey: ['admin', 'classrooms', schoolId], + queryFn: () => adminAPI.classrooms.getAll({ schoolId }), + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes - data considered fresh + gcTime: 10 * 60 * 1000, // 10 minutes - cache garbage collection + refetchOnWindowFocus: false, + retry: 2, + }); + + return { + classrooms, + isLoading, + error: error as Error | null, + refetch, + }; +} diff --git a/projects/gamilit/apps/frontend/src/apps/admin/hooks/useGamificationConfig.ts b/projects/gamilit/apps/frontend/src/apps/admin/hooks/useGamificationConfig.ts index 1c62a49..55c061b 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/hooks/useGamificationConfig.ts +++ b/projects/gamilit/apps/frontend/src/apps/admin/hooks/useGamificationConfig.ts @@ -303,6 +303,23 @@ export function useGamificationConfig() { }, }); + /** + * Mutation for restoring all settings to defaults (MEDIO-002 fix) + * Invalidates all gamification queries on success + * @added 2025-12-15 + */ + const restoreDefaults = useMutation({ + mutationFn: () => gamificationConfigApi.restoreDefaults(), + onSuccess: (data) => { + toast.success(`${data.restored_count} parámetros restaurados a valores por defecto`); + // Invalidate all gamification queries to refresh data + queryClient.invalidateQueries({ queryKey: ['gamification'] }); + }, + onError: (error: any) => { + toast.error(error?.response?.data?.message || 'Error al restaurar valores por defecto'); + }, + }); + return { // Queries useParameters, @@ -316,5 +333,6 @@ export function useGamificationConfig() { bulkUpdateParameters, updateMayaRank, previewImpact, + restoreDefaults, }; } diff --git a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAlertsPage.tsx b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAlertsPage.tsx index 8d7c8b8..9d508ac 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAlertsPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAlertsPage.tsx @@ -45,7 +45,7 @@ export default function AdminAlertsPage() { // Gamification data const { gamificationData } = useUserGamification(user?.id); const displayGamificationData = gamificationData || { - userId: user?.id || 'mock-admin-id', + userId: user?.id || '', level: 1, totalXP: 0, mlCoins: 0, diff --git a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAssignmentsPage.tsx b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAssignmentsPage.tsx index 5f8ed91..4466339 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAssignmentsPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminAssignmentsPage.tsx @@ -65,7 +65,7 @@ export default function AdminAssignmentsPage() { // Gamification data const { gamificationData } = useUserGamification(user?.id); const displayGamificationData = gamificationData || { - userId: user?.id || 'mock-admin-id', + userId: user?.id || '', level: 1, totalXP: 0, mlCoins: 0, diff --git a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminClassroomTeacherPage.tsx b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminClassroomTeacherPage.tsx index c86a274..5df8bfb 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminClassroomTeacherPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminClassroomTeacherPage.tsx @@ -31,7 +31,7 @@ export default function AdminClassroomTeacherPage() { // Fallback gamification data const displayGamificationData = gamificationData || { - userId: user?.id || 'mock-admin-id', + userId: user?.id || '', level: 1, totalXP: 0, mlCoins: 0, diff --git a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminGamificationPage.tsx b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminGamificationPage.tsx index a04799e..43c04d1 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminGamificationPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminGamificationPage.tsx @@ -61,6 +61,7 @@ export default function AdminGamificationPage() { resetParameter, updateMayaRank, bulkUpdateParameters, + restoreDefaults, // previewImpact is available but not used yet } = useGamificationConfig(); @@ -610,10 +611,7 @@ export default function AdminGamificationPage() { parameters={safeParameters} totalUsers={1250} onConfirm={async () => { - // TODO: Implement restore defaults endpoint in backend - // This endpoint is not yet available in the API - // await restoreDefaults.mutateAsync(); - console.warn('Restore defaults endpoint not yet implemented in backend'); + await restoreDefaults.mutateAsync(); setRestoreDefaultsOpen(false); }} /> diff --git a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminInstitutionsPage.tsx b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminInstitutionsPage.tsx index 045caa0..f42a69a 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminInstitutionsPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminInstitutionsPage.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useCallback } from 'react'; import { useAuth } from '@features/auth/hooks/useAuth'; import { useUserGamification } from '@/shared/hooks/useUserGamification'; import { AdminLayout } from '../layouts/AdminLayout'; @@ -9,6 +9,7 @@ import { FormField } from '@shared/components/common/FormField'; import { ConfirmDialog } from '@shared/components/common/ConfirmDialog'; import { Plus, Settings, Trash2 } from 'lucide-react'; import { useOrganizations } from '../hooks/useOrganizations'; +import { getOrganizationStats, type OrganizationStats } from '@/services/api/adminAPI'; import type { Organization } from '../types'; import { InstitutionFilters, @@ -59,8 +60,33 @@ export default function AdminInstitutionsPage() { plan: [], }); - // Mock stats data (replace with API call when available) - const [institutionStats] = useState(null); + // Stats data loaded from API (MEDIO-001 fix: replaced mock with real API call) + const [institutionStats, setInstitutionStats] = useState(null); + const [statsLoading, setStatsLoading] = useState(false); + + // Load organization stats when viewing detail + const loadOrganizationStats = useCallback(async (orgId: string) => { + setStatsLoading(true); + try { + const stats = await getOrganizationStats(orgId); + // Map API response to InstitutionStatsData format + setInstitutionStats({ + totalStudents: stats.totalStudents, + activeStudents: stats.activeStudents, + totalTeachers: stats.totalTeachers, + totalClassrooms: stats.totalClassrooms, + averageProgress: stats.averageProgress, + storageUsed: stats.storageUsed, + lastActivity: stats.lastActivity, + trialEndsAt: stats.trialEndsAt, + }); + } catch (err) { + console.error('Failed to load organization stats:', err); + setInstitutionStats(null); + } finally { + setStatsLoading(false); + } + }, []); const handleLogout = () => { logout(); @@ -119,7 +145,7 @@ export default function AdminInstitutionsPage() { } }; - // Handler para ver detalle + // Handler para ver detalle (MEDIO-001: Now loads stats from API) const handleViewInstitution = (org: Organization) => { if (!org?.id) { console.error('[AdminInstitutionsPage] Invalid org for view:', org); @@ -127,6 +153,8 @@ export default function AdminInstitutionsPage() { } setSelectedOrg(org); setIsDetailModalOpen(true); + // Load stats from API + loadOrganizationStats(org.id); }; // Handler para editar @@ -275,15 +303,16 @@ export default function AdminInstitutionsPage() { />
- {/* Institution Detail Modal */} + {/* Institution Detail Modal (MEDIO-001: Now uses real stats from API) */} { setIsDetailModalOpen(false); setSelectedOrg(null); + setInstitutionStats(null); }} onEdit={handleEditInstitution} onManageFeatures={handleManageFeatures} diff --git a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminProgressPage.tsx b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminProgressPage.tsx index e3822ca..770f31d 100644 --- a/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminProgressPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/admin/pages/AdminProgressPage.tsx @@ -19,7 +19,7 @@ import React, { useState, useEffect, useMemo } from 'react'; import { useAuth } from '@features/auth/hooks/useAuth'; import { AdminLayout } from '../layouts/AdminLayout'; import { useUserGamification } from '@shared/hooks/useUserGamification'; -import { useProgress } from '../hooks/useProgress'; +import { useProgress, useClassroomsList } from '../hooks'; // Components import { OverviewView } from '../components/progress/OverviewView'; @@ -36,27 +36,23 @@ import { DetectiveCard } from '@shared/components/base/DetectiveCard'; // Types type ViewType = 'overview' | 'classrooms' | 'students'; -// Mock data for classrooms - in production, this would come from an API -const MOCK_CLASSROOMS = [ - { id: '550e8400-e29b-41d4-a716-446655440001', name: 'Matemáticas 1A' }, - { id: '550e8400-e29b-41d4-a716-446655440002', name: 'Matemáticas 1B' }, - { id: '550e8400-e29b-41d4-a716-446655440003', name: 'Matemáticas 2A' }, -]; - /** * AdminProgressPage Component */ export default function AdminProgressPage() { const { user, logout } = useAuth(); + // Fetch classrooms from API (replaces classrooms) + const { classrooms, isLoading: classroomsLoading } = useClassroomsList(); + // Gamification data const { gamificationData } = useUserGamification(user?.id); const displayGamificationData = gamificationData || { - userId: user?.id || 'mock-admin-id', + userId: user?.id || '', level: 1, totalXP: 0, mlCoins: 0, - rank: 'Novato', + rank: 'Admin', achievements: [], }; @@ -162,7 +158,7 @@ export default function AdminProgressPage() { const crumbs = ['Progreso']; if (activeView === 'classrooms' && selectedClassroomId) { - const classroom = MOCK_CLASSROOMS.find((c) => c.id === selectedClassroomId); + const classroom = classrooms.find((c) => c.id === selectedClassroomId); if (classroom) { crumbs.push(classroom.name); } @@ -256,7 +252,7 @@ export default function AdminProgressPage() {
{activeView === 'classrooms' && ( {isLocked ? ( @@ -219,15 +219,19 @@ export function AchievementsPreview({ achievements }: AchievementsPreviewProps)

)} - {/* Rewards (if available - mock data) */} + {/* Rewards (IMPL-007: dynamic values from achievement data) */}
- +50 ML + + +{achievement.mlCoinsReward ?? achievement.rewards?.ml_coins ?? 50} ML +
- +100 XP + + +{achievement.xpReward ?? achievement.rewards?.xp ?? 100} XP +
diff --git a/projects/gamilit/apps/frontend/src/apps/student/hooks/useDashboardData.ts b/projects/gamilit/apps/frontend/src/apps/student/hooks/useDashboardData.ts index 6dc61e9..7354e34 100644 --- a/projects/gamilit/apps/frontend/src/apps/student/hooks/useDashboardData.ts +++ b/projects/gamilit/apps/frontend/src/apps/student/hooks/useDashboardData.ts @@ -84,13 +84,23 @@ export interface RankData { export interface AchievementData { id: string; name: string; + title?: string; // Alias for compatibility with Achievement type description: string; rarity: 'common' | 'rare' | 'epic' | 'legendary'; + category?: string; icon: string; unlocked: boolean; + isUnlocked?: boolean; // Alias for compatibility with Achievement type unlockedAt?: string; progress?: number; required?: number; + // Reward fields (IMPL-005: added for dynamic rewards display) + mlCoinsReward?: number; + xpReward?: number; + rewards?: { + ml_coins?: number; + xp?: number; + }; } export interface ProgressData { @@ -156,45 +166,69 @@ async function fetchDashboardData(userId: string): Promise { : []; // Transform rank data from API format to component format + // NOTE: apiClient does NOT transform snake_case -> camelCase, we use snake_case const rankCurrent = rankCurrentRes.data; const rankProgress = rankProgressRes.data; - const currentRankName = rankCurrent?.current_rank || rankProgress?.current_rank || 'Ajaw'; + console.log('🔍 [useDashboardData] rankCurrent:', rankCurrent); + console.log('🔍 [useDashboardData] rankProgress:', rankProgress); + + // Backend returns snake_case: current_rank, xp_current, xp_required, progress_percentage + const currentRankName = + rankCurrent?.current_rank || + rankProgress?.current_rank || + rankCurrent?.currentRank || + rankProgress?.currentRank || + 'Ajaw'; + const transformedRankData: RankData | null = - rankCurrent && rankProgress + rankCurrent || rankProgress ? { currentRank: currentRankName, - currentXP: rankProgress.xp_current || 0, - nextRankXP: rankProgress.xp_required || (rankProgress.xp_current || 0) + 1000, + currentXP: rankProgress?.xp_current || rankProgress?.xpCurrent || 0, + nextRankXP: + rankProgress?.xp_required || + rankProgress?.xpRequired || + (rankProgress?.xp_current || rankProgress?.xpCurrent || 0) + 1000, multiplier: getRankMultiplier(currentRankName), rankIcon: getRankIcon(currentRankName), - progress: rankProgress.progress_percentage || 0, + progress: rankProgress?.progress_percentage || rankProgress?.progressPercentage || 0, } : null; - // Process coins data + // Process coins data (backend uses snake_case) const coinsData: MLCoinsData = { - balance: coinsRes.data?.current_balance || 0, - todayEarned: coinsRes.data?.earned_today || 0, - todaySpent: 0, + balance: + coinsRes.data?.current_balance || + coinsRes.data?.currentBalance || + coinsRes.data?.ml_coins || + coinsRes.data?.mlCoins || + 0, + todayEarned: + coinsRes.data?.earned_today || + coinsRes.data?.earnedToday || + coinsRes.data?.ml_coins_earned_today || + coinsRes.data?.mlCoinsEarnedToday || + 0, + todaySpent: coinsRes.data?.spent_today || coinsRes.data?.spentToday || 0, recentTransactions: [], }; - // Transform progress data from snake_case to camelCase + // Transform progress data (backend uses snake_case) const progressRaw = progressRes.data?.data || progressRes.data || null; const transformedProgress: ProgressData | null = progressRaw ? { - totalModules: progressRaw.total_modules || 0, - completedModules: progressRaw.completed_modules || 0, - totalExercises: progressRaw.total_exercises || 0, - completedExercises: progressRaw.completed_exercises || 0, - averageScore: progressRaw.average_score || 0, + totalModules: progressRaw.total_modules || progressRaw.totalModules || 0, + completedModules: progressRaw.completed_modules || progressRaw.completedModules || 0, + totalExercises: progressRaw.total_exercises || progressRaw.totalExercises || 0, + completedExercises: progressRaw.completed_exercises || progressRaw.completedExercises || 0, + averageScore: progressRaw.average_score || progressRaw.averageScore || 0, totalTimeSpent: - typeof progressRaw.total_time_spent === 'string' - ? parseTimeToSeconds(progressRaw.total_time_spent) - : progressRaw.total_time_spent || 0, - currentStreak: progressRaw.current_streak || 0, - longestStreak: progressRaw.longest_streak || 0, + typeof (progressRaw.total_time_spent || progressRaw.totalTimeSpent) === 'string' + ? parseTimeToSeconds(progressRaw.total_time_spent || progressRaw.totalTimeSpent) + : progressRaw.total_time_spent || progressRaw.totalTimeSpent || 0, + currentStreak: progressRaw.current_streak || progressRaw.currentStreak || 0, + longestStreak: progressRaw.longest_streak || progressRaw.longestStreak || 0, } : null; diff --git a/projects/gamilit/apps/frontend/src/apps/student/pages/AchievementsPage.tsx b/projects/gamilit/apps/frontend/src/apps/student/pages/AchievementsPage.tsx index eade0f8..4d226eb 100644 --- a/projects/gamilit/apps/frontend/src/apps/student/pages/AchievementsPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/student/pages/AchievementsPage.tsx @@ -36,10 +36,13 @@ import { ProgressTreeVisualizer } from '@/features/gamification/social/component import { useAchievements } from '@/features/gamification/social/hooks/useAchievements'; import { useAuthStore } from '@/features/auth/store/authStore'; import { usePersistedFilters } from '@/shared/hooks/usePersistedFilters'; +import { useEconomyStore } from '@/features/gamification/economy/store/economyStore'; +import { claimAchievementRewards } from '@/features/gamification/social/api/achievementsAPI'; import type { Achievement, AchievementCategory, } from '@/features/gamification/social/types/achievementsTypes'; +import toast from 'react-hot-toast'; // Utils import { cn } from '@shared/utils/cn'; @@ -103,6 +106,16 @@ export default function AchievementsPage() { const [showUnlockModal, setShowUnlockModal] = useState(false); const [selectedAchievement, setSelectedAchievement] = useState(null); const [showFilters, setShowFilters] = useState(false); + const [claimingAchievementId, setClaimingAchievementId] = useState(null); + const [localAchievements, setLocalAchievements] = useState([]); + + // Economy store for balance refresh + const fetchBalance = useEconomyStore((state) => state.fetchBalance); + + // Sync achievements to local state for claim status updates + useEffect(() => { + setLocalAchievements(achievements); + }, [achievements]); // WebSocket Integration for real-time updates is handled globally via App.tsx // The useAchievements hook will automatically update when new achievements are unlocked @@ -124,9 +137,48 @@ export default function AchievementsPage() { return () => clearTimeout(timer); }, [searchQuery]); + // Handle claim rewards + const handleClaimRewards = async (achievementId: string) => { + if (!user?.id || claimingAchievementId) return; + + try { + setClaimingAchievementId(achievementId); + + const result = await claimAchievementRewards(user.id, achievementId); + + if (result.success) { + // Update local state to mark as claimed + setLocalAchievements((prev) => + prev.map((a) => + a.id === achievementId ? { ...a, rewardsClaimed: true } : a + ) + ); + + // Refresh balance to show new ML Coins + await fetchBalance(); + + // Find the achievement for the toast message + const achievement = localAchievements.find((a) => a.id === achievementId); + const rewardText = achievement + ? `+${achievement.mlCoinsReward} ML Coins, +${achievement.xpReward} XP` + : 'Recompensas'; + + toast.success(`${rewardText} reclamadas!`, { + icon: '🎁', + duration: 4000, + }); + } + } catch (error) { + console.error('Failed to claim rewards:', error); + toast.error('Error al reclamar recompensas. Intenta de nuevo.'); + } finally { + setClaimingAchievementId(null); + } + }; + // Filter and sort achievements const filteredAchievements = useMemo(() => { - let filtered = achievements; + let filtered = localAchievements; // Category filter if (filters.category !== 'all') { @@ -464,6 +516,8 @@ export default function AchievementsPage() { handleAchievementClick(achievement)} + onClaimRewards={handleClaimRewards} + isClaiming={claimingAchievementId === achievement.id} /> ))} diff --git a/projects/gamilit/apps/frontend/src/apps/student/pages/ExercisePage.tsx b/projects/gamilit/apps/frontend/src/apps/student/pages/ExercisePage.tsx index f4a19a0..1b7d482 100644 --- a/projects/gamilit/apps/frontend/src/apps/student/pages/ExercisePage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/student/pages/ExercisePage.tsx @@ -130,6 +130,8 @@ const loadMechanic = (mechanicType: string) => { import('@/features/mechanics/module3/TribunalOpiniones/TribunalOpinionesExercise'), // Module 4 - Textos Digitales y Multimediales + verificador_fake_news: () => + import('@/features/mechanics/module4/VerificadorFakeNews/VerificadorFakeNewsExercise'), verificador_fakenews: () => import('@/features/mechanics/module4/VerificadorFakeNews/VerificadorFakeNewsExercise'), fake_news: () => @@ -141,13 +143,7 @@ const loadMechanic = (mechanicType: string) => { import('@/features/mechanics/module4/AnalisisMemes/AnalisisMemesExercise'), infografia_interactiva: () => import('@/features/mechanics/module4/InfografiaInteractiva/InfografiaInteractivaExercise'), - email_formal: () => import('@/features/mechanics/module4/EmailFormal/EmailFormalExercise'), - chat_literario: () => - import('@/features/mechanics/module4/ChatLiterario/ChatLiterarioExercise'), - ensayo_argumentativo: () => - import('@/features/mechanics/module4/EnsayoArgumentativo/EnsayoArgumentativoExercise'), - resena_critica: () => - import('@/features/mechanics/module4/ResenaCritica/ResenaCriticaExercise'), + // Removed: email_formal, chat_literario, ensayo_argumentativo, resena_critica (exercises deleted) // Module 5 - Producción Creativa diario_multimedia: () => @@ -267,7 +263,8 @@ export default function ExercisePage() { const mappedExercise: ExerciseData = { id: exerciseData.id, - module_id: exerciseData.module_id, + // API returns camelCase after apiClient transformation (snake_case → camelCase) + module_id: exerciseData.moduleId || exerciseData.module_id, title: exerciseData.title, type: exerciseType, description: exerciseData.description || '', @@ -557,7 +554,14 @@ export default function ExercisePage() { const handleSkip = () => { if (window.confirm('¿Estás seguro de que deseas omitir este ejercicio?')) { - navigate(`/modules/${exercise?.module_id || moduleId}`); + // Priorizar module_id del ejercicio, luego moduleId del URL, luego dashboard + const targetModuleId = exercise?.module_id || (exercise as any)?.moduleId || moduleId; + if (targetModuleId && targetModuleId !== 'undefined') { + navigate(`/modules/${targetModuleId}`); + } else { + console.warn('[ExercisePage] No valid moduleId found, navigating to dashboard'); + navigate('/dashboard'); + } } }; @@ -714,10 +718,10 @@ export default function ExercisePage() {

No se pudo cargar el ejercicio

navigate(`/modules/${moduleId || 'dashboard'}`)} + onClick={() => navigate('/dashboard')} className="mt-4" > - Volver al módulo + Volver al Dashboard
@@ -880,6 +884,7 @@ export default function ExercisePage() { > { setShowFeedback(false); if (feedback.type === 'success') { - navigate(`/modules/${exercise?.module_id || moduleId}`); + // Priorizar module_id del ejercicio, luego moduleId del URL, luego dashboard + const targetModuleId = exercise?.module_id || (exercise as any)?.moduleId || moduleId; + if (targetModuleId && targetModuleId !== 'undefined') { + navigate(`/modules/${targetModuleId}`); + } else { + console.warn('[ExercisePage] No valid moduleId found after completion, navigating to dashboard'); + navigate('/dashboard'); + } } }} onRetry={() => { diff --git a/projects/gamilit/apps/frontend/src/apps/student/pages/ModuleDetailPage.tsx b/projects/gamilit/apps/frontend/src/apps/student/pages/ModuleDetailPage.tsx index a634a8b..6d848ee 100644 --- a/projects/gamilit/apps/frontend/src/apps/student/pages/ModuleDetailPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/student/pages/ModuleDetailPage.tsx @@ -292,6 +292,7 @@ export default function ModuleDetailPage() { } // Calculate progress percentage based on actual completed exercises from progress data + // NOTE: apiClient does NOT transform snake_case -> camelCase, data comes in snake_case const completedExercises = progress?.completed_exercises || 0; const totalExercises = progress?.total_exercises || exercises.length; const progressPercentage = @@ -320,15 +321,16 @@ export default function ModuleDetailPage() { {/* Header Section - Compact */} + {/* NOTE: apiClient does NOT transform, data comes in snake_case */}
- {module.difficulty && ( + {module.difficulty_level && ( {( - difficultyLabels[module.difficulty] || - module.difficulty || + difficultyLabels[module.difficulty_level] || + module.difficulty_level || 'DESCONOCIDO' ).toUpperCase()} @@ -378,6 +380,7 @@ export default function ModuleDetailPage() { {/* Statistics Section with colorful cards - Compact */} + {/* NOTE: apiClient does NOT transform, data comes in snake_case */}
{/* Duration */} {module.estimated_duration_minutes && ( @@ -391,11 +394,11 @@ export default function ModuleDetailPage() { )} {/* Difficulty */} - {module.difficulty && ( + {module.difficulty_level && (

- {difficultyLabels[module.difficulty] || module.difficulty} + {difficultyLabels[module.difficulty_level] || module.difficulty_level}

Dificultad

@@ -428,6 +431,7 @@ export default function ModuleDetailPage() {
{/* Learning Objectives Section - Compact */} + {/* NOTE: apiClient does NOT transform, data comes in snake_case */} {module.learning_objectives && module.learning_objectives.length > 0 && (

@@ -446,6 +450,7 @@ export default function ModuleDetailPage() { )} {/* Competencies and Skills Section - Compact */} + {/* NOTE: apiClient does NOT transform, data comes in snake_case */}
{/* Competencies */} {module.competencies && module.competencies.length > 0 && ( diff --git a/projects/gamilit/apps/frontend/src/apps/student/pages/ShopPage.tsx b/projects/gamilit/apps/frontend/src/apps/student/pages/ShopPage.tsx index 3d23c0a..4df314b 100644 --- a/projects/gamilit/apps/frontend/src/apps/student/pages/ShopPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/student/pages/ShopPage.tsx @@ -27,6 +27,19 @@ import { Check, ShoppingBag, Loader, + Award, + BookOpen, + Compass, + Flag, + Image, + Shield, + ShieldCheck, + Smile, + Square, + Sticker, + UserCircle, + Zap, + type LucideIcon, } from 'lucide-react'; // Components @@ -50,6 +63,7 @@ import { getShopCategories, getShopItems, purchaseShopItem, + getUserPurchases, type ShopCategory as ApiShopCategory, type ShopItemCategory, } from '@/features/gamification/economy/api/shopAPI'; @@ -57,6 +71,35 @@ import { // Utils import { cn } from '@shared/utils/cn'; +// Map icon names from backend to Lucide components +const shopIconMap: Record = { + 'award': Award, + 'book-open': BookOpen, + 'coins': Coins, + 'compass': Compass, + 'flag': Flag, + 'image': Image, + 'shield': Shield, + 'shield-check': ShieldCheck, + 'smile': Smile, + 'sparkles': Sparkles, + 'square': Square, + 'sticker': Sticker, + 'user-circle': UserCircle, + 'zap': Zap, +}; + +// Get icon component from name +const getShopIcon = (iconName: string): LucideIcon => { + return shopIconMap[iconName?.toLowerCase()] || Package; +}; + +// Render icon component +const ShopIcon: React.FC<{ name: string; className?: string }> = ({ name, className }) => { + const IconComponent = getShopIcon(name); + return ; +}; + export default function ShopPage() { const { user, logout } = useAuth(); @@ -79,6 +122,13 @@ export default function ShopPage() { const [cart] = useState([]); const [apiCategories, setApiCategories] = useState([]); + // Fetch balance when user is available + useEffect(() => { + if (user?.id) { + fetchBalance(); + } + }, [user?.id, fetchBalance]); + // Fetch shop items and categories useEffect(() => { const fetchData = async () => { @@ -95,25 +145,50 @@ export default function ShopPage() { const items = await getShopItems(filters); - // Transform API items to ShopItem format - const transformedItems: ShopItem[] = items.map((item) => ({ - id: item.id, - name: item.name, - description: item.description || '', - category: item.category as ShopCategory, - price: item.discount_price || item.price, - icon: item.icon, - rarity: item.rarity as ItemRarity, - tags: item.tags || [], - isOwned: false, // Will be updated with ownership check if needed - isPurchasable: item.is_available && (item.stock === null || item.stock === undefined || item.stock > 0), - metadata: { - effectDescription: item.effect_data?.description, - duration: item.duration_days, - stackable: !item.is_consumable, - tradeable: false, - }, - })); + // Fetch user purchases to determine ownership + let ownedItemIds = new Set(); + if (user?.id) { + try { + const purchases = await getUserPurchases(user.id); + ownedItemIds = new Set( + purchases + .filter((p) => p.status === 'completed') + .map((p) => p.item_id) + ); + } catch (purchaseError) { + console.warn('Could not fetch purchases for ownership check:', purchaseError); + } + } + + // Transform API items to ShopItem format with ownership check + const transformedItems: ShopItem[] = items.map((item) => { + const isOwned = ownedItemIds.has(item.id); + // is_available defaults to true if not specified + const isAvailable = item.is_available !== false; + // Stock check: null/undefined means unlimited + const hasStock = item.stock === null || item.stock === undefined || item.stock > 0; + // Item is purchasable if: available, has stock, and NOT already owned + const isPurchasable = isAvailable && hasStock && !isOwned; + + return { + id: item.id, + name: item.name, + description: item.description || '', + category: item.category as ShopCategory, + price: item.discount_price || item.price, + icon: item.icon, + rarity: item.rarity as ItemRarity, + tags: item.tags || [], + isOwned, + isPurchasable, + metadata: { + effectDescription: item.effect_data?.description, + duration: item.duration_days, + stackable: !item.is_consumable, + tradeable: false, + }, + }; + }); setShopItems(transformedItems); } catch (error) { @@ -125,7 +200,7 @@ export default function ShopPage() { }; fetchData(); - }, [selectedCategory]); + }, [selectedCategory, user?.id]); // Categories - Dynamic from API with icon mapping const categories = useMemo(() => { @@ -383,11 +458,11 @@ export default function ShopPage() {
- {item.icon} +
{item.isOwned ? ( - + Owned @@ -424,13 +499,13 @@ export default function ShopPage() { onClick={() => handlePurchase(item)} disabled={!item.isPurchasable || balance.current < item.price} className={cn( - 'rounded-lg px-4 py-2 font-medium transition-colors', + 'rounded-lg px-4 py-2 font-medium transition-all', item.isPurchasable && balance.current >= item.price - ? 'bg-detective-orange text-white hover:bg-detective-orange-dark' - : 'cursor-not-allowed bg-gray-200 text-gray-400', + ? 'btn-detective' + : 'cursor-not-allowed bg-gray-200 text-gray-400 opacity-60', )} > - Buy Now + {!item.isPurchasable ? 'Not Available' : balance.current < item.price ? 'Not Enough Coins' : 'Buy Now'} )}
@@ -460,11 +535,11 @@ export default function ShopPage() {
- {selectedItem.icon} +

{selectedItem.name}

@@ -523,9 +598,9 @@ export default function ShopPage() { onClick={confirmPurchase} disabled={balance.current < selectedItem.price || isPurchasing} className={cn( - 'flex flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2 font-medium transition-colors', + 'flex flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2 font-medium transition-all', balance.current >= selectedItem.price && !isPurchasing - ? 'bg-detective-orange text-white hover:bg-detective-orange-dark' + ? 'btn-detective' : 'cursor-not-allowed bg-gray-300 text-gray-500', )} > diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentDetailModal.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentDetailModal.tsx index bc67d21..6b37566 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentDetailModal.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentDetailModal.tsx @@ -146,7 +146,7 @@ export function StudentDetailModal({ student, onClose, classroomId }: StudentDet Progreso

- {student.progress_percentage.toFixed(0)}% + {(student.progress_percentage ?? 0).toFixed(0)}%

@@ -156,7 +156,7 @@ export function StudentDetailModal({ student, onClose, classroomId }: StudentDet Score Promedio

- {student.score_average.toFixed(0)}% + {(student.score_average ?? 0).toFixed(0)}%

@@ -220,7 +220,7 @@ export function StudentDetailModal({ student, onClose, classroomId }: StudentDet Tasa Primer Intento

- {statsData.first_attempt_success_rate.toFixed(0)}% + {(statsData.first_attempt_success_rate ?? 0).toFixed(0)}%

@@ -268,7 +268,7 @@ export function StudentDetailModal({ student, onClose, classroomId }: StudentDet

{module.module_name}

- {module.score.toFixed(0)}% + {(module.score ?? 0).toFixed(0)}%
diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentMonitoringPanel.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentMonitoringPanel.tsx index 117f5db..41c8c91 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentMonitoringPanel.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentMonitoringPanel.tsx @@ -1,12 +1,22 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useState, useEffect, useRef } from 'react'; -import { Search, RefreshCw, Users } from 'lucide-react'; +import { useState, useEffect, useRef, useMemo } from 'react'; +import { + Search, + RefreshCw, + Users, + TrendingUp, + TrendingDown, + Minus, + LayoutGrid, + List, +} from 'lucide-react'; import { DetectiveCard } from '@shared/components/base/DetectiveCard'; import { DetectiveButton } from '@shared/components/base/DetectiveButton'; import { InputDetective } from '@shared/components/base/InputDetective'; import { StudentStatusCard } from './StudentStatusCard'; import { StudentDetailModal } from './StudentDetailModal'; import { RefreshControl } from './RefreshControl'; +import { StudentPagination } from './StudentPagination'; import { useStudentMonitoring } from '../../hooks/useStudentMonitoring'; import { useToast } from '@shared/components/base/Toast'; import type { StudentFilter, StudentMonitoring } from '../../types'; @@ -19,11 +29,27 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr const [filters, setFilters] = useState({}); const [selectedStudent, setSelectedStudent] = useState(null); const [searchTerm, setSearchTerm] = useState(''); + const [viewMode, setViewMode] = useState<'cards' | 'table'>('cards'); + const [sortField, setSortField] = useState<'name' | 'score' | 'completion' | 'activity'>('name'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); const previousStudentsRef = useRef([]); const { showToast } = useToast(); - const { students, loading, error, refreshInterval, setRefreshInterval, refresh, lastUpdate } = - useStudentMonitoring(classroomId, filters); + // CORR-2025-12-18: Agregado soporte de paginacion server-side + const { + students, + loading, + error, + page, + limit, + pagination, + setPage, + setPageLimit, + refreshInterval, + setRefreshInterval, + refresh, + lastUpdate, + } = useStudentMonitoring(classroomId, filters); // Detect student events and show notifications useEffect(() => { @@ -92,6 +118,84 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr }); }; + // Calculate performance level based on score + const calculatePerformanceLevel = (student: StudentMonitoring): 'high' | 'medium' | 'low' => { + const score = student.score_average || 0; + if (score >= 80) return 'high'; + if (score >= 60) return 'medium'; + return 'low'; + }; + + // Handle performance filter + const handlePerformanceFilter = (level: 'high' | 'medium' | 'low') => { + setFilters((prev) => { + const currentLevels = (prev as any).performanceLevel || []; + const newLevels = currentLevels.includes(level) + ? currentLevels.filter((l: string) => l !== level) + : [...currentLevels, level]; + + return { + ...prev, + performanceLevel: newLevels.length > 0 ? newLevels : undefined, + } as StudentFilter; + }); + }; + + // Handle sorting + const handleSort = (field: typeof sortField) => { + if (sortField === field) { + setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc')); + } else { + setSortField(field); + setSortDirection('asc'); + } + }; + + // Apply sorting and performance filtering to students + const filteredAndSortedStudents = useMemo(() => { + let filtered = [...students]; + + // Filter by performance level if specified + const performanceLevels = (filters as any).performanceLevel; + if (performanceLevels && performanceLevels.length > 0) { + filtered = filtered.filter((student) => { + const level = calculatePerformanceLevel(student); + return performanceLevels.includes(level); + }); + } + + // Sort + return filtered.sort((a, b) => { + let aValue: string | number; + let bValue: string | number; + + switch (sortField) { + case 'name': + aValue = a.full_name.toLowerCase(); + bValue = b.full_name.toLowerCase(); + break; + case 'score': + aValue = a.score_average || 0; + bValue = b.score_average || 0; + break; + case 'completion': + aValue = a.progress_percentage || 0; + bValue = b.progress_percentage || 0; + break; + case 'activity': + aValue = a.last_activity ? new Date(a.last_activity).getTime() : 0; + bValue = b.last_activity ? new Date(b.last_activity).getTime() : 0; + break; + default: + return 0; + } + + if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1; + if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + }, [students, filters, sortField, sortDirection]); + // Calculate counts based on improved status logic const getStudentStatus = (student: StudentMonitoring) => { const now = new Date(); @@ -109,6 +213,11 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr const inactiveCount = students.filter((s) => getStudentStatus(s) === 'inactive').length; const offlineCount = students.filter((s) => getStudentStatus(s) === 'offline').length; + // Performance stats + const highPerformanceCount = students.filter((s) => calculatePerformanceLevel(s) === 'high').length; + const mediumPerformanceCount = students.filter((s) => calculatePerformanceLevel(s) === 'medium').length; + const lowPerformanceCount = students.filter((s) => calculatePerformanceLevel(s) === 'low').length; + if (error) { return ( @@ -124,7 +233,7 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr return (
- {/* Header with RefreshControl */} + {/* Header with RefreshControl and View Toggle */}
@@ -133,21 +242,43 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr

Vista en tiempo real del aula

- +
+ {/* View Toggle */} +
+ setViewMode('cards')} + size="sm" + > + + + setViewMode('table')} + size="sm" + > + + +
+ +
{/* Stats Overview with improved categorization */} + {/* CORR-2025-12-18: Usar pagination.total para mostrar el total real */}
-

{students.length}

+

+ {pagination?.total ?? students.length} +

Total

@@ -159,7 +290,7 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr

{activeCount}

-

+ Activos

@@ -171,7 +302,7 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr

{inExerciseCount}

-

+ En ejercicio

@@ -183,7 +314,7 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr

{inactiveCount}

-

+ Inactivos

@@ -195,7 +326,7 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr

{offlineCount}

-

+ Offline

@@ -203,6 +334,45 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr
+ {/* Performance Stats */} +
+ +
+
+

{highPerformanceCount}

+

+ + Alto Rendimiento +

+
+
+
+ + +
+
+

{mediumPerformanceCount}

+

+ + Rendimiento Medio +

+
+
+
+ + +
+
+

{lowPerformanceCount}

+

+ + Bajo Rendimiento +

+
+
+
+
+ {/* Search and Filters */}
@@ -219,46 +389,203 @@ export function StudentMonitoringPanel({ classroomId }: StudentMonitoringPanelPr />
-
+
+ {/* Status Filters */} handleStatusFilter('active')} + size="sm" > - 🟢 Activos + Activos handleStatusFilter('inactive')} + size="sm" > - 🟡 Inactivos + Inactivos handleStatusFilter('offline')} + size="sm" > - 🔴 Offline + Offline + + + {/* Separator */} +
+ + {/* Performance Filters */} + handlePerformanceFilter('high')} + size="sm" + > + Alto + + handlePerformanceFilter('medium')} + size="sm" + > + Medio + + handlePerformanceFilter('low')} + size="sm" + > + Bajo
- {/* Students Grid */} + {/* Students Grid/Table */} {loading && !students.length ? (

Cargando estudiantes...

- ) : students.length === 0 ? ( + ) : filteredAndSortedStudents.length === 0 ? (

No se encontraron estudiantes

+ ) : viewMode === 'table' ? ( + /* Table View */ + +
+ + + + + + + + + + + + + {filteredAndSortedStudents.map((student) => ( + setSelectedStudent(student)} + > + + + + + + + + ))} + +
handleSort('name')} + > + Nombre {sortField === 'name' && (sortDirection === 'asc' ? '↑' : '↓')} + + Estado + handleSort('score')} + > + Puntuacion {sortField === 'score' && (sortDirection === 'asc' ? '↑' : '↓')} + handleSort('completion')} + > + Completitud {sortField === 'completion' && (sortDirection === 'asc' ? '↑' : '↓')} + + Rendimiento + handleSort('activity')} + > + Ultima Actividad {sortField === 'activity' && (sortDirection === 'asc' ? '↑' : '↓')} +
+
+

{student.full_name}

+

{student.email}

+
+
+ + {getStudentStatus(student) === 'active' && 'Activo'} + {getStudentStatus(student) === 'in_exercise' && 'En ejercicio'} + {getStudentStatus(student) === 'inactive' && 'Inactivo'} + {getStudentStatus(student) === 'offline' && 'Offline'} + + + = 80 + ? 'text-green-500' + : (student.score_average || 0) >= 60 + ? 'text-yellow-500' + : 'text-red-500' + }`} + > + {(student.score_average || 0).toFixed(1)}% + + +
+
+
= 70 + ? 'bg-green-500' + : (student.progress_percentage || 0) >= 50 + ? 'bg-yellow-500' + : 'bg-red-500' + }`} + style={{ width: `${student.progress_percentage || 0}%` }} + /> +
+ {(student.progress_percentage || 0).toFixed(0)}% +
+
+ + {calculatePerformanceLevel(student) === 'high' && 'Alto'} + {calculatePerformanceLevel(student) === 'medium' && 'Medio'} + {calculatePerformanceLevel(student) === 'low' && 'Bajo'} + + + {student.last_activity + ? new Date(student.last_activity).toLocaleDateString('es-ES') + : 'Sin actividad'} +
+
+
) : ( + /* Cards View */
- {students.map((student) => ( + {filteredAndSortedStudents.map((student) => ( )} + {/* CORR-2025-12-18: Componente de paginacion server-side */} + {pagination && ( + + + + )} + {/* Student Detail Modal */} {selectedStudent && ( setSelectedStudent(null)} /> diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentPagination.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentPagination.tsx new file mode 100644 index 0000000..a542ffa --- /dev/null +++ b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentPagination.tsx @@ -0,0 +1,175 @@ +/** + * StudentPagination Component + * + * Componente de paginacion para el panel de monitoreo de estudiantes. + * Incluye selector de limite por pagina y controles de navegacion. + * + * CORR-2025-12-18: Implementacion de paginacion server-side + * + * @component + */ + +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { DetectiveButton } from '@shared/components/base/DetectiveButton'; + +// ============================================================================ +// TYPES +// ============================================================================ + +interface StudentPaginationProps { + /** Pagina actual (1-indexed) */ + page: number; + /** Limite de registros por pagina */ + limit: number; + /** Total de registros */ + total: number; + /** Total de paginas */ + totalPages: number; + /** Si hay pagina siguiente */ + hasNextPage: boolean; + /** Si hay pagina anterior */ + hasPreviousPage: boolean; + /** Estado de carga */ + loading?: boolean; + /** Callback al cambiar de pagina */ + onPageChange: (page: number) => void; + /** Callback al cambiar limite por pagina */ + onLimitChange: (limit: number) => void; +} + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +const LIMIT_OPTIONS = [10, 25, 50, 100]; + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +/** + * Genera array de numeros de pagina con ellipsis para navegacion + * @param current - Pagina actual + * @param total - Total de paginas + * @returns Array de numeros o '...' para ellipsis + */ +function generatePageNumbers(current: number, total: number): (number | string)[] { + if (total <= 7) { + return Array.from({ length: total }, (_, i) => i + 1); + } + + if (current <= 3) { + return [1, 2, 3, 4, '...', total]; + } + + if (current >= total - 2) { + return [1, '...', total - 3, total - 2, total - 1, total]; + } + + return [1, '...', current - 1, current, current + 1, '...', total]; +} + +// ============================================================================ +// COMPONENT +// ============================================================================ + +export function StudentPagination({ + page, + limit, + total, + totalPages, + hasNextPage, + hasPreviousPage, + loading = false, + onPageChange, + onLimitChange, +}: StudentPaginationProps) { + // Calcular rango de items mostrados + const startItem = total === 0 ? 0 : (page - 1) * limit + 1; + const endItem = Math.min(page * limit, total); + + // No mostrar paginacion si no hay datos + if (total === 0) { + return null; + } + + return ( +
+ {/* Info y Selector de Limite */} +
+ + Mostrando{' '} + {startItem} -{' '} + {endItem} de{' '} + {total} estudiantes + + +
+ Por pagina: + +
+
+ + {/* Controles de Navegacion */} +
+ {/* Boton Anterior */} + onPageChange(page - 1)} + disabled={!hasPreviousPage || loading} + aria-label="Pagina anterior" + > + + + + {/* Numeros de pagina */} +
+ {generatePageNumbers(page, totalPages).map((pageNum, idx) => + pageNum === '...' ? ( + + ... + + ) : ( + onPageChange(pageNum as number)} + disabled={loading} + className="min-w-[32px]" + > + {pageNum} + + ) + )} +
+ + {/* Boton Siguiente */} + onPageChange(page + 1)} + disabled={!hasNextPage || loading} + aria-label="Pagina siguiente" + > + + +
+
+ ); +} diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentStatusCard.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentStatusCard.tsx index c3c89d2..0865ba9 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentStatusCard.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/components/monitoring/StudentStatusCard.tsx @@ -151,7 +151,7 @@ export function StudentStatusCard({ student, onClick }: StudentStatusCardProps)

- {student.exercises_completed}/{student.exercises_total} + {student.exercises_completed ?? 0}/{student.exercises_total ?? 0}

Ejercicios

@@ -161,7 +161,7 @@ export function StudentStatusCard({ student, onClick }: StudentStatusCardProps)

- {student.score_average.toFixed(0)}% + {(student.score_average ?? 0).toFixed(0)}%

Score Prom.

@@ -171,7 +171,7 @@ export function StudentStatusCard({ student, onClick }: StudentStatusCardProps)

- {Math.floor(student.time_spent_minutes / 60)}h + {Math.floor((student.time_spent_minutes ?? 0) / 60)}h

Tiempo

@@ -182,13 +182,13 @@ export function StudentStatusCard({ student, onClick }: StudentStatusCardProps)
Progreso General - {student.progress_percentage.toFixed(0)}% + {(student.progress_percentage ?? 0).toFixed(0)}%
diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/components/responses/ResponseDetailModal.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/components/responses/ResponseDetailModal.tsx index efd6286..341adc2 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/components/responses/ResponseDetailModal.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/components/responses/ResponseDetailModal.tsx @@ -233,7 +233,7 @@ export const ResponseDetailModal: React.FC = ({ className="relative my-8 w-full max-w-5xl overflow-hidden rounded-2xl bg-white shadow-2xl" > {/* Header */} -
+

Detalle de Respuesta @@ -406,19 +406,17 @@ export const ResponseDetailModal: React.FC = ({

{/* Footer */} -
+
{/* Botón Calificar - Solo para ejercicios que requieren revisión manual */} -
- {attempt && requiresManualGrading(attempt.exercise_type) && ( - - )} -
+ {attempt && requiresManualGrading(attempt.exercise_type) && ( + + )} + +
+ )} + + {/* Tab Progress Content */} + {!loading && !error && activeTab === 'progress' && ( + <> + {selectedClassroomId !== 'all' ? ( + + ) : ( + +
+
+ +
+

Selecciona una clase

+

+ Para ver el progreso detallado, analisis por modulos y estudiantes rezagados, + selecciona una clase especifica del menu desplegable superior. +

+ {classrooms.length === 0 && ( + navigate('/teacher/classes')} + > + Crear Primera Clase + + )} +
+
+ )} + + {/* Info Card - Tips para el Teacher */} + +
+
+ +
+
+

+ Consejos para el Seguimiento de Progreso +

+
    +
  • + + + Revisa las alertas de estudiantes rezagados semanalmente para intervenir a tiempo + +
  • +
  • + + + Los graficos de progreso por modulo te ayudan a identificar temas que necesitan refuerzo + +
  • +
  • + + + Exporta reportes en PDF o Excel para compartir con directivos o padres de familia + +
  • +
  • + + + Compara el rendimiento entre clases para adaptar tus estrategias de ensenanza + +
  • +
+
-
-

- Consejos para el Seguimiento de Progreso -

-
    -
  • - - - Revisa las alertas de estudiantes rezagados semanalmente para intervenir a - tiempo - -
  • -
  • - - - Los gráficos de progreso por módulo te ayudan a identificar temas que - necesitan refuerzo - -
  • -
  • - - - Exporta reportes en PDF o Excel para compartir con directivos o padres de - familia - -
  • -
  • - - - Compara el rendimiento entre clases para adaptar tus estrategias de enseñanza - -
  • -
+ + + )} + + {/* Tab Engagement Content */} + {!loading && !error && activeTab === 'engagement' && ( +
+ {/* Date Range Filter */} + +
+ setDateRange({ ...dateRange, start: e.target.value })} + /> + setDateRange({ ...dateRange, end: e.target.value })} + /> +
+ + + Exportar CSV + + + + +
-
- + + + {/* Loading state for analytics */} + {analyticsLoading && ( +
+ +

Cargando metricas de engagement...

+
+ )} + + {/* Error state for analytics */} + {analyticsError && !analyticsLoading && ( + +
+ +
+

+ Error al cargar engagement +

+

{analyticsError.message}

+ + + Reintentar + +
+
+
+ )} + + {/* Select class prompt for engagement */} + {selectedClassroomId === 'all' && !analyticsLoading && ( + +
+ +

+ Selecciona una clase +

+

+ Las metricas de engagement requieren seleccionar una clase especifica +

+
+
+ )} + + {/* Engagement Metrics - Main Cards */} + {engagementData && selectedClassroomId !== 'all' && !analyticsLoading && ( + <> +
+ +
+
+ +
+
+

Usuarios Activos Diarios

+

{engagementData.dau}

+
+
+
+ + +
+
+ +
+
+

Usuarios Activos Semanales

+

{engagementData.wau}

+
+
+
+ + +
+
+ +
+
+

Duracion Promedio (min)

+

+ {safeFormat(engagementData?.session_duration_avg, 0, '', '0')} +

+
+
+
+ + +
+
+ +
+
+

Sesiones por Usuario

+

+ {safeFormat(engagementData?.sessions_per_user, 1, '', '0.0')} +

+
+
+
+
+ + {/* Comparison with Previous Period */} + +

+ Comparacion con Periodo Anterior +

+
+
+ {(engagementData?.comparison_previous_period?.dau_change ?? 0) >= 0 ? ( + + ) : ( + + )} +
+

Cambio en DAU

+

= 0 + ? 'text-green-500' + : 'text-red-500' + }`} + > + {(engagementData?.comparison_previous_period?.dau_change ?? 0) >= 0 ? '+' : ''} + {safeFormat(engagementData?.comparison_previous_period?.dau_change, 1, '%', '0.0%')} +

+
+
+ +
+ {(engagementData?.comparison_previous_period?.wau_change ?? 0) >= 0 ? ( + + ) : ( + + )} +
+

Cambio en WAU

+

= 0 + ? 'text-green-500' + : 'text-red-500' + }`} + > + {(engagementData?.comparison_previous_period?.wau_change ?? 0) >= 0 ? '+' : ''} + {safeFormat(engagementData?.comparison_previous_period?.wau_change, 1, '%', '0.0%')} +

+
+
+ +
+ {(engagementData?.comparison_previous_period?.engagement_change ?? 0) >= 0 ? ( + + ) : ( + + )} +
+

Cambio en Engagement

+

= 0 + ? 'text-green-500' + : 'text-red-500' + }`} + > + {(engagementData?.comparison_previous_period?.engagement_change ?? 0) >= 0 ? '+' : ''} + {safeFormat(engagementData?.comparison_previous_period?.engagement_change, 1, '%', '0.0%')} +

+
+
+
+
+ + {/* Feature Usage Table */} + {engagementData?.feature_usage && engagementData.feature_usage.length > 0 && ( + +

+ Uso de Funcionalidades +

+
+ + + + + + + + + + {engagementData.feature_usage + .filter( + (feature) => + feature && + typeof feature.feature_name === 'string' && + typeof feature.usage_count === 'number', + ) + .map((feature, index) => ( + + + + + + ))} + +
+ Funcionalidad + + Usos Totales + + Usuarios Unicos +
+ {feature.feature_name} + + {feature.usage_count.toLocaleString()} + + {feature.unique_users ?? 0} +
+
+
+ )} + + )} +
)}
diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherReportsPage.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherReportsPage.tsx index 626056a..31c1a50 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherReportsPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherReportsPage.tsx @@ -106,7 +106,7 @@ export default function TeacherReportsPage() { const [filterType, setFilterType] = useState('all'); const [loading, setLoading] = useState(true); - // Use useUserGamification hook (currently with mock data until backend endpoint is ready) + // Use useUserGamification hook for real-time gamification data const { gamificationData } = useUserGamification(user?.id); // Fallback gamification data in case hook fails or user is not loaded diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherResourcesPage.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherResourcesPage.tsx index 02914a9..d70798a 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherResourcesPage.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherResourcesPage.tsx @@ -12,7 +12,7 @@ import { UnderConstruction } from '@shared/components/UnderConstruction'; export default function TeacherResourcesPage() { const { user, logout } = useAuth(); - // Use useUserGamification hook (currently with mock data until backend endpoint is ready) + // Use useUserGamification hook for real-time gamification data const { gamificationData } = useUserGamification(user?.id); // Fallback gamification data in case hook fails or user is not loaded diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherStudents.tsx b/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherStudents.tsx index 93c3c3e..e185129 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherStudents.tsx +++ b/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherStudents.tsx @@ -69,8 +69,9 @@ export default function TeacherStudents() { const classroomStudents = response.data; // Enrich student data with classroom information + // API returns user_id (not id) per StudentInClassroomDto return classroomStudents.map((student) => ({ - student_id: student.id, + student_id: student.user_id || student.id, student_name: student.full_name, email: student.email || 'N/A', average_score: student.score_average, @@ -182,8 +183,12 @@ export default function TeacherStudents() { bValue = b.completion_rate; break; case 'last_active': - aValue = new Date(a.last_active).getTime(); - bValue = new Date(b.last_active).getTime(); + // Manejar fechas inválidas o nulas + aValue = a.last_active ? new Date(a.last_active).getTime() : 0; + bValue = b.last_active ? new Date(b.last_active).getTime() : 0; + // Validar que sean números válidos + if (isNaN(aValue as number)) aValue = 0; + if (isNaN(bValue as number)) bValue = 0; break; default: return 0; @@ -328,7 +333,17 @@ export default function TeacherStudents() { ), sortable: false, - render: (row) => new Date(row.last_active).toLocaleDateString('es-ES'), + render: (row) => { + // Validar que last_active sea una fecha válida + if (!row.last_active) { + return Sin actividad; + } + const date = new Date(row.last_active); + if (isNaN(date.getTime())) { + return Sin actividad; + } + return date.toLocaleDateString('es-ES'); + }, }, ]; diff --git a/projects/gamilit/apps/frontend/src/apps/teacher/types/index.ts b/projects/gamilit/apps/frontend/src/apps/teacher/types/index.ts index 485682c..50cc349 100644 --- a/projects/gamilit/apps/frontend/src/apps/teacher/types/index.ts +++ b/projects/gamilit/apps/frontend/src/apps/teacher/types/index.ts @@ -3,7 +3,10 @@ export type StudentStatus = 'active' | 'inactive' | 'offline'; export interface StudentMonitoring { + /** Student ID - used internally */ id: string; + /** User ID - as returned by API (StudentInClassroomDto.user_id) */ + user_id?: string; full_name: string; email: string; status: StudentStatus; diff --git a/projects/gamilit/apps/frontend/src/config/api.config.ts b/projects/gamilit/apps/frontend/src/config/api.config.ts index 2587cc6..fd52867 100644 --- a/projects/gamilit/apps/frontend/src/config/api.config.ts +++ b/projects/gamilit/apps/frontend/src/config/api.config.ts @@ -209,6 +209,7 @@ export const API_ENDPOINTS = { create: '/admin/organizations', update: (id: string) => `/admin/organizations/${id}`, delete: (id: string) => `/admin/organizations/${id}`, + stats: (id: string) => `/admin/organizations/${id}/stats`, users: (id: string) => `/admin/organizations/${id}/users`, updateSubscription: (id: string) => `/admin/organizations/${id}/subscription`, updateFeatures: (id: string) => `/admin/organizations/${id}/features`, @@ -370,6 +371,8 @@ export const API_ENDPOINTS = { `/teacher/assignments/${assignmentId}/submissions`, submission: (submissionId: string) => `/teacher/submissions/${submissionId}`, gradeSubmission: (submissionId: string) => `/teacher/submissions/${submissionId}/feedback`, + sendReminder: (assignmentId: string) => `/teacher/assignments/${assignmentId}/send-reminder`, + upcomingAssignments: '/teacher/assignments/upcoming', // Analytics analytics: '/teacher/analytics', diff --git a/projects/gamilit/apps/frontend/src/features/auth/types/auth.types.ts b/projects/gamilit/apps/frontend/src/features/auth/types/auth.types.ts index b847295..1677891 100644 --- a/projects/gamilit/apps/frontend/src/features/auth/types/auth.types.ts +++ b/projects/gamilit/apps/frontend/src/features/auth/types/auth.types.ts @@ -194,7 +194,7 @@ export interface AuthProfile { /** Multi-tenancy: Links to auth_management.tenants */ tenant_id: string; - /** Links to auth.users (Supabase auth) - may be null for profiles without auth */ + /** Links to auth.users - may be null for profiles without auth */ user_id: string | null; /** Public display name - shown in UI, leaderboards */ diff --git a/projects/gamilit/apps/frontend/src/features/exercises/hooks/useExerciseSubmission.ts b/projects/gamilit/apps/frontend/src/features/exercises/hooks/useExerciseSubmission.ts index e884195..27c5034 100644 --- a/projects/gamilit/apps/frontend/src/features/exercises/hooks/useExerciseSubmission.ts +++ b/projects/gamilit/apps/frontend/src/features/exercises/hooks/useExerciseSubmission.ts @@ -5,15 +5,19 @@ * FECHA: 2025-11-04 * SPRINT: Sprint 1 * - * Hook for submitting exercise answers and handling results + * Hook for submitting exercise answers and handling results. + * Automatically invalidates dashboard and modules cache on successful submission. */ import { useState } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; import { apiClient } from '@/services/api/apiClient'; import type { ExerciseSubmission, ExerciseSubmissionResult, } from '../types/exercise.types'; +import { dashboardKeys } from '@/apps/student/hooks/useDashboardData'; +import { userModulesKeys } from '@/apps/student/hooks/useUserModules'; interface UseExerciseSubmissionOptions { onSuccess?: (result: ExerciseSubmissionResult) => void; @@ -21,6 +25,7 @@ interface UseExerciseSubmissionOptions { } export const useExerciseSubmission = (options?: UseExerciseSubmissionOptions) => { + const queryClient = useQueryClient(); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [result, setResult] = useState(null); @@ -30,12 +35,33 @@ export const useExerciseSubmission = (options?: UseExerciseSubmissionOptions) => setIsSubmitting(true); setError(null); + // Backend endpoint: POST /progress/submissions/submit + // Expects: { userId, exerciseId, answers } const { data } = await apiClient.post( - '/progress/exercise-submissions', + '/progress/submissions/submit', submission ); setResult(data); + + // Invalidate dashboard and modules cache to refresh progress data + // This ensures the UI updates immediately after completing an exercise + if (submission.userId) { + console.log('🔄 [useExerciseSubmission] Invalidating dashboard and modules cache...'); + + // Invalidate all dashboard data (rank, progress, coins, achievements) + await queryClient.invalidateQueries({ + queryKey: dashboardKeys.user(submission.userId), + }); + + // Invalidate modules data to update progress bars + await queryClient.invalidateQueries({ + queryKey: userModulesKeys.user(submission.userId), + }); + + console.log('✅ [useExerciseSubmission] Cache invalidated successfully'); + } + options?.onSuccess?.(data); return data; } catch (err) { diff --git a/projects/gamilit/apps/frontend/src/features/gamification/economy/components/Shop/ShopItem.tsx b/projects/gamilit/apps/frontend/src/features/gamification/economy/components/Shop/ShopItem.tsx index 7e25951..d0ced20 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/economy/components/Shop/ShopItem.tsx +++ b/projects/gamilit/apps/frontend/src/features/gamification/economy/components/Shop/ShopItem.tsx @@ -3,10 +3,52 @@ */ import { motion } from 'framer-motion'; -import { ShoppingCart, Lock, Check, Sparkles } from 'lucide-react'; +import { + ShoppingCart, + Lock, + Check, + Sparkles, + Award, + BookOpen, + Coins, + Compass, + Flag, + Image, + Shield, + ShieldCheck, + Smile, + Square, + Sticker, + UserCircle, + Zap, + type LucideIcon, +} from 'lucide-react'; import type { ShopItem as ShopItemType } from '../../types/economyTypes'; import { useEconomyStore } from '../../store/economyStore'; +// Map icon names from backend to Lucide components +const iconMap: Record = { + 'award': Award, + 'book-open': BookOpen, + 'coins': Coins, + 'compass': Compass, + 'flag': Flag, + 'image': Image, + 'shield': Shield, + 'shield-check': ShieldCheck, + 'smile': Smile, + 'sparkles': Sparkles, + 'square': Square, + 'sticker': Sticker, + 'user-circle': UserCircle, + 'zap': Zap, +}; + +// Get icon component from name +const getIconComponent = (iconName: string): LucideIcon => { + return iconMap[iconName.toLowerCase()] || Award; +}; + interface ShopItemProps { item: ShopItemType; onPurchase?: (item: ShopItemType) => void; @@ -68,7 +110,10 @@ export const ShopItem: React.FC = ({ item, onPurchase: _onPurchas {/* Item Icon */}
-
{item.icon}
+ {(() => { + const IconComponent = getIconComponent(item.icon || 'award'); + return ; + })()}
{/* Item Info */} diff --git a/projects/gamilit/apps/frontend/src/features/gamification/ranks/api/ranksAPI.ts b/projects/gamilit/apps/frontend/src/features/gamification/ranks/api/ranksAPI.ts index 809d65d..73ed8ca 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/ranks/api/ranksAPI.ts +++ b/projects/gamilit/apps/frontend/src/features/gamification/ranks/api/ranksAPI.ts @@ -63,10 +63,57 @@ const mockGetCurrentRank = async (): Promise => { return MOCK_USER_NACOM; }; +// ============================================================================ +// TYPES FOR RANK CONFIG +// ============================================================================ + +/** + * Rank metadata from backend + */ +export interface RankMetadata { + rank: string; + name: string; + description: string; + xp_min: number; + xp_max: number; // -1 means Infinity + ml_coins_bonus: number; + order: number; +} + // ============================================================================ // RANKS API FUNCTIONS // ============================================================================ +/** + * Get all ranks configuration from backend + * P0-004: New function to fetch rank thresholds dynamically + * + * @returns Array of rank metadata with XP thresholds + */ +export const getRanksConfig = async (): Promise => { + try { + if (FEATURE_FLAGS.USE_MOCK_DATA) { + // Mock data with v2.1 thresholds + return [ + { rank: 'Ajaw', name: 'Ajaw', description: 'Señor', xp_min: 0, xp_max: 499, ml_coins_bonus: 0, order: 1 }, + { rank: 'Nacom', name: 'Nacom', description: 'Capitán de Guerra', xp_min: 500, xp_max: 999, ml_coins_bonus: 100, order: 2 }, + { rank: "Ah K'in", name: "Ah K'in", description: 'Sacerdote del Sol', xp_min: 1000, xp_max: 1499, ml_coins_bonus: 250, order: 3 }, + { rank: 'Halach Uinic', name: 'Halach Uinic', description: 'Hombre Verdadero', xp_min: 1500, xp_max: 1899, ml_coins_bonus: 500, order: 4 }, + { rank: "K'uk'ulkan", name: "K'uk'ulkan", description: 'Serpiente Emplumada', xp_min: 1900, xp_max: -1, ml_coins_bonus: 1000, order: 5 }, + ]; + } + + const { data } = await apiClient.get( + '/gamification/ranks', + ); + + // Handle both direct array response and wrapped response + return Array.isArray(data) ? data : (data as any).data || []; + } catch (error) { + throw handleAPIError(error); + } +}; + /** * Get current rank progress * @@ -473,6 +520,7 @@ export const removeMultiplier = async (type: string): Promise = ({ initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.3 }} - className="rounded-xl bg-gradient-to-r from-detective-orange to-detective-orange-dark p-6 text-center text-white" + className="rounded-xl bg-gradient-to-r from-orange-500 to-orange-600 p-6 text-center text-white" >

¡Sigue así, Detective!

diff --git a/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankProgressBar.tsx b/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankProgressBar.tsx index e716d5f..5bec1a3 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankProgressBar.tsx +++ b/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankProgressBar.tsx @@ -38,7 +38,7 @@ const heightConfig = { const colorConfig = { detective: { bg: 'bg-detective-bg-secondary', - fill: 'bg-gradient-to-r from-detective-orange to-detective-orange-dark', + fill: 'bg-gradient-to-r from-orange-500 to-orange-600', text: 'text-detective-orange', }, rank: { diff --git a/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankUpModal.tsx b/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankUpModal.tsx index 79ba692..a4fb736 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankUpModal.tsx +++ b/projects/gamilit/apps/frontend/src/features/gamification/ranks/components/RankUpModal.tsx @@ -233,7 +233,7 @@ export const RankUpModal: React.FC = ({ isOpen, onClose }) => transition={{ delay: 1.2 }} className="text-center" > -

+
{currentRank.multiplier.toFixed(2)}x Multiplicador
diff --git a/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRank.ts b/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRank.ts index 99b210e..d773182 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRank.ts +++ b/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRank.ts @@ -2,11 +2,18 @@ * useRank Hook * * Custom hook for accessing current rank information and rank-related utilities. + * + * CORRECCIONES APLICADAS (2025-12-14): + * - P0-002: isMinRank corregido a 'Ajaw' (era 'Nacom') + * - P0-003: Progreso ahora usa XP en lugar de ML Coins (umbrales v2.1) + * - P0-004: COMPLETADO - Usa useRanksConfig para obtener datos del backend + * + * Ver: orchestration/reportes/TECH-LEADER-VALIDATION-REPORT-2025-12-14.md */ import { useMemo } from 'react'; import { useRanksStore, selectUserProgress, selectCurrentRank } from '../store/ranksStore'; -import { getRankById, getNextRank, getPreviousRank } from '../mockData/ranksMockData'; +import { useRanksConfig } from './useRanksConfig'; import type { RankDefinition, RankComparison } from '../types/ranksTypes'; /** @@ -37,24 +44,33 @@ interface UseRankReturn { /** * Custom hook to access rank information + * P0-004: Uses useRanksConfig to get rank data from backend instead of mockData */ export function useRank(): UseRankReturn { const userProgress = useRanksStore(selectUserProgress); const currentRankId = useRanksStore(selectCurrentRank); + // P0-004: Use useRanksConfig instead of mockData + const { + getRankById, + getNextRank, + getPreviousRank, + getRankThresholds, + } = useRanksConfig(); + const currentRank = useMemo( () => getRankById(currentRankId), - [currentRankId] + [currentRankId, getRankById] ); const nextRank = useMemo( () => getNextRank(currentRankId), - [currentRankId] + [currentRankId, getNextRank] ); const previousRank = useMemo( () => getPreviousRank(currentRankId), - [currentRankId] + [currentRankId, getPreviousRank] ); const isMaxRank = useMemo( @@ -62,18 +78,30 @@ export function useRank(): UseRankReturn { [currentRankId] ); + // P0-002 FIX: Ajaw is the minimum rank, not Nacom const isMinRank = useMemo( - () => currentRankId === 'Nacom', + () => currentRankId === 'Ajaw', [currentRankId] ); - // Calculate progress to next rank (based on ML Coins) + // P0-003 & P0-004: Calculate progress using thresholds from backend const progress = useMemo(() => { - if (!nextRank) return 100; // Max rank - const coinsNeeded = nextRank.mlCoinsRequired - currentRank.mlCoinsRequired; - const coinsEarned = userProgress.mlCoinsEarned - currentRank.mlCoinsRequired; - return Math.min(100, Math.max(0, (coinsEarned / coinsNeeded) * 100)); - }, [userProgress.mlCoinsEarned, currentRank, nextRank]); + if (!nextRank) return 100; // Max rank (K'uk'ulkan) + + // P0-004: Get thresholds from useRanksConfig instead of hardcoded values + const currentThreshold = getRankThresholds(currentRankId); + + // Use totalXP from store (falls back to currentXP if not available) + const currentXP = userProgress.totalXP || userProgress.currentXP || 0; + + // Calculate progress within current rank + const xpInRank = Math.max(0, currentXP - currentThreshold.min); + const xpRangeSize = currentThreshold.max - currentThreshold.min; + + if (xpRangeSize <= 0 || !isFinite(xpRangeSize)) return 100; + + return Math.min(100, Math.max(0, (xpInRank / xpRangeSize) * 100)); + }, [userProgress.totalXP, userProgress.currentXP, currentRankId, nextRank, getRankThresholds]); /** * Compare current rank to next rank @@ -101,11 +129,14 @@ export function useRank(): UseRankReturn { /** * Compare current rank to a specific target rank + * P0-004: Uses getRankById from useRanksConfig */ const compareToRank = (targetRankId: string): RankComparison | null => { try { - const targetRank = getRankById(targetRankId as never); - if (!targetRank) return null; + const targetRank = getRankById(targetRankId); + if (!targetRank || targetRank.id === 'Ajaw' && targetRankId !== 'Ajaw') { + return null; // getRankById returns default if not found + } const mlCoinsDifference = targetRank.mlCoinsRequired - userProgress.mlCoinsEarned; const multiplierIncrease = targetRank.multiplier - currentRank.multiplier; diff --git a/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRanksConfig.ts b/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRanksConfig.ts new file mode 100644 index 0000000..99dba00 --- /dev/null +++ b/projects/gamilit/apps/frontend/src/features/gamification/ranks/hooks/useRanksConfig.ts @@ -0,0 +1,293 @@ +/** + * useRanksConfig Hook + * + * Hook para obtener la configuracion de rangos del backend. + * P0-004: Reemplaza la dependencia de mockData con datos del API. + * + * Uso: + * ```typescript + * const { ranksConfig, isLoading, error, getRankById, getNextRank, getPreviousRank } = useRanksConfig(); + * ``` + * + * Creado: 2025-12-14 + * Ver: orchestration/reportes/TECH-LEADER-VALIDATION-REPORT-2025-12-14.md + */ + +import { useState, useEffect, useCallback, useMemo } from 'react'; +import { getRanksConfig, type RankMetadata } from '../api/ranksAPI'; +import type { RankDefinition } from '../types/ranksTypes'; + +/** + * Estado del hook + */ +interface UseRanksConfigReturn { + // Datos + ranksConfig: RankMetadata[]; + ranksMap: Record; + isLoaded: boolean; + isLoading: boolean; + error: string | null; + + // Funciones de utilidad (reemplazan mockData) + getRankById: (rankId: string) => RankDefinition; + getNextRank: (currentRankId: string) => RankDefinition | null; + getPreviousRank: (currentRankId: string) => RankDefinition | null; + getRankThresholds: (rankId: string) => { min: number; max: number }; + + // Recarga + refresh: () => Promise; +} + +/** + * Cache global para evitar multiples llamadas al API + */ +let globalRanksConfig: RankMetadata[] | null = null; +let globalRanksPromise: Promise | null = null; + +/** + * Rank definition por defecto (fallback) + */ +const DEFAULT_RANK: RankDefinition = { + id: 'Ajaw', + name: 'Ajaw', + nameSpanish: 'Señor', + description: 'Inicio del camino del conocimiento', + xpRequired: 0, + mlCoinsRequired: 0, + multiplier: 1.0, + order: 1, + icon: 'ajaw', + color: 'from-amber-400 to-yellow-500', + benefits: [], +}; + +/** + * Convierte RankMetadata del API a RankDefinition del frontend + */ +function metadataToDefinition(metadata: RankMetadata, index: number): RankDefinition { + // Mapeo de colores por rango + const colorMap: Record = { + 'Ajaw': 'from-amber-400 to-yellow-500', + 'Nacom': 'from-green-400 to-emerald-500', + "Ah K'in": 'from-blue-400 to-cyan-500', + 'Halach Uinic': 'from-purple-400 to-violet-500', + "K'uk'ulkan": 'from-red-400 to-orange-500', + }; + + // Mapeo de iconos por rango + const iconMap: Record = { + 'Ajaw': 'ajaw', + 'Nacom': 'nacom', + "Ah K'in": 'ahkin', + 'Halach Uinic': 'halachuinic', + "K'uk'ulkan": 'kukulkan', + }; + + // Mapeo de nombres en español + const spanishNameMap: Record = { + 'Ajaw': 'Señor', + 'Nacom': 'Capitán de Guerra', + "Ah K'in": 'Sacerdote del Sol', + 'Halach Uinic': 'Hombre Verdadero', + "K'uk'ulkan": 'Serpiente Emplumada', + }; + + // Beneficios por rango + const benefitsMap: Record = { + 'Ajaw': ['Acceso a ejercicios básicos', 'Tutorial interactivo'], + 'Nacom': ['Ejercicios intermedios', 'Pistas nivel 1', 'Multiplicador 1.1x'], + "Ah K'in": ['Ejercicios avanzados', 'Pistas nivel 2', 'Multiplicador 1.25x', 'Misiones especiales'], + 'Halach Uinic': ['Ejercicios expertos', 'Pistas nivel 3', 'Multiplicador 1.5x', 'Acceso a torneos'], + "K'uk'ulkan": ['Todos los ejercicios', 'Pistas ilimitadas', 'Multiplicador 2x', 'Sistema de prestigio', 'Badge exclusivo'], + }; + + // Multiplicador por rango + const multiplierMap: Record = { + 'Ajaw': 1.0, + 'Nacom': 1.1, + "Ah K'in": 1.25, + 'Halach Uinic': 1.5, + "K'uk'ulkan": 2.0, + }; + + return { + id: metadata.rank, + name: metadata.name, + nameSpanish: spanishNameMap[metadata.rank] || metadata.description, + description: metadata.description, + xpRequired: metadata.xp_min, + mlCoinsRequired: metadata.ml_coins_bonus, + multiplier: multiplierMap[metadata.rank] || 1.0, + order: metadata.order, + icon: iconMap[metadata.rank] || 'default', + color: colorMap[metadata.rank] || 'from-gray-400 to-gray-500', + benefits: benefitsMap[metadata.rank] || [], + }; +} + +/** + * Hook para obtener y cachear la configuracion de rangos + */ +export function useRanksConfig(): UseRanksConfigReturn { + const [ranksConfig, setRanksConfig] = useState(globalRanksConfig || []); + const [isLoading, setIsLoading] = useState(!globalRanksConfig); + const [error, setError] = useState(null); + + /** + * Carga la configuracion de rangos + */ + const loadConfig = useCallback(async () => { + // Si ya tenemos datos en cache global, usarlos + if (globalRanksConfig) { + setRanksConfig(globalRanksConfig); + setIsLoading(false); + return; + } + + // Si ya hay una peticion en curso, esperarla + if (globalRanksPromise) { + try { + const data = await globalRanksPromise; + setRanksConfig(data); + setIsLoading(false); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error loading ranks config'); + setIsLoading(false); + } + return; + } + + // Iniciar nueva peticion + setIsLoading(true); + setError(null); + + globalRanksPromise = getRanksConfig(); + + try { + const data = await globalRanksPromise; + globalRanksConfig = data; + setRanksConfig(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Error loading ranks config'); + // Usar fallback en caso de error + const fallbackConfig: RankMetadata[] = [ + { rank: 'Ajaw', name: 'Ajaw', description: 'Señor', xp_min: 0, xp_max: 499, ml_coins_bonus: 0, order: 1 }, + { rank: 'Nacom', name: 'Nacom', description: 'Capitán de Guerra', xp_min: 500, xp_max: 999, ml_coins_bonus: 100, order: 2 }, + { rank: "Ah K'in", name: "Ah K'in", description: 'Sacerdote del Sol', xp_min: 1000, xp_max: 1499, ml_coins_bonus: 250, order: 3 }, + { rank: 'Halach Uinic', name: 'Halach Uinic', description: 'Hombre Verdadero', xp_min: 1500, xp_max: 1899, ml_coins_bonus: 500, order: 4 }, + { rank: "K'uk'ulkan", name: "K'uk'ulkan", description: 'Serpiente Emplumada', xp_min: 1900, xp_max: -1, ml_coins_bonus: 1000, order: 5 }, + ]; + globalRanksConfig = fallbackConfig; + setRanksConfig(fallbackConfig); + } finally { + setIsLoading(false); + globalRanksPromise = null; + } + }, []); + + // Cargar configuracion al montar + useEffect(() => { + loadConfig(); + }, [loadConfig]); + + /** + * Mapa de rangos por ID para acceso rapido + */ + const ranksMap = useMemo(() => { + const map: Record = {}; + ranksConfig.forEach((rank) => { + map[rank.rank] = rank; + }); + return map; + }, [ranksConfig]); + + /** + * Obtiene un rango por ID + */ + const getRankById = useCallback( + (rankId: string): RankDefinition => { + const metadata = ranksMap[rankId]; + if (!metadata) { + console.warn(`Rank not found: ${rankId}, using default`); + return DEFAULT_RANK; + } + const index = ranksConfig.findIndex((r) => r.rank === rankId); + return metadataToDefinition(metadata, index); + }, + [ranksMap, ranksConfig] + ); + + /** + * Obtiene el siguiente rango + */ + const getNextRank = useCallback( + (currentRankId: string): RankDefinition | null => { + const currentIndex = ranksConfig.findIndex((r) => r.rank === currentRankId); + if (currentIndex === -1 || currentIndex >= ranksConfig.length - 1) { + return null; + } + const nextMetadata = ranksConfig[currentIndex + 1]; + return metadataToDefinition(nextMetadata, currentIndex + 1); + }, + [ranksConfig] + ); + + /** + * Obtiene el rango anterior + */ + const getPreviousRank = useCallback( + (currentRankId: string): RankDefinition | null => { + const currentIndex = ranksConfig.findIndex((r) => r.rank === currentRankId); + if (currentIndex <= 0) { + return null; + } + const prevMetadata = ranksConfig[currentIndex - 1]; + return metadataToDefinition(prevMetadata, currentIndex - 1); + }, + [ranksConfig] + ); + + /** + * Obtiene los umbrales de XP para un rango + */ + const getRankThresholds = useCallback( + (rankId: string): { min: number; max: number } => { + const metadata = ranksMap[rankId]; + if (!metadata) { + return { min: 0, max: 500 }; + } + return { + min: metadata.xp_min, + max: metadata.xp_max === -1 ? Infinity : metadata.xp_max, + }; + }, + [ranksMap] + ); + + /** + * Recarga la configuracion desde el API + */ + const refresh = useCallback(async () => { + globalRanksConfig = null; + globalRanksPromise = null; + await loadConfig(); + }, [loadConfig]); + + return { + ranksConfig, + ranksMap, + isLoaded: !isLoading && ranksConfig.length > 0, + isLoading, + error, + getRankById, + getNextRank, + getPreviousRank, + getRankThresholds, + refresh, + }; +} + +/** + * Export por defecto + */ +export default useRanksConfig; diff --git a/projects/gamilit/apps/frontend/src/features/gamification/social/api/achievementsAPI.ts b/projects/gamilit/apps/frontend/src/features/gamification/social/api/achievementsAPI.ts index c2e0107..8d5ea26 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/social/api/achievementsAPI.ts +++ b/projects/gamilit/apps/frontend/src/features/gamification/social/api/achievementsAPI.ts @@ -24,18 +24,26 @@ import type { Achievement } from '../types/achievementsTypes'; /** * Backend Achievement Response - * Maps from backend schema to frontend types + * Note: Uses snake_case to match backend response (apiClient does NOT transform) */ export interface BackendAchievement { id: string; + tenant_id?: string; name: string; description: string; icon: string; category: string; rarity: 'common' | 'rare' | 'epic' | 'legendary'; - mlCoinsReward: number; - xpReward: number; - isRepeatable: boolean; + difficulty_level?: string; + ml_coins_reward: number; + is_repeatable: boolean; + is_secret?: boolean; + is_active?: boolean; + order_index?: number; + points_value?: number; + unlock_message?: string; + instructions?: string; + tips?: string[]; conditions: { type: string; requirements: Record; @@ -43,28 +51,50 @@ export interface BackendAchievement { rewards: { xp: number; ml_coins: number; + badge?: string; }; + metadata?: Record; + created_at?: string; + updated_at?: string; } /** * Backend User Achievement Response + * Note: Uses snake_case to match backend response (apiClient does NOT transform) */ export interface BackendUserAchievement { - achievementId: string; - userId: string; + id: string; + achievement_id: string; + user_id: string; progress: number; - maxProgress: number; - isCompleted: boolean; - completionPercentage: number; - completedAt?: string; - unlockedAt?: string; - rewardsClaimed: boolean; + max_progress: number; + is_completed: boolean; + completion_percentage: string; // Backend returns as string + completed_at?: string; + started_at?: string; + rewards_claimed: boolean; + rewards_received?: { + xp: number; + ml_coins: number; + }; + progress_data?: Record; + milestones_reached?: string[]; + notified?: boolean; + viewed?: boolean; + metadata?: Record; } /** - * Combined Achievement with User Progress + * Combined Achievement with User Progress (API Response format) + * + * CORR-P0-002: Renombrado de AchievementWithProgress para evitar colisión + * con achievementsTypes.ts que tiene una interfaz con el mismo nombre + * pero estructura diferente (view model). + * + * Esta interfaz representa la respuesta de la API combinada. + * Para el view model de componentes, usar AchievementWithProgress de achievementsTypes.ts */ -export interface AchievementWithProgress extends BackendAchievement { +export interface AchievementAPIResponse extends BackendAchievement { isUnlocked: boolean; unlockedAt?: Date; progress?: { @@ -72,6 +102,7 @@ export interface AchievementWithProgress extends BackendAchievement { required: number; }; completionPercentage?: number; + rewardsClaimed?: boolean; } // ============================================================================ @@ -101,7 +132,7 @@ export const getAllAchievements = async (): Promise => { * @param userId - User ID * @returns List of user's achievements with unlock status and progress */ -export const getUserAchievements = async (userId: string): Promise => { +export const getUserAchievements = async (userId: string): Promise => { try { // Get all achievements const allAchievements = await getAllAchievements(); @@ -114,21 +145,24 @@ export const getUserAchievements = async (userId: string): Promise { - const userProgress = userAchievements.find((ua) => ua.achievementId === achievement.id); + const userProgress = userAchievements.find((ua) => ua.achievement_id === achievement.id); return { ...achievement, - isUnlocked: userProgress?.isCompleted || false, - unlockedAt: userProgress?.completedAt ? new Date(userProgress.completedAt) : undefined, + isUnlocked: userProgress?.is_completed || false, + unlockedAt: userProgress?.completed_at ? new Date(userProgress.completed_at) : undefined, progress: userProgress ? { current: userProgress.progress, - required: userProgress.maxProgress, + required: userProgress.max_progress, } : undefined, - completionPercentage: userProgress?.completionPercentage, + completionPercentage: userProgress?.completion_percentage + ? parseFloat(userProgress.completion_percentage) + : undefined, + rewardsClaimed: userProgress?.rewards_claimed || false, }; }, ); @@ -253,6 +287,52 @@ export const checkAchievements = async ( } }; +/** + * Claim rewards for a completed achievement + * + * Marks the achievement rewards as claimed and credits ML Coins/XP to user. + * + * @param userId - User ID + * @param achievementId - Achievement ID to claim rewards for + * @returns Claim result with rewards credited + * + * @example + * const result = await claimAchievementRewards('user-123', 'achievement-456'); + * console.log(`Received ${result.ml_coins_awarded} ML Coins!`); + */ +export const claimAchievementRewards = async ( + userId: string, + achievementId: string, +): Promise<{ + success: boolean; + achievement_id: string; + rewards_claimed: boolean; + ml_coins_awarded?: number; + xp_awarded?: number; +}> => { + try { + const { data } = await apiClient.post< + ApiResponse<{ + id: string; + user_id: string; + achievement_id: string; + rewards_claimed: boolean; + completed_at: string; + }> + >(`/gamification/users/${userId}/achievements/${achievementId}/claim`); + + // The backend returns the updated user_achievement record + // We need to get the achievement details to know the rewards + return { + success: true, + achievement_id: achievementId, + rewards_claimed: data.data.rewards_claimed, + }; + } catch (error) { + throw handleAPIError(error); + } +}; + // ============================================================================ // HELPER FUNCTIONS // ============================================================================ @@ -282,6 +362,9 @@ const mapCategory = (backendCategory: string): 'progress' | 'mastery' | 'social' /** * Map backend achievement to frontend Achievement type * + * CORR-P0-003: Usar ?? (nullish coalescing) en lugar de || para rewards + * Esto permite que valores de 0 sean respetados (0 ML coins es válido) + * * @param backendAchievement - Backend achievement data * @param userProgress - Optional user progress data * @returns Frontend Achievement object @@ -297,20 +380,23 @@ export const mapToFrontendAchievement = ( category: mapCategory(backendAchievement.category), rarity: backendAchievement.rarity, icon: backendAchievement.icon, - mlCoinsReward: backendAchievement.rewards.ml_coins, - xpReward: backendAchievement.rewards.xp, - isUnlocked: userProgress?.isCompleted || false, - unlockedAt: userProgress?.completedAt ? new Date(userProgress.completedAt) : undefined, + // CORR-P0-003: Usar ?? para respetar valores de 0 (0 ML coins es válido) + mlCoinsReward: backendAchievement.rewards?.ml_coins ?? backendAchievement.ml_coins_reward ?? 0, + xpReward: backendAchievement.rewards?.xp ?? backendAchievement.points_value ?? 0, + isUnlocked: userProgress?.is_completed ?? false, + unlockedAt: userProgress?.completed_at ? new Date(userProgress.completed_at) : undefined, progress: userProgress ? { current: userProgress.progress, - required: userProgress.maxProgress, + required: userProgress.max_progress, } : undefined, - requirements: backendAchievement.conditions.requirements, + requirements: backendAchievement.conditions?.requirements, isHidden: - backendAchievement.category.toLowerCase() === 'hidden' || - backendAchievement.category.toLowerCase() === 'special', + backendAchievement.is_secret ?? + (backendAchievement.category.toLowerCase() === 'hidden' || + backendAchievement.category.toLowerCase() === 'special'), + rewardsClaimed: userProgress?.rewards_claimed ?? false, }; }; @@ -326,7 +412,7 @@ export const mapAchievementsToFrontend = ( userAchievements?: BackendUserAchievement[], ): Achievement[] => { return backendAchievements.map((achievement) => { - const userProgress = userAchievements?.find((ua) => ua.achievementId === achievement.id); + const userProgress = userAchievements?.find((ua) => ua.achievement_id === achievement.id); return mapToFrontendAchievement(achievement, userProgress); }); }; @@ -343,6 +429,7 @@ export default { updateAchievementProgress, unlockAchievement, checkAchievements, + claimAchievementRewards, mapToFrontendAchievement, mapAchievementsToFrontend, }; diff --git a/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/AchievementCard.tsx b/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/AchievementCard.tsx index 98a9308..78d661d 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/AchievementCard.tsx +++ b/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/AchievementCard.tsx @@ -20,12 +20,14 @@ import { Flag, Flame, Footprints, + Gift, GraduationCap, Handshake, HeartHandshake, Key, Layers, Link, + Loader, Lock, Moon, Puzzle, @@ -51,6 +53,8 @@ import type { Achievement } from '../../types/achievementsTypes'; interface AchievementCardProps { achievement: Achievement; onClick?: () => void; + onClaimRewards?: (achievementId: string) => Promise; + isClaiming?: boolean; } // Icon mapping for achievement icons @@ -108,9 +112,22 @@ const rarityGlow = { legendary: 'shadow-xl shadow-yellow-300 animate-gold-shine', }; -export const AchievementCard: React.FC = ({ achievement, onClick }) => { +export const AchievementCard: React.FC = ({ + achievement, + onClick, + onClaimRewards, + isClaiming = false, +}) => { const IconComponent = achievementIconMap[achievement.icon] || Award; const isLocked = !achievement.isUnlocked; + const canClaimRewards = achievement.isUnlocked && !achievement.rewardsClaimed && onClaimRewards; + + const handleClaimClick = async (e: React.MouseEvent) => { + e.stopPropagation(); // Prevent triggering onClick of parent + if (onClaimRewards && !isClaiming) { + await onClaimRewards(achievement.id); + } + }; return ( = ({ achievement, o {/* Icon */}
@@ -187,6 +204,39 @@ export const AchievementCard: React.FC = ({ achievement, o
+ {/* Claim Rewards Button */} + {canClaimRewards && ( + + {isClaiming ? ( + <> + + Reclamando... + + ) : ( + <> + + Reclamar Recompensas + + )} + + )} + + {/* Rewards Claimed Badge */} + {achievement.isUnlocked && achievement.rewardsClaimed && ( +
+ Recompensas reclamadas +
+ )} + {/* Unlocked Badge */} {achievement.isUnlocked && (
diff --git a/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/ProgressTreeVisualizer.tsx b/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/ProgressTreeVisualizer.tsx index ca29231..37a06cb 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/ProgressTreeVisualizer.tsx +++ b/projects/gamilit/apps/frontend/src/features/gamification/social/components/Achievements/ProgressTreeVisualizer.tsx @@ -1,19 +1,99 @@ /** * ProgressTreeVisualizer Component * Tree view of interconnected achievements + * + * IMPL-006: Updated icon map to include all achievement icons */ import React from 'react'; import { motion } from 'framer-motion'; -import { TrendingUp, Award, Users, EyeOff, type LucideIcon } from 'lucide-react'; +import { + Award, + BookOpen, + Brain, + Calendar, + Check, + CheckCircle, + Clock, + Compass, + Crown, + Egg, + EyeOff, + Flag, + Flame, + Footprints, + Gem, + GraduationCap, + Handshake, + HeartHandshake, + Key, + Layers, + Link, + Moon, + Puzzle, + Search, + Shield, + Sparkles, + Star, + Sunrise, + Target, + ThumbsUp, + Timer, + TrendingUp, + Trophy, + UserPlus, + Users, + UsersRound, + Zap, + type LucideIcon, +} from 'lucide-react'; import { useAchievements } from '../../hooks/useAchievements'; -// Icon mapping helper +// IMPL-006: Complete icon mapping (aligned with AchievementCard.tsx) const iconMap: Record = { + // Category icons TrendingUp, Award, Users, EyeOff, + // Achievement icons (kebab-case from DB) + 'footprints': Footprints, + 'target': Target, + 'book-open': BookOpen, + 'graduation-cap': GraduationCap, + 'compass': Compass, + 'trophy': Trophy, + 'zap': Zap, + 'star': Star, + 'flame': Flame, + 'award': Award, + 'sunrise': Sunrise, + 'moon': Moon, + 'calendar': Calendar, + 'trending-up': TrendingUp, + 'shield': Shield, + 'check-circle': CheckCircle, + 'sparkles': Sparkles, + 'search': Search, + 'timer': Timer, + 'link': Link, + 'check': Check, + 'crown': Crown, + 'brain': Brain, + 'layers': Layers, + 'focus': Target, + 'user-plus': UserPlus, + 'users': Users, + 'flag': Flag, + 'heart-handshake': HeartHandshake, + 'users-round': UsersRound, + 'thumbs-up': ThumbsUp, + 'handshake': Handshake, + 'egg': Egg, + 'clock': Clock, + 'key': Key, + 'puzzle': Puzzle, + 'gem': Gem, }; export const ProgressTreeVisualizer: React.FC = () => { diff --git a/projects/gamilit/apps/frontend/src/features/gamification/social/store/achievementsStore.ts b/projects/gamilit/apps/frontend/src/features/gamification/social/store/achievementsStore.ts index 41617de..c5912b4 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/social/store/achievementsStore.ts +++ b/projects/gamilit/apps/frontend/src/features/gamification/social/store/achievementsStore.ts @@ -10,7 +10,7 @@ import type { AchievementStats, } from '../types/achievementsTypes'; import { allAchievements } from '../mockData/achievementsMockData'; -import { getUserAchievements, mapAchievementsToFrontend } from '../api/achievementsAPI'; +import { getUserAchievements } from '../api/achievementsAPI'; interface AchievementsStore { achievements: Achievement[]; @@ -146,8 +146,23 @@ export const useAchievementsStore = create((set) => ({ // Fetch user achievements with progress from backend const achievementsWithProgress = await getUserAchievements(userId); - // Map to frontend Achievement type - const achievements = mapAchievementsToFrontend(achievementsWithProgress); + // Map backend response to frontend Achievement type + const achievements: Achievement[] = achievementsWithProgress.map((ach) => ({ + id: ach.id, + title: ach.name, + description: ach.description, + category: ach.category as Achievement['category'], + rarity: ach.rarity, + icon: ach.icon, + mlCoinsReward: ach.rewards?.ml_coins || ach.ml_coins_reward || 0, + xpReward: ach.rewards?.xp || 0, + isUnlocked: ach.isUnlocked || false, + unlockedAt: ach.unlockedAt, + progress: ach.progress, + requirements: ach.conditions?.requirements, + isHidden: ach.is_secret || ach.category === 'hidden' || ach.category === 'special', + rewardsClaimed: ach.rewardsClaimed || false, + })); set({ achievements, diff --git a/projects/gamilit/apps/frontend/src/features/gamification/social/types/achievementsTypes.ts b/projects/gamilit/apps/frontend/src/features/gamification/social/types/achievementsTypes.ts index d7cf40d..3a3963b 100644 --- a/projects/gamilit/apps/frontend/src/features/gamification/social/types/achievementsTypes.ts +++ b/projects/gamilit/apps/frontend/src/features/gamification/social/types/achievementsTypes.ts @@ -14,10 +14,16 @@ * P2-001: Consolidación de types - re-export desde SSOT */ +// Import for local use +import type { + AchievementCategory, + Achievement as BaseAchievement, + AchievementReward, +} from '@shared/types/achievement.types'; + // Re-export canonical types from SSOT -export type { AchievementCategory } from '@shared/types/achievement.types'; -export type { Achievement as BaseAchievement } from '@shared/types/achievement.types'; -export type { AchievementReward } from '@shared/types/achievement.types'; +export type { AchievementCategory, AchievementReward }; +export type { BaseAchievement }; // Local alias for rarity (matches SSOT NonNullable) export type AchievementRarity = 'common' | 'rare' | 'epic' | 'legendary'; @@ -70,6 +76,7 @@ export interface AchievementWithProgress { progress?: AchievementProgress; requirements?: AchievementRequirements; isHidden?: boolean; + rewardsClaimed?: boolean; // True if user has claimed rewards for this achievement } /** diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/index.ts b/projects/gamilit/apps/frontend/src/features/mechanics/index.ts index 8ff3f92..47c0b24 100644 --- a/projects/gamilit/apps/frontend/src/features/mechanics/index.ts +++ b/projects/gamilit/apps/frontend/src/features/mechanics/index.ts @@ -1,9 +1,8 @@ // Module 4: Additional Reading Mechanics +// NOTA: Ejercicios eliminados según DocumentoDeDiseño v6.1: +// - EmailFormal, ChatLiterario, EnsayoArgumentativo, ResenaCritica +// Solo se mantienen los 5 ejercicios oficiales del M4 export { VerificadorFakeNewsExercise } from './module4/VerificadorFakeNews/VerificadorFakeNewsExercise'; -export { EmailFormalExercise } from './module4/EmailFormal/EmailFormalExercise'; -export { ChatLiterarioExercise } from './module4/ChatLiterario/ChatLiterarioExercise'; -export { EnsayoArgumentativoExercise } from './module4/EnsayoArgumentativo/EnsayoArgumentativoExercise'; -export { ResenaCriticaExercise } from './module4/ResenaCritica/ResenaCriticaExercise'; export { QuizTikTokExercise } from './module4/QuizTikTok/QuizTikTokExercise'; export { AnalisisMemesExercise } from './module4/AnalisisMemes/AnalisisMemesExercise'; export { InfografiaInteractivaExercise } from './module4/InfografiaInteractiva/InfografiaInteractivaExercise'; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module1/Crucigrama/CrucigramaExercise.tsx b/projects/gamilit/apps/frontend/src/features/mechanics/module1/Crucigrama/CrucigramaExercise.tsx index 3660b06..8b1f044 100644 --- a/projects/gamilit/apps/frontend/src/features/mechanics/module1/Crucigrama/CrucigramaExercise.tsx +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module1/Crucigrama/CrucigramaExercise.tsx @@ -51,16 +51,23 @@ export const CrucigramaExercise: React.FC = ({ // FE-059: Calculate clue length from grid instead of using answer field const getClueLength = (clue: (typeof exercise.clues)[0]): number => { + // Guard against empty grid + if (!grid || grid.length === 0 || !grid[0]) return 0; + let length = 0; if (clue.direction === 'horizontal') { for (let i = 0; i < grid[0].length; i++) { - const cell = grid[clue.startRow]?.[clue.startCol + i]; + const row = grid[clue.startRow]; + if (!row) break; + const cell = row[clue.startCol + i]; if (!cell || cell.isBlack) break; length++; } } else { for (let i = 0; i < grid.length; i++) { - const cell = grid[clue.startRow + i]?.[clue.startCol]; + const row = grid[clue.startRow + i]; + if (!row) break; + const cell = row[clue.startCol]; if (!cell || cell.isBlack) break; length++; } @@ -74,17 +81,21 @@ export const CrucigramaExercise: React.FC = ({ if (!clue) return false; const length = getClueLength(clue); + if (length === 0) return false; + let userAnswer = ''; if (clue.direction === 'horizontal') { for (let i = 0; i < length; i++) { - const cell = grid[clue.startRow][clue.startCol + i]; - userAnswer += cell.userInput || ''; + const row = grid[clue.startRow]; + const cell = row?.[clue.startCol + i]; + userAnswer += cell?.userInput || ''; } } else { for (let i = 0; i < length; i++) { - const cell = grid[clue.startRow + i][clue.startCol]; - userAnswer += cell.userInput || ''; + const row = grid[clue.startRow + i]; + const cell = row?.[clue.startCol]; + userAnswer += cell?.userInput || ''; } } diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module2/DetectiveTextual/DetectiveTextualExercise.tsx b/projects/gamilit/apps/frontend/src/features/mechanics/module2/DetectiveTextual/DetectiveTextualExercise.tsx index f777189..163eb4d 100644 --- a/projects/gamilit/apps/frontend/src/features/mechanics/module2/DetectiveTextual/DetectiveTextualExercise.tsx +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module2/DetectiveTextual/DetectiveTextualExercise.tsx @@ -50,9 +50,11 @@ const QuestionCard: React.FC = ({

{question.question}

- - {question.inference_type.replace('_', ' ')} - + {question.inference_type && ( + + {question.inference_type.replace('_', ' ')} + + )}
diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module4/AnalisisMemes/AnalisisMemesExercise.tsx b/projects/gamilit/apps/frontend/src/features/mechanics/module4/AnalisisMemes/AnalisisMemesExercise.tsx index 0714c55..dc43563 100644 --- a/projects/gamilit/apps/frontend/src/features/mechanics/module4/AnalisisMemes/AnalisisMemesExercise.tsx +++ b/projects/gamilit/apps/frontend/src/features/mechanics/module4/AnalisisMemes/AnalisisMemesExercise.tsx @@ -13,12 +13,23 @@ import { } from '@shared/components/mechanics/mechanicsTypes'; import { useExerciseSubmission } from '@/features/mechanics/shared/hooks/useExerciseSubmission'; +interface ProgressData { + progress: { + currentStep: number; + totalSteps: number; + score: number; + hintsUsed: number; + timeSpent: number; + }; + answers: Record; +} + interface ExerciseProps { exerciseId: string; - userId: string; + userId?: string; onComplete?: (score: number, timeSpent: number) => void; onExit?: () => void; - onProgressUpdate?: (progress: number) => void; + onProgressUpdate?: (data: ProgressData) => void; initialData?: ExerciseState; difficulty?: 'easy' | 'medium' | 'hard'; exercise?: AnalisisMemesData; @@ -125,11 +136,27 @@ export const AnalisisMemesExercise: React.FC = ({ useEffect(() => { const progress = calculateProgress(); + const minAnnotations = 3; const elapsed = Math.floor((new Date().getTime() - startTime.getTime()) / 1000); setTimeSpent(elapsed); - onProgressUpdate?.(progress); + onProgressUpdate?.({ + progress: { + currentStep: annotations.length, + totalSteps: minAnnotations, + score: Math.round(progress), + hintsUsed: 0, + timeSpent: elapsed, + }, + answers: { + annotations: annotations.map((a) => ({ + memeId: exercise.id, + category: a.category, + text: a.text, + })), + }, + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [annotations, onProgressUpdate, startTime]); @@ -242,12 +269,12 @@ export const AnalisisMemesExercise: React.FC = ({ return ( <> -
+
- -

{exercise.title}

+ +

{exercise.title}

-

{exercise.description}

+

{exercise.description}

= ({ ); }; + +export default AnalisisMemesExercise; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module4/ChatLiterario/ChatLiterarioExercise.tsx b/projects/gamilit/apps/frontend/src/features/mechanics/module4/ChatLiterario/ChatLiterarioExercise.tsx deleted file mode 100644 index 5f92384..0000000 --- a/projects/gamilit/apps/frontend/src/features/mechanics/module4/ChatLiterario/ChatLiterarioExercise.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import { Send, User, Bot, MessageCircle, Award } from 'lucide-react'; -import { DetectiveCard } from '@shared/components/base/DetectiveCard'; -import { DetectiveButton } from '@shared/components/base/DetectiveButton'; -import { FeedbackModal } from '@shared/components/mechanics/FeedbackModal'; -import { normalizeProgressUpdate } from '@shared/components/mechanics/mechanicsTypes'; -import { ExerciseProps, ChatLiterarioState, Message } from './chatLiterarioTypes'; -import { saveProgress as saveProgressUtil } from '@/shared/utils/storage'; - -export const ChatLiterarioExercise: React.FC = ({ - exerciseId, - onComplete, - onProgressUpdate, - initialData, - exercise, -}) => { - // State management - const [messages, setMessages] = useState( - initialData?.messages || [ - { - id: '1', - sender: 'marie', - text: '¡Bonjour! Soy Marie Curie. Estoy trabajando en mi laboratorio investigando materiales radioactivos. ¿En qué puedo ayudarte hoy?', - timestamp: new Date(), - }, - ], - ); - const [input, setInput] = useState(''); - const [activeCharacter, setActiveCharacter] = useState<'marie' | 'pierre'>( - initialData?.activeCharacter || 'marie', - ); - const [startTime] = useState(new Date()); - const [_showFeedback, _setShowFeedback] = useState(false); - const [_feedback] = useState(null); - const messagesEndRef = useRef(null); - - const responses = { - marie: [ - 'La investigación científica requiere dedicación y curiosidad incansable. ¿Qué aspectos de la radioactividad te interesan?', - 'El descubrimiento del polonio y el radio fue resultado de años de trabajo meticuloso. La perseverancia es clave en la ciencia.', - 'Como mujer en la ciencia, he enfrentado muchos obstáculos, pero la pasión por el conocimiento siempre me ha impulsado.', - 'Los dos Premios Nobel han sido un honor, pero lo más importante es contribuir al avance científico de la humanidad.', - ], - pierre: [ - 'Marie y yo trabajamos juntos en la investigación de la radioactividad. La colaboración científica es fundamental.', - 'El estudio de los rayos invisibles emitidos por el uranio abrió un mundo completamente nuevo en la física.', - 'La investigación científica requiere precisión y observación cuidadosa. Cada experimento nos acerca más a la verdad.', - 'Marie es una científica extraordinaria. Su dedicación y talento son incomparables.', - ], - }; - - // Calculate progress - const calculateProgress = () => { - const minMessages = exercise?.minMessages || 5; - const userMessages = messages.filter((m) => m.sender === 'user').length; - return Math.min(Math.round((userMessages / minMessages) * 100), 100); - }; - - // Calculate score - const calculateScore = () => { - const userMessages = messages.filter((m) => m.sender === 'user').length; - const minMessages = exercise?.minMessages || 5; - const conversationScore = Math.min((userMessages / minMessages) * 70, 70); - const engagementScore = userMessages > minMessages ? 30 : (userMessages / minMessages) * 30; - return Math.round(conversationScore + engagementScore); - }; - - // Get exercise instance - const exerciseInstance = exercise || { minMessages: 5 }; - - // Progress tracking - - useEffect(() => { - const progress = calculateProgress(); - const timeSpent = Math.floor((new Date().getTime() - startTime.getTime()) / 1000); - onProgressUpdate?.( - normalizeProgressUpdate( - progress, - messages.length, - exerciseInstance.minMessages, - 0, // hintsUsed - no disponible en este componente - timeSpent, - ), - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messages, exerciseInstance.minMessages, startTime, onProgressUpdate]); - - // Auto-save functionality - useEffect(() => { - const autoSaveInterval = setInterval(() => { - const currentState: ChatLiterarioState = { - messages, - activeCharacter, - }; - saveProgressUtil(exerciseId || 'chat-literario', currentState); - }, 30000); // Every 30 seconds - - return () => clearInterval(autoSaveInterval); - }, [messages, activeCharacter, exerciseId]); - - // Auto-scroll - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); - - const handleSend = () => { - if (!input.trim()) return; - - const userMessage: Message = { - id: Date.now().toString(), - sender: 'user', - text: input, - timestamp: new Date(), - }; - - setMessages((prev) => [...prev, userMessage]); - setInput(''); - - // Simulate response after 1 second - setTimeout(() => { - const responseText = - responses[activeCharacter][Math.floor(Math.random() * responses[activeCharacter].length)]; - const botMessage: Message = { - id: (Date.now() + 1).toString(), - sender: activeCharacter, - text: responseText, - timestamp: new Date(), - }; - setMessages((prev) => [...prev, botMessage]); - }, 1000); - }; - - const getAvatar = (sender: Message['sender']) => { - if (sender === 'user') { - return ; - } - return ; - }; - - const getAvatarBg = (sender: Message['sender']) => { - if (sender === 'user') return 'bg-detective-blue'; - if (sender === 'marie') return 'bg-detective-orange'; - return 'bg-detective-gold'; - }; - - return ( - <> - -
- {/* Exercise Description */} -
-
- -

Chat Literario con Marie Curie

-
-

- Conversa con Marie Curie y Pierre Curie sobre sus descubrimientos científicos. -

-
- - {/* Character Selection */} -
- setActiveCharacter('marie')} - className="flex-1" - > - Marie Curie - - setActiveCharacter('pierre')} - className="flex-1" - > - Pierre Curie - -
- - {/* Chat Interface */} - -
- {messages.map((message) => ( - -
- {getAvatar(message.sender)} -
-
-
- {message.sender !== 'user' && ( -

- {message.sender === 'marie' ? 'Marie Curie' : 'Pierre Curie'} -

- )} -

{message.text}

-
-

- {message.timestamp.toLocaleTimeString()} -

-
-
- ))} -
-
- -
-
- setInput(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleSend()} - placeholder={`Escribe tu mensaje a ${activeCharacter === 'marie' ? 'Marie' : 'Pierre'}...`} - className="flex-1 rounded-detective border-2 border-detective-border-medium px-4 py-2 transition-colors focus:border-detective-orange focus:outline-none" - /> - } - disabled={!input.trim()} - > - Enviar - -
-
- - - {/* Stats Card */} - -
- -

Estadísticas

-
-
-
-

Mensajes enviados

-

- {messages.filter((m) => m.sender === 'user').length} -

-
-
-

Mensajes totales

-

{messages.length}

-
-
-

Personaje activo

-

- {activeCharacter === 'marie' ? 'Marie' : 'Pierre'} -

-
-
-
-
-
- - {/* Feedback Modal */} - {_feedback && ( - { - _setShowFeedback(false); - if (_feedback.type === 'success' && onComplete) { - const timeSpent = Math.floor((new Date().getTime() - startTime.getTime()) / 1000); - onComplete(calculateScore(), timeSpent); - } - }} - onRetry={() => { - _setShowFeedback(false); - }} - /> - )} - - ); -}; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module4/ChatLiterario/chatLiterarioTypes.ts b/projects/gamilit/apps/frontend/src/features/mechanics/module4/ChatLiterario/chatLiterarioTypes.ts deleted file mode 100644 index a1d3b34..0000000 --- a/projects/gamilit/apps/frontend/src/features/mechanics/module4/ChatLiterario/chatLiterarioTypes.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { BaseExercise, DifficultyLevel } from '@shared/components/mechanics/mechanicsTypes'; - -export interface Message { - id: string; - sender: 'user' | 'marie' | 'pierre'; - text: string; - timestamp: Date; -} - -export interface ChatLiterarioData extends BaseExercise { - characters: { - id: string; - name: string; - description: string; - responses: string[]; - }[]; - minMessages: number; - timeLimit?: number; // seconds -} - -export interface ChatLiterarioState { - messages: Message[]; - activeCharacter: 'marie' | 'pierre'; -} - -export interface ExerciseProgressUpdate { - currentStep: number; - totalSteps: number; - score: number; - hintsUsed: number; - timeSpent: number; -} - -// Exercise Actions Interface for Parent Control -export interface ChatLiterarioActions { - getState: () => ChatLiterarioState & { score: number; timeSpent: number; hintsUsed: number }; - reset: () => void; - validate: () => Promise; - sendMessage?: (message: string) => Promise; - switchCharacter?: (character: 'marie' | 'pierre') => void; -} - -export interface ChatLiterarioExerciseProps { - moduleId?: number; - lessonId?: number; - exerciseId?: string; - userId?: string; - onComplete?: (score: number, timeSpent: number) => void; - onExit?: () => void; - onProgressUpdate?: (progress: ExerciseProgressUpdate) => void; - initialData?: ChatLiterarioState; - difficulty?: DifficultyLevel; - exercise?: ChatLiterarioData; - actionsRef?: React.MutableRefObject; -} - -// Alias for generic import -export type ExerciseProps = ChatLiterarioExerciseProps; diff --git a/projects/gamilit/apps/frontend/src/features/mechanics/module4/EmailFormal/EmailFormalExercise.tsx b/projects/gamilit/apps/frontend/src/features/mechanics/module4/EmailFormal/EmailFormalExercise.tsx deleted file mode 100644 index 78e5651..0000000 --- a/projects/gamilit/apps/frontend/src/features/mechanics/module4/EmailFormal/EmailFormalExercise.tsx +++ /dev/null @@ -1,322 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import { Mail, CheckCircle, AlertTriangle, Send } from 'lucide-react'; -import { DetectiveCard } from '@shared/components/base/DetectiveCard'; -import { DetectiveButton } from '@shared/components/base/DetectiveButton'; -import { FeedbackModal } from '@shared/components/mechanics/FeedbackModal'; -import { ExerciseProps, EmailFormalState, ToneAnalysis, EmailTemplate } from './emailFormalTypes'; -import { FeedbackData, normalizeProgressUpdate } from '@shared/components/mechanics/mechanicsTypes'; -import { saveProgress as saveProgressUtil } from '@/shared/utils/storage'; - -export const EmailFormalExercise: React.FC = ({ - exerciseId, - onComplete, - onProgressUpdate, - initialData, - exercise, -}) => { - // State management - const [to, setTo] = useState(initialData?.to || ''); - const [subject, setSubject] = useState(initialData?.subject || ''); - const [body, setBody] = useState(initialData?.body || ''); - const [analysis, setAnalysis] = useState(initialData?.analysis || null); - - const [startTime] = useState(new Date()); - const [showFeedback, setShowFeedback] = useState(false); - const [feedback] = useState(null); - - const templates: EmailTemplate[] = exercise?.templates || [ - { - id: '1', - name: 'Solicitud de Información', - greeting: 'Estimado/a', - purpose: 'Solicitar información sobre Marie Curie', - template: {}, - }, - { - id: '2', - name: 'Agradecimiento Formal', - greeting: 'Distinguido/a', - purpose: 'Agradecer una conferencia científica', - template: {}, - }, - { - id: '3', - name: 'Invitación Académica', - greeting: 'Apreciado/a', - purpose: 'Invitar a evento sobre mujeres en ciencia', - template: {}, - }, - ]; - - // Calculate progress - const calculateProgress = () => { - let progress = 0; - if (to && to.includes('@')) progress += 15; - if (subject.length >= 5) progress += 15; - if (body.length >= 50) progress += 40; - if (analysis) progress += 30; - return Math.min(progress, 100); - }; - - // Progress tracking - useEffect(() => { - const progress = calculateProgress(); - onProgressUpdate?.(normalizeProgressUpdate(progress, 0, 1, 0, 0)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [to, subject, body, analysis]); - - // Auto-save functionality - useEffect(() => { - const autoSaveInterval = setInterval(() => { - const currentState: EmailFormalState = { - to, - subject, - body, - analysis, - }; - saveProgressUtil(exerciseId, currentState); - }, 30000); - - return () => clearInterval(autoSaveInterval); - }, [to, subject, body, analysis, exerciseId]); - - // Analyze tone - const analyzeTone = () => { - const formalWords = ['estimado', 'cordialmente', 'atentamente', 'distinguido', 'apreciado']; - const formalityScore = - formalWords.filter( - (word) => body.toLowerCase().includes(word) || subject.toLowerCase().includes(word), - ).length * 20; - - const hasGreeting = /^(estimado|distinguido|apreciado)/i.test(body); - const hasClosing = /(atentamente|cordialmente|saludos cordiales)/i.test(body); - - const suggestions: string[] = []; - if (!hasGreeting) suggestions.push('Añade un saludo formal al inicio'); - if (!hasClosing) suggestions.push('Incluye un cierre formal'); - if (subject.length < 5) suggestions.push('El asunto debe ser más descriptivo'); - if (!to.includes('@')) suggestions.push('Verifica la dirección de correo'); - - const newAnalysis = { - formality: Math.min(formalityScore, 100), - clarity: body.length > 50 ? 85 : 60, - professionalism: hasGreeting && hasClosing ? 90 : 70, - suggestions, - }; - - setAnalysis(newAnalysis); - }; - - // Apply template - const applyTemplate = (templateId: string) => { - const template = templates.find((t) => t.id === templateId); - if (template) { - setSubject(`${template.purpose}`); - setBody( - `${template.greeting} Dr./Dra.,\n\n[Escribe tu mensaje aquí]\n\nAtentamente,\n[Tu nombre]`, - ); - } - }; - - const getMetricColor = (score: number) => { - if (score >= 80) return 'text-detective-success'; - if (score >= 60) return 'text-detective-gold'; - return 'text-detective-danger'; - }; - - return ( - <> - -
- {/* Exercise Description */} -
-
- -

Redacción de Email Formal

-
-

- Redacta un correo formal relacionado con Marie Curie y su legado científico. -

-
- {/* Templates Card */} - - -
- {templates.map((template) => ( - - applyTemplate(template.id)} - className="h-auto w-full p-3 text-left" - > -

{template.name}

-

{template.purpose}

-
-
- ))} -
-
- - {/* Email Form Card */} - -
-
- - setTo(e.target.value)} - placeholder="destinatario@universidad.edu" - className="w-full rounded-detective border-2 border-detective-border-medium px-4 py-2 transition-colors focus:border-detective-orange focus:outline-none" - /> -
- -
- - setSubject(e.target.value)} - placeholder="Consulta sobre investigaciones de Marie Curie" - className="w-full rounded-detective border-2 border-detective-border-medium px-4 py-2 transition-colors focus:border-detective-orange focus:outline-none" - /> -
- -
- -