db: Homologar seeds entre DEV y PROD

Sincronización completa de archivos de seeds:

DEV -> PROD (16 archivos):
- audit_logging: activity_log_sample, audit-logs, system-metrics
- content_management: marie-curie-bio, media-files, tags, moderation_rules
- gamification_system: initialize_user_gamification
- progress_tracking: demo-progress, exercise-attempts
- social_features: teams
- system_configuration: feature_flags
- educational_content: 3 archivos de test
- auth: test-users

PROD -> DEV (8 archivos):
- audit_logging: default-config
- content_management: default-templates
- lti_integration: lti_consumers
- progress_tracking: module_progress
- system_configuration: feature_flags_seeds, gamification_parameters,
  notification_settings, rate_limits

Incluye reporte de validación DDL/Seeds.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rckrdmrd 2025-12-26 17:25:04 -06:00
parent 0f29d35578
commit 83bd04525a
24 changed files with 8122 additions and 0 deletions

View File

@ -0,0 +1,47 @@
-- =====================================================
-- Seed: audit_logging configuration (PROD)
-- Description: Configuración inicial de auditoría para producción
-- Environment: PRODUCTION
-- Dependencies: audit_logging schema
-- Order: 01
-- Created: 2025-11-11
-- Version: 1.0
-- =====================================================
--
-- PROPÓSITO:
-- - Configurar retención de logs por defecto
-- - Establecer umbrales de métricas y alertas
-- - Preparar sistema de auditoría para producción
--
-- VALIDADO CONTRA:
-- - DDL: ddl/schemas/audit_logging/tables/
--
-- =====================================================
SET search_path TO audit_logging, public;
-- =====================================================
-- COMENTARIO INICIAL
-- =====================================================
-- Este archivo configura los valores iniciales mínimos para el sistema de auditoría en producción.
-- Los logs y métricas se almacenarán según las políticas definidas aquí.
-- =====================================================
-- NOTA: En producción, las tablas de audit_logging se llenarán dinámicamente
-- Este seed solo establece configuración inicial si es necesaria.
-- La mayoría de las tablas (audit_logs, system_logs, etc.) se poblarán en tiempo de ejecución.
-- =====================================================
-- Puedes agregar configuración inicial aquí si es necesaria en el futuro
-- Por ejemplo:
-- - Umbrales de alertas por defecto
-- - Políticas de retención
-- - Configuración de métricas críticas
-- Por ahora, este seed está vacío intencionalmente ya que audit_logging
-- es principalmente un sink de datos generados por la aplicación.
-- =====================================================
-- FIN DEL SEED
-- =====================================================

View File

@ -0,0 +1,123 @@
-- =====================================================
-- Seed: content_management templates (PROD)
-- Description: Templates iniciales de contenido para Marie Curie
-- Environment: PRODUCTION
-- Dependencies: content_management schema
-- Order: 01
-- Created: 2025-11-11
-- Version: 1.0
-- =====================================================
--
-- PROPÓSITO:
-- - Proveer templates base para generación de contenido Marie Curie
-- - Establecer estructura inicial de contenido educativo
--
-- VALIDADO CONTRA:
-- - DDL: ddl/schemas/content_management/tables/01-content_templates.sql
--
-- =====================================================
SET search_path TO content_management, public;
-- =====================================================
-- INSERT: Templates de contenido base (PRODUCTION)
-- =====================================================
-- Template 1: Texto de comprensión lectora
INSERT INTO content_management.content_templates (
id,
tenant_id,
name,
description,
template_type,
structure,
is_active,
created_at,
updated_at
) VALUES (
'a1b2c3d4-0001-0000-0000-000000000001'::uuid,
NULL, -- Disponible para todos los tenants
'Comprensión Lectora Básica',
'Template para generar ejercicios de comprensión lectora con texto, preguntas y opciones múltiples',
'reading_comprehension',
'{
"sections": [
{"type": "text", "label": "Texto principal", "required": true},
{"type": "questions", "count": 5, "format": "multiple_choice"}
],
"difficulty_levels": ["facil", "medio", "dificil"],
"bloom_taxonomy": ["recordar", "comprender", "aplicar"]
}'::jsonb,
true,
gamilit.now_mexico(),
gamilit.now_mexico()
) ON CONFLICT (id) DO NOTHING;
-- Template 2: Inferencia y análisis
INSERT INTO content_management.content_templates (
id,
tenant_id,
name,
description,
template_type,
structure,
is_active,
created_at,
updated_at
) VALUES (
'a1b2c3d4-0002-0000-0000-000000000002'::uuid,
NULL,
'Comprensión Inferencial',
'Template para ejercicios que requieren inferencia y análisis de información implícita',
'inferential_reading',
'{
"sections": [
{"type": "text", "label": "Texto con información implícita", "required": true},
{"type": "questions", "count": 4, "format": "multiple_choice"},
{"type": "explanation", "label": "Justificación de respuesta", "required": false}
],
"difficulty_levels": ["medio", "dificil"],
"bloom_taxonomy": ["analizar", "evaluar"]
}'::jsonb,
true,
gamilit.now_mexico(),
gamilit.now_mexico()
) ON CONFLICT (id) DO NOTHING;
-- Template 3: Producción de textos
INSERT INTO content_management.content_templates (
id,
tenant_id,
name,
description,
template_type,
structure,
is_active,
created_at,
updated_at
) VALUES (
'a1b2c3d4-0003-0000-0000-000000000003'::uuid,
NULL,
'Producción de Textos',
'Template para ejercicios de escritura y producción textual',
'text_production',
'{
"sections": [
{"type": "prompt", "label": "Indicaciones", "required": true},
{"type": "rubric", "label": "Criterios de evaluación", "required": true},
{"type": "text_area", "label": "Espacio de escritura", "min_words": 50}
],
"difficulty_levels": ["facil", "medio", "dificil"],
"bloom_taxonomy": ["crear", "evaluar"]
}'::jsonb,
true,
gamilit.now_mexico(),
gamilit.now_mexico()
) ON CONFLICT (id) DO NOTHING;
-- =====================================================
-- RESULTADO ESPERADO
-- =====================================================
-- 3 templates base insertados en content_templates
-- Disponibles para generación de contenido por Marie Curie
-- =====================================================

View File

@ -0,0 +1,282 @@
-- =====================================================
-- Seed: lti_integration.lti_consumers (PROD)
-- Description: Configuración inicial de consumidores LTI 1.3
-- Environment: PRODUCTION
-- Dependencies: lti_integration tables must exist
-- Order: 01
-- Created: 2025-11-11
-- Version: 1.0
-- =====================================================
--
-- PROPÓSITO:
-- Cargar configuración inicial de plataformas LMS que pueden conectarse
-- vía LTI 1.3 (Learning Tools Interoperability).
--
-- ALCANCE:
-- - Configuración genérica para plataformas LMS comunes
-- - Placeholders para credenciales (deben configurarse después)
-- - Capacidades LTI estándar habilitadas
--
-- IMPORTANTE:
-- Las credenciales reales (client_id, client_secret, etc.) deben
-- configurarse vía variables de entorno o vault después del deployment.
-- Los valores aquí son PLACEHOLDERS que deben reemplazarse.
--
-- SEGURIDAD:
-- - NO incluir credenciales reales en este archivo
-- - Usar manage-secrets.sh para credenciales de producción
-- - Validar JWKs y endpoints antes de habilitar en producción
--
-- =====================================================
SET search_path TO lti_integration, public;
-- =====================================================
-- VALIDACIÓN PREVIA
-- =====================================================
DO $$
DECLARE
missing_columns TEXT[];
expected_columns TEXT[] := ARRAY[
'id', 'platform_name', 'platform_id', 'client_id', 'deployment_id',
'public_keyset_url', 'access_token_url', 'oidc_auth_url',
'is_enabled', 'supports_deep_linking', 'supports_nrps', 'supports_ags',
'custom_parameters', 'created_at', 'updated_at'
];
col TEXT;
BEGIN
-- Verificar que la tabla existe
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'lti_integration'
AND table_name = 'lti_consumers'
) THEN
RAISE EXCEPTION 'Tabla lti_integration.lti_consumers no existe. Ejecutar DDL primero.';
END IF;
-- Verificar columnas esperadas
SELECT ARRAY_AGG(column_name) INTO missing_columns
FROM (SELECT UNNEST(expected_columns) AS column_name) expected
WHERE column_name NOT IN (
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'lti_integration'
AND table_name = 'lti_consumers'
);
IF missing_columns IS NOT NULL AND array_length(missing_columns, 1) > 0 THEN
RAISE WARNING 'Columnas faltantes en lti_consumers: %', missing_columns;
END IF;
RAISE NOTICE '✓ Validación de estructura completada';
END $$;
-- =====================================================
-- INSERCIÓN DE CONSUMIDORES LTI (PRODUCCIÓN)
-- =====================================================
-- NOTA: Estos son templates/placeholders para configuración inicial.
-- Las credenciales reales deben cargarse después vía secrets management.
INSERT INTO lti_integration.lti_consumers (
id,
platform_name,
platform_id,
client_id,
deployment_id,
public_keyset_url,
access_token_url,
oidc_auth_url,
is_enabled,
supports_deep_linking,
supports_nrps,
supports_ags,
custom_parameters,
metadata,
created_at,
updated_at
) VALUES
-- =====================================================
-- 1. MOODLE LMS (Placeholder)
-- =====================================================
(
'10000000-0000-0000-0000-000000000001'::uuid,
'Moodle LMS',
'https://moodle.example.edu',
'MOODLE_CLIENT_ID_PLACEHOLDER', -- REEMPLAZAR con credencial real
'1',
'https://moodle.example.edu/mod/lti/certs.php',
'https://moodle.example.edu/mod/lti/token.php',
'https://moodle.example.edu/mod/lti/auth.php',
false, -- Deshabilitado hasta configurar credenciales reales
true, -- Supports Deep Linking
true, -- Supports Name and Role Provisioning Services
true, -- Supports Assignment and Grade Services
jsonb_build_object(
'custom_context_id', '$Context.id',
'custom_course_id', '$CourseSection.sourcedId',
'custom_user_id', '$User.id'
),
jsonb_build_object(
'platform_type', 'moodle',
'platform_version', '4.x',
'configuration_status', 'pending',
'notes', 'Configurar credenciales antes de habilitar'
),
gamilit.now_mexico(),
gamilit.now_mexico()
),
-- =====================================================
-- 2. CANVAS LMS (Placeholder)
-- =====================================================
(
'20000000-0000-0000-0000-000000000002'::uuid,
'Canvas LMS',
'https://canvas.example.edu',
'CANVAS_CLIENT_ID_PLACEHOLDER', -- REEMPLAZAR con credencial real
'1',
'https://canvas.example.edu/api/lti/security/jwks',
'https://canvas.example.edu/login/oauth2/token',
'https://canvas.example.edu/api/lti/authorize_redirect',
false, -- Deshabilitado hasta configurar credenciales reales
true, -- Supports Deep Linking
true, -- Supports NRPS
true, -- Supports AGS
jsonb_build_object(
'custom_canvas_course_id', '$Canvas.course.id',
'custom_canvas_user_id', '$Canvas.user.id',
'custom_canvas_enrollment_state', '$Canvas.enrollment.enrollmentState'
),
jsonb_build_object(
'platform_type', 'canvas',
'platform_version', 'cloud',
'configuration_status', 'pending',
'notes', 'Configurar credenciales antes de habilitar'
),
gamilit.now_mexico(),
gamilit.now_mexico()
),
-- =====================================================
-- 3. BLACKBOARD LEARN (Placeholder)
-- =====================================================
(
'30000000-0000-0000-0000-000000000003'::uuid,
'Blackboard Learn',
'https://blackboard.example.edu',
'BLACKBOARD_CLIENT_ID_PLACEHOLDER', -- REEMPLAZAR con credencial real
'1',
'https://blackboard.example.edu/learn/api/v1/lti/jwks',
'https://blackboard.example.edu/learn/api/v1/lti/token',
'https://blackboard.example.edu/learn/api/v1/lti/authorize',
false, -- Deshabilitado hasta configurar credenciales reales
true, -- Supports Deep Linking
true, -- Supports NRPS
true, -- Supports AGS
jsonb_build_object(
'custom_bb_course_id', '$Context.id',
'custom_bb_user_id', '$User.id'
),
jsonb_build_object(
'platform_type', 'blackboard',
'platform_version', 'ultra',
'configuration_status', 'pending',
'notes', 'Configurar credenciales antes de habilitar'
),
gamilit.now_mexico(),
gamilit.now_mexico()
)
ON CONFLICT (platform_id, client_id) DO UPDATE SET
platform_name = EXCLUDED.platform_name,
deployment_id = EXCLUDED.deployment_id,
public_keyset_url = EXCLUDED.public_keyset_url,
access_token_url = EXCLUDED.access_token_url,
oidc_auth_url = EXCLUDED.oidc_auth_url,
is_enabled = EXCLUDED.is_enabled,
supports_deep_linking = EXCLUDED.supports_deep_linking,
supports_nrps = EXCLUDED.supports_nrps,
supports_ags = EXCLUDED.supports_ags,
custom_parameters = EXCLUDED.custom_parameters,
metadata = EXCLUDED.metadata,
updated_at = gamilit.now_mexico();
-- =====================================================
-- VERIFICACIÓN DE INSERCIÓN
-- =====================================================
DO $$
DECLARE
consumers_count INTEGER;
enabled_count INTEGER;
BEGIN
SELECT COUNT(*) INTO consumers_count FROM lti_integration.lti_consumers;
SELECT COUNT(*) INTO enabled_count FROM lti_integration.lti_consumers WHERE is_enabled = true;
RAISE NOTICE '════════════════════════════════════════════════════════';
RAISE NOTICE ' SEED COMPLETADO: lti_consumers';
RAISE NOTICE '════════════════════════════════════════════════════════';
RAISE NOTICE '✓ Consumidores LTI insertados: %', consumers_count;
RAISE NOTICE ' └─ Habilitados: %', enabled_count;
RAISE NOTICE ' └─ Pendientes configuración: %', consumers_count - enabled_count;
RAISE NOTICE '';
RAISE NOTICE '⚠️ IMPORTANTE:';
RAISE NOTICE ' 1. Configurar credenciales reales vía manage-secrets.sh';
RAISE NOTICE ' 2. Actualizar client_id con valores de cada plataforma';
RAISE NOTICE ' 3. Validar URLs y endpoints';
RAISE NOTICE ' 4. Habilitar (is_enabled = true) después de configurar';
RAISE NOTICE '════════════════════════════════════════════════════════';
IF consumers_count = 0 THEN
RAISE EXCEPTION 'ERROR: No se insertó ningún consumidor LTI';
END IF;
END $$;
-- =====================================================
-- DOCUMENTACIÓN DE CONFIGURACIÓN POST-DEPLOYMENT
-- =====================================================
/*
PASOS PARA CONFIGURAR EN PRODUCCIÓN:
1. Obtener credenciales de cada plataforma LMS:
- Registrar GAMILIT como LTI Tool en la plataforma
- Obtener client_id, deployment_id
- Obtener URLs de OIDC, token, JWKs
2. Configurar secrets:
```bash
./manage-secrets.sh set --env prod LTI_MOODLE_CLIENT_ID "real-client-id"
./manage-secrets.sh set --env prod LTI_CANVAS_CLIENT_ID "real-client-id"
./manage-secrets.sh set --env prod LTI_BLACKBOARD_CLIENT_ID "real-client-id"
```
3. Actualizar consumidores con credenciales reales:
```sql
UPDATE lti_integration.lti_consumers
SET client_id = 'real-client-id',
is_enabled = true,
updated_at = gamilit.now_mexico()
WHERE platform_name = 'Moodle LMS';
```
4. Validar configuración:
```sql
SELECT platform_name, is_enabled, configuration_status
FROM lti_integration.lti_consumers;
```
5. Testing:
- Iniciar LTI launch desde la plataforma LMS
- Verificar OIDC authentication flow
- Validar Deep Linking (si aplica)
- Probar grade passback (AGS)
REFERENCIAS:
- Docs: docs/02-especificaciones-tecnicas/lti-integration/
- Epic: docs/03-fase-extensiones/EXT-007-lti-integration/
- Spec: https://www.imsglobal.org/spec/lti/v1p3/
*/

View File

@ -0,0 +1,54 @@
-- =====================================================
-- Seed: progress_tracking.module_progress (PROD)
-- Description: Progreso inicial de usuarios (SOLO DEMO, NO TESTING)
-- Environment: PRODUCTION
-- Dependencies: auth_management.profiles, educational_content.modules
-- Order: 01
-- Created: 2025-11-11
-- Version: 3.0 (Limpiado - NO carga datos para usuarios de testing)
-- =====================================================
--
-- CAMBIOS v3.0 (2025-11-16):
-- - ❌ ELIMINADO: Progreso pre-cargado para student@gamilit.com
-- - ✅ RAZÓN: Los usuarios de testing deben iniciar en blanco
-- - ✅ POLÍTICA: Solo usuarios DEMO pueden tener progreso pre-cargado
--
-- USUARIOS DE TESTING (deben iniciar en BLANCO):
-- - admin@gamilit.com
-- - teacher@gamilit.com
-- - student@gamilit.com
--
-- USUARIOS DEMO (pueden tener progreso pre-cargado):
-- - estudiante1@demo.glit.edu.mx
-- - estudiante2@demo.glit.edu.mx
-- - etc.
--
-- PROGRESO INCLUIDO:
-- - NINGUNO (usuarios de testing inician en blanco)
--
-- TOTAL: 0 registros de module_progress para testing
-- =====================================================
SET search_path TO progress_tracking, educational_content, auth_management, public;
-- =====================================================
-- NOTA: Seed vacío intencionalmente
-- =====================================================
-- Los usuarios de testing (admin@gamilit.com, teacher@gamilit.com, student@gamilit.com)
-- deben iniciar sin progreso pre-cargado para permitir testing limpio.
--
-- Si se requiere progreso demo, agregar aquí solo para usuarios demo
-- (estudiante1@demo.glit.edu.mx, etc.)
-- =====================================================
-- =====================================================
-- Verification
-- =====================================================
DO $$
DECLARE
progress_count INTEGER;
BEGIN
SELECT COUNT(*) INTO progress_count FROM progress_tracking.module_progress;
RAISE NOTICE '✅ Registros de module_progress creados: %', progress_count;
END $$;

View File

@ -0,0 +1,117 @@
-- =============================================================================
-- Seeds: system_configuration.feature_flags
-- Description: Production feature flags for GAMILIT platform
-- Priority: P0 - Required for Admin Portal functionality
-- Created: 2025-11-19
-- =============================================================================
-- Clean existing seeds (if any)
TRUNCATE TABLE system_configuration.feature_flags CASCADE;
-- =============================================================================
-- Core Platform Features
-- =============================================================================
INSERT INTO system_configuration.feature_flags
(flag_key, flag_name, description, category, is_enabled, is_system_wide, rollout_percentage, rollout_strategy, required_role, is_user_configurable, tags)
VALUES
-- Gamification Features
('enable_gamification', 'Gamificación Habilitada', 'Habilita todo el sistema de gamificación (XP, niveles, rangos, recompensas)', 'gamification', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["core", "gamification"]'),
('enable_ml_coins', 'Monedas ML (Maya Ludens)', 'Habilita el sistema de monedas ML para recompensas', 'gamification', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["gamification", "rewards"]'),
('enable_xp_system', 'Sistema de Experiencia (XP)', 'Habilita puntos de experiencia y niveles', 'gamification', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["gamification", "progression"]'),
('enable_maya_ranks', 'Rangos Maya', 'Habilita el sistema de rangos mayas (Ajaw, Halach Uinic, etc.)', 'gamification', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["gamification", "ranks"]'),
('enable_badges', 'Insignias y Logros', 'Habilita el sistema de insignias y logros', 'gamification', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["gamification", "achievements"]'),
('enable_leaderboards', 'Tablas de Clasificación', 'Habilita leaderboards globales y por aula', 'gamification', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["gamification", "social"]'),
-- Educational Features
('enable_ai_hints', 'Pistas con IA', 'Habilita sugerencias generadas por IA para ejercicios', 'educational', FALSE, FALSE, 0, 'beta_users', 'super_admin', FALSE, '["beta", "ai", "educational"]'),
('enable_adaptive_difficulty', 'Dificultad Adaptativa', 'Ajusta dificultad de ejercicios según rendimiento del estudiante', 'educational', FALSE, FALSE, 0, 'beta_users', 'super_admin', FALSE, '["beta", "educational", "adaptive"]'),
('allow_exercise_retries', 'Reintentos de Ejercicios', 'Permite a estudiantes reintentar ejercicios', 'educational', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["educational", "pedagogy"]'),
('enable_collaborative_missions', 'Misiones Colaborativas', 'Habilita misiones que requieren trabajo en equipo', 'educational', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["educational", "social", "collaboration"]'),
-- Social Features
('enable_chat', 'Chat en Aulas', 'Habilita chat entre estudiantes y maestros', 'social', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["social", "communication"]'),
('enable_teams', 'Equipos de Estudiantes', 'Permite creación de equipos dentro de aulas', 'social', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["social", "collaboration"]'),
('enable_student_profiles', 'Perfiles Públicos', 'Permite a estudiantes tener perfiles visibles por compañeros', 'social', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["social", "privacy"]'),
('enable_friend_system', 'Sistema de Amigos', 'Permite a estudiantes agregar amigos', 'social', FALSE, FALSE, 50, 'percentage', 'admin_teacher', TRUE, '["social", "beta"]'),
-- Admin & Analytics Features
('enable_advanced_analytics', 'Analytics Avanzados', 'Habilita dashboards y reportes avanzados para maestros', 'admin', TRUE, TRUE, 100, 'all', 'admin_teacher', FALSE, '["admin", "analytics"]'),
('enable_bulk_operations', 'Operaciones Masivas', 'Permite acciones en lote (ej: inscribir múltiples estudiantes)', 'admin', TRUE, TRUE, 100, 'all', 'admin_teacher', FALSE, '["admin", "efficiency"]'),
('enable_custom_reports', 'Reportes Personalizados', 'Permite a maestros crear reportes personalizados', 'admin', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["admin", "analytics", "customization"]'),
('enable_parent_access', 'Acceso para Padres', 'Permite a padres ver progreso de sus hijos', 'admin', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["admin", "communication", "family"]'),
-- Integration Features
('enable_lti_integration', 'Integración LTI', 'Habilita integración con plataformas LMS vía LTI', 'integration', FALSE, TRUE, 0, 'whitelist', 'super_admin', FALSE, '["integration", "lti", "enterprise"]'),
('enable_google_classroom', 'Google Classroom', 'Sincronización con Google Classroom', 'integration', FALSE, FALSE, 0, 'whitelist', 'super_admin', FALSE, '["integration", "google", "beta"]'),
('enable_api_access', 'Acceso API Externo', 'Permite acceso a API para integraciones de terceros', 'integration', FALSE, TRUE, 0, 'whitelist', 'super_admin', FALSE, '["integration", "api", "enterprise"]'),
-- Content & Moderation
('enable_user_generated_content', 'Contenido Generado por Usuarios', 'Permite a maestros crear contenido personalizado', 'content', TRUE, FALSE, 100, 'all', 'admin_teacher', TRUE, '["content", "customization"]'),
('enable_content_moderation', 'Moderación de Contenido', 'Habilita revisión de contenido generado por usuarios', 'content', TRUE, TRUE, 100, 'all', 'super_admin', FALSE, '["content", "safety", "moderation"]'),
-- Performance & Debugging
('enable_performance_monitoring', 'Monitoreo de Rendimiento', 'Habilita métricas de performance del sistema', 'system', TRUE, TRUE, 100, 'all', 'super_admin', FALSE, '["system", "monitoring", "performance"]'),
('enable_debug_mode', 'Modo Debug', 'Habilita logs detallados y herramientas de debugging', 'system', FALSE, TRUE, 0, 'whitelist', 'super_admin', FALSE, '["system", "debugging", "development"]'),
('enable_feature_usage_tracking', 'Tracking de Uso de Features', 'Rastrea qué features se usan más', 'system', TRUE, TRUE, 100, 'all', 'super_admin', FALSE, '["system", "analytics", "product"]');
-- =============================================================================
-- Update timestamps for initial flags
-- =============================================================================
UPDATE system_configuration.feature_flags
SET enabled_at = NOW()
WHERE is_enabled = TRUE;
-- =============================================================================
-- Set dependencies and conflicts
-- =============================================================================
-- ML Coins depends on gamification being enabled
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_gamification"]'::jsonb
WHERE flag_key = 'enable_ml_coins';
-- XP System depends on gamification
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_gamification"]'::jsonb
WHERE flag_key = 'enable_xp_system';
-- Maya Ranks depends on XP system
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_gamification", "enable_xp_system"]'::jsonb
WHERE flag_key = 'enable_maya_ranks';
-- Badges depend on gamification
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_gamification"]'::jsonb
WHERE flag_key = 'enable_badges';
-- Leaderboards depend on XP system
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_gamification", "enable_xp_system"]'::jsonb
WHERE flag_key = 'enable_leaderboards';
-- Friend system depends on student profiles
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_student_profiles"]'::jsonb
WHERE flag_key = 'enable_friend_system';
-- Content moderation conflicts with user generated content being disabled
UPDATE system_configuration.feature_flags
SET depends_on_flags = '["enable_user_generated_content"]'::jsonb
WHERE flag_key = 'enable_content_moderation';
-- =============================================================================
-- Verification
-- =============================================================================
-- Show created flags
SELECT
flag_key,
flag_name,
category,
is_enabled,
is_system_wide
FROM system_configuration.feature_flags
ORDER BY category, flag_key;

View File

@ -0,0 +1,117 @@
-- =============================================================================
-- Seeds: system_configuration.gamification_parameters
-- Description: Production gamification parameters for GAMILIT platform
-- Priority: P0 - Required for gamification functionality
-- Reference: Documento de Diseño Mecánicas GAMILIT v6.1
-- Created: 2025-11-19
-- =============================================================================
-- Clean existing seeds (if any)
TRUNCATE TABLE system_configuration.gamification_parameters CASCADE;
-- =============================================================================
-- POINTS & XP PARAMETERS
-- =============================================================================
INSERT INTO system_configuration.gamification_parameters
(param_key, param_name, description, category, param_value, default_value, value_type, min_value, max_value, scope, is_system_managed, is_overridable, affects_systems, tags)
VALUES
-- Base points for exercises
('points_per_exercise_easy', 'Puntos por Ejercicio Fácil', 'Puntos XP otorgados por completar ejercicio de dificultad baja', 'points', '50'::jsonb, '50'::jsonb, 'number', 1, 1000, 'global', FALSE, TRUE, '["xp_calculation", "level_progression"]'::jsonb, '["xp", "exercises"]'::jsonb),
('points_per_exercise_medium', 'Puntos por Ejercicio Medio', 'Puntos XP otorgados por completar ejercicio de dificultad media', 'points', '100'::jsonb, '100'::jsonb, 'number', 1, 1000, 'global', FALSE, TRUE, '["xp_calculation", "level_progression"]'::jsonb, '["xp", "exercises"]'::jsonb),
('points_per_exercise_hard', 'Puntos por Ejercicio Difícil', 'Puntos XP otorgados por completar ejercicio de dificultad alta', 'points', '200'::jsonb, '200'::jsonb, 'number', 1, 1000, 'global', FALSE, TRUE, '["xp_calculation", "level_progression"]'::jsonb, '["xp", "exercises"]'::jsonb),
-- Mission completion points
('points_per_mission', 'Puntos por Misión Completada', 'Puntos XP base por completar una misión completa', 'points', '500'::jsonb, '500'::jsonb, 'number', 100, 5000, 'global', FALSE, TRUE, '["xp_calculation", "mission_completion"]'::jsonb, '["xp", "missions"]'::jsonb),
('bonus_perfect_mission', 'Bonus Misión Perfecta', 'Puntos XP adicionales por completar misión sin errores', 'points', '200'::jsonb, '200'::jsonb, 'number', 0, 1000, 'global', FALSE, TRUE, '["xp_calculation", "mission_completion"]'::jsonb, '["xp", "bonus", "missions"]'::jsonb),
-- Streak bonuses
('points_daily_streak', 'Bonus Racha Diaria', 'Puntos adicionales por días consecutivos activos', 'points', '25'::jsonb, '25'::jsonb, 'number', 0, 500, 'global', FALSE, TRUE, '["xp_calculation", "engagement"]'::jsonb, '["xp", "streaks", "engagement"]'::jsonb),
('max_streak_multiplier', 'Multiplicador Máximo de Racha', 'Multiplicador máximo por rachas largas (ej: 2x en racha de 30 días)', 'multipliers', '2.0'::jsonb, '2.0'::jsonb, 'number', 1, 5, 'global', FALSE, TRUE, '["xp_calculation"]'::jsonb, '["multiplier", "streaks"]'::jsonb),
-- =============================================================================
-- ML COINS (MAYA LUDENS COINS) PARAMETERS
-- =============================================================================
('ml_coins_per_mission', 'ML Coins por Misión', 'Monedas ML otorgadas por completar una misión', 'rewards', '10'::jsonb, '10'::jsonb, 'number', 1, 100, 'global', FALSE, TRUE, '["ml_coins", "mission_completion"]'::jsonb, '["ml_coins", "missions"]'::jsonb),
('ml_coins_per_level_up', 'ML Coins por Subir de Nivel', 'Monedas ML otorgadas al subir de nivel', 'rewards', '50'::jsonb, '50'::jsonb, 'number', 10, 500, 'global', FALSE, TRUE, '["ml_coins", "level_progression"]'::jsonb, '["ml_coins", "levels"]'::jsonb),
('ml_coins_per_rank_up', 'ML Coins por Ascenso de Rango', 'Monedas ML otorgadas al ascender de rango maya', 'rewards', '100'::jsonb, '100'::jsonb, 'number', 50, 1000, 'global', FALSE, TRUE, '["ml_coins", "rank_advancement"]'::jsonb, '["ml_coins", "ranks"]'::jsonb),
('ml_coins_daily_login', 'ML Coins Login Diario', 'Monedas ML por iniciar sesión cada día', 'rewards', '5'::jsonb, '5'::jsonb, 'number', 0, 50, 'global', FALSE, TRUE, '["ml_coins", "engagement"]'::jsonb, '["ml_coins", "daily", "engagement"]'::jsonb),
-- ML Coins shop prices (reference values)
('ml_coins_avatar_item_price', 'Precio Item de Avatar (ML)', 'Costo en ML coins de items de avatar', 'rewards', '20'::jsonb, '20'::jsonb, 'number', 1, 1000, 'global', FALSE, TRUE, '["ml_coins", "shop"]'::jsonb, '["ml_coins", "shop", "avatar"]'::jsonb),
('ml_coins_hint_price', 'Precio Pista (ML)', 'Costo en ML coins de una pista para ejercicio', 'rewards', '10'::jsonb, '10'::jsonb, 'number', 1, 100, 'global', FALSE, TRUE, '["ml_coins", "shop", "hints"]'::jsonb, '["ml_coins", "shop", "gameplay"]'::jsonb),
('ml_coins_retry_price', 'Precio Reintento Extra (ML)', 'Costo en ML coins de un reintento adicional', 'rewards', '15'::jsonb, '15'::jsonb, 'number', 1, 100, 'global', FALSE, TRUE, '["ml_coins", "shop", "retries"]'::jsonb, '["ml_coins", "shop", "gameplay"]'::jsonb),
-- =============================================================================
-- LEVEL PROGRESSION PARAMETERS
-- =============================================================================
('xp_base_for_level_1', 'XP Base Nivel 1', 'XP requerido para alcanzar nivel 1 (desde nivel 0)', 'levels', '100'::jsonb, '100'::jsonb, 'number', 50, 500, 'global', TRUE, FALSE, '["level_progression"]'::jsonb, '["levels", "progression"]'::jsonb),
('xp_level_growth_factor', 'Factor Crecimiento XP por Nivel', 'Factor multiplicador de XP requerido por nivel (ej: 1.15 = 15% más por nivel)', 'levels', '1.15'::jsonb, '1.15'::jsonb, 'number', 1.05, 2, 'global', TRUE, FALSE, '["level_progression"]'::jsonb, '["levels", "progression", "scaling"]'::jsonb),
('max_level', 'Nivel Máximo', 'Nivel máximo alcanzable en el sistema', 'levels', '100'::jsonb, '100'::jsonb, 'number', 50, 200, 'global', TRUE, FALSE, '["level_progression"]'::jsonb, '["levels", "cap"]'::jsonb),
-- =============================================================================
-- MAYA RANKS PARAMETERS
-- =============================================================================
('rank_xp_threshold_ajaw', 'XP para Rango Ajaw', 'XP total requerido para alcanzar rango Ajaw (nivel de entrada)', 'ranks', '0'::jsonb, '0'::jsonb, 'number', 0, 1000, 'global', TRUE, FALSE, '["rank_advancement"]'::jsonb, '["ranks", "maya", "thresholds"]'::jsonb),
('rank_xp_threshold_halach_uinic', 'XP para Rango Halach Uinic', 'XP total requerido para Halach Uinic', 'ranks', '5000'::jsonb, '5000'::jsonb, 'number', 1000, 20000, 'global', TRUE, FALSE, '["rank_advancement"]'::jsonb, '["ranks", "maya", "thresholds"]'::jsonb),
('rank_xp_threshold_nacom', 'XP para Rango Nacom', 'XP total requerido para Nacom', 'ranks', '15000'::jsonb, '15000'::jsonb, 'number', 5000, 50000, 'global', TRUE, FALSE, '["rank_advancement"]'::jsonb, '["ranks", "maya", "thresholds"]'::jsonb),
('rank_xp_threshold_ah_kin', 'XP para Rango Ah Kin', 'XP total requerido para Ah Kin', 'ranks', '30000'::jsonb, '30000'::jsonb, 'number', 10000, 100000, 'global', TRUE, FALSE, '["rank_advancement"]'::jsonb, '["ranks", "maya", "thresholds"]'::jsonb),
('rank_xp_threshold_kukulkan', 'XP para Rango Kukulkan', 'XP total requerido para Kukulkan (máximo rango)', 'ranks', '50000'::jsonb, '50000'::jsonb, 'number', 20000, 200000, 'global', TRUE, FALSE, '["rank_advancement"]'::jsonb, '["ranks", "maya", "thresholds"]'::jsonb),
-- Rank benefits
('rank_ml_coins_bonus_multiplier', 'Multiplicador ML por Rango', 'Multiplicador de ML coins ganados según rango (ej: 0.1 = +10% por rango)', 'multipliers', '0.1'::jsonb, '0.1'::jsonb, 'number', 0, 1, 'global', FALSE, TRUE, '["ml_coins", "rank_advancement"]'::jsonb, '["multiplier", "ranks", "ml_coins"]'::jsonb),
('rank_xp_bonus_multiplier', 'Multiplicador XP por Rango', 'Multiplicador de XP ganado según rango', 'multipliers', '0.05'::jsonb, '0.05'::jsonb, 'number', 0, 0.5, 'global', FALSE, TRUE, '["xp_calculation", "rank_advancement"]'::jsonb, '["multiplier", "ranks", "xp"]'::jsonb),
-- =============================================================================
-- PENALTY PARAMETERS
-- =============================================================================
('penalty_wrong_answer', 'Penalización Respuesta Incorrecta', 'Puntos XP deducidos por respuesta incorrecta (0 = sin penalización)', 'penalties', '0'::jsonb, '0'::jsonb, 'number', 0, 100, 'global', FALSE, TRUE, '["xp_calculation"]'::jsonb, '["penalties", "exercises"]'::jsonb),
('penalty_late_submission', 'Penalización Entrega Tardía', 'Porcentaje de puntos deducidos por entregar tarde (0-100)', 'penalties', '20'::jsonb, '20'::jsonb, 'number', 0, 100, 'global', FALSE, TRUE, '["grading", "assignments"]'::jsonb, '["penalties", "assignments", "deadlines"]'::jsonb),
('max_attempts_per_exercise', 'Intentos Máximos por Ejercicio', 'Número máximo de intentos permitidos por ejercicio', 'penalties', '3'::jsonb, '3'::jsonb, 'number', 1, 10, 'global', FALSE, TRUE, '["exercise_completion"]'::jsonb, '["attempts", "exercises"]'::jsonb),
-- =============================================================================
-- MULTIPLIERS & BONUSES
-- =============================================================================
('multiplier_weekend_bonus', 'Bonus Fin de Semana', 'Multiplicador de XP aplicado en fines de semana', 'multipliers', '1.2'::jsonb, '1.2'::jsonb, 'number', 1, 3, 'global', FALSE, TRUE, '["xp_calculation"]'::jsonb, '["multiplier", "temporal", "engagement"]'::jsonb),
('multiplier_team_collaboration', 'Bonus Colaboración en Equipo', 'Multiplicador de XP cuando se completa en equipo', 'multipliers', '1.3'::jsonb, '1.3'::jsonb, 'number', 1, 2, 'global', FALSE, TRUE, '["xp_calculation", "team_system"]'::jsonb, '["multiplier", "collaboration", "teams"]'::jsonb),
('multiplier_first_completion', 'Bonus Primera Vez', 'Multiplicador para primera vez que completa un tipo de ejercicio', 'multipliers', '1.5'::jsonb, '1.5'::jsonb, 'number', 1, 3, 'global', FALSE, TRUE, '["xp_calculation"]'::jsonb, '["multiplier", "first_time", "discovery"]'::jsonb),
-- =============================================================================
-- ENGAGEMENT & RETENTION PARAMETERS
-- =============================================================================
('daily_mission_goal', 'Meta Diaria de Misiones', 'Número de misiones sugeridas por día para mantener progreso', 'engagement', '3'::jsonb, '3'::jsonb, 'number', 1, 10, 'global', FALSE, TRUE, '["engagement", "mission_system"]'::jsonb, '["daily", "goals", "missions"]'::jsonb),
('weekly_xp_goal', 'Meta Semanal de XP', 'XP sugerido por semana para progreso óptimo', 'engagement', '2000'::jsonb, '2000'::jsonb, 'number', 500, 10000, 'global', FALSE, TRUE, '["engagement", "xp_calculation"]'::jsonb, '["weekly", "goals", "xp"]'::jsonb),
('inactivity_grace_period_days', 'Días de Gracia Inactividad', 'Días antes de penalizar por inactividad', 'engagement', '7'::jsonb, '7'::jsonb, 'number', 1, 30, 'global', FALSE, TRUE, '["engagement", "retention"]'::jsonb, '["inactivity", "retention"]'::jsonb),
-- =============================================================================
-- BADGE & ACHIEVEMENT PARAMETERS
-- =============================================================================
('badge_collection_bonus_ml', 'Bonus ML por Colección de Insignias', 'ML coins extra por completar colección de insignias', 'rewards', '100'::jsonb, '100'::jsonb, 'number', 10, 500, 'global', FALSE, TRUE, '["ml_coins", "badge_system"]'::jsonb, '["ml_coins", "badges", "collections"]'::jsonb),
('achievements_count_for_rank', 'Logros Requeridos para Rango', 'Si se requieren logros además de XP para ascender de rango', 'ranks', 'false'::jsonb, 'false'::jsonb, 'boolean', NULL, NULL, 'global', FALSE, TRUE, '["rank_advancement", "badge_system"]'::jsonb, '["ranks", "achievements", "requirements"]'::jsonb),
-- =============================================================================
-- CLASSROOM-SPECIFIC PARAMETERS (Examples)
-- =============================================================================
('classroom_custom_xp_multiplier', 'Multiplicador XP Personalizado por Aula', 'Permite a maestros ajustar XP ganado en su aula', 'multipliers', '1.0'::jsonb, '1.0'::jsonb, 'number', 0.5, 3, 'classroom', FALSE, TRUE, '["xp_calculation"]'::jsonb, '["multiplier", "classroom", "customization"]'::jsonb),
('classroom_ml_coins_multiplier', 'Multiplicador ML Coins por Aula', 'Permite a maestros ajustar ML coins en su aula', 'multipliers', '1.0'::jsonb, '1.0'::jsonb, 'number', 0.5, 3, 'classroom', FALSE, TRUE, '["ml_coins"]'::jsonb, '["multiplier", "classroom", "customization"]'::jsonb);
-- =============================================================================
-- Verification
-- =============================================================================
SELECT
param_key,
param_name,
category,
param_value,
scope
FROM system_configuration.gamification_parameters
ORDER BY category, param_key;

View File

@ -0,0 +1,327 @@
-- ============================================================================
-- GAMILIT Platform - Production Seeds
-- Archivo: seeds/prod/system_configuration/03-notification_settings_global.sql
-- Propósito: Configuración GLOBAL de notificaciones del sistema
-- Creado: 2025-11-08
-- Actualizado: 2025-11-11 (Migrado a notification_settings_global)
-- ============================================================================
--
-- IMPORTANTE: Esta tabla configura notificaciones a nivel SISTEMA (sin user_id).
-- Para preferencias por usuario, ver auth_management.notification_settings
--
-- ============================================================================
-- Configuración de tipos de notificaciones
INSERT INTO system_configuration.notification_settings_global (
notification_type,
channel,
is_enabled,
priority,
template_id,
throttle_minutes,
batch_enabled,
batch_window_minutes,
settings
)
VALUES
-- Notificaciones de Achievements
(
'achievement_unlocked',
'in_app',
true,
'high',
NULL,
0,
false,
NULL,
jsonb_build_object(
'sound_enabled', true,
'show_badge', true,
'auto_dismiss_seconds', 10
)
),
(
'achievement_unlocked',
'email',
false,
'low',
NULL,
60,
true,
1440,
jsonb_build_object(
'batch_size', 5,
'batch_window_hours', 24
)
),
-- Notificaciones de Rank Promotion
(
'rank_promotion',
'in_app',
true,
'high',
NULL,
0,
false,
NULL,
jsonb_build_object(
'sound_enabled', true,
'show_animation', true,
'celebration_effects', true
)
),
(
'rank_promotion',
'email',
true,
'high',
NULL,
0,
false,
NULL,
'{}'::jsonb
),
-- Notificaciones de Module Progress
(
'module_completed',
'in_app',
true,
'normal',
NULL,
0,
false,
NULL,
jsonb_build_object(
'show_progress_bar', true,
'show_next_module', true
)
),
-- Notificaciones de Assignment
(
'assignment_due',
'in_app',
true,
'high',
NULL,
0,
false,
NULL,
jsonb_build_object(
'advance_notice_hours', 24,
'reminder_hours', jsonb_build_array(24, 6, 1)
)
),
(
'assignment_due',
'email',
true,
'high',
NULL,
0,
false,
NULL,
'{}'::jsonb
),
(
'assignment_submitted',
'in_app',
true,
'normal',
NULL,
0,
false,
NULL,
'{}'::jsonb
),
-- Notificaciones de Classroom
(
'classroom_invitation',
'in_app',
true,
'high',
NULL,
0,
false,
NULL,
'{}'::jsonb
),
(
'classroom_invitation',
'email',
true,
'high',
NULL,
0,
false,
NULL,
'{}'::jsonb
),
-- Notificaciones de Peer Challenges
(
'challenge_received',
'in_app',
true,
'normal',
NULL,
0,
false,
NULL,
jsonb_build_object(
'auto_accept_timeout_hours', 24
)
),
(
'challenge_completed',
'in_app',
true,
'normal',
NULL,
0,
false,
NULL,
jsonb_build_object(
'show_leaderboard', true,
'show_rewards', true
)
),
-- Notificaciones para Padres (Parent Portal)
(
'daily_summary',
'email',
false,
'low',
NULL,
1440,
true,
1440,
jsonb_build_object(
'send_time', '18:00',
'timezone', 'America/Mexico_City',
'include_screenshots', false
)
),
(
'weekly_report',
'email',
false,
'low',
NULL,
10080,
false,
NULL,
jsonb_build_object(
'send_day', 'sunday',
'send_time', '18:00',
'include_charts', true
)
),
(
'monthly_report',
'email',
false,
'low',
NULL,
43200,
false,
NULL,
jsonb_build_object(
'send_day_of_month', 1,
'include_detailed_analytics', true
)
),
(
'low_performance',
'email',
false,
'high',
NULL,
1440,
false,
NULL,
jsonb_build_object(
'threshold_percentage', 60,
'consecutive_failures', 3
)
),
(
'inactivity_alert',
'email',
false,
'normal',
NULL,
2880,
false,
NULL,
jsonb_build_object(
'inactivity_days', 7
)
),
-- Notificaciones de Sistema
(
'system_announcement',
'in_app',
true,
'high',
NULL,
0,
false,
NULL,
jsonb_build_object(
'persistent', true,
'dismissable', true
)
),
(
'maintenance_scheduled',
'email',
true,
'urgent',
NULL,
0,
false,
NULL,
jsonb_build_object(
'advance_notice_hours', 48
)
)
ON CONFLICT (notification_type, channel) DO UPDATE SET
is_enabled = EXCLUDED.is_enabled,
priority = EXCLUDED.priority,
template_id = EXCLUDED.template_id,
throttle_minutes = EXCLUDED.throttle_minutes,
batch_enabled = EXCLUDED.batch_enabled,
batch_window_minutes = EXCLUDED.batch_window_minutes,
settings = EXCLUDED.settings,
updated_at = gamilit.now_mexico();
-- Verificación
SELECT
notification_type,
channel,
is_enabled,
priority,
CASE
WHEN is_enabled THEN ''
ELSE ''
END as status,
throttle_minutes,
batch_enabled,
batch_window_minutes
FROM system_configuration.notification_settings_global
ORDER BY
CASE priority
WHEN 'urgent' THEN 1
WHEN 'high' THEN 2
WHEN 'normal' THEN 3
WHEN 'low' THEN 4
END,
notification_type,
channel;

View File

@ -0,0 +1,335 @@
-- ============================================================================
-- GAMILIT Platform - Production Seeds
-- Archivo: seeds/prod/system_configuration/04-rate_limits.sql
-- Propósito: Configuración de rate limiting para seguridad y prevención de abuso
-- Creado: 2025-11-08
-- ============================================================================
-- Rate limits por endpoint/operación
INSERT INTO system_configuration.rate_limits (
resource_type,
resource_identifier,
max_requests,
window_seconds,
scope,
is_enabled,
burst_size,
description
)
VALUES
-- Auth endpoints
(
'endpoint',
'/api/auth/login',
5,
300,
'ip',
true,
10,
'Login attempts - 5 per 5 minutes per IP'
),
(
'endpoint',
'/api/auth/register',
3,
3600,
'ip',
true,
5,
'Registration - 3 per hour per IP'
),
(
'endpoint',
'/api/auth/reset-password',
3,
3600,
'ip',
true,
5,
'Password reset - 3 per hour per IP'
),
(
'endpoint',
'/api/auth/verify-email',
10,
3600,
'user',
true,
15,
'Email verification - 10 per hour per user'
),
-- API general
(
'endpoint',
'/api/*',
1000,
60,
'user',
true,
1200,
'General API - 1000 requests per minute per user'
),
(
'endpoint',
'/api/*',
5000,
60,
'ip',
true,
6000,
'General API - 5000 requests per minute per IP'
),
-- Exercise submissions
(
'operation',
'exercise_submission',
100,
3600,
'user',
true,
120,
'Exercise submissions - 100 per hour per user'
),
(
'operation',
'exercise_attempt',
200,
3600,
'user',
true,
250,
'Exercise attempts - 200 per hour per user'
),
-- File uploads
(
'operation',
'file_upload',
50,
3600,
'user',
true,
60,
'File uploads - 50 per hour per user'
),
(
'operation',
'file_upload_size',
524288000,
86400,
'user',
true,
629145600,
'Total upload size - 500MB per day per user (bytes)'
),
-- Social features
(
'operation',
'classroom_create',
10,
86400,
'user',
true,
15,
'Classroom creation - 10 per day per user'
),
(
'operation',
'classroom_invitation',
100,
3600,
'user',
true,
120,
'Classroom invitations - 100 per hour per user'
),
(
'operation',
'peer_challenge_create',
50,
3600,
'user',
true,
60,
'Peer challenges - 50 per hour per user'
),
-- Gamification
(
'operation',
'achievement_claim',
1000,
3600,
'user',
true,
1200,
'Achievement claims - 1000 per hour per user'
),
(
'operation',
'comodin_use',
100,
3600,
'user',
true,
120,
'Comodin usage - 100 per hour per user'
),
-- ML Coins transactions
(
'operation',
'ml_coins_transaction',
500,
3600,
'user',
true,
600,
'ML Coins transactions - 500 per hour per user'
),
-- ⚠️ FUTURE: Parent Portal (Extension EXT-010, v1.3) - Rate limits anticipados
-- Descomentarlos cuando se active Extension EXT-010
-- (
-- 'operation',
-- 'parent_report_generate',
-- 10,
-- 3600,
-- 'user',
-- true,
-- 15,
-- 'Parent report generation - 10 per hour per parent'
-- ),
-- (
-- 'operation',
-- 'parent_student_link',
-- 20,
-- 86400,
-- 'user',
-- true,
-- 25,
-- 'Parent-student linking - 20 per day per parent'
-- ),
-- LTI Integration
(
'operation',
'lti_launch',
1000,
3600,
'consumer',
true,
1500,
'LTI launches - 1000 per hour per LMS'
),
(
'operation',
'lti_grade_passback',
500,
3600,
'consumer',
true,
600,
'Grade passback - 500 per hour per LMS'
),
-- Admin operations
(
'operation',
'admin_bulk_update',
50,
3600,
'user',
true,
60,
'Bulk updates - 50 per hour per admin'
),
(
'operation',
'admin_report_export',
20,
3600,
'user',
true,
25,
'Report exports - 20 per hour per admin'
),
-- Email sending
(
'operation',
'email_send',
100,
3600,
'user',
true,
120,
'Email sending - 100 per hour per user'
),
-- Search operations
(
'operation',
'search_query',
200,
60,
'user',
true,
250,
'Search queries - 200 per minute per user'
),
-- Content creation
(
'operation',
'content_create',
50,
3600,
'user',
true,
60,
'Content creation - 50 per hour per user'
),
(
'operation',
'content_update',
200,
3600,
'user',
true,
250,
'Content updates - 200 per hour per user'
)
ON CONFLICT (resource_type, resource_identifier, scope) DO UPDATE SET
max_requests = EXCLUDED.max_requests,
window_seconds = EXCLUDED.window_seconds,
is_enabled = EXCLUDED.is_enabled,
burst_size = EXCLUDED.burst_size,
description = EXCLUDED.description,
updated_at = NOW();
-- Verificación
SELECT
resource_type,
resource_identifier,
max_requests,
CASE
WHEN window_seconds < 60 THEN max_requests || ' per ' || window_seconds || 's'
WHEN window_seconds < 3600 THEN max_requests || ' per ' || (window_seconds / 60) || 'm'
WHEN window_seconds < 86400 THEN max_requests || ' per ' || (window_seconds / 3600) || 'h'
ELSE max_requests || ' per ' || (window_seconds / 86400) || 'd'
END as rate_limit,
scope,
CASE WHEN is_enabled THEN '' ELSE '' END as enabled,
description
FROM system_configuration.rate_limits
ORDER BY
CASE resource_type
WHEN 'endpoint' THEN 1
WHEN 'operation' THEN 2
END,
resource_identifier;

View File

@ -0,0 +1,207 @@
-- =====================================================
-- Seed Data: Activity Log Sample Data (DEV ONLY)
-- =====================================================
-- Description: Sample activity log data for testing admin dashboard
-- Environment: DEVELOPMENT ONLY (NO production/staging)
-- Date: 2025-11-24
-- Gap: GAP-DB-001 (supporting data)
-- =====================================================
--
-- 📚 Documentación:
-- Requerimiento: orchestration/reportes/REPORTE-COHERENCIA-DATABASE-BACKEND-2025-11-24.md
-- Backend: apps/backend/src/modules/admin/services/admin-dashboard.service.ts
-- =====================================================
SET search_path TO audit_logging, auth, public;
BEGIN;
-- =====================================================
-- INSERTAR DATOS DE ACTIVIDAD DE PRUEBA
-- =====================================================
-- Get sample user IDs (will fail gracefully if no users exist)
DO $$
DECLARE
sample_user_id UUID;
student_user_id UUID;
teacher_user_id UUID;
BEGIN
-- Get first student user
SELECT id INTO student_user_id
FROM auth.users
WHERE gamilit_role = 'student' AND deleted_at IS NULL
LIMIT 1;
-- Get first teacher user
SELECT id INTO teacher_user_id
FROM auth.users
WHERE gamilit_role = 'admin_teacher' AND deleted_at IS NULL
LIMIT 1;
-- If no specific users found, get any user
IF student_user_id IS NULL THEN
SELECT id INTO student_user_id
FROM auth.users
WHERE deleted_at IS NULL
LIMIT 1;
END IF;
IF teacher_user_id IS NULL THEN
SELECT id INTO teacher_user_id
FROM auth.users
WHERE deleted_at IS NULL
OFFSET 1 LIMIT 1;
END IF;
-- Exit if no users found
IF student_user_id IS NULL OR teacher_user_id IS NULL THEN
RAISE NOTICE 'No users found. Skipping activity_log seed data.';
RETURN;
END IF;
-- =====================================================
-- LOGIN ACTIVITIES (Recent 7 days)
-- =====================================================
INSERT INTO audit_logging.activity_log (user_id, action_type, description, metadata, created_at)
VALUES
-- Today's logins
(student_user_id, 'user_login', 'Usuario inició sesión', '{"ip": "192.168.1.10", "device": "Chrome/Windows"}'::jsonb, NOW()),
(teacher_user_id, 'user_login', 'Maestro inició sesión', '{"ip": "192.168.1.20", "device": "Safari/MacOS"}'::jsonb, NOW() - INTERVAL '2 hours'),
-- Yesterday's activity
(student_user_id, 'user_login', 'Usuario inició sesión', '{"ip": "192.168.1.10"}'::jsonb, NOW() - INTERVAL '1 day'),
(student_user_id, 'exercise_start', 'Ejercicio 1-3 iniciado', '{"exercise_id": "ex-1-3", "module_id": "mod-1"}'::jsonb, NOW() - INTERVAL '1 day' + INTERVAL '5 minutes'),
(student_user_id, 'exercise_complete', 'Ejercicio 1-3 completado', '{"exercise_id": "ex-1-3", "score": 85, "time_spent": "00:12:30"}'::jsonb, NOW() - INTERVAL '1 day' + INTERVAL '17 minutes'),
-- 2 days ago
(teacher_user_id, 'assignment_create', 'Tarea "Ejercicios Módulo 1" creada', '{"assignment_id": "asg-001", "title": "Ejercicios Módulo 1"}'::jsonb, NOW() - INTERVAL '2 days'),
(student_user_id, 'module_start', 'Módulo 1 iniciado', '{"module_id": "mod-1"}'::jsonb, NOW() - INTERVAL '2 days' + INTERVAL '3 hours'),
-- 3 days ago
(student_user_id, 'user_login', 'Usuario inició sesión', '{"ip": "192.168.1.10"}'::jsonb, NOW() - INTERVAL '3 days'),
(student_user_id, 'exercise_complete', 'Ejercicio 1-1 completado', '{"exercise_id": "ex-1-1", "score": 100}'::jsonb, NOW() - INTERVAL '3 days' + INTERVAL '10 minutes'),
(student_user_id, 'achievement_earned', 'Logro "Primera Victoria" desbloqueado', '{"achievement_id": "first-win"}'::jsonb, NOW() - INTERVAL '3 days' + INTERVAL '11 minutes'),
-- 4 days ago
(teacher_user_id, 'classroom_create', 'Aula "5to Grado A" creada', '{"classroom_id": "cls-001", "name": "5to Grado A"}'::jsonb, NOW() - INTERVAL '4 days'),
(student_user_id, 'user_login', 'Usuario inició sesión', '{}'::jsonb, NOW() - INTERVAL '4 days'),
-- 5 days ago
(student_user_id, 'exercise_complete', 'Ejercicio 1-2 completado', '{"exercise_id": "ex-1-2", "score": 75}'::jsonb, NOW() - INTERVAL '5 days'),
(teacher_user_id, 'content_review', 'Contenido revisado y aprobado', '{"content_type": "exercise", "content_id": "ex-2-5"}'::jsonb, NOW() - INTERVAL '5 days'),
-- 6 days ago
(student_user_id, 'user_login', 'Usuario inició sesión', '{}'::jsonb, NOW() - INTERVAL '6 days'),
(student_user_id, 'video_play', 'Video tutorial reproducido', '{"video_id": "vid-intro-mod1", "duration": "00:05:30"}'::jsonb, NOW() - INTERVAL '6 days' + INTERVAL '15 minutes'),
-- 7 days ago
(teacher_user_id, 'user_login', 'Maestro inició sesión', '{}'::jsonb, NOW() - INTERVAL '7 days'),
(student_user_id, 'module_complete', 'Módulo 1 completado', '{"module_id": "mod-1", "final_score": 85}'::jsonb, NOW() - INTERVAL '7 days');
-- =====================================================
-- ADDITIONAL ACTIVITY TYPES (for diversity)
-- =====================================================
INSERT INTO audit_logging.activity_log (user_id, action_type, description, metadata, created_at)
VALUES
-- Resource downloads
(student_user_id, 'resource_download', 'Guía PDF descargada', '{"resource_id": "guide-mod1.pdf"}'::jsonb, NOW() - INTERVAL '2 days' + INTERVAL '6 hours'),
-- Search queries
(student_user_id, 'search_query', 'Búsqueda realizada', '{"query": "inferencias", "results": 12}'::jsonb, NOW() - INTERVAL '1 day' + INTERVAL '8 hours'),
-- Form submissions
(teacher_user_id, 'form_submit', 'Formulario de evaluación enviado', '{"form_type": "student_assessment", "student_count": 25}'::jsonb, NOW() - INTERVAL '3 days' + INTERVAL '2 hours'),
-- Button clicks (UI interaction tracking)
(student_user_id, 'button_click', 'Botón "Ver Estadísticas" clickeado', '{"button_id": "view-stats", "page": "dashboard"}'::jsonb, NOW() - INTERVAL '12 hours'),
-- Page views
(student_user_id, 'page_view', 'Dashboard visitado', '{"page": "/student/dashboard"}'::jsonb, NOW() - INTERVAL '6 hours'),
(teacher_user_id, 'page_view', 'Panel de administración visitado', '{"page": "/admin/dashboard"}'::jsonb, NOW() - INTERVAL '4 hours');
RAISE NOTICE '✅ Sample activity log data inserted successfully';
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE '⚠️ Error inserting activity_log sample data: %', SQLERRM;
RAISE NOTICE 'This is expected if no users exist yet. Run user seeds first.';
END $$;
COMMIT;
-- =====================================================
-- VERIFICACIÓN
-- =====================================================
DO $$
DECLARE
activity_count INT;
unique_users INT;
recent_7days INT;
BEGIN
SELECT COUNT(*) INTO activity_count FROM audit_logging.activity_log;
SELECT COUNT(DISTINCT user_id) INTO unique_users FROM audit_logging.activity_log;
SELECT COUNT(*) INTO recent_7days FROM audit_logging.activity_log
WHERE created_at >= NOW() - INTERVAL '7 days';
RAISE NOTICE '';
RAISE NOTICE '========================================';
RAISE NOTICE ' Activity Log Sample Data Summary';
RAISE NOTICE '========================================';
RAISE NOTICE 'Total activity records: %', activity_count;
RAISE NOTICE 'Unique users with activity: %', unique_users;
RAISE NOTICE 'Activities (last 7 days): %', recent_7days;
RAISE NOTICE '';
IF activity_count > 0 THEN
RAISE NOTICE '✅ Activity log populated with sample data';
ELSE
RAISE NOTICE '⚠️ No activity log data inserted (users may not exist)';
END IF;
RAISE NOTICE '========================================';
RAISE NOTICE '';
END $$;
-- =====================================================
-- SAMPLE QUERIES (for testing)
-- =====================================================
-- Show recent activity (like admin dashboard would)
-- SELECT
-- action_type,
-- description,
-- created_at
-- FROM audit_logging.activity_log
-- ORDER BY created_at DESC
-- LIMIT 10;
-- Count active users in last 24h
-- SELECT COUNT(DISTINCT user_id) as active_users_24h
-- FROM audit_logging.activity_log
-- WHERE created_at >= NOW() - INTERVAL '24 hours';
-- Exercises completed in last 24h
-- SELECT COUNT(*) as exercises_completed_24h
-- FROM audit_logging.activity_log
-- WHERE action_type LIKE '%exercise%'
-- AND created_at >= NOW() - INTERVAL '24 hours';
-- =====================================================
-- MIGRATION NOTES
-- =====================================================
-- Environment: DEV ONLY
-- Purpose: Provide sample data for testing admin dashboard endpoints
--
-- Dependencies:
-- - audit_logging.activity_log table must exist (created by DDL)
-- - auth.users must have at least 2 users (student + teacher)
--
-- Related Files:
-- - apps/database/ddl/schemas/audit_logging/tables/06-activity_log.sql
-- - apps/backend/src/modules/admin/services/admin-dashboard.service.ts
--
-- =====================================================

View File

@ -0,0 +1,581 @@
-- =====================================================
-- GLIT Platform - Audit Logging Seeds
-- =====================================================
-- Schema: audit_logging
-- Description: Audit logs, system logs históricos demo
-- Dependencies: auth schema (users)
-- =====================================================
SET search_path TO audit_logging, auth, public;
DO $$
DECLARE
admin_id UUID;
instructor_id UUID;
student1_id UUID;
student2_id UUID;
student3_id UUID;
BEGIN
-- =====================================================
-- OBTENER USER IDs
-- =====================================================
SELECT id INTO admin_id FROM auth.users WHERE email = 'admin@glit.edu.mx';
SELECT id INTO instructor_id FROM auth.users WHERE email = 'instructor@demo.glit.edu.mx';
SELECT id INTO student1_id FROM auth.users WHERE email = 'estudiante1@demo.glit.edu.mx';
SELECT id INTO student2_id FROM auth.users WHERE email = 'estudiante2@demo.glit.edu.mx';
SELECT id INTO student3_id FROM auth.users WHERE email = 'estudiante3@demo.glit.edu.mx';
-- =====================================================
-- AUDIT LOGS: Logs de acciones importantes
-- =====================================================
INSERT INTO audit_logging.audit_logs (
actor_id, action, resource_type, resource_id,
changes, actor_ip, actor_user_agent,
severity, status,
additional_data, created_at
) VALUES
-- ============ ADMIN ACTIONS ============
-- Admin: Creación de system settings
(
admin_id,
'create',
'system_settings',
gen_random_uuid(),
'{
"before": null,
"after": {
"setting_key": "ml_coins_welcome_bonus",
"setting_value": "100"
}
}'::jsonb,
'192.168.1.100',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"category": "system_configuration",
"impact": "medium",
"automated": false,
"context": "Initial platform setup"
}'::jsonb,
NOW() - INTERVAL '15 days'
),
-- Admin: Actualización de feature flag
(
admin_id,
'update',
'feature_flags',
gen_random_uuid(),
'{
"before": {"is_enabled": false, "rollout_percentage": 0},
"after": {"is_enabled": true, "rollout_percentage": 100}
}'::jsonb,
'192.168.1.100',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"flag_name": "missions_system",
"impact": "high",
"reason": "Production rollout",
"approved_by": "admin@glit.edu.mx"
}'::jsonb,
NOW() - INTERVAL '14 days'
),
-- Admin: Creación de achievement
(
admin_id,
'create',
'achievements',
gen_random_uuid(),
'{
"before": null,
"after": {
"achievement_code": "reading_master",
"achievement_name": "Maestro Lector",
"ml_coins_reward": 500,
"xp_reward": 250
}
}'::jsonb,
'192.168.1.100',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"category": "gamification",
"impact": "medium",
"automated": false
}'::jsonb,
NOW() - INTERVAL '13 days'
),
-- Admin: Intento fallido de eliminar módulo (protegido)
(
admin_id,
'delete',
'modules',
gen_random_uuid(),
'{
"before": {"module_code": "MOD-01-LITERAL", "module_name": "Comprensión Literal"},
"after": null,
"error": "Cannot delete module with active exercises"
}'::jsonb,
'192.168.1.100',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
'warning',
'failure',
'{
"constraint_violated": "fk_exercises_module",
"active_exercises_count": 15,
"automated": false,
"error_code": "CONSTRAINT_VIOLATION"
}'::jsonb,
NOW() - INTERVAL '5 days'
),
-- Admin: Bulk user role update
(
admin_id,
'bulk_update',
'users',
NULL,
'{
"users_affected": 3,
"changes": {
"role": "student",
"is_active": true
}
}'::jsonb,
'192.168.1.100',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"operation": "activate_students",
"automated": false,
"execution_time_ms": 250
}'::jsonb,
NOW() - INTERVAL '10 days'
),
-- ============ INSTRUCTOR ACTIONS ============
-- Instructor: Creación de aula
(
instructor_id,
'create',
'classrooms',
gen_random_uuid(),
'{
"before": null,
"after": {
"classroom_name": "2° A - Comprensión Lectora",
"classroom_code": "2A-LECT-2025",
"grade_level": "2nd_grade",
"max_students": 30
}
}'::jsonb,
'10.0.2.50',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15',
'info',
'success',
'{
"school": "SF-015-CDMX",
"automated": false,
"context": "New semester setup"
}'::jsonb,
NOW() - INTERVAL '12 days'
),
-- Instructor: Asignación de estudiante a aula
(
instructor_id,
'create',
'classroom_members',
gen_random_uuid(),
'{
"before": null,
"after": {
"classroom_code": "2A-LECT-2025",
"student_email": "estudiante1@demo.glit.edu.mx",
"role": "student",
"enrollment_date": "2025-10-21"
}
}'::jsonb,
'10.0.2.50',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15',
'info',
'success',
'{
"enrollment_type": "manual",
"automated": false,
"classroom_size": 1
}'::jsonb,
NOW() - INTERVAL '11 days'
),
-- Instructor: Actualización de mission assignment
(
instructor_id,
'update',
'mission_assignments',
gen_random_uuid(),
'{
"before": {"due_date": "2025-11-10"},
"after": {"due_date": "2025-11-15"}
}'::jsonb,
'10.0.2.50',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15',
'info',
'success',
'{
"mission_code": "MISSION-LITERAL-01",
"reason": "Student request for extension",
"automated": false
}'::jsonb,
NOW() - INTERVAL '3 days'
),
-- ============ STUDENT ACTIONS ============
-- Student1: Login exitoso
(
student1_id,
'login',
'auth_session',
gen_random_uuid(),
'{
"login_method": "email_password",
"mfa_used": false,
"session_created": true
}'::jsonb,
'192.168.1.45',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"session_duration_hours": 8,
"device_type": "desktop",
"browser": "Chrome",
"os": "Linux"
}'::jsonb,
NOW() - INTERVAL '1 hour'
),
-- Student1: Completó ejercicio (achievement unlock)
(
student1_id,
'achievement_unlocked',
'user_achievements',
gen_random_uuid(),
'{
"achievement": {
"achievement_code": "first_steps",
"achievement_name": "Primeros Pasos",
"tier": "bronze"
}
}'::jsonb,
'192.168.1.45',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"ml_coins_awarded": 50,
"xp_awarded": 25,
"automated": true,
"trigger": "exercise_completion_count"
}'::jsonb,
NOW() - INTERVAL '30 minutes'
),
-- Student2: Password reset request
(
student2_id,
'password_reset_request',
'auth_session',
gen_random_uuid(),
'{
"reset_token_sent": true,
"email_sent": true
}'::jsonb,
'192.168.1.67',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0',
'info',
'success',
'{
"reset_method": "email",
"token_expiry_hours": 24,
"automated": true
}'::jsonb,
NOW() - INTERVAL '8 hours'
),
-- Student3: Failed login attempt
(
student3_id,
'login',
'auth_session',
gen_random_uuid(),
'{
"login_method": "email_password",
"error": "Invalid credentials"
}'::jsonb,
'192.168.1.89',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148',
'warning',
'failure',
'{
"attempt_number": 2,
"max_attempts": 5,
"device_type": "mobile",
"automated": false
}'::jsonb,
NOW() - INTERVAL '2 hours'
),
-- ============ SYSTEM ACTIONS ============
-- System: Scheduled backup
(
NULL,
'backup',
'database',
NULL,
'{
"backup_type": "automated",
"size_mb": 450,
"duration_seconds": 45,
"status": "completed"
}'::jsonb,
'127.0.0.1',
'PostgreSQL Backup Scheduler v1.0',
'info',
'success',
'{
"backup_location": "/backups/glit_dev_2025-11-02.sql.gz",
"automated": true,
"retention_days": 30,
"compression": "gzip"
}'::jsonb,
NOW() - INTERVAL '6 hours'
),
-- System: Data cleanup job
(
NULL,
'cleanup',
'database',
NULL,
'{
"tables_cleaned": ["audit_logs", "system_logs"],
"records_deleted": 1500,
"retention_days": 90
}'::jsonb,
'127.0.0.1',
'GLIT Cleanup Service v1.0',
'info',
'success',
'{
"automated": true,
"execution_time_seconds": 12,
"freed_space_mb": 25
}'::jsonb,
NOW() - INTERVAL '12 hours'
)
ON CONFLICT DO NOTHING;
-- =====================================================
-- SYSTEM LOGS: Logs del sistema
-- =====================================================
INSERT INTO audit_logging.system_logs (
log_level, component, message,
error_code, stack_trace,
additional_data, created_at
) VALUES
-- ============ INFO LOGS ============
-- Info: Aplicación iniciada
(
'info',
'application',
'GLIT application started successfully',
NULL,
NULL,
'{
"version": "1.0.0",
"environment": "development",
"startup_time_ms": 1250,
"node_version": "v20.11.0",
"postgres_version": "15.5"
}'::jsonb,
NOW() - INTERVAL '1 day'
),
-- Debug: Database connection pool
(
'debug',
'database',
'Connection pool initialized',
NULL,
NULL,
'{
"pool_size": 20,
"min_connections": 5,
"max_connections": 50,
"idle_timeout_ms": 30000
}'::jsonb,
NOW() - INTERVAL '1 day' + INTERVAL '2 seconds'
),
-- Info: Cache initialized
(
'info',
'cache',
'Redis cache connected successfully',
NULL,
NULL,
'{
"redis_version": "7.2.0",
"connection_time_ms": 45,
"max_memory_mb": 256
}'::jsonb,
NOW() - INTERVAL '1 day' + INTERVAL '5 seconds'
),
-- Info: Scheduled job completed
(
'info',
'scheduler',
'Daily achievement recalculation completed',
NULL,
NULL,
'{
"users_processed": 5,
"achievements_updated": 12,
"duration_seconds": 3.5,
"next_run": "2025-11-03T00:00:00Z"
}'::jsonb,
NOW() - INTERVAL '2 hours'
),
-- ============ WARNING LOGS ============
-- Warning: Slow query detected
(
'warning',
'database',
'Slow query detected',
'SLOW_QUERY_001',
NULL,
'{
"query": "SELECT * FROM progress_tracking.exercise_attempts WHERE student_id = $1 ORDER BY attempted_at DESC",
"duration_ms": 2500,
"threshold_ms": 1000,
"optimization_suggested": true,
"suggested_index": "idx_exercise_attempts_student_attempted"
}'::jsonb,
NOW() - INTERVAL '12 hours'
),
-- Warning: Cache miss rate high
(
'warning',
'cache',
'High cache miss rate detected',
'CACHE_MISS_HIGH',
NULL,
'{
"miss_rate_percentage": 45,
"threshold_percentage": 30,
"total_requests": 10000,
"cache_hits": 5500,
"cache_misses": 4500
}'::jsonb,
NOW() - INTERVAL '4 hours'
),
-- ============ ERROR LOGS ============
-- Error: Failed to send email
(
'error',
'email_service',
'Failed to send welcome email',
'EMAIL_SEND_FAILURE',
'at EmailService.send (email.service.ts:45)\nat UserController.register (user.controller.ts:123)\nat Layer.handle (express/lib/router/layer.js:95)',
'{
"recipient": "test@example.com",
"error": "SMTP connection timeout",
"retry_count": 3,
"smtp_host": "smtp.example.com",
"smtp_port": 587
}'::jsonb,
NOW() - INTERVAL '6 hours'
),
-- Error: API rate limit exceeded
(
'error',
'api_server',
'Rate limit exceeded for endpoint',
'RATE_LIMIT_EXCEEDED',
NULL,
'{
"endpoint": "/api/exercises/search",
"client_ip": "203.0.113.42",
"requests_count": 150,
"limit": 100,
"window_seconds": 60,
"action_taken": "Request blocked"
}'::jsonb,
NOW() - INTERVAL '3 hours'
),
-- Error: Database connection failed
(
'error',
'database',
'Database connection attempt failed',
'DB_CONNECTION_FAILED',
'Error: connect ECONNREFUSED 127.0.0.1:5432\nat TCPConnectWrap.afterConnect (net.js:1144:16)',
'{
"host": "localhost",
"port": 5432,
"database": "glit_dev",
"retry_attempt": 1,
"max_retries": 5
}'::jsonb,
NOW() - INTERVAL '18 hours'
),
-- ============ DEBUG LOGS ============
-- Debug: API request details
(
'debug',
'api_server',
'API request received',
NULL,
NULL,
'{
"method": "GET",
"path": "/api/modules",
"query_params": {"grade_level": "2nd_grade"},
"actor_id": "' || student1_id || '",
"response_time_ms": 45,
"status_code": 200
}'::jsonb,
NOW() - INTERVAL '15 minutes'
)
ON CONFLICT DO NOTHING;
RAISE NOTICE 'Audit logging seeds completed successfully';
RAISE NOTICE '- % audit_logs inserted', (SELECT COUNT(*) FROM audit_logging.audit_logs);
RAISE NOTICE '- % system_logs inserted', (SELECT COUNT(*) FROM audit_logging.system_logs);
END $$;

View File

@ -0,0 +1,617 @@
-- =====================================================
-- GLIT Platform - System Metrics & Alerts Seeds
-- =====================================================
-- Schema: audit_logging
-- Description: Performance metrics, system alerts, user activity logs
-- Dependencies: auth schema (users)
-- =====================================================
SET search_path TO audit_logging, auth, public;
-- =====================================================
-- PERFORMANCE METRICS: Métricas históricas
-- =====================================================
INSERT INTO audit_logging.performance_metrics (
metric_name, metric_type, metric_metric_value,
unit, component, environment,
dimensions, recorded_at, created_at
) VALUES
-- ============ DATABASE METRICS ============
(
'database_connections_active',
'gauge',
15.0,
'connections',
'postgresql',
'development',
'{
"pool_size": 20,
"utilization_percentage": 75,
"idle_connections": 5,
"waiting_clients": 0
}'::jsonb,
NOW() - INTERVAL '10 minutes',
NOW() - INTERVAL '10 minutes'
),
(
'database_query_avg_duration',
'histogram',
45.5,
'milliseconds',
'postgresql',
'development',
'{
"queries_sampled": 1250,
"p50": 25.0,
"p75": 55.0,
"p95": 85.0,
"p99": 150.0,
"max": 500.0
}'::jsonb,
NOW() - INTERVAL '10 minutes',
NOW() - INTERVAL '10 minutes'
),
(
'database_cache_hit_ratio',
'gauge',
98.5,
'percentage',
'postgresql',
'development',
'{
"cache_hits": 9850,
"cache_misses": 150,
"total_queries": 10000,
"shared_buffers_mb": 128
}'::jsonb,
NOW() - INTERVAL '15 minutes',
NOW() - INTERVAL '15 minutes'
),
(
'database_transactions_per_second',
'gauge',
45.8,
'transactions',
'postgresql',
'development',
'{
"commits": 2748,
"rollbacks": 12,
"sample_duration_seconds": 60
}'::jsonb,
NOW() - INTERVAL '20 minutes',
NOW() - INTERVAL '20 minutes'
),
-- ============ APPLICATION METRICS ============
(
'api_requests_total',
'counter',
5432.0,
'requests',
'api_server',
'development',
'{
"endpoint": "/api/exercises",
"method": "GET",
"status_2xx": 5200,
"status_4xx": 200,
"status_5xx": 32,
"time_window_hours": 1
}'::jsonb,
NOW() - INTERVAL '1 hour',
NOW() - INTERVAL '1 hour'
),
(
'api_response_time',
'histogram',
125.5,
'milliseconds',
'api_server',
'development',
'{
"endpoint": "/api/modules",
"method": "GET",
"p50": 95.0,
"p75": 180.0,
"p95": 250.0,
"p99": 450.0,
"requests_count": 850
}'::jsonb,
NOW() - INTERVAL '30 minutes',
NOW() - INTERVAL '30 minutes'
),
(
'api_error_rate',
'gauge',
0.8,
'percentage',
'api_server',
'development',
'{
"total_requests": 5000,
"errors": 40,
"status_5xx": 32,
"status_4xx": 8,
"threshold_percentage": 2.0
}'::jsonb,
NOW() - INTERVAL '45 minutes',
NOW() - INTERVAL '45 minutes'
),
(
'websocket_connections_active',
'gauge',
12.0,
'connections',
'websocket_server',
'development',
'{
"max_connections": 1000,
"utilization_percentage": 1.2,
"messages_per_second": 45
}'::jsonb,
NOW() - INTERVAL '5 minutes',
NOW() - INTERVAL '5 minutes'
),
-- ============ USER ACTIVITY METRICS ============
(
'active_users_daily',
'gauge',
5.0,
'users',
'application',
'development',
'{
"date": "2025-11-02",
"students": 3,
"instructors": 1,
"admins": 1,
"total_sessions": 8,
"avg_session_duration_minutes": 45
}'::jsonb,
NOW() - INTERVAL '6 hours',
NOW() - INTERVAL '6 hours'
),
(
'exercises_completed',
'counter',
12.0,
'exercises',
'application',
'development',
'{
"period": "24h",
"module_breakdown": {
"MOD-01-LITERAL": 8,
"MOD-02-INFERENCIAL": 3,
"MOD-03-CRITICA": 1
},
"avg_score_percentage": 75.5
}'::jsonb,
NOW() - INTERVAL '1 hour',
NOW() - INTERVAL '1 hour'
),
(
'ml_coins_transactions',
'counter',
45.0,
'transactions',
'gamification',
'development',
'{
"period": "24h",
"total_earned": 1250,
"total_spent": 350,
"net_change": 900,
"unique_users": 3
}'::jsonb,
NOW() - INTERVAL '2 hours',
NOW() - INTERVAL '2 hours'
),
(
'mission_completion_rate',
'gauge',
65.5,
'percentage',
'application',
'development',
'{
"missions_assigned": 20,
"missions_completed": 13,
"missions_in_progress": 5,
"missions_overdue": 2
}'::jsonb,
NOW() - INTERVAL '3 hours',
NOW() - INTERVAL '3 hours'
),
-- ============ SYSTEM RESOURCES ============
(
'memory_usage',
'gauge',
512.5,
'megabytes',
'system',
'development',
'{
"total_mb": 2048,
"utilization_percentage": 25,
"heap_used_mb": 380,
"heap_total_mb": 512,
"rss_mb": 650
}'::jsonb,
NOW() - INTERVAL '5 minutes',
NOW() - INTERVAL '5 minutes'
),
(
'cpu_usage',
'gauge',
35.2,
'percentage',
'system',
'development',
'{
"cores": 4,
"load_average": [1.2, 1.5, 1.3],
"user_percentage": 25.5,
"system_percentage": 9.7
}'::jsonb,
NOW() - INTERVAL '5 minutes',
NOW() - INTERVAL '5 minutes'
),
(
'disk_usage',
'gauge',
1024.0,
'megabytes',
'system',
'development',
'{
"total_gb": 50,
"used_gb": 1.0,
"available_gb": 49.0,
"utilization_percentage": 2.0
}'::jsonb,
NOW() - INTERVAL '10 minutes',
NOW() - INTERVAL '10 minutes'
),
(
'network_throughput',
'gauge',
2.5,
'megabytes_per_second',
'system',
'development',
'{
"inbound_mbps": 1.8,
"outbound_mbps": 0.7,
"packets_per_second": 1250,
"errors": 0
}'::jsonb,
NOW() - INTERVAL '5 minutes',
NOW() - INTERVAL '5 minutes'
),
-- ============ CACHE METRICS ============
(
'cache_hit_rate',
'gauge',
85.5,
'percentage',
'redis',
'development',
'{
"total_requests": 5000,
"hits": 4275,
"misses": 725,
"evictions": 12
}'::jsonb,
NOW() - INTERVAL '15 minutes',
NOW() - INTERVAL '15 minutes'
),
(
'cache_memory_usage',
'gauge',
128.5,
'megabytes',
'redis',
'development',
'{
"max_memory_mb": 256,
"utilization_percentage": 50.2,
"keys_count": 1250,
"expired_keys": 45
}'::jsonb,
NOW() - INTERVAL '10 minutes',
NOW() - INTERVAL '10 minutes'
)
ON CONFLICT DO NOTHING;
-- =====================================================
-- SYSTEM ALERTS: Alertas generadas
-- =====================================================
INSERT INTO audit_logging.system_alerts (
alert_type, severity, title, message,
component, threshold_metric_value, current_metric_value,
status, triggered_at, resolved_at,
dimensions, created_at, updated_at
) VALUES
-- ============ RESOLVED ALERTS ============
-- High memory usage (resolved)
(
'resource',
'warning',
'High Memory Usage Detected',
'Memory usage exceeded 80% threshold',
'system',
80.0,
85.5,
'resolved',
NOW() - INTERVAL '2 hours',
NOW() - INTERVAL '1 hour',
'{
"actions_taken": [
"Memory cache cleared",
"Garbage collection forced",
"Unused connections closed"
],
"resolution_time_minutes": 60,
"peak_memory_mb": 1740,
"final_memory_mb": 512
}'::jsonb,
NOW() - INTERVAL '2 hours',
NOW() - INTERVAL '1 hour'
),
-- Failed login attempts (resolved)
(
'security',
'info',
'Multiple Failed Login Attempts',
'User exceeded login attempt threshold',
'authentication',
5.0,
6.0,
'resolved',
NOW() - INTERVAL '3 hours',
NOW() - INTERVAL '2 hours 55 minutes',
'{
"user_email": "test@example.com",
"ip_address": "203.0.113.42",
"actions_taken": [
"Account locked for 15 minutes",
"Security notification sent"
],
"resolution": "User successfully logged in after password reset",
"lockout_duration_minutes": 15
}'::jsonb,
NOW() - INTERVAL '3 hours',
NOW() - INTERVAL '2 hours 55 minutes'
),
-- Database backup success (acknowledged)
(
'maintenance',
'info',
'Automated Backup Completed',
'Daily database backup completed successfully',
'database',
NULL,
NULL,
'acknowledged',
NOW() - INTERVAL '6 hours',
NOW() - INTERVAL '6 hours',
'{
"backup_size_mb": 450,
"duration_seconds": 45,
"location": "/backups/glit_dev_2025-11-02.sql.gz",
"compression_ratio": 4.5,
"integrity_check": "passed"
}'::jsonb,
NOW() - INTERVAL '6 hours',
NOW() - INTERVAL '6 hours'
),
-- ============ ACTIVE ALERTS ============
-- Slow API endpoint (active)
(
'performance',
'warning',
'Slow API Response Time',
'Endpoint /api/leaderboards exceeding SLA',
'api_server',
200.0,
450.0,
'active',
NOW() - INTERVAL '15 minutes',
NULL,
'{
"endpoint": "/api/leaderboards",
"method": "GET",
"sla_ms": 200,
"p99_ms": 450,
"requests_affected": 25,
"suggested_actions": [
"Add database index on leaderboards.student_id",
"Implement Redis caching for leaderboard data",
"Consider pagination for large result sets"
]
}'::jsonb,
NOW() - INTERVAL '15 minutes',
NOW()
),
-- High error rate (active)
(
'performance',
'error',
'High API Error Rate',
'Error rate exceeded 2% threshold',
'api_server',
2.0,
3.5,
'active',
NOW() - INTERVAL '25 minutes',
NULL,
'{
"total_requests": 1000,
"errors": 35,
"error_breakdown": {
"500_internal_error": 20,
"503_service_unavailable": 10,
"504_gateway_timeout": 5
},
"affected_endpoints": [
"/api/exercises/submit",
"/api/ml-coins/transactions"
],
"investigating": true
}'::jsonb,
NOW() - INTERVAL '25 minutes',
NOW()
),
-- ============ ACKNOWLEDGED ALERTS ============
-- SSL certificate expiry warning (acknowledged)
(
'security',
'warning',
'SSL Certificate Expiring Soon',
'SSL certificate will expire in 30 days',
'security',
30.0,
30.0,
'acknowledged',
NOW() - INTERVAL '1 day',
NOW() - INTERVAL '1 day',
'{
"domain": "*.glit.edu.mx",
"expiry_date": "2025-12-02",
"days_remaining": 30,
"renewal_initiated": true,
"auto_renewal": true
}'::jsonb,
NOW() - INTERVAL '1 day',
NOW() - INTERVAL '1 day'
),
-- Disk space warning (acknowledged)
(
'resource',
'info',
'Disk Space Usage',
'Disk usage reached 70% threshold',
'system',
70.0,
72.5,
'acknowledged',
NOW() - INTERVAL '8 hours',
NOW() - INTERVAL '8 hours',
'{
"mount_point": "/var/lib/postgresql",
"total_gb": 100,
"used_gb": 72.5,
"available_gb": 27.5,
"cleanup_scheduled": true,
"retention_policy": "90 days"
}'::jsonb,
NOW() - INTERVAL '8 hours',
NOW() - INTERVAL '8 hours'
)
ON CONFLICT DO NOTHING;
-- =====================================================
-- USER ACTIVITY LOGS: Actividad de usuarios
-- =====================================================
DO $$
DECLARE
student_record RECORD;
activity_types TEXT[] := ARRAY['page_view', 'exercise_start', 'exercise_complete', 'mission_view', 'leaderboard_view', 'store_visit', 'achievement_view'];
pages TEXT[] := ARRAY['/modules', '/missions', '/leaderboard', '/ml-store', '/profile', '/achievements'];
BEGIN
-- Generar activity logs para cada estudiante
FOR student_record IN
SELECT user_id, email FROM auth.users WHERE role = 'student' LIMIT 3
LOOP
-- 5 actividades por estudiante
FOR i IN 1..5 LOOP
INSERT INTO audit_logging.user_activity_logs (
user_id, activity_type, activity_description,
ip_address, user_agent, session_id,
dimensions, created_at
) VALUES (
student_record.user_id,
activity_types[1 + floor(random() * array_length(activity_types, 1))::int],
'User activity: ' || pages[1 + floor(random() * array_length(pages, 1))::int],
'192.168.1.' || (50 + i)::TEXT,
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0',
gen_random_uuid(),
jsonb_build_object(
'page', pages[1 + floor(random() * array_length(pages, 1))::int],
'duration_seconds', 30 + floor(random() * 120)::int,
'interactions', 1 + floor(random() * 5)::int,
'referrer', '/dashboard',
'device_type', CASE WHEN random() > 0.7 THEN 'mobile' ELSE 'desktop' END
),
NOW() - (random() * INTERVAL '24 hours')
)
ON CONFLICT DO NOTHING;
END LOOP;
END LOOP;
-- Logs específicos adicionales
INSERT INTO audit_logging.user_activity_logs (
user_id, activity_type, activity_description,
ip_address, user_agent, session_id,
dimensions, created_at
)
SELECT
u.user_id,
'logout',
'User logged out',
'192.168.1.45',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0',
gen_random_uuid(),
'{
"session_duration_minutes": 45,
"activities_count": 12,
"logout_type": "manual"
}'::jsonb,
NOW() - INTERVAL '30 minutes'
FROM auth.users u
WHERE u.email = 'estudiante1@demo.glit.edu.mx'
ON CONFLICT DO NOTHING;
END $$;
-- =====================================================
-- SUMMARY
-- =====================================================
DO $$
DECLARE
metrics_count INT;
alerts_count INT;
activity_count INT;
BEGIN
SELECT COUNT(*) INTO metrics_count FROM audit_logging.performance_metrics;
SELECT COUNT(*) INTO alerts_count FROM audit_logging.system_alerts;
SELECT COUNT(*) INTO activity_count FROM audit_logging.user_activity_logs;
RAISE NOTICE 'System metrics seeds completed successfully';
RAISE NOTICE '- % performance_metrics inserted', metrics_count;
RAISE NOTICE '- % system_alerts inserted', alerts_count;
RAISE NOTICE '- % user_activity_logs inserted', activity_count;
END $$;

View File

@ -0,0 +1,622 @@
-- ============================================================================
-- SEED: Marie Curie Biography - Content Library
-- ============================================================================
-- Descripción: Biografía completa de Marie Curie dividida en 4 periodos
-- históricos para enriquecer el contenido educativo
-- Schema: content_management
-- Tablas: content_library
-- Prioridad: 2
-- Dependencias: Schema content_management debe existir
-- ============================================================================
SET search_path TO content_management, public;
-- ============================================================================
-- CONTENT LIBRARY: Biografía de Marie Curie (4 periodos)
-- ============================================================================
INSERT INTO content_management.marie_curie_content (
title,
subtitle,
content_type,
content_format,
content_body,
summary,
author,
tags,
subjects,
grade_levels,
language,
status,
is_published,
is_featured,
metadata,
created_at,
updated_at
) VALUES
-- ============================================================================
-- Periodo 1: Primeros Años en Polonia (1867-1891)
-- ============================================================================
(
'Marie Curie: Primeros Años en Polonia',
'La infancia de Maria Sklodowska en Varsovia',
'biography',
'html',
'<div class="biography-content">
<h1>Los Primeros Años de Maria Sklodowska</h1>
<section class="birth-family">
<h2>Nacimiento y Familia</h2>
<p>Maria Sklodowska nació el 7 de noviembre de 1867 en Varsovia, Polonia, en una época en que su país estaba bajo ocupación rusa. Era la menor de cinco hermanos en una familia de educadores comprometidos con el conocimiento y la resistencia cultural polaca.</p>
<p>Su padre, Wladyslaw Sklodowski, era profesor de matemáticas y física, mientras que su madre, Bronislawa Boguska, dirigía un prestigioso internado para niñas. Ambos padres inculcaron en sus hijos el amor por el aprendizaje y el orgullo por su identidad polaca, a pesar de las restricciones impuestas por el régimen zarista.</p>
<p>La familia Sklodowski vivía en condiciones modestas pero intelectualmente ricas. El apartamento familiar estaba lleno de libros, instrumentos científicos y conversaciones estimulantes sobre ciencia, literatura y política.</p>
</section>
<section class="childhood-education">
<h2>Infancia y Educación Temprana</h2>
<p>Desde muy pequeña, Maria mostró una capacidad intelectual excepcional. A los cuatro años ya sabía leer, y su memoria fotográfica asombraba a todos. Era especialmente hábil con los números y mostraba una curiosidad insaciable por comprender cómo funcionaban las cosas.</p>
<p>La educación formal en la Polonia ocupada era complicada. Las autoridades rusas habían prohibido la enseñanza en polaco y controlaban estrictamente el currículo. Sin embargo, Maria asistió a una escuela clandestina donde se enseñaba la historia y cultura polaca en secreto, arriesgando severos castigos si eran descubiertos.</p>
<p>A los 11 años, Maria enfrentó la primera gran tragedia de su vida: su madre murió de tuberculosis. Dos años antes, había perdido también a su hermana mayor, Zofia, víctima del tifus. Estas pérdidas marcaron profundamente a la joven Maria, quien encontró consuelo en los estudios y en el apoyo de su padre.</p>
</section>
<section class="gymnasium-years">
<h2>Años de Gymnasium y Graduación</h2>
<p>Maria asistió al Gymnasium número 3 de Varsovia, una escuela exclusiva para niñas. A pesar de tener que estudiar en ruso bajo la vigilancia de inspectores zaristas, destacó en todas las materias. Su dedicación era tal que a menudo estudiaba hasta altas horas de la noche, lo que preocupaba a su padre por su salud.</p>
<p>En 1883, a los 15 años, Maria se graduó con honores, obteniendo la medalla de oro por sus logros académicos excepcionales. Sin embargo, su alegría estaba mezclada con frustración: como mujer y como polaca, no podía acceder a la educación universitaria en su propio país. La Universidad de Varsovia no admitía mujeres.</p>
<p>Después de su graduación, Maria pasó un año en el campo con familiares, descansando de la intensidad de sus estudios. Fue un periodo de reflexión sobre su futuro incierto.</p>
</section>
<section class="underground-university">
<h2>La Universidad Volante</h2>
<p>Al regresar a Varsovia, Maria y su hermana Bronya se unieron a la "Universidad Volante" (Uniwersytet Latający), una institución clandestina que ofrecía educación superior en polaco a estudiantes que no podían acceder a universidades oficiales. Las clases se realizaban en secreto en casas particulares, cambiando constantemente de ubicación para evitar ser detectados por la policía rusa.</p>
<p>Allí, Maria estudió ciencias naturales, química, anatomía y sociología, alimentando su pasión por la investigación científica. Participaba también en reuniones de pensamiento progresista, donde se discutían ideas sobre justicia social y la emancipación de la mujer.</p>
<p>Durante este periodo, Maria trabajó como tutora privada para ayudar económicamente a su familia y ahorrar dinero para futuros estudios.</p>
</section>
<section class="the-pact">
<h2>El Pacto de las Hermanas</h2>
<p>Maria y su hermana Bronya hicieron un pacto que cambiaría sus vidas. Bronya soñaba con estudiar medicina en París, pero ninguna de las dos tenía suficiente dinero. Acordaron que Maria trabajaría como institutriz para financiar los estudios de Bronya en París. Una vez que Bronya se estableciera como médica, devolvería el favor financiando los estudios de Maria.</p>
<p>En 1885, a los 18 años, Maria aceptó un puesto como institutriz en la familia Zorawski en Szczuki, una zona rural a unos 100 kilómetros de Varsovia. Allí pasaría casi tres años, enseñando a los hijos de la familia mientras ahorraba cada zloty que podía.</p>
<p>Durante esos años en Szczuki, Maria vivió un romance con Kazimierz Zorawski, el hijo mayor de la familia. Sin embargo, cuando hablaron de matrimonio, los padres de Kazimierz se opusieron firmemente a que su hijo se casara con una simple institutriz sin fortuna. Esta experiencia dejó a Maria profundamente herida y reforzó su determinación de conseguir educación y respetabilidad por misma.</p>
</section>
<section class="preparation-paris">
<h2>Preparándose para París</h2>
<p>En 1889, Maria regresó a Varsovia y continuó trabajando como institutriz privada. Su hermana Bronya, ya establecida en París y casada con otro médico polaco, comenzó a enviar cartas instándola a que viniera a Francia para comenzar sus estudios universitarios.</p>
<p>Sin embargo, Maria dudaba. Se sentía responsable de su padre, que ahora estaba solo, y no estaba segura de tener el nivel académico necesario para la Universidad de la Sorbona. Pasó meses estudiando por su cuenta matemáticas, física y francés, preparándose para el gran salto.</p>
<p>Finalmente, en el otoño de 1891, con 24 años y sus ahorros en el bolsillo, Maria Sklodowska abordó un tren de cuarta clase que la llevaría en un viaje de tres días a París. Llevaba consigo una maleta de ropa, algunos libros y un sueño que parecía imposible: convertirse en científica.</p>
<p>No podía imaginar que décadas después sería la primera mujer en ganar un Premio Nobel, la primera persona en ganar dos Premios Nobel en diferentes ciencias, y una de las científicas más influyentes de todos los tiempos.</p>
</section>
<div class="educational-reflection">
<h3>Reflexiones para el Estudiante</h3>
<ul>
<li><strong>Perseverancia ante la adversidad:</strong> Maria enfrentó la pérdida familiar, discriminación de género, ocupación extranjera y limitaciones económicas, pero nunca abandonó su sueño.</li>
<li><strong>El poder de la educación:</strong> En un contexto donde la educación era vista como subversiva, Maria arriesgó su seguridad para aprender.</li>
<li><strong>Solidaridad familiar:</strong> El pacto con su hermana Bronya demuestra el poder del apoyo mutuo para superar obstáculos.</li>
<li><strong>Educación como resistencia:</strong> Aprender en polaco era un acto de resistencia cultural contra la opresión rusa.</li>
</ul>
</div>
</div>',
'Biografía de los primeros años de Marie Curie en Polonia bajo ocupación rusa, desde su nacimiento en 1867 hasta su partida a París en 1891. Incluye su infancia en Varsovia, educación clandestina, trabajo como institutriz, y la preparación para sus estudios universitarios en Francia.',
'GLIT Content Team',
ARRAY['Marie Curie', 'Biografía', 'Polonia', 'Infancia', 'Educación', 'Varsovia', 'Institutriz', 'Universidad Volante', 'Perseverancia']::text[],
ARRAY['Historia', 'Ciencias', 'Estudios Sociales', 'Biografías']::text[],
ARRAY['6','7','8']::text[],
'es',
'published',
true,
true,
'{
"period": "1867-1891",
"location": "Varsovia, Polonia",
"key_dates": {
"1867": "Nacimiento en Varsovia",
"1878": "Muerte de su madre",
"1883": "Graduación del Gymnasium con medalla de oro",
"1885": "Comienza trabajo como institutriz",
"1891": "Partida a París"
},
"key_themes": ["adversidad", "educación clandestina", "discriminación de género", "ocupación rusa", "familia"],
"word_count": 1285,
"reading_time_minutes": 6,
"educational_level": "secundaria",
"curriculum_connections": ["Historia de Europa siglo XIX", "Movimientos de resistencia", "Educación de la mujer"]
}'::jsonb,
NOW(),
NOW()
),
-- ============================================================================
-- Periodo 2: Estudios en la Sorbona (1891-1895)
-- ============================================================================
(
'Marie Curie: Estudios en la Sorbona',
'Los años universitarios de Marie en París',
'biography',
'html',
'<div class="biography-content">
<h1>Estudios en la Sorbona: Una Nueva Vida</h1>
<section class="arrival-paris">
<h2>Llegada a París</h2>
<p>En noviembre de 1891, Maria Sklodowska llegó a París y se matriculó en la Facultad de Ciencias de la Universidad de la Sorbona. Por fin, después de años de espera, podía estudiar libremente en una universidad de verdad. Para adaptarse a su nuevo entorno francés, comenzó a usar la versión francesa de su nombre: Marie.</p>
<p>Los primeros meses fueron difíciles. Su francés era básico, y descubrió que su preparación académica en Polonia, aunque sólida, tenía lagunas comparada con la de sus compañeros franceses. Además, era una de las pocas mujeres en la facultad de ciencias - de 1,825 estudiantes, solo 23 eran mujeres.</p>
<p>Al principio vivió con su hermana Bronya y su cuñado Kazimierz, pero el apartamento quedaba lejos de la Sorbona y el ambiente familiar la distraía de sus estudios. Después de unos meses, se mudó sola a una buhardilla en el Barrio Latino, cerca de la universidad.</p>
</section>
<section class="student-life">
<h2>Vida de Estudiante</h2>
<p>La vida de Marie en París era de extrema austeridad. Su buhardilla en el sexto piso era tan pequeña que apenas cabían una cama de hierro, una mesa, una silla y una estufa. En invierno hacía tanto frío que a veces el agua se congelaba en la palangana. Su dieta consistía principalmente en pan, chocolate y , ocasionalmente algunos huevos o fruta.</p>
<p>A menudo, Marie se desmayaba en clase por el hambre y el agotamiento. Su hermana Bronya, alarmada, intentaba convencerla de comer más y cuidarse mejor, pero Marie estaba completamente absorta en sus estudios. Cada franco que ahorraba en comida o calefacción era un franco que prolongaba su tiempo en la universidad.</p>
<p>Sin embargo, Marie era feliz. Por primera vez en su vida, podía dedicarse completamente al estudio de la física y las matemáticas. Pasaba sus días en conferencias, en la biblioteca y en laboratorios. Por las noches, estudiaba bajo la luz de una lámpara de queroseno hasta que sus ojos ya no podían más.</p>
</section>
<section class="academic-achievements">
<h2>Logros Académicos</h2>
<p>A pesar de las dificultades iniciales con el idioma y las lagunas en su preparación, Marie destacó rápidamente. Su dedicación obsesiva a los estudios dio frutos extraordinarios.</p>
<p>En julio de 1893, apenas dos años después de llegar a París, Marie se graduó en física con el primer lugar de su promoción (licence ès sciences physiques). Fue la primera mujer en Francia en obtener este título con tal distinción.</p>
<p>Este logro le valió una beca de 600 francos de la Sociedad para el Fomento de la Industria Nacional, que le permitió continuar sus estudios. Decidió obtener un segundo grado, esta vez en matemáticas.</p>
<p>En 1894, obtuvo su licence ès sciences mathématiques, graduándose en segundo lugar de su clase. Era extraordinario: en solo tres años, había conseguido dos títulos universitarios en disciplinas altamente exigentes, siendo mujer y extranjera en un ambiente predominantemente masculino y francés.</p>
</section>
<section class="meeting-pierre">
<h2>El Encuentro con Pierre Curie</h2>
<p>En la primavera de 1894, Marie buscaba espacio de laboratorio para realizar investigaciones sobre las propiedades magnéticas de diferentes tipos de acero, un proyecto encargado por la Sociedad para el Fomento de la Industria Nacional. Un profesor polaco, sabiendo de sus dificultades para encontrar espacio adecuado, le sugirió que conociera a Pierre Curie, un joven científico que trabajaba en la École de Physique et Chimie de París.</p>
<p>Pierre Curie, a sus 35 años, ya era un físico respetado. Junto con su hermano Jacques, había descubierto la piezoelectricidad y había realizado importantes investigaciones sobre cristalografía y magnetismo. Era también un hombre tímido, idealista, completamente dedicado a la ciencia.</p>
<p>Cuando Marie y Pierre se conocieron, la conexión fue inmediata. Encontraron en el otro no solo atracción personal, sino una afinidad intelectual profunda. Compartían una pasión por la ciencia, una disposición a la investigación rigurosa, y un cierto ascetismo en su dedicación al trabajo científico.</p>
<p>Pierre quedó fascinado por esta joven polaca de ojos grises intensos que hablaba de física con una comprensión que igualaba la suya. Marie, por su parte, encontró en Pierre a alguien que no solo respetaba su inteligencia, sino que la valoraba profundamente.</p>
</section>
<section class="courtship-decision">
<h2>Noviazgo y una Decisión Difícil</h2>
<p>Durante el verano de 1894, Pierre cortejó a Marie con persistencia gentil. Le escribía cartas expresando no solo su amor, sino su deseo de compartir con ella una vida dedicada a la ciencia: "Sería algo hermoso pasar la vida uno al lado del otro, hipnotizados por nuestros sueños: tu sueño patriótico, nuestro sueño humanitario y nuestro sueño científico."</p>
<p>Sin embargo, Marie estaba dividida. Había planeado regresar a Polonia al terminar sus estudios, cumplir con su deber patriótico y estar cerca de su padre. La idea de quedarse permanentemente en Francia la llenaba de culpa.</p>
<p>Ese verano regresó a Varsovia, esperando que la distancia clarificara sus sentimientos. Intentó encontrar un puesto académico en Polonia, pero se enfrentó nuevamente con la dura realidad: como mujer, no era bienvenida en las instituciones científicas polacas. La Universidad de Cracovia rechazó su solicitud simplemente por su género.</p>
<p>Mientras tanto, Pierre le escribía constantemente. En una carta memorable, le escribió: "Si no puedes amarte a ti misma, ¿podrías al menos amarme un poco?" Y añadió una propuesta extraordinaria para la época: si ella no podía quedarse en Francia, él renunciaría a su carrera y se mudaría a Polonia con ella.</p>
<p>Esta declaración convenció a Marie. Pocos hombres de esa época habrían considerado sacrificar su carrera por seguir a una mujer. La propuesta de Pierre demostraba que él la veía verdaderamente como una igual.</p>
</section>
<section class="marriage">
<h2>Matrimonio</h2>
<p>Marie regresó a París en octubre de 1894. El 26 de julio de 1895, Marie Sklodowska y Pierre Curie se casaron en una ceremonia civil simple en Sceaux, en las afueras de París. No hubo iglesia, no hubo anillos de oro, no hubo vestido blanco.</p>
<p>Marie llevó un traje azul oscuro práctico que podría usar después en el laboratorio. El padre de Marie viajó desde Polonia para la ocasión, y la familia de Pierre también estuvo presente. Después de la ceremonia, los recién casados se montaron en bicicletas nuevas - regalo de un primo - y se fueron de luna de miel en bicicleta por la campiña francesa.</p>
<p>No era un matrimonio convencional. Ambos acordaron que su relación se basaría en la igualdad intelectual y en el compromiso compartido con la investigación científica. Marie no renunciaría a sus ambiciones científicas, y Pierre no esperaba que lo hiciera.</p>
<p>Se mudaron a un pequeño apartamento de tres habitaciones en la Rue de la Glacière. Era modesto pero cómodo - una mejora considerable sobre la buhardilla de Marie. Organizaron su hogar con sencillez espartana: muebles mínimos, sin cortinas en las ventanas, sin alfombras. El tiempo que otros dedicaban a decorar o a entretenimientos sociales, ellos lo dedicarían a la ciencia.</p>
</section>
<section class="partnership-begins">
<h2>El Inicio de una Colaboración Histórica</h2>
<p>Después del matrimonio, Marie comenzó a trabajar junto a Pierre en su laboratorio. Mientras Pierre continuaba sus investigaciones sobre cristales, Marie empezó a buscar un tema para su doctorado - un paso crucial que la convertiría en una de las primeras mujeres en Francia en obtener un doctorado en física.</p>
<p>Lo que Marie no sabía aún era que estaba a punto de embarcarse en una investigación que no solo cumpliría su sueño de convertirse en doctora, sino que revolucionaría la ciencia y cambiaría nuestra comprensión fundamental de la materia.</p>
<p>Los años de dificultad, sacrificio y preparación estaban a punto de dar frutos extraordinarios.</p>
</section>
<div class="educational-reflection">
<h3>Reflexiones para el Estudiante</h3>
<ul>
<li><strong>Sacrificio por los sueños:</strong> Marie vivió en pobreza extrema para poder estudiar, demostrando que los sueños grandes requieren sacrificios grandes.</li>
<li><strong>Superación de barreras:</strong> Como mujer extranjera en un ambiente masculino francés, Marie enfrentó múltiples formas de discriminación pero perseveró.</li>
<li><strong>Asociaciones igualitarias:</strong> El matrimonio de Marie y Pierre fue revolucionario para su época, basado en respeto mutuo e igualdad intelectual.</li>
<li><strong>Excelencia académica:</strong> A pesar de comenzar con desventajas, Marie se graduó primera en física y segunda en matemáticas.</li>
</ul>
</div>
</div>',
'Biografía de Marie Curie durante sus estudios universitarios en la Sorbona (1891-1895), incluyendo su vida como estudiante en París, sus logros académicos, el encuentro con Pierre Curie, y su matrimonio basado en igualdad intelectual y compromiso científico compartido.',
'GLIT Content Team',
ARRAY['Marie Curie', 'Pierre Curie', 'Sorbona', 'París', 'Universidad', 'Física', 'Matemáticas', 'Matrimonio', 'Igualdad']::text[],
ARRAY['Historia', 'Ciencias', 'Estudios de Género', 'Biografías']::text[],
ARRAY['7','8','9']::text[],
'es',
'published',
true,
true,
'{
"period": "1891-1895",
"location": "París, Francia",
"key_dates": {
"1891": "Llegada a París y matrícula en la Sorbona",
"1893": "Graduación en Física (1er lugar)",
"1894": "Graduación en Matemáticas (2do lugar), Conoce a Pierre Curie",
"1895": "Matrimonio con Pierre Curie"
},
"key_themes": ["educación superior", "superación", "discriminación de género", "amor y ciencia", "asociación igualitaria"],
"word_count": 1620,
"reading_time_minutes": 8,
"educational_level": "secundaria-preparatoria",
"curriculum_connections": ["Historia de la ciencia", "Estudios de género", "Educación superior", "Francia del siglo XIX"]
}'::jsonb,
NOW(),
NOW()
),
-- ============================================================================
-- Periodo 3: Los Grandes Descubrimientos (1895-1911)
-- ============================================================================
(
'Marie Curie: Los Grandes Descubrimientos',
'Radio, Polonio y los Premios Nobel',
'biography',
'html',
'<div class="biography-content">
<h1>Los Descubrimientos que Revolucionaron la Ciencia</h1>
<section class="search-topic">
<h2>La Búsqueda de un Tema de Doctorado</h2>
<p>En 1896, Marie estaba buscando un tema para su tesis doctoral. Necesitaba algo original, un fenómeno inexplorado que le permitiera hacer una contribución significativa a la ciencia. Ese mismo año, el físico francés Henri Becquerel había hecho un descubrimiento intrigante: el uranio emitía espontáneamente rayos misteriosos que podían atravesar papel negro y impresionar placas fotográficas.</p>
<p>Este fenómeno era completamente desconcertante. Los rayos X, descubiertos por Röntgen el año anterior, requerían electricidad y equipamiento especial. Pero el uranio emitía estos rayos por solo, sin energía externa. Becquerel llamó al fenómeno "rayos uraniosos" pero no lo investigó más allá.</p>
<p>Marie decidió que este sería su tema. Quería determinar si otros elementos además del uranio emitían estos rayos misteriosos, y entender la naturaleza fundamental del fenómeno. Pierre, reconociendo la importancia potencial del trabajo, decidió dejar sus propias investigaciones sobre cristales para unirse a ella.</p>
</section>
<section class="research-conditions">
<h2>Condiciones de Investigación</h2>
<p>El lugar de trabajo que Pierre consiguió para su investigación conjunta era lamentable. Era un cobertizo acristalado en el patio de la École de Physique et Chimie, anteriormente usado como sala de disección. En verano era sofocante; en invierno, helado. El techo tenía goteras. No había ventilación adecuada, y el lugar estaba lleno de corrientes de aire que hacían imposible mantener temperaturas constantes para experimentos delicados.</p>
<p>Allí, Marie y Pierre instalaron su laboratorio improvisado. Conseguían equipo científico de segunda mano, lo reparaban ellos mismos, y construían instrumentos a partir de materiales baratos. Usaron el electrómetro piezoeléctrico de cuarzo que Pierre y su hermano habían inventado años antes - era perfecto para medir las pequeñas corrientes eléctricas que producían los rayos misteriosos.</p>
</section>
<section class="radioactivity">
<h2>El Descubrimiento de la Radioactividad</h2>
<p>Marie comenzó su investigación metódicamente. Probó todos los elementos conocidos para ver si alguno además del uranio emitía estos rayos. Desarrolló un método preciso para cuantificar la intensidad de la radiación midiendo la conductividad eléctrica del aire alrededor de las muestras.</p>
<p>Descubrió que el torio también emitía rayos. Pero más importante, hizo una observación crucial: la intensidad de la radiación dependía solamente de la cantidad de uranio o torio presente, no de su forma química ni de condiciones externas como temperatura o luz. Esto significaba que la radiación no era resultado de reacciones químicas, sino una propiedad del átomo mismo.</p>
<p>Este fue un descubrimiento revolucionario. Sugería que el átomo, que se creía indivisible e inmutable, tenía una estructura interna y podía cambiar. Marie necesitaba un término para este fenómeno nuevo. Tomando prestado del latín "radius" (rayo), acuñó el término "radioactividad" (radioactivité).</p>
<p>En abril de 1898, Marie presentó sus hallazgos preliminares a la Academia de Ciencias de París. Fue su primera publicación científica importante.</p>
</section>
<section class="polonium-radium">
<h2>Descubrimiento del Polonio y el Radio</h2>
<p>Marie hizo otra observación crucial: algunos minerales de uranio eran más radioactivos que el uranio puro. Esto solo podía significar una cosa: esos minerales contenían otro elemento, desconocido, más radioactivo que el uranio.</p>
<p>Los Curie comenzaron la ardua tarea de separar este elemento misterioso de toneladas de pechblenda, un mineral de uranio. Era un trabajo agotador, químicamente complicado y físicamente extenuante. Marie procesaba hasta 20 kilogramos de material al día, removiendo calderos hirvientes con una barra de hierro casi tan grande como ella.</p>
<p>En julio de 1898, aislaron una fracción altamente radioactiva. Contenía un elemento nuevo. Marie eligió llamarlo "polonio" en honor a su Polonia natal, un acto de patriotismo que también era una protesta política - Polonia no existía como país independiente en los mapas de 1898.</p>
<p>Pero había más. En diciembre de 1898, los Curie anunciaron el descubrimiento de un segundo elemento nuevo, incluso más radioactivo que el polonio. Lo llamaron "radio" por su radiación intensa. El radio era extraordinario: brillaba en la oscuridad con una luz azul-verde fantasmal y generaba calor constantemente sin fuente externa de energía.</p>
</section>
<section class="isolation-radium">
<h2>El Aislamiento del Radio</h2>
<p>Anunciar el descubrimiento era solo el primer paso. Para probar que el polonio y el radio eran elementos verdaderos, Marie necesitaba aislarlos en forma pura y determinar su peso atómico.</p>
<p>Esto requirió cuatro años más de trabajo brutal. La Academia de Ciencias de Austria les donó una tonelada de residuos de pechblenda de sus minas de Joachimsthal (ahora Jáchymov, República Checa). Los residuos se amontonaban en el patio junto al cobertizo-laboratorio.</p>
<p>El trabajo era inmensamente tedioso. Marie hervía enormes calderos de mineral, filtraba precipitados, cristalizaba y re-cristalizaba sales una y otra vez, cada vez obteniendo material un poco más puro. El proceso se repetía docenas de veces. Los vapores químicos corrosivos llenaban el cobertizo. En verano, el calor de los hornos era insoportable. En invierno, sus manos se agrietaban por el frío y los productos químicos.</p>
<p>Durante este período, Marie también dio a luz a dos hijas: Irène en 1897 y Eve en 1904. Continuó trabajando incluso durante sus embarazos, solo tomando breves descansos para los partos.</p>
<p>Finalmente, en 1902, después de procesar literalmente toneladas de pechblenda, Marie logró aislar un decigramo (0.1 gramos) de cloruro de radio puro. Determinó su peso atómico: 226. El radio era oficial e irrefutablemente un elemento nuevo.</p>
</section>
<section class="doctorate-nobel">
<h2>Doctorado y el Primer Nobel</h2>
<p>En 1903, Marie presentó su tesis doctoral: "Investigaciones sobre Sustancias Radioactivas". El jurado, que incluía a los físicos más eminentes de Francia, la declaró el mejor trabajo de investigación jamás presentado en una tesis doctoral.</p>
<p>Marie Curie se convirtió en la primera mujer en Francia en obtener un doctorado en física. Tenía 36 años.</p>
<p>Ese mismo año, el mundo científico reconoció la trascendencia de su trabajo. Marie y Pierre Curie, junto con Henri Becquerel, recibieron el Premio Nobel de Física "en reconocimiento de los extraordinarios servicios que han prestado mediante sus investigaciones conjuntas sobre los fenómenos de radiación descubiertos por el profesor Henri Becquerel".</p>
<p>Hubo controversia: inicialmente, el comité del Nobel solo iba a premiar a Pierre y a Becquerel. Fue Pierre quien insistió en que Marie fuera incluida, amenazando con rechazar el premio si no lo hacían. Marie se convirtió en la primera mujer en ganar un Premio Nobel.</p>
<p>Los Curie no viajaron a Estocolmo para la ceremonia - estaban demasiado ocupados con su trabajo y Pierre estaba enfermo. Recogieron su premio y dieron su conferencia Nobel más de un año después.</p>
</section>
<section class="tragedy-pierre">
<h2>Tragedia: La Muerte de Pierre</h2>
<p>El 19 de abril de 1906, sucedió lo impensable. Pierre, caminando bajo una lluvia intensa en París, fue atropellado por un carruaje tirado por caballos. Murió instantáneamente.</p>
<p>Marie quedó devastada. En su diario personal, escribió: "Entraron y me dijeron: 'Pierre está muerto'. ¿Puedo creer lo que he escrito? ... Me siento como si no pudiera vivir más." Durante semanas, apenas comió o durmió, sumida en una depresión profunda.</p>
<p>Pero Marie tenía dos hijas que criar y un legado científico que preservar. Tomó la extraordinaria decisión de continuar el trabajo que ella y Pierre habían comenzado juntos. La Universidad de la Sorbona le ofreció la cátedra de Pierre - un honor sin precedentes. Se convirtió en la primera mujer profesora en la Universidad de París en sus 650 años de historia.</p>
<p>Su conferencia inaugural, el 5 de noviembre de 1906, atrajo multitudes. El aula estaba repleta de curiosos que querían ver a la viuda del famoso Pierre Curie. Marie llegó, vestida de negro, y comenzó su conferencia exactamente donde Pierre había dejado su última clase, como si continuara una conversación interrumpida. Su voz temblaba, pero no lloró. Habló de física, no de Pierre. Fue un momento de dignidad extraordinaria.</p>
</section>
<section class="second-nobel">
<h2>El Segundo Premio Nobel</h2>
<p>Marie continuó investigando con determinación renovada. Se enfocó en aislar radio metálico puro (hasta entonces solo había obtenido sales de radio) y en determinar con precisión las propiedades del radio y otros elementos radioactivos.</p>
<p>En 1910, logró aislar radio metálico puro por primera vez en colaboración con André Debierne. Fue un triunfo químico extraordinario.</p>
<p>En 1911, la Academia Sueca de Ciencias le otorgó a Marie su segundo Premio Nobel, esta vez en Química, "por el descubrimiento de los elementos radio y polonio, por el aislamiento del radio y el estudio de la naturaleza y compuestos de este elemento notable".</p>
<p>Marie Curie se convirtió en la primera persona en la historia en ganar dos Premios Nobel. Además, eran en dos ciencias diferentes: Física y Química. Hasta el día de hoy, solo una persona más (Linus Pauling) ha igualado esta hazaña.</p>
<p>Sin embargo, el año 1911 fue agridulce. Mientras recibía el Nobel en Estocolmo, en París estallaba un escándalo público sobre su supuesto romance con el físico Paul Langevin, un hombre casado. La prensa francesa la atacó brutalmente con titulares xenófobos y sexistas. Algunos llamaban a que devolviera su Nobel y abandonara Francia.</p>
<p>Marie consideró seriamente dejar Francia y regresar a Polonia. Fue Albert Einstein, entre otros, quien la convenció de quedarse, escribiéndole: "Si la chusma continúa ocupándose de usted, simplemente deje de leer esa basura. Déjesela a las víboras para quienes fue fabricada."</p>
</section>
<div class="educational-reflection">
<h3>Reflexiones para el Estudiante</h3>
<ul>
<li><strong>Método científico riguroso:</strong> Marie midió sistemáticamente todos los elementos conocidos, estableciendo el rigor metodológico que caracteriza la ciencia moderna.</li>
<li><strong>Persistencia monumental:</strong> Procesar toneladas de pechblenda durante cuatro años para obtener 0.1 gramos de radio puro demuestra dedicación extraordinaria.</li>
<li><strong>Revolución conceptual:</strong> El descubrimiento de la radioactividad cambió nuestra comprensión del átomo y abrió la era de la física moderna.</li>
<li><strong>Resiliencia ante la tragedia:</strong> Después de la muerte de Pierre, Marie continuó su trabajo, convirtiéndose en la primera mujer profesora de la Sorbona.</li>
<li><strong>Doble estándar de género:</strong> A pesar de ganar dos Premios Nobel, Marie enfrentó ataques misóginos que ningún científico hombre habría sufrido.</li>
</ul>
</div>
</div>',
'Biografía de Marie Curie durante sus años de investigación y descubrimientos (1895-1911). Incluye el descubrimiento de la radioactividad, el aislamiento del polonio y el radio, su primer Premio Nobel de Física (1903), la trágica muerte de Pierre Curie (1906), su nombramiento como primera mujer profesora de la Sorbona, y su segundo Premio Nobel de Química (1911).',
'GLIT Content Team',
ARRAY['Marie Curie', 'Pierre Curie', 'Radio', 'Polonio', 'Radioactividad', 'Premio Nobel', 'Descubrimientos', 'Física', 'Química']::text[],
ARRAY['Ciencias', 'Química', 'Física', 'Historia de la Ciencia', 'Biografías']::text[],
ARRAY['7','8','9','10']::text[],
'es',
'published',
true,
true,
'{
"period": "1895-1911",
"key_dates": {
"1896": "Inicio investigación radioactividad",
"1898": "Descubrimiento Polonio (julio) y Radio (diciembre)",
"1902": "Aislamiento de 0.1g de radio puro",
"1903": "Doctorado y primer Nobel (Física)",
"1906": "Muerte de Pierre, Primera mujer profesora Sorbona",
"1910": "Aislamiento de radio metálico puro",
"1911": "Segundo Nobel (Química)"
},
"key_discoveries": [
"Término radioactividad",
"Elemento Polonio (84)",
"Elemento Radio (88)",
"Radioactividad como propiedad atómica"
],
"key_themes": ["descubrimiento científico", "método riguroso", "colaboración científica", "resiliencia", "discriminación de género"],
"word_count": 2180,
"reading_time_minutes": 11,
"educational_level": "secundaria-preparatoria-universidad",
"curriculum_connections": ["Química nuclear", "Historia de la física", "Método científico", "Estructura atómica"]
}'::jsonb,
NOW(),
NOW()
),
-- ============================================================================
-- Periodo 4: Legado e Impacto (1911-1934)
-- ============================================================================
(
'Marie Curie: Legado e Impacto',
'Los últimos años y el impacto en la ciencia',
'biography',
'html',
'<div class="biography-content">
<h1>Legado: Los Últimos Años y el Impacto Duradero</h1>
<section class="radium-institute">
<h2>El Instituto del Radio</h2>
<p>Después de ganar su segundo Nobel en 1911, Marie se convirtió en una figura científica de importancia internacional. Utilizó su influencia para fundar el Instituto del Radio (Institut du Radium) en París, una iniciativa conjunta entre la Universidad de París y el Instituto Pasteur.</p>
<p>El Instituto, inaugurado en 1914, estaba diseñado específicamente para investigar la radioactividad y sus aplicaciones, particularmente en el tratamiento del cáncer. Marie soñaba con un centro de excelencia donde jóvenes científicos pudieran realizar investigaciones de vanguardia en condiciones adecuadas - muy diferente del cobertizo donde ella y Pierre habían trabajado.</p>
<p>El Instituto del Radio (ahora Instituto Curie) se convertiría en uno de los centros de investigación oncológica más importantes del mundo, y sigue funcionando hasta hoy, más de 110 años después.</p>
</section>
<section class="world-war-one">
<h2>La Primera Guerra Mundial: Los "Petits Curies"</h2>
<p>El Instituto del Radio apenas había abierto cuando estalló la Primera Guerra Mundial en agosto de 1914. En lugar de continuar su investigación fundamental, Marie vio una necesidad urgente y actuó con decisión.</p>
<p>Los rayos X podían localizar balas y fragmentos de metralla en los cuerpos de los soldados heridos, permitiendo cirugías más precisas y salvando vidas. Pero la mayoría de los hospitales de campaña no tenían equipamiento de rayos X. Marie decidió llevar los rayos X al frente de batalla.</p>
<p>Con su propio dinero y fondos que recaudó, equipó vehículos con máquinas de rayos X portátiles, generadores eléctricos y equipo de fotografía. Estos "petits Curies" (pequeños Curies) eran las primeras unidades móviles de radiología del mundo.</p>
<p>Marie, a sus 47 años, aprendió a conducir y a reparar automóviles. Durante la guerra, ella misma condujo estas unidades móviles a hospitales de campaña cerca del frente, a menudo bajo fuego de artillería. Su hija Irène, entonces de apenas 18 años, la acompañaba frecuentemente, operando el equipo de rayos X.</p>
<p>Marie también estableció 200 puestos de radiología fijos en hospitales y entrenó a 150 operadores de rayos X. Se estima que sus esfuerzos permitieron realizar radiografías a más de un millón de soldados heridos durante la guerra.</p>
<p>Marie nunca buscó reconocimiento por este trabajo. Lo veía como su deber patriótico - no solo hacia Francia, sino hacia la humanidad. Cuando terminó la guerra en 1918, simplemente regresó a su laboratorio.</p>
</section>
<section class="post-war">
<h2>Años de Posguerra</h2>
<p>Después de la guerra, Marie se dedicó a construir el Instituto del Radio como un centro de excelencia científica. Reclutó jóvenes investigadores talentosos, incluyendo muchas mujeres - algo muy inusual para la época. Bajo su dirección, el Instituto produjo investigación de primera calidad sobre radioactividad, física nuclear y aplicaciones médicas.</p>
<p>Marie también se involucró en la cooperación científica internacional. Fue nombrada miembro de la Comisión Internacional de Cooperación Intelectual de la Sociedad de Naciones (precursora de la UNESCO), trabajando junto a figuras como Albert Einstein para promover la colaboración científica entre naciones.</p>
<p>En 1921, emprendió un agotador viaje a Estados Unidos. La periodista estadounidense Marie Mattingly Meloney había organizado una campaña de recaudación para comprarle a Marie un gramo de radio puro para su investigación - el radio era extremadamente caro, y Marie no tenía fondos suficientes. El viaje fue un triunfo: Marie fue recibida por el presidente Warren Harding en la Casa Blanca y aclamada en universidades de todo el país.</p>
<p>Sin embargo, el viaje también fue extenuante. Marie, siempre privada y tímida, encontraba agotadoras las multitudes y la atención pública. Pero sonreía y daba conferencias, porque necesitaba ese radio para continuar su investigación.</p>
</section>
<section class="health-decline">
<h2>Declive de Salud</h2>
<p>Durante años, Marie había trabajado con materiales intensamente radioactivos sin ninguna protección. Ella y Pierre habían manipulado radio con las manos desnudas. Marie llevaba tubos de ensayo con radio en los bolsillos de su delantal. A veces, admiraban el brillo fosforescente del radio en la oscuridad de su laboratorio, sin comprender el peligro.</p>
<p>Los efectos de décadas de exposición comenzaron a manifestarse. Marie desarrolló cataratas en ambos ojos, requiriendo múltiples cirugías. Sus manos estaban quemadas y agrietadas permanentemente. Sufría de fatiga constante y mareos.</p>
<p>Más grave, sus recuentos sanguíneos mostraban anormalidades preocupantes. Los médicos diagnosticaron anemia perniciosa aplásica - su médula ósea había sido dañada por la radiación y ya no producía suficientes células sanguíneas.</p>
<p>Marie minimizaba sus síntomas y continuaba trabajando. Incluso cuando estaba tan débil que apenas podía caminar, insistía en ir al laboratorio. La ciencia era su vida; no podía imaginarse deteniéndose.</p>
</section>
<section class="final-days">
<h2>Los Últimos Días</h2>
<p>En la primavera de 1934, la salud de Marie se deterioró rápidamente. Tenía fiebre persistente y estaba extremadamente débil. Su hija Eve la llevó a un sanatorio en Sancellemoz, en los Alpes franceses, esperando que el aire de montaña la ayudara.</p>
<p>Pero era demasiado tarde. El 4 de julio de 1934, Marie Curie murió a los 66 años. Su muerte fue causada por anemia aplásica, consecuencia directa de su exposición prolongada a la radiación.</p>
<p>Fue enterrada junto a Pierre en el cementerio de Sceaux. El gobierno francés ofreció un funeral de estado, pero la familia Curie lo rechazó, prefiriendo una ceremonia simple y privada, como Marie habría querido.</p>
<p>En 1995, sesenta años después de su muerte, los restos de Marie y Pierre Curie fueron trasladados al Panteón de París, el mausoleo donde Francia honra a sus ciudadanos más ilustres. Marie se convirtió en la primera mujer honrada en el Panteón por sus propios méritos (otras mujeres habían sido enterradas allí, pero como esposas de hombres famosos).</p>
<p>Incluso su ataúd era radioactivo - tuvo que ser recubierto de plomo. Sus documentos de laboratorio de la década de 1890 todavía están contaminados con radiación y se conservan en cajas forradas de plomo. Cualquiera que desee consultarlos debe usar protección y firmar un formulario de responsabilidad.</p>
</section>
<section class="scientific-legacy">
<h2>Legado Científico</h2>
<p>El impacto científico de Marie Curie es incalculable:</p>
<ul>
<li><strong>Fundó una nueva ciencia:</strong> Su trabajo estableció las bases de la física nuclear y la química nuclear como disciplinas científicas.</li>
<li><strong>Cambió la comprensión del átomo:</strong> Al demostrar que la radioactividad es una propiedad atómica, contribuyó a desmantelar la idea del átomo como indivisible e inmutable, abriendo el camino para la física cuántica.</li>
<li><strong>Aplicaciones médicas:</strong> El radio fue la primera forma de radioterapia para el cáncer. Aunque hoy usamos isótopos más seguros, Marie inició el campo de la oncología por radiación que ha salvado millones de vidas.</li>
<li><strong>Dinastía científica:</strong> Su hija Irène Joliot-Curie ganó el Premio Nobel de Química en 1935 (un año después de la muerte de Marie) por el descubrimiento de la radioactividad artificial. El yerno de Marie, Frédéric Joliot, compartió ese Nobel. Su nieto Pierre Joliot se convirtió en un bioquímico reconocido.</li>
<li><strong>Nomenclatura científica:</strong> El elemento curio (Cm, número atómico 96) fue nombrado en honor a Marie y Pierre Curie. Una unidad de radioactividad, el curie (Ci), también lleva su nombre.</li>
</ul>
</section>
<section class="social-legacy">
<h2>Legado Social y Cultural</h2>
<p>Más allá de la ciencia, Marie Curie se convirtió en un símbolo y una inspiración:</p>
<ul>
<li><strong>Pionera para las mujeres:</strong> Como la primera mujer profesora en la Sorbona, la primera mujer en ganar un Nobel, y la primera persona en ganar dos Nobeles, Marie rompió barreras de género que parecían infranqueables.</li>
<li><strong>Modelo de determinación:</strong> Su historia de superación - desde institutriz pobre en Polonia hasta científica laureada con dos Nobel - inspira a generaciones a perseguir sus sueños a pesar de los obstáculos.</li>
<li><strong>Integridad científica:</strong> Marie y Pierre rechazaron patentar el proceso de aislamiento del radio, a pesar de que podría haberlos hecho millonarios. Creían que el conocimiento científico debía compartirse libremente para el beneficio de la humanidad.</li>
<li><strong>Ciencia como vocación:</strong> Marie vivió modestamente toda su vida, invirtiendo cualquier dinero de premios en su investigación. Para ella, la ciencia no era un medio para la riqueza o la fama, sino una búsqueda apasionada de comprensión.</li>
</ul>
</section>
<section class="modern-impact">
<h2>Impacto en el Mundo Moderno</h2>
<p>El trabajo de Marie Curie resuena en el siglo XXI:</p>
<ul>
<li><strong>Medicina nuclear:</strong> Los escáneres PET, la radioterapia moderna, y el diagnóstico por isótopos radiactivos son descendientes directos de la investigación de Marie.</li>
<li><strong>Energía nuclear:</strong> Aunque Marie nunca trabajó con fisión nuclear, su investigación sobre radioactividad fue esencial para comprender los procesos nucleares que eventualmente condujeron a la energía nuclear.</li>
<li><strong>Datación radiométrica:</strong> Métodos como el carbono-14 para datar artefactos arqueológicos se basan en principios de radioactividad que Marie ayudó a establecer.</li>
<li><strong>Seguridad radiológica:</strong> Irónicamente, la muerte de Marie por exposición a radiación ayudó a establecer la necesidad de protocolos de seguridad radiológica que ahora protegen a científicos y trabajadores médicos.</li>
</ul>
</section>
<section class="lessons-today">
<h2>Lecciones para Hoy</h2>
<p>¿Qué podemos aprender de Marie Curie en el siglo XXI?</p>
<ul>
<li><strong>La perseverancia transforma obstáculos:</strong> Marie enfrentó discriminación de género, xenofobia, pobreza, y tragedia personal, pero nunca se rindió.</li>
<li><strong>La educación es poder:</strong> Marie arriesgó todo - confort, seguridad, convención social - para obtener educación. Esa educación transformó no solo su vida, sino el mundo.</li>
<li><strong>La curiosidad impulsa el progreso:</strong> Marie investigó la radioactividad no porque fuera práctica o rentable, sino porque era misterioso. La ciencia básica, impulsada por curiosidad pura, eventualmente produce aplicaciones transformadoras.</li>
<li><strong>La colaboración multiplica el impacto:</strong> El trabajo de Marie con Pierre demuestra que las asociaciones basadas en respeto mutuo e igualdad pueden lograr más que cualquier individuo solo.</li>
<li><strong>La ciencia tiene responsabilidad social:</strong> El rechazo de Marie a patentar sus descubrimientos y su trabajo humanitario durante la Primera Guerra Mundial muestran que los científicos tienen obligaciones más allá del laboratorio.</li>
</ul>
</section>
<section class="final-reflection">
<h2>Reflexión Final</h2>
<p>Marie Curie vivió una vida de contrastes extraordinarios. Fue una mujer que rompió las normas de género más rígidas de su época, pero que era profundamente reservada y evitaba el centro de atención. Ganó premios internacionales y honores, pero vivió modestamente y reinvertía todo en su ciencia. Hizo descubrimientos que revolucionaron nuestra comprensión del universo, pero pagó el precio último: su propia vida.</p>
<p>Su historia nos recuerda que el progreso humano - en ciencia, en derechos, en comprensión - lo construyen personas reales con dudas, miedos, y limitaciones, que eligen perseverar de todas formas. Marie Curie no era sobrehumana. Era extraordinariamente humana: vulnerable, terca, apasionada, imperfecta, brillante.</p>
<p>Y precisamente por eso, su ejemplo es tan poderoso. Si Marie Sklodowska, una niña de la Polonia ocupada, pudo cambiar el mundo, quizás nosotros también podemos.</p>
</section>
<div class="educational-reflection">
<h3>Reflexiones para el Estudiante</h3>
<ul>
<li><strong>Ciencia y humanidad:</strong> Durante la Primera Guerra Mundial, Marie usó su ciencia para salvar vidas, demostrando que el conocimiento tiene propósito humano.</li>
<li><strong>Precio del pionerismo:</strong> Marie murió por exposición a radiación porque trabajó antes de que se conocieran los peligros - los pioneros a menudo pagan un precio alto.</li>
<li><strong>Legado intergeneracional:</strong> La familia Curie produjo cinco ganadores del Nobel, demostrando cómo el ambiente intelectual familiar puede cultivar excelencia.</li>
<li><strong>Reconocimiento póstumo:</strong> El traslado de Marie al Panteón en 1995 muestra cómo el reconocimiento histórico puede tardar, pero eventualmente se rinde justicia.</li>
<li><strong>Ciencia abierta:</strong> El rechazo de Marie a patentar sus descubrimientos estableció un precedente de ciencia como bien público.</li>
</ul>
</div>
</div>',
'Biografía de los últimos años de Marie Curie (1911-1934) y su legado duradero. Incluye la fundación del Instituto del Radio, su trabajo humanitario durante la Primera Guerra Mundial con las unidades móviles de rayos X, su declive de salud por exposición a radiación, su muerte en 1934, y el impacto profundo de su trabajo en la ciencia moderna, medicina, y como inspiración para generaciones de científicas.',
'GLIT Content Team',
ARRAY['Marie Curie', 'Legado', 'Instituto del Radio', 'Primera Guerra Mundial', 'Radiología', 'Impacto Científico', 'Panteón', 'Inspiración']::text[],
ARRAY['Historia', 'Ciencias', 'Medicina', 'Impacto Social', 'Biografías']::text[],
ARRAY['8','9','10','11']::text[],
'es',
'published',
true,
true,
'{
"period": "1911-1934",
"key_dates": {
"1914": "Fundación Instituto del Radio, Inicio WWI (petits Curies)",
"1918": "Fin Primera Guerra Mundial",
"1921": "Viaje a Estados Unidos",
"1934": "Muerte por anemia aplásica (4 julio)",
"1995": "Traslado al Panteón de París"
},
"key_contributions": [
"Instituto del Radio/Instituto Curie",
"Unidades móviles de radiología (WWI)",
"Más de 1 millón de soldados radiografiados",
"200 puestos fijos de radiología",
"Entrenamiento de 150 operadores de rayos X"
],
"legacy_impact": [
"Fundación de física y química nuclear",
"Radioterapia para cáncer",
"Primera mujer profesora Sorbona",
"Primera mujer Nobel, única con 2 Nobeles en ciencias diferentes",
"Elemento Curio nombrado en su honor",
"Inspiración para mujeres en STEM"
],
"key_themes": ["legado científico", "servicio humanitario", "precio del pionerismo", "ciencia abierta", "inspiración generacional"],
"word_count": 2450,
"reading_time_minutes": 12,
"educational_level": "secundaria-preparatoria-universidad",
"curriculum_connections": ["Historia de la ciencia", "Medicina nuclear", "Primera Guerra Mundial", "Ética científica", "Impacto social de la ciencia"]
}'::jsonb,
NOW(),
NOW()
)
ON CONFLICT (title, content_type)
DO UPDATE SET
content_body = EXCLUDED.content_body,
summary = EXCLUDED.summary,
metadata = EXCLUDED.metadata,
tags = EXCLUDED.tags,
subjects = EXCLUDED.subjects,
updated_at = NOW();
-- ============================================================================
-- FIN: Marie Curie Biography Seeds
-- ============================================================================

View File

@ -0,0 +1,550 @@
-- ============================================================================
-- SEED: Media Files for Marie Curie Content
-- ============================================================================
-- Descripción: Archivos multimedia relacionados con Marie Curie para
-- enriquecer el contenido educativo (imágenes, videos, audio)
-- Schema: content_management
-- Tablas: media_files
-- Prioridad: 2
-- Dependencias: Schema content_management debe existir
-- ============================================================================
SET search_path TO content_management, public;
-- ============================================================================
-- MEDIA FILES: Imágenes, Videos, y Audio de Marie Curie
-- ============================================================================
INSERT INTO content_management.media_files (
filename,
folder_path,
file_extension,
mime_type,
file_size_bytes,
title,
description,
alt_text,
tags,
is_active,
metadata,
created_at,
updated_at
) VALUES
-- ============================================================================
-- IMÁGENES: Fotografías históricas de Marie Curie
-- ============================================================================
(
'marie-curie-portrait-1903.jpg',
'/media/images/marie-curie/marie-curie-portrait-1903.jpg',
'image',
'image/jpeg',
245678,
'Retrato de Marie Curie (1903)',
'Fotografía oficial de Marie Curie tomada en 1903, año de su primer Premio Nobel de Física. Marie aparece con vestimenta formal de la época, mostrando la dignidad y seriedad que caracterizaban su presencia pública. Esta imagen es icónica y representa el momento en que se convirtió en la primera mujer en ganar un Premio Nobel.',
'Retrato en blanco y negro de Marie Curie en 1903, mirando directamente a la cámara con expresión seria, vestida con ropa formal de principios del siglo XX',
ARRAY['Marie Curie', 'Retrato', '1903', 'Nobel', 'Fotografía Histórica', 'Mujer Científica']::text[],
'active',
'{
"year": 1903,
"photographer": "Desconocido",
"source": "Dominio Público",
"copyright": "Public Domain",
"dimensions": {
"width": 800,
"height": 1000,
"unit": "pixels"
},
"historical_context": "Tomada el año de su primer Premio Nobel de Física, compartido con Pierre Curie y Henri Becquerel",
"location": "París, Francia",
"quality": "alta",
"format_original": "negativo de vidrio",
"educational_use": true,
"notable_features": ["Primera mujer Nobel", "Vestimenta victoriana", "Expresión determinada"]
}'::jsonb,
NOW(),
NOW()
),
(
'marie-pierre-curie-laboratory.jpg',
'/media/images/marie-curie/marie-pierre-curie-laboratory.jpg',
'image',
'image/jpeg',
312456,
'Marie y Pierre Curie en su laboratorio',
'Marie y Pierre Curie trabajando juntos en su laboratorio improvisado de París. Esta fotografía captura la colaboración científica extraordinaria entre los dos científicos. Se puede observar el equipamiento científico de la época y las condiciones modestas en las que realizaron descubrimientos revolucionarios.',
'Marie y Pierre Curie en su laboratorio, ambos inclinados sobre una mesa de trabajo manipulando equipamiento científico, rodeados de instrumentos y frascos',
ARRAY['Marie Curie', 'Pierre Curie', 'Laboratorio', 'Investigación', 'Colaboración Científica', 'Pechblenda']::text[],
'active',
'{
"year": 1898,
"year_approx": true,
"location": "París, Francia",
"specific_location": "Cobertizo de la École de Physique et Chimie",
"source": "Dominio Público",
"copyright": "Public Domain",
"dimensions": {
"width": 1200,
"height": 800,
"unit": "pixels"
},
"historical_context": "Período de descubrimiento del Radio y Polonio",
"visible_equipment": ["Electrómetro", "Frascos de vidrio", "Mesa de trabajo"],
"educational_use": true,
"notable_features": ["Condiciones laborales modestas", "Colaboración igualitaria", "Equipamiento improvisado"]
}'::jsonb,
NOW(),
NOW()
),
(
'marie-curie-sorbonne-lecture.jpg',
'/media/images/marie-curie/marie-curie-sorbonne-lecture.jpg',
'image',
'image/jpeg',
278934,
'Marie Curie dando clase en la Sorbona',
'Fotografía de Marie Curie como profesora en la Universidad de la Sorbona, la primera mujer en ocupar una cátedra en esta prestigiosa institución. Se la ve frente a una pizarra con ecuaciones, demostrando su rol como educadora además de investigadora.',
'Marie Curie de pie junto a una pizarra con fórmulas científicas, vestida formalmente, en un aula de la Sorbona',
ARRAY['Marie Curie', 'Sorbona', 'Profesora', 'Educación', 'Primera Mujer', 'Universidad']::text[],
'active',
'{
"year": 1906,
"year_range": "1906-1910",
"location": "Universidad de la Sorbona, París",
"source": "Dominio Público",
"copyright": "Public Domain",
"dimensions": {
"width": 900,
"height": 700,
"unit": "pixels"
},
"historical_significance": "Primera mujer profesora en la Sorbona en 650 años de historia",
"context": "Asumió la cátedra de Pierre Curie después de su muerte en 1906",
"educational_use": true,
"notable_features": ["Pizarra con ecuaciones", "Postura profesoral", "Aula histórica"]
}'::jsonb,
NOW(),
NOW()
),
(
'marie-curie-radium-glowing.jpg',
'/media/images/marie-curie/marie-curie-radium-glowing.jpg',
'image',
'image/jpeg',
198234,
'Marie Curie observando el brillo del Radio',
'Recreación artística basada en descripciones históricas de Marie Curie observando tubos de ensayo con radio en la oscuridad. El radio emitía un brillo azul-verdoso natural que fascinaba a los Curie, aunque no conocían los peligros de la radiación.',
'Imagen artística de Marie Curie en la oscuridad observando tubos de ensayo que emiten un brillo fosforescente azul-verdoso',
ARRAY['Marie Curie', 'Radio', 'Fosforescencia', 'Descubrimiento', 'Noche', 'Laboratorio']::text[],
'active',
'{
"type": "recreación artística",
"based_on": "Descripciones históricas de los Curie",
"year_depicted": 1898,
"source": "Ilustración educativa",
"copyright": "Uso educativo permitido",
"dimensions": {
"width": 1024,
"height": 768,
"unit": "pixels"
},
"historical_accuracy": "Alta - basada en diarios y cartas de Marie",
"educational_value": "Ilustra el descubrimiento y las propiedades del radio",
"safety_note": "Esta práctica era peligrosa - hoy sabemos que la radiación requiere protección",
"notable_features": ["Brillo fosforescente del radio", "Oscuridad del laboratorio", "Fascinación científica"]
}'::jsonb,
NOW(),
NOW()
),
(
'marie-curie-nobel-ceremony-1903.jpg',
'/media/images/marie-curie/marie-curie-nobel-ceremony-1903.jpg',
'image',
'image/jpeg',
289456,
'Ceremonia Premio Nobel 1903',
'Fotografía conmemorativa relacionada con el Premio Nobel de Física de 1903. Marie y Pierre Curie no asistieron a la ceremonia original por enfermedad y trabajo, pero esta imagen documenta el reconocimiento histórico.',
'Fotografía formal relacionada con la ceremonia del Premio Nobel de Física 1903',
ARRAY['Marie Curie', 'Pierre Curie', 'Premio Nobel', 'Física', '1903', 'Ceremonia']::text[],
'active',
'{
"year": 1903,
"event": "Premio Nobel de Física 1903",
"recipients": ["Marie Curie", "Pierre Curie", "Henri Becquerel"],
"source": "Archivos Nobel",
"copyright": "Dominio Público",
"dimensions": {
"width": 1100,
"height": 850,
"unit": "pixels"
},
"historical_note": "Los Curie no asistieron a la ceremonia original en diciembre 1903",
"significance": "Primera mujer en ganar Premio Nobel",
"educational_use": true
}'::jsonb,
NOW(),
NOW()
),
(
'marie-curie-wwi-xray-vehicle.jpg',
'/media/images/marie-curie/marie-curie-wwi-xray-vehicle.jpg',
'image',
'image/jpeg',
334567,
'Marie Curie con vehículo de rayos X (Petit Curie)',
'Marie Curie junto a una de las unidades móviles de radiología que equipó durante la Primera Guerra Mundial. Estos "petits Curies" llevaban equipamiento de rayos X a hospitales de campaña cerca del frente de batalla.',
'Marie Curie de pie junto a un vehículo automóvil equipado con máquina de rayos X, durante la Primera Guerra Mundial',
ARRAY['Marie Curie', 'Primera Guerra Mundial', 'Rayos X', 'Petit Curie', 'Servicio Humanitario', 'Medicina']::text[],
'active',
'{
"year": 1914,
"year_range": "1914-1918",
"period": "Primera Guerra Mundial",
"location": "Francia",
"source": "Archivos de guerra",
"copyright": "Dominio Público",
"dimensions": {
"width": 1150,
"height": 900,
"unit": "pixels"
},
"historical_context": "Marie equipó 20 unidades móviles y 200 puestos fijos de radiología",
"impact": "Más de 1 millón de soldados radiografiados",
"educational_value": "Demuestra aplicación práctica de la ciencia para ayudar a la humanidad",
"notable_features": ["Vehículo equipado", "Equipo de rayos X portátil", "Compromiso humanitario"]
}'::jsonb,
NOW(),
NOW()
),
-- ============================================================================
-- DIAGRAMAS: Ilustraciones educativas
-- ============================================================================
(
'periodic-table-curie-elements.svg',
'/media/diagrams/periodic-table-curie-elements.svg',
'image',
'image/svg+xml',
45678,
'Tabla Periódica: Radio (Ra) y Polonio (Po)',
'Tabla periódica de los elementos con los elementos descubiertos por Marie Curie claramente destacados: Polonio (Po, elemento 84) y Radio (Ra, elemento 88). Incluye información básica sobre cada elemento: número atómico, símbolo, masa atómica, y año de descubrimiento.',
'Tabla periódica de elementos químicos con el Polonio (Po) y el Radio (Ra) resaltados en color diferente, mostrando sus propiedades básicas',
ARRAY['Tabla Periódica', 'Radio', 'Polonio', 'Química', 'Elementos', 'Descubrimientos']::text[],
'active',
'{
"type": "diagrama educativo",
"format": "SVG vectorial",
"elements_highlighted": [
{
"symbol": "Po",
"name": "Polonio",
"atomic_number": 84,
"discoverer": "Marie Curie",
"year": 1898,
"named_after": "Polonia"
},
{
"symbol": "Ra",
"name": "Radio",
"atomic_number": 88,
"discoverer": "Marie y Pierre Curie",
"year": 1898,
"properties": "Altamente radioactivo, brilla en la oscuridad"
}
],
"educational_use": true,
"interactive": false,
"can_be_made_interactive": true,
"dimensions": {
"width": 1920,
"height": 1080,
"unit": "pixels",
"scalable": true
},
"color_scheme": "Educativo - elementos Curie en azul",
"includes": ["Números atómicos", "Símbolos", "Nombres", "Categorías"]
}'::jsonb,
NOW(),
NOW()
),
(
'radioactivity-decay-diagram.svg',
'/media/diagrams/radioactivity-decay-diagram.svg',
'image',
'image/svg+xml',
38924,
'Diagrama: Desintegración Radioactiva',
'Diagrama educativo que ilustra el concepto de desintegración radioactiva descubierto por Marie Curie. Muestra cómo un átomo radioactivo emite partículas alfa, beta, o rayos gamma, transformándose en otro elemento.',
'Diagrama científico mostrando un núcleo atómico emitiendo radiación, con flechas indicando partículas alfa, beta y rayos gamma',
ARRAY['Radioactividad', 'Desintegración', 'Física Nuclear', 'Partículas', 'Diagrama Educativo']::text[],
'active',
'{
"type": "diagrama científico",
"format": "SVG vectorial",
"concept": "Desintegración radioactiva",
"discovered_by": "Marie Curie (término acuñado)",
"educational_level": "Secundaria-Preparatoria",
"shows": [
"Núcleo atómico",
"Emisión de partículas alfa (He)",
"Emisión de partículas beta (electrones)",
"Emisión de rayos gamma (ondas electromagnéticas)"
],
"dimensions": {
"width": 1200,
"height": 900,
"unit": "pixels",
"scalable": true
},
"color_coded": true,
"labels": "Español",
"curriculum_connections": ["Física nuclear", "Química nuclear", "Estructura atómica"]
}'::jsonb,
NOW(),
NOW()
),
(
'curie-timeline-infographic.svg',
'/media/diagrams/curie-timeline-infographic.svg',
'image',
'image/svg+xml',
67890,
'Línea de Tiempo: Vida de Marie Curie',
'Infografía interactiva que presenta los momentos clave de la vida de Marie Curie desde su nacimiento en 1867 hasta su muerte en 1934. Incluye hitos personales, descubrimientos científicos, premios, y contexto histórico.',
'Línea de tiempo horizontal ilustrada con iconos y fechas clave de la vida de Marie Curie, desde 1867 hasta 1934',
ARRAY['Marie Curie', 'Línea de Tiempo', 'Biografía', 'Infografía', 'Historia']::text[],
'active',
'{
"type": "infografía educativa",
"format": "SVG vectorial",
"span": "1867-1934",
"major_events": [
{"year": 1867, "event": "Nacimiento en Varsovia"},
{"year": 1891, "event": "Llegada a París"},
{"year": 1895, "event": "Matrimonio con Pierre"},
{"year": 1898, "event": "Descubrimiento Po y Ra"},
{"year": 1903, "event": "Primer Nobel (Física)"},
{"year": 1906, "event": "Muerte de Pierre"},
{"year": 1911, "event": "Segundo Nobel (Química)"},
{"year": 1914, "event": "WWI - Petits Curies"},
{"year": 1934, "event": "Muerte"}
],
"dimensions": {
"width": 2400,
"height": 600,
"unit": "pixels",
"scalable": true
},
"visual_elements": ["Iconos", "Ilustraciones", "Códigos de color por tipo de evento"],
"interactive": false,
"can_be_made_interactive": true,
"educational_use": true
}'::jsonb,
NOW(),
NOW()
),
-- ============================================================================
-- VIDEO: Contenido multimedia educativo
-- ============================================================================
(
'marie-curie-documentary-intro.mp4',
'/media/videos/marie-curie-documentary-intro.mp4',
'video',
'video/mp4',
15678900,
'Introducción: La Vida de Marie Curie',
'Video introductorio de 3 minutos sobre la vida de Marie Curie. Combina fotografías históricas, animaciones educativas, y narración clara para presentar los momentos más importantes de su vida y sus descubrimientos científicos. Ideal como introducción antes de estudiar su biografía en detalle.',
'Video documental introductorio sobre Marie Curie con fotografías históricas y narración',
ARRAY['Marie Curie', 'Documental', 'Video Educativo', 'Biografía', 'Introducción']::text[],
'active',
'{
"duration_seconds": 180,
"duration_formatted": "03:00",
"resolution": "1080p",
"width": 1920,
"height": 1080,
"fps": 30,
"codec": "H.264",
"has_subtitles": true,
"subtitle_languages": ["es", "en"],
"has_audio_description": false,
"languages": {
"audio": ["es"],
"subtitles": ["es", "en"]
},
"content_structure": [
{"timestamp": "00:00-00:30", "section": "Infancia en Polonia"},
{"timestamp": "00:30-01:00", "section": "Estudios en París"},
{"timestamp": "01:00-02:00", "section": "Descubrimientos científicos"},
{"timestamp": "02:00-02:30", "section": "Premios Nobel"},
{"timestamp": "02:30-03:00", "section": "Legado"}
],
"educational_level": "Secundaria-Preparatoria",
"visual_elements": ["Fotografías históricas", "Animaciones", "Texto en pantalla", "Transiciones"],
"accessibility": {
"closed_captions": true,
"transcript_available": true
},
"curriculum_connections": ["Historia de la Ciencia", "Biografías", "Física", "Química"]
}'::jsonb,
NOW(),
NOW()
),
(
'radioactivity-explanation-animated.mp4',
'/media/videos/radioactivity-explanation-animated.mp4',
'video',
'video/mp4',
22345678,
'Explicación Animada: ¿Qué es la Radioactividad?',
'Video educativo de 5 minutos que explica el concepto de radioactividad usando animaciones claras y accesibles. Cubre el descubrimiento por Marie Curie, la naturaleza de la radiación, tipos de desintegración radioactiva, y aplicaciones modernas.',
'Video animado explicando el concepto de radioactividad con gráficos de átomos y partículas',
ARRAY['Radioactividad', 'Educación', 'Animación', 'Física Nuclear', 'Concepto Científico']::text[],
'active',
'{
"duration_seconds": 300,
"duration_formatted": "05:00",
"resolution": "1080p",
"width": 1920,
"height": 1080,
"fps": 60,
"codec": "H.264",
"animation_style": "2D educativo",
"has_subtitles": true,
"subtitle_languages": ["es", "en", "fr"],
"languages": {
"audio": ["es"],
"subtitles": ["es", "en", "fr"]
},
"content_structure": [
{"timestamp": "00:00-01:00", "topic": "Introducción - Descubrimiento de Marie Curie"},
{"timestamp": "01:00-02:30", "topic": "¿Qué es un átomo radioactivo?"},
{"timestamp": "02:30-03:30", "topic": "Tipos de radiación (alfa, beta, gamma)"},
{"timestamp": "03:30-04:30", "topic": "Aplicaciones: medicina, datación"},
{"timestamp": "04:30-05:00", "topic": "Seguridad y protección radiológica"}
],
"educational_level": "Secundaria",
"visual_style": "Animación clara con colores educativos",
"pedagogy": "Construye de simple a complejo",
"includes_quiz": false,
"accessibility": {
"closed_captions": true,
"transcript_available": true,
"audio_description": false
}
}'::jsonb,
NOW(),
NOW()
),
-- ============================================================================
-- AUDIO: Pronunciaciones y contenido auditivo
-- ============================================================================
(
'scientific-terms-pronunciation.mp3',
'/media/audio/scientific-terms-pronunciation.mp3',
'audio',
'audio/mpeg',
2345678,
'Pronunciación: Términos Científicos Marie Curie',
'Audio educativo con la pronunciación correcta de términos científicos clave relacionados con Marie Curie y sus descubrimientos. Incluye: Radio, Polonio, Radioactividad, Pechblenda, Sorbona, Curie (unidad), Becquerel. Cada término se pronuncia dos veces con una pausa entre repeticiones.',
'Audio educativo de pronunciación de términos científicos relacionados con Marie Curie',
ARRAY['Pronunciación', 'Vocabulario', 'Ciencias', 'Audio Educativo', 'Términos Técnicos']::text[],
'active',
'{
"duration_seconds": 120,
"duration_formatted": "02:00",
"bitrate": "192kbps",
"sample_rate": "44.1kHz",
"channels": "stereo",
"language": "es",
"narrator": "Profesional nativo español",
"terms_included": [
{"term": "Radio", "timestamp": "00:05", "etymology": "Del latín radius (rayo)"},
{"term": "Polonio", "timestamp": "00:15", "etymology": "Nombrado por Polonia"},
{"term": "Radioactividad", "timestamp": "00:25", "note": "Término acuñado por Marie Curie"},
{"term": "Pechblenda", "timestamp": "00:40", "note": "Mineral de uranio"},
{"term": "Sorbona", "timestamp": "00:50", "note": "Universidad de París"},
{"term": "Curie", "timestamp": "01:00", "note": "Unidad de radioactividad"},
{"term": "Becquerel", "timestamp": "01:15", "note": "Otra unidad de radioactividad"},
{"term": "Marie Sklodowska-Curie", "timestamp": "01:30", "note": "Nombre completo"}
],
"repetitions": 2,
"pause_between": "1 segundo",
"educational_use": true,
"curriculum_connections": ["Vocabulario científico", "Pronunciación", "Ciencias"]
}'::jsonb,
NOW(),
NOW()
),
(
'marie-curie-quotes-narrated.mp3',
'/media/audio/marie-curie-quotes-narrated.mp3',
'audio',
'audio/mpeg',
3456789,
'Frases Célebres de Marie Curie (Narradas)',
'Colección de 10 frases inspiradoras de Marie Curie, narradas con música de fondo suave. Incluye citas sobre ciencia, perseverancia, educación, y el papel de la mujer en la ciencia. Cada cita se presenta primero en español, luego en su inglés o francés original.',
'Audio con frases célebres de Marie Curie narradas con música de fondo',
ARRAY['Marie Curie', 'Frases', 'Inspiración', 'Audio Educativo', 'Motivación']::text[],
'active',
'{
"duration_seconds": 240,
"duration_formatted": "04:00",
"bitrate": "192kbps",
"sample_rate": "44.1kHz",
"channels": "stereo",
"primary_language": "es",
"includes_original_language": true,
"has_background_music": true,
"music_type": "Clásica suave (sin derechos de autor)",
"quotes_included": [
{
"quote_es": "Nada en la vida debe ser temido, solamente comprendido",
"quote_en": "Nothing in life is to be feared, it is only to be understood",
"context": "Sobre el miedo y el conocimiento"
},
{
"quote_es": "Yo estaba enseñada a la convicción de que no hay que hacer nunca nada a medias",
"quote_fr": "J étais convaincue qu il ne faut jamais faire les choses à moitié",
"context": "Sobre dedicación y excelencia"
},
{
"quote_es": "La vida no es fácil para ninguno de nosotros. Pero qué importa. Hay que perseverar y sobre todo tener confianza en uno mismo",
"quote_fr": "La vie n est facile pour aucun de nous. Mais quoi, il faut avoir de la persévérance",
"context": "Sobre perseverancia"
}
],
"quotes_count": 10,
"educational_use": true,
"use_cases": ["Inicio de clase", "Motivación", "Reflexión", "Contexto histórico"]
}'::jsonb,
NOW(),
NOW()
)
ON CONFLICT (folder_path)
DO UPDATE SET
title = EXCLUDED.title,
description = EXCLUDED.description,
alt_text = EXCLUDED.alt_text,
metadata = EXCLUDED.metadata,
updated_at = NOW();
-- ============================================================================
-- FIN: Media Files Seeds
-- ============================================================================

View File

@ -0,0 +1,562 @@
-- ============================================================================
-- SEED: Tags for Content Organization
-- ============================================================================
-- Descripción: Sistema de tags organizacionales para contenido educativo
-- relacionado con Marie Curie y temas científicos/históricos
-- Schema: content_management
-- Tablas: tags
-- Prioridad: 2
-- Dependencias: Schema content_management debe existir
-- ============================================================================
SET search_path TO content_management, public;
-- ============================================================================
-- TAGS: Sistema de organización de contenido
-- ============================================================================
INSERT INTO content_management.tags (
tag_name,
tag_slug,
tag_category,
description,
usage_count,
created_at,
updated_at
) VALUES
-- ============================================================================
-- CATEGORÍA: Personas (person)
-- ============================================================================
(
'Marie Curie',
'marie-curie',
'person',
'Marie Curie (Maria Sklodowska-Curie, 1867-1934): Científica polaco-francesa, pionera en el estudio de la radioactividad. Primera mujer en ganar un Premio Nobel, primera persona en ganar dos Premios Nobel en diferentes ciencias (Física 1903, Química 1911). Descubridora de los elementos Radio y Polonio.',
0,
NOW(),
NOW()
),
(
'Pierre Curie',
'pierre-curie',
'person',
'Pierre Curie (1859-1906): Físico francés, esposo y colaborador científico de Marie Curie. Co-descubridor del Radio y Polonio. Ganador del Premio Nobel de Física 1903 junto con Marie Curie y Henri Becquerel. Pionero en el estudio de la piezoelectricidad y el magnetismo.',
0,
NOW(),
NOW()
),
(
'Henri Becquerel',
'henri-becquerel',
'person',
'Henri Becquerel (1852-1908): Físico francés que descubrió la radioactividad natural del uranio en 1896. Premio Nobel de Física 1903 compartido con Marie y Pierre Curie. Su descubrimiento inspiró la investigación de Marie Curie.',
0,
NOW(),
NOW()
),
(
'Irène Joliot-Curie',
'irene-joliot-curie',
'person',
'Irène Joliot-Curie (1897-1956): Hija mayor de Marie y Pierre Curie. Física y química francesa, Premio Nobel de Química 1935 por el descubrimiento de la radioactividad artificial. Continuó el legado científico de sus padres.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Conceptos Científicos (scientific_concept)
-- ============================================================================
(
'Radioactividad',
'radioactividad',
'scientific_concept',
'Fenómeno por el cual ciertos núcleos atómicos inestables emiten radiación espontáneamente. Término acuñado por Marie Curie en 1898. Representa una propiedad fundamental del átomo que llevó a revolucionar la física y la química modernas.',
0,
NOW(),
NOW()
),
(
'Radio',
'radio',
'scientific_concept',
'Radio (Ra, elemento 88): Elemento químico altamente radioactivo descubierto por Marie y Pierre Curie en diciembre de 1898. Emite un brillo azul-verdoso en la oscuridad y genera calor constantemente. Fue crucial en el desarrollo de la física nuclear y la radioterapia.',
0,
NOW(),
NOW()
),
(
'Polonio',
'polonio',
'scientific_concept',
'Polonio (Po, elemento 84): Elemento químico extremadamente radioactivo descubierto por Marie Curie en julio de 1898. Nombrado en honor a Polonia, país natal de Marie. Fue el primer elemento nuevo que descubrió Marie en su investigación sobre sustancias radioactivas.',
0,
NOW(),
NOW()
),
(
'Pechblenda',
'pechblenda',
'scientific_concept',
'Pechblenda (uraninita): Mineral de uranio del cual Marie y Pierre Curie extrajeron el Radio y el Polonio. Los Curie procesaron literalmente toneladas de pechblenda durante cuatro años para aislar fracciones de gramo de elementos radioactivos puros.',
0,
NOW(),
NOW()
),
(
'Rayos X',
'rayos-x',
'scientific_concept',
'Forma de radiación electromagnética de alta energía capaz de atravesar tejidos. Descubiertos por Wilhelm Röntgen en 1895. Marie Curie desarrolló unidades móviles de rayos X durante la Primera Guerra Mundial para uso médico en el campo de batalla.',
0,
NOW(),
NOW()
),
(
'Desintegración Radioactiva',
'desintegracion-radioactiva',
'scientific_concept',
'Proceso por el cual un núcleo atómico inestable pierde energía mediante la emisión de radiación (partículas alfa, beta, o rayos gamma). Estudiado extensamente por Marie Curie, demostró que los átomos no son inmutables sino que pueden transformarse.',
0,
NOW(),
NOW()
),
(
'Física Nuclear',
'fisica-nuclear',
'scientific_concept',
'Rama de la física que estudia el núcleo atómico y sus interacciones. Fundada en gran parte por el trabajo pionero de Marie Curie sobre radioactividad. Llevó al desarrollo de aplicaciones como la energía nuclear y la medicina nuclear.',
0,
NOW(),
NOW()
),
(
'Curio',
'curio',
'scientific_concept',
'Curio (Cm, elemento 96): Elemento químico sintético nombrado en honor a Marie y Pierre Curie. También el nombre de una unidad obsoleta de radioactividad (Ci), reemplazada por el Becquerel en el Sistema Internacional.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Lugares (location)
-- ============================================================================
(
'Varsovia',
'varsovia',
'location',
'Varsovia, Polonia: Ciudad natal de Marie Curie (entonces Maria Sklodowska). Nació allí el 7 de noviembre de 1867. Durante su juventud, Polonia estaba bajo ocupación rusa, lo que motivó su activismo educativo clandestino y su orgullo patriótico.',
0,
NOW(),
NOW()
),
(
'París',
'paris',
'location',
'París, Francia: Ciudad donde Marie estudió en la Universidad de la Sorbona, realizó sus descubrimientos científicos revolucionarios, y pasó la mayor parte de su vida adulta. Centro de su carrera científica y del desarrollo de la física nuclear moderna.',
0,
NOW(),
NOW()
),
(
'Sorbona',
'sorbona',
'location',
'Universidad de la Sorbona (Universidad de París): Institución donde Marie Curie estudió física y matemáticas (1891-1894), se doctoró en física (1903), y se convirtió en la primera mujer profesora en sus 650 años de historia (1906).',
0,
NOW(),
NOW()
),
(
'Instituto del Radio',
'instituto-del-radio',
'location',
'Instituto del Radio (Institut du Radium, ahora Instituto Curie): Centro de investigación fundado por Marie Curie en París en 1914. Dedicado al estudio de la radioactividad y sus aplicaciones médicas. Continúa siendo un centro líder mundial en investigación oncológica.',
0,
NOW(),
NOW()
),
(
'Polonia',
'polonia',
'location',
'Polonia: País natal de Marie Curie. Durante su vida estuvo dividido entre Rusia, Prusia y Austria. Marie nombró el Polonio en honor a su país, un acto de patriotismo y protesta política. Polonia recuperó su independencia en 1918.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Logros y Premios (achievement)
-- ============================================================================
(
'Premio Nobel',
'premio-nobel',
'achievement',
'Premio Nobel: Máximo galardón científico internacional. Marie Curie ganó dos: Nobel de Física 1903 (con Pierre Curie y Henri Becquerel) por la investigación sobre radioactividad, y Nobel de Química 1911 por el descubrimiento del Radio y Polonio. Fue la primera persona en ganar dos Nobeles en ciencias diferentes.',
0,
NOW(),
NOW()
),
(
'Primera Mujer Nobel',
'primera-mujer-nobel',
'achievement',
'Marie Curie fue la primera mujer en ganar un Premio Nobel (Física, 1903). Rompió una barrera de género histórica en el reconocimiento científico. Solo 61 mujeres han ganado el Nobel en sus más de 120 años de historia (hasta 2024).',
0,
NOW(),
NOW()
),
(
'Doctorado en Física',
'doctorado-en-fisica',
'achievement',
'Marie Curie fue la primera mujer en Francia en obtener un doctorado en física (1903). Su tesis "Investigaciones sobre Sustancias Radioactivas" fue considerada el mejor trabajo doctoral jamás presentado. Abrió el camino para mujeres en física.',
0,
NOW(),
NOW()
),
(
'Primera Profesora Sorbona',
'primera-profesora-sorbona',
'achievement',
'En 1906, Marie Curie se convirtió en la primera mujer profesora de la Universidad de la Sorbona en sus 650 años de historia. Asumió la cátedra de su esposo Pierre después de su muerte, continuando su legado científico y educativo.',
0,
NOW(),
NOW()
),
(
'Descubrimiento de Elementos',
'descubrimiento-de-elementos',
'achievement',
'Marie Curie descubrió dos elementos químicos nuevos: Polonio (Po, elemento 84) en julio de 1898 y Radio (Ra, elemento 88) en diciembre de 1898. Expandió la tabla periódica y demostró la existencia de elementos más radioactivos que el uranio.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Eventos Históricos (historical_event)
-- ============================================================================
(
'Primera Guerra Mundial',
'primera-guerra-mundial',
'historical_event',
'Primera Guerra Mundial (1914-1918): Conflicto durante el cual Marie Curie desarrolló unidades móviles de radiología ("petits Curies") para ayudar a los soldados heridos. Equipó 20 vehículos móviles y 200 puestos fijos de rayos X, permitiendo radiografiar a más de 1 millón de soldados.',
0,
NOW(),
NOW()
),
(
'Belle Époque',
'belle-epoque',
'historical_event',
'Belle Époque (1871-1914): Período de florecimiento cultural y científico en Francia durante el cual Marie Curie realizó sus descubrimientos más importantes. Fue una era de optimismo, innovación artística y avances científicos sin precedentes en Europa.',
0,
NOW(),
NOW()
),
(
'Ocupación Rusa de Polonia',
'ocupacion-rusa-polonia',
'historical_event',
'Ocupación Rusa de Polonia (1795-1918): Período durante el cual Polonia fue dividida entre Rusia, Prusia y Austria. Marie Curie creció bajo ocupación rusa en Varsovia, enfrentando la supresión de la cultura polaca y restricciones educativas, lo que motivó su participación en la "Universidad Volante".',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Materias Educativas (subject)
-- ============================================================================
(
'Física',
'fisica',
'subject',
'Física: Ciencia natural que estudia la materia, energía, espacio y tiempo. Marie Curie se graduó en física en la Sorbona (1893, primer lugar) y ganó el Nobel de Física (1903). Su trabajo sobre radioactividad revolucionó la física moderna.',
0,
NOW(),
NOW()
),
(
'Química',
'quimica',
'subject',
'Química: Ciencia que estudia la composición, estructura y propiedades de las sustancias. Marie Curie se graduó en matemáticas (con fuerte componente químico) y ganó el Nobel de Química (1911) por aislar el Radio puro y determinar sus propiedades.',
0,
NOW(),
NOW()
),
(
'Historia de la Ciencia',
'historia-de-la-ciencia',
'subject',
'Historia de la Ciencia: Disciplina que estudia el desarrollo del conocimiento científico. Marie Curie es una figura central en la transición de la física clásica a la moderna, y su biografía ilustra la evolución de la ciencia a finales del siglo XIX y principios del XX.',
0,
NOW(),
NOW()
),
(
'Biografía',
'biografia',
'subject',
'Biografía: Género literario que narra la vida de personas reales. La biografía de Marie Curie es particularmente rica en lecciones sobre perseverancia, superación de barreras, colaboración científica, y el papel de las mujeres en la ciencia.',
0,
NOW(),
NOW()
),
(
'Matemáticas',
'matematicas',
'subject',
'Matemáticas: Ciencia formal que estudia patrones, estructuras y relaciones abstractas. Marie Curie se graduó en matemáticas en la Sorbona (1894, segundo lugar). Las matemáticas fueron esenciales para sus cálculos de pesos atómicos y análisis de datos experimentales.',
0,
NOW(),
NOW()
),
(
'Medicina Nuclear',
'medicina-nuclear',
'subject',
'Medicina Nuclear: Especialidad médica que usa materiales radioactivos para diagnóstico y tratamiento. Fundada en gran parte por el descubrimiento del Radio por Marie Curie. La radioterapia moderna es descendiente directa de sus investigaciones.',
0,
NOW(),
NOW()
),
(
'Estudios de Género',
'estudios-de-genero',
'subject',
'Estudios de Género: Campo académico que examina las construcciones sociales de género. Marie Curie es un caso de estudio fundamental sobre barreras de género en la ciencia, discriminación académica, y el papel de las mujeres como pioneras intelectuales.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Temas y Conceptos Transversales (theme)
-- ============================================================================
(
'Mujeres en Ciencia',
'mujeres-en-ciencia',
'theme',
'Tema que explora la participación, contribuciones y desafíos de las mujeres en campos científicos. Marie Curie es el ejemplo paradigmático de mujer científica que superó barreras de género masivas para lograr reconocimiento mundial.',
0,
NOW(),
NOW()
),
(
'Ciencia Abierta',
'ciencia-abierta',
'theme',
'Movimiento que promueve que el conocimiento científico sea accesible libremente. Marie y Pierre Curie rechazaron patentar el proceso de aislamiento del Radio, creyendo que el conocimiento científico debe compartirse para el beneficio de la humanidad.',
0,
NOW(),
NOW()
),
(
'Colaboración Científica',
'colaboracion-cientifica',
'theme',
'Trabajo conjunto de científicos para investigar problemas complejos. La asociación entre Marie y Pierre Curie ejemplifica una colaboración científica exitosa basada en respeto mutuo, igualdad intelectual y pasión compartida por la ciencia.',
0,
NOW(),
NOW()
),
(
'Ética Científica',
'etica-cientifica',
'theme',
'Principios morales que guían la investigación y aplicación de la ciencia. Marie Curie demostró ética ejemplar al rechazar lucrar con sus descubrimientos, compartir conocimiento abiertamente, y usar la ciencia para ayudar durante la Primera Guerra Mundial.',
0,
NOW(),
NOW()
),
(
'Ciencia y Sociedad',
'ciencia-y-sociedad',
'theme',
'Relación bidireccional entre avances científicos y cambios sociales. El trabajo de Marie Curie tuvo impactos profundos en medicina, energía, y la percepción social de las capacidades intelectuales de las mujeres.',
0,
NOW(),
NOW()
),
(
'Educación Superior',
'educacion-superior',
'theme',
'Educación universitaria y de posgrado. La historia de Marie Curie ilustra la importancia del acceso a educación superior de calidad y los obstáculos que enfrentaban las mujeres y extranjeros en el sistema educativo del siglo XIX.',
0,
NOW(),
NOW()
),
(
'Innovación Científica',
'innovacion-cientifica',
'theme',
'Desarrollo de nuevas teorías, métodos o aplicaciones científicas. Marie Curie innovó en metodología experimental, acuñó nuevos términos (radioactividad), descubrió nuevos elementos, y desarrolló nuevas aplicaciones médicas.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Valores y Lecciones (value)
-- ============================================================================
(
'Perseverancia',
'perseverancia',
'value',
'Persistencia constante en la consecución de un objetivo a pesar de dificultades. Marie Curie demostró perseverancia extraordinaria: procesó toneladas de pechblenda durante años, superó discriminación de género, y continuó investigando después de la muerte de Pierre.',
0,
NOW(),
NOW()
),
(
'Curiosidad Intelectual',
'curiosidad-intelectual',
'value',
'Deseo profundo de comprender y aprender. La curiosidad de Marie sobre los "rayos uraniosos" de Becquerel la llevó a investigación que revolucionó la ciencia. Eligió un tema por pura curiosidad, no por utilidad práctica inmediata.',
0,
NOW(),
NOW()
),
(
'Rigor Científico',
'rigor-cientifico',
'value',
'Aplicación estricta del método científico y estándares de precisión. Marie Curie midió sistemáticamente todos los elementos conocidos, repitió experimentos incansablemente, y solo aceptó conclusiones respaldadas por datos sólidos.',
0,
NOW(),
NOW()
),
(
'Humildad',
'humildad',
'value',
'Modestia sobre los propios logros. A pesar de ganar dos Premios Nobel, Marie Curie vivió modestamente, evitaba la fama, y consideraba su trabajo como servicio a la ciencia más que como búsqueda de gloria personal.',
0,
NOW(),
NOW()
),
(
'Sacrificio',
'sacrificio',
'value',
'Renuncia a comodidad o beneficio personal por un objetivo mayor. Marie sacrificó confort, salud, riqueza potencial y tiempo con familia para dedicarse a la ciencia. Literalmente dio su vida por su investigación, muriendo por exposición a radiación.',
0,
NOW(),
NOW()
),
(
'Igualdad',
'igualdad',
'value',
'Principio de que todas las personas merecen trato equitativo. Marie Curie luchó por ser reconocida como científica igual a sus colegas hombres. Su matrimonio con Pierre se basó en igualdad intelectual poco común para su época.',
0,
NOW(),
NOW()
),
(
'Patriotismo',
'patriotismo',
'value',
'Amor y lealtad al país de origen. Marie Curie nunca olvidó sus raíces polacas: nombró el Polonio por Polonia, enseñó polaco a sus hijas, y consideró regresar a Polonia múltiples veces. Su patriotismo coexistía con su identidad como científica internacional.',
0,
NOW(),
NOW()
),
-- ============================================================================
-- CATEGORÍA: Métodos y Técnicas (method)
-- ============================================================================
(
'Método Científico',
'metodo-cientifico',
'method',
'Proceso sistemático de investigación basado en observación, hipótesis, experimentación y análisis. Marie Curie ejemplificó el método científico riguroso: observó anomalías en minerales de uranio, formuló hipótesis sobre nuevos elementos, experimentó sistemáticamente, y llegó a conclusiones basadas en datos.',
0,
NOW(),
NOW()
),
(
'Cristalización Fraccionada',
'cristalizacion-fraccionada',
'method',
'Técnica química para separar sustancias basada en diferencias de solubilidad. Marie Curie usó cristalización fraccionada repetida (docenas de veces) para purificar gradualmente el Radio de toneladas de pechblenda hasta obtener una fracción de gramo puro.',
0,
NOW(),
NOW()
),
(
'Medición de Radioactividad',
'medicion-de-radioactividad',
'method',
'Técnicas para cuantificar emisiones radioactivas. Marie Curie desarrolló métodos precisos usando el electrómetro piezoeléctrico de cuarzo inventado por Pierre y su hermano, midiendo la conductividad eléctrica del aire ionizado por radiación.',
0,
NOW(),
NOW()
)
ON CONFLICT (tag_slug)
DO UPDATE SET
tag_name = EXCLUDED.tag_name,
description = EXCLUDED.description,
updated_at = NOW();
-- ============================================================================
-- FIN: Tags Seeds
-- ============================================================================

View File

@ -0,0 +1,124 @@
-- =====================================================
-- Seeds: Reglas básicas de moderación automática
-- Schema: content_management
-- Descripción: Reglas de ejemplo para el sistema de moderación
-- Relacionado: EXT-002 (Admin Extendido - Moderación Automática)
-- Fecha: 2025-11-11
-- =====================================================
-- Insertar usuario sistema para created_by (si no existe)
INSERT INTO auth.users (id, email, encrypted_password, gamilit_role)
VALUES (
'00000000-0000-0000-0000-000000000001',
'system@gamilit.com',
'n/a', -- No se puede hacer login con esta cuenta
'super_admin'
)
ON CONFLICT (email) DO NOTHING;
-- Insertar reglas básicas de moderación
INSERT INTO content_management.moderation_rules
(rule_name, rule_type, target_entity, rule_config, action, severity, auto_execute, require_review, is_active, priority, created_by) VALUES
-- ===== REGLAS CRÍTICAS (auto_execute = true) =====
-- Palabras prohibidas críticas (SPAM, phishing, malware)
('Palabras Prohibidas - Crítico', 'keyword', 'content',
'{
"keywords": ["spam", "phishing", "scam", "malware", "virus", "hack"],
"case_sensitive": false,
"match_whole_word": true
}'::jsonb,
'block', 'critical', true, true, true, 100,
'00000000-0000-0000-0000-000000000001'),
-- Contenido ofensivo extremo
('Lenguaje Ofensivo Extremo', 'keyword', 'content',
'{
"keywords": ["palabra1", "palabra2", "palabra3"],
"case_sensitive": false,
"match_whole_word": true
}'::jsonb,
'block', 'critical', true, true, true, 95,
'00000000-0000-0000-0000-000000000001'),
-- ===== REGLAS DE ALTA PRIORIDAD =====
-- Detección de números de teléfono
('Detección Teléfonos', 'pattern', 'content',
'{
"regex": "\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b",
"description": "Detecta números de teléfono formato XXX-XXX-XXXX",
"case_sensitive": false
}'::jsonb,
'flag', 'high', false, true, true, 80,
'00000000-0000-0000-0000-000000000001'),
-- Detección de emails (posible spam)
('Detección Emails', 'pattern', 'content',
'{
"regex": "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b",
"description": "Detecta direcciones de email",
"case_sensitive": false
}'::jsonb,
'flag', 'medium', false, true, true, 70,
'00000000-0000-0000-0000-000000000001'),
-- Detección de URLs sospechosas
('URLs Externas', 'pattern', 'content',
'{
"regex": "https?://[^\\s]+",
"description": "Detecta URLs en el contenido",
"case_sensitive": false
}'::jsonb,
'flag', 'medium', false, true, true, 60,
'00000000-0000-0000-0000-000000000001'),
-- ===== REGLAS DE CALIDAD =====
-- Contenido excesivamente corto
('Contenido Muy Corto', 'length', 'content',
'{
"min_length": 10,
"max_length": 999999,
"action_on": "below"
}'::jsonb,
'flag', 'low', false, false, true, 20,
'00000000-0000-0000-0000-000000000001'),
-- Contenido excesivamente largo (posible spam)
('Contenido Muy Largo', 'length', 'content',
'{
"min_length": 0,
"max_length": 10000,
"action_on": "exceed"
}'::jsonb,
'flag', 'medium', false, true, true, 30,
'00000000-0000-0000-0000-000000000001'),
-- ===== REGLAS PARA COMENTARIOS =====
-- Palabras prohibidas en comentarios
('Palabras Prohibidas - Comentarios', 'keyword', 'comment',
'{
"keywords": ["spam", "scam", "phishing"],
"case_sensitive": false,
"match_whole_word": true
}'::jsonb,
'flag', 'high', false, true, true, 85,
'00000000-0000-0000-0000-000000000001'),
-- ===== REGLAS PARA MENSAJES =====
-- Detección de spam en mensajes privados
('Spam en Mensajes', 'keyword', 'message',
'{
"keywords": ["oferta", "promoción", "descuento", "gratis", "premio"],
"case_sensitive": false,
"match_whole_word": false
}'::jsonb,
'flag', 'medium', false, true, true, 50,
'00000000-0000-0000-0000-000000000001');
-- Comentarios
COMMENT ON COLUMN content_management.moderation_rules.created_by IS 'Usuario que creó la regla (system user para seeds)';

View File

@ -0,0 +1,618 @@
-- ============================================================================
-- SEED: Ejercicios de Prueba para Validadores
-- Descripción: 15 ejercicios de prueba (uno por tipo) para testing de validadores
-- Autor: Database Agent
-- Fecha: 2025-11-19
-- Tarea: DB-117
-- Ambiente: DEV/TEST (no production)
-- ============================================================================
-- NOTA: Este seed requiere que exista al menos un módulo
-- Si no existe, crear módulo de prueba
DO $$
DECLARE
v_test_module_id UUID;
v_test_user_id UUID := 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'::uuid; -- Usuario demo teacher
BEGIN
-- ========================================================================
-- CREAR MÓDULO DE PRUEBA SI NO EXISTE
-- ========================================================================
SELECT id INTO v_test_module_id
FROM educational_content.modules
WHERE title = 'Módulo de Prueba - Validadores'
LIMIT 1;
IF v_test_module_id IS NULL THEN
INSERT INTO educational_content.modules (
id,
title,
description,
order_index,
is_active
) VALUES (
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid,
'Módulo de Prueba - Validadores',
'Módulo de prueba con ejercicios para testing de validadores',
99,
true
) RETURNING id INTO v_test_module_id;
RAISE NOTICE 'Módulo de prueba creado: %', v_test_module_id;
ELSE
RAISE NOTICE 'Módulo de prueba ya existe: %', v_test_module_id;
END IF;
-- ========================================================================
-- MÓDULO 1: COMPRENSIÓN LITERAL (5 ejercicios)
-- ========================================================================
-- 1. CRUCIGRAMA
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'11111111-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Crucigrama - Científicos Famosos',
'crucigrama',
1,
'{
"instructions": "Completa el crucigrama con nombres de científicos",
"clues": {
"h1": {"question": "Universidad donde estudió Marie Curie", "answer_length": 7},
"h2": {"question": "Premio que ganó Marie Curie", "answer_length": 5}
}
}'::jsonb,
'{
"clues": {
"h1": "SORBONA",
"h2": "NOBEL"
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 2. LÍNEA DE TIEMPO
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'22222222-2222-2222-2222-222222222222'::uuid,
v_test_module_id,
'TEST: Línea de Tiempo - Vida de Marie Curie',
'linea_tiempo',
2,
'{
"instructions": "Ordena los eventos cronológicamente",
"events": {
"event_1": {"text": "Nace en Polonia", "year": 1867},
"event_2": {"text": "Se muda a París", "year": 1891},
"event_3": {"text": "Gana primer Nobel", "year": 1903},
"event_4": {"text": "Gana segundo Nobel", "year": 1911}
}
}'::jsonb,
'{
"correctOrder": ["event_1", "event_2", "event_3", "event_4"]
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 3. SOPA DE LETRAS
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'33333333-3333-3333-3333-333333333333'::uuid,
v_test_module_id,
'TEST: Sopa de Letras - Elementos Químicos',
'sopa_letras',
3,
'{
"instructions": "Encuentra las palabras ocultas",
"grid": "...",
"words_to_find": ["RADIO", "POLONIO", "URANIO"]
}'::jsonb,
'{
"words": ["RADIO", "POLONIO", "URANIO"]
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 4. COMPLETAR ESPACIOS
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'44444444-4444-4444-4444-444444444444'::uuid,
v_test_module_id,
'TEST: Completar Espacios - Biografía',
'completar_espacios',
4,
'{
"instructions": "Completa los espacios en blanco",
"text": "Marie Curie fue una {{blank1}} polaca que ganó el premio {{blank2}} en {{blank3}}.",
"blanks": {
"blank1": {"hint": "Profesión"},
"blank2": {"hint": "Premio famoso"},
"blank3": {"hint": "Área de estudio"}
}
}'::jsonb,
'{
"blanks": {
"blank1": "científica",
"blank2": "Nobel",
"blank3": "física"
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 5. VERDADERO/FALSO
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'55555555-5555-5555-5555-555555555555'::uuid,
v_test_module_id,
'TEST: Verdadero/Falso - Datos de Marie Curie',
'verdadero_falso',
5,
'{
"instructions": "Indica si las afirmaciones son verdaderas o falsas",
"statements": {
"stmt1": {"text": "Marie Curie nació en Francia"},
"stmt2": {"text": "Ganó dos premios Nobel"},
"stmt3": {"text": "Descubrió el radio"}
}
}'::jsonb,
'{
"statements": {
"stmt1": false,
"stmt2": true,
"stmt3": true
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- ========================================================================
-- MÓDULO 2: COMPRENSIÓN INFERENCIAL (5 ejercicios)
-- ========================================================================
-- 6. DETECTIVE TEXTUAL
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'66666666-6666-6666-6666-666666666666'::uuid,
v_test_module_id,
'TEST: Detective Textual - Inferencias',
'detective_textual',
6,
'{
"instructions": "Responde las preguntas basándote en inferencias del texto",
"text": "Marie Curie trabajaba con materiales peligrosos sin protección...",
"questions": {
"q1": {
"question": "¿Por qué Marie Curie murió joven?",
"options": {
"option_a": "Accidente de laboratorio",
"option_b": "Exposición a radiación",
"option_c": "Enfermedad congénita"
}
}
}
}'::jsonb,
'{
"correctAnswers": {
"q1": "option_b"
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 7. CONSTRUCCIÓN DE HIPÓTESIS
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'77777777-7777-7777-7777-777777777777'::uuid,
v_test_module_id,
'TEST: Construcción de Hipótesis - Descubrimiento',
'construccion_hipotesis',
7,
'{
"instructions": "Construye una hipótesis sobre el descubrimiento del radio (mínimo 20 palabras)",
"context": "Marie Curie trabajó años con minerales radiactivos..."
}'::jsonb,
'{
"keywords": ["descubrió", "radio", "experimento", "investigación", "evidencia"],
"min_words": 20
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 8. PREDICCIÓN NARRATIVA
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'88888888-8888-8888-8888-888888888888'::uuid,
v_test_module_id,
'TEST: Predicción Narrativa - Consecuencias',
'prediccion_narrativa',
8,
'{
"instructions": "Predice qué pasará después (mínimo 30 palabras)",
"narrative": "Marie Curie acaba de ganar su primer Nobel en 1903..."
}'::jsonb,
'{
"keywords": ["continuará", "investigación", "premio", "descubrimiento"],
"min_words": 30
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 9. PUZZLE DE CONTEXTO
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'99999999-9999-9999-9999-999999999999'::uuid,
v_test_module_id,
'TEST: Puzzle de Contexto - Análisis Contextual',
'puzzle_contexto',
9,
'{
"instructions": "Analiza el contexto y responde",
"context": "A principios del siglo XX, pocas mujeres estudiaban ciencias...",
"questions": {
"q1": {
"question": "¿Qué podemos inferir del contexto?",
"options": {
"option_a": "Marie Curie fue excepcional",
"option_b": "Era común que mujeres fueran científicas",
"option_c": "No había discriminación"
}
}
}
}'::jsonb,
'{
"correctAnswers": {
"q1": "option_a"
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 10. RUEDA DE INFERENCIAS
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'aaaaaaaa-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Rueda de Inferencias - Causa y Efecto',
'rueda_inferencias',
10,
'{
"instructions": "Relaciona cada inferencia con su conclusión",
"inferences": {
"inf1": {"text": "Marie trabajó con materiales radiactivos sin protección"},
"inf2": {"text": "Dedicó toda su vida a la ciencia"}
},
"conclusions": ["conclusion1", "conclusion2"]
}'::jsonb,
'{
"correctInferences": {
"inf1": "conclusion1",
"inf2": "conclusion2"
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- ========================================================================
-- MÓDULO 3: PENSAMIENTO CRÍTICO (5 ejercicios)
-- ========================================================================
-- 11. TRIBUNAL DE OPINIONES
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'bbbbbbbb-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Tribunal de Opiniones - Argumento',
'tribunal_opiniones',
11,
'{
"instructions": "Escribe tu opinión argumentada sobre el papel de Marie Curie en la ciencia (mínimo 100 palabras)",
"topic": "Impacto de Marie Curie en la ciencia moderna"
}'::jsonb,
'{
"keywords": ["opinión", "argumento", "evidencia", "conclusión", "porque"],
"min_words": 100
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 12. DEBATE DIGITAL
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'cccccccc-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Debate Digital - Pros y Contras',
'debate_digital',
12,
'{
"instructions": "Presenta argumento y contraargumento sobre el uso de radiación (mínimo 150 palabras totales)",
"topic": "Uso de radiación en medicina"
}'::jsonb,
'{
"keywords": ["argumento", "contraargumento", "sin embargo", "por el contrario"],
"min_words": 150
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 13. ANÁLISIS DE FUENTES
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'dddddddd-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Análisis de Fuentes - Credibilidad',
'analisis_fuentes',
13,
'{
"instructions": "Analiza la credibilidad de las fuentes",
"sources": [
{
"id": "source1",
"title": "Artículo científico revisado por pares",
"author": "Dr. Smith, Universidad de Oxford"
}
],
"questions": {
"q1": {
"question": "¿Cuál es el nivel de credibilidad de la fuente 1?",
"options": {
"option_a": "Alta",
"option_b": "Media",
"option_c": "Baja"
}
}
}
}'::jsonb,
'{
"correctAnswers": {
"q1": "option_a"
},
"criticalQuestions": {
"q1": true
}
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 14. PODCAST ARGUMENTATIVO
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'eeeeeeee-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Podcast Argumentativo - Audio',
'podcast_argumentativo',
14,
'{
"instructions": "Graba un podcast argumentativo sobre Marie Curie (2-10 minutos)"
}'::jsonb,
'{
"min_duration_seconds": 120,
"max_duration_seconds": 600,
"max_size_mb": 50,
"allowed_formats": ["mp3", "m4a", "wav", "ogg"]
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
-- 15. MATRIZ DE PERSPECTIVAS
INSERT INTO educational_content.exercises (
id,
module_id,
title,
exercise_type,
order_index,
content,
solution,
auto_gradable,
max_points,
created_by
) VALUES (
'ffffffff-1111-1111-1111-111111111111'::uuid,
v_test_module_id,
'TEST: Matriz de Perspectivas - Análisis Multidimensional',
'matriz_perspectivas',
15,
'{
"instructions": "Analiza el tema desde diferentes perspectivas (mínimo 50 caracteres por celda)",
"topic": "Impacto de Marie Curie",
"perspectives": ["perspective1", "perspective2", "perspective3"]
}'::jsonb,
'{
"requiredPerspectives": ["perspective1", "perspective2", "perspective3"],
"min_chars_per_cell": 50
}'::jsonb,
true,
100,
v_test_user_id
) ON CONFLICT (module_id, exercise_type, order_index) DO NOTHING;
RAISE NOTICE '15 ejercicios de prueba creados exitosamente';
END $$;
-- ============================================================================
-- VALIDACIÓN
-- ============================================================================
-- Verificar que se crearon los 15 ejercicios
DO $$
DECLARE
v_count INTEGER;
BEGIN
SELECT COUNT(*) INTO v_count
FROM educational_content.exercises
WHERE module_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid;
IF v_count = 15 THEN
RAISE NOTICE '✅ VALIDACIÓN EXITOSA: 15 ejercicios de prueba creados';
ELSE
RAISE WARNING '⚠️ VALIDACIÓN FALLIDA: Se esperaban 15 ejercicios, se encontraron %', v_count;
END IF;
END $$;

View File

@ -0,0 +1,354 @@
-- =====================================================
-- Seed Data: Testing para Nuevos Validadores DB-117
-- =====================================================
-- Description: Ejercicios de prueba para validar los 3 nuevos validadores
-- Validators: validate_detective_connections, validate_prediction_scenarios, validate_cause_effect_matching
-- Created by: Database Agent - DB-117 (Ajustado de seed FE-059)
-- Date: 2025-11-19
-- Status: READY FOR TESTING
-- Purpose: Proveer datos de prueba compatibles con validadores implementados
-- =====================================================
SET search_path TO educational_content, public;
DO $$
DECLARE
mod_id UUID;
BEGIN
-- Crear o usar módulo de prueba
SELECT id INTO mod_id FROM educational_content.modules WHERE module_code = 'MOD-TEST-VALIDADORES';
IF mod_id IS NULL THEN
-- Crear módulo de prueba
INSERT INTO educational_content.modules (
module_code, title, description, order_index, is_published, status
) VALUES (
'MOD-TEST-VALIDADORES',
'[TEST] Módulo de Validadores DB-117',
'Módulo de prueba para testing de nuevos validadores',
999,
false,
'draft'
) RETURNING id INTO mod_id;
RAISE NOTICE 'Módulo de prueba creado con ID: %', mod_id;
END IF;
-- ========================================================================
-- TEST 1: DETECTIVE TEXTUAL - Conexión de Evidencias
-- ========================================================================
-- Tipo: detective_textual
-- Validador: validate_detective_connections()
-- Frontend DTO: { "connections": [{"from": "...", "to": "...", "relationship": "..."}] }
-- DB Solution: { "connections": [{"from": "...", "to": "...", "relationship": "..."}] }
-- ========================================================================
INSERT INTO educational_content.exercises (
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, enable_hints, hint_cost_ml_coins,
xp_reward, ml_coins_reward,
is_active, version
) VALUES (
mod_id,
'[TEST] Detective Textual: Investigación de los Descubrimientos de Curie',
'Conecta las Evidencias para Resolver el Caso',
'Analiza los documentos históricos y conecta las evidencias que están relacionadas. Explica la relación entre cada par de documentos.',
'Lee cada evidencia cuidadosamente. Arrastra desde un documento hacia otro para crear una conexión. Escribe una breve descripción de la relación que identificas.',
'detective_textual', 101,
'{
"allowMultipleConnections": true,
"minConnectionsRequired": 3,
"showEvidenceDetails": true,
"validationFunction": "validate_detective_connections"
}'::jsonb,
'{
"evidences": [
{
"id": "evidence-1",
"title": "Artículo Científico: Comptes Rendus (1898)",
"type": "publication",
"content": "Presentamos el descubrimiento de dos nuevos elementos altamente radiactivos. El primero, al que denominamos POLONIO en honor a mi país natal, y el segundo, el RADIO, presenta una radiactividad extraordinaria.",
"date": "1898-12-26",
"author": "Pierre y Marie Curie"
},
{
"id": "evidence-2",
"title": "Cuaderno de Laboratorio Personal de Marie",
"type": "notes",
"content": "15 de diciembre, 1898: Después de procesar toneladas de pechblenda, finalmente hemos aislado una muestra pura. Los experimentos del 10 al 14 de diciembre confirmaron que el nuevo elemento mantiene una actividad constante.",
"date": "1898-12-15",
"author": "Marie Curie"
},
{
"id": "evidence-3",
"title": "Carta a la Academia de Ciencias",
"type": "correspondence",
"content": "Informo formalmente sobre el descubrimiento que hemos anunciado en Comptes Rendus. Los experimentos documentados en nuestros cuadernos de laboratorio desde septiembre confirman la naturaleza extraordinaria de este hallazgo.",
"date": "1899-01-03",
"author": "Marie Curie"
},
{
"id": "evidence-4",
"title": "Acta de Reunión de la Academia",
"type": "official_record",
"content": "Se recibió la comunicación de Madame Curie sobre sus descubrimientos. Tras examinar las muestras y revisar la documentación experimental, la Academia reconoce la importancia de este trabajo.",
"date": "1899-01-18",
"author": "Academia de Ciencias de Francia"
}
]
}'::jsonb,
'{
"connections": [
{
"from": "evidence-1",
"to": "evidence-2",
"relationship": "documentation"
},
{
"from": "evidence-2",
"to": "evidence-3",
"relationship": "reference"
},
{
"from": "evidence-1",
"to": "evidence-3",
"relationship": "announcement"
},
{
"from": "evidence-3",
"to": "evidence-4",
"relationship": "response"
}
]
}'::jsonb,
'intermediate', 100, 70,
25, 3,
ARRAY[
'Busca fechas y referencias cruzadas entre documentos',
'El cuaderno de laboratorio suele ser la fuente primaria de los artículos',
'Las cartas oficiales típicamente hacen referencia a publicaciones previas'
]::text[],
true, 20,
150, 30,
true, 1
) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET
content = EXCLUDED.content,
solution = EXCLUDED.solution,
config = EXCLUDED.config,
updated_at = NOW();
-- ========================================================================
-- TEST 2: PREDICCIÓN NARRATIVA - Escenarios Múltiples
-- ========================================================================
-- Tipo: prediccion_narrativa
-- Validador: validate_prediction_scenarios()
-- Frontend DTO: { "scenarios": { "s1": "pred_a", "s2": "pred_b" } }
-- DB Solution: { "scenarios": { "scenario-1": "prediction-a", ... } }
-- ========================================================================
INSERT INTO educational_content.exercises (
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, enable_hints, hint_cost_ml_coins,
xp_reward, ml_coins_reward,
is_active, version
) VALUES (
mod_id,
'[TEST] Predicción Narrativa: Las Decisiones de Marie Curie',
'Predice las Acciones más Probables en Diferentes Escenarios',
'Basándote en el contexto histórico y el carácter de Marie Curie, predice qué decisión tomaría en cada situación hipotética.',
'Lee cada escenario cuidadosamente. Considera el contexto histórico, los valores de Marie, y sus motivaciones. Selecciona la predicción más probable.',
'prediccion_narrativa', 102,
'{
"showContext": true,
"allowExplanations": true,
"multipleScenarios": true,
"validationFunction": "validate_prediction_scenarios"
}'::jsonb,
'{
"introduction": "Marie Curie fue una científica pionera conocida por su dedicación a la ciencia, su altruismo, y su determinación ante la adversidad.",
"scenarios": [
{
"id": "scenario-1",
"title": "La Oferta de Patente (1902)",
"context": "Tras descubrir el proceso de aislamiento del radio, Marie y Pierre reciben una oferta millonaria para patentar el proceso. La patente les haría inmensamente ricos.",
"question": "¿Qué decisión tomaría Marie?",
"predictions": [
{"id": "prediction-a", "text": "Rechazar la patente para que la ciencia sea libre y accesible"},
{"id": "prediction-b", "text": "Aceptar la patente para financiar más investigación"},
{"id": "prediction-c", "text": "Patentar pero licenciar gratuitamente a instituciones científicas"}
]
},
{
"id": "scenario-2",
"title": "La Cátedra de la Sorbona (1906)",
"context": "La Universidad de la Sorbona ofrece a Marie suceder a su esposo como profesora de física, siendo la primera mujer profesora en 650 años.",
"question": "¿Qué haría Marie?",
"predictions": [
{"id": "prediction-d", "text": "Rechazar para continuar su investigación en el laboratorio"},
{"id": "prediction-e", "text": "Aceptar para inspirar a futuras mujeres científicas"},
{"id": "prediction-f", "text": "Delegar la cátedra en un colega masculino"}
]
},
{
"id": "scenario-3",
"title": "El Segundo Nobel (1911)",
"context": "Marie es nominada para un segundo Nobel, pero enfrenta un escándalo mediático. Algunos le aconsejan rechazar el Nobel para evitar controversia.",
"question": "¿Qué decisión tomaría Marie?",
"predictions": [
{"id": "prediction-g", "text": "Rechazar el Nobel para proteger su reputación"},
{"id": "prediction-h", "text": "Retirarse de la vida pública por completo"},
{"id": "prediction-i", "text": "Aceptar el Nobel con dignidad, separando vida personal de logros científicos"}
]
},
{
"id": "scenario-4",
"title": "La Primera Guerra Mundial (1914)",
"context": "Cuando estalla la Primera Guerra Mundial, Marie podría continuar su investigación en seguridad o contribuir directamente.",
"question": "¿Cómo contribuiría Marie?",
"predictions": [
{"id": "prediction-j", "text": "Permanecer en su laboratorio investigando"},
{"id": "prediction-k", "text": "Desarrollar unidades móviles de rayos X para hospitales de campaña"},
{"id": "prediction-l", "text": "Emigrar a un país neutral para proteger su trabajo"}
]
}
]
}'::jsonb,
'{
"scenarios": {
"scenario-1": "prediction-a",
"scenario-2": "prediction-e",
"scenario-3": "prediction-i",
"scenario-4": "prediction-k"
}
}'::jsonb,
'advanced', 100, 75,
20, 3,
ARRAY[
'Considera los valores documentados de Marie: altruismo, dedicación científica',
'Marie históricamente eligió el beneficio de la humanidad sobre el beneficio personal',
'Nunca se rindió ante la adversidad o los prejuicios de género'
]::text[],
true, 20,
150, 30,
true, 1
) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET
content = EXCLUDED.content,
solution = EXCLUDED.solution,
config = EXCLUDED.config,
updated_at = NOW();
-- ========================================================================
-- TEST 3: CONSTRUCCIÓN DE HIPÓTESIS - Asociación Causa-Efecto
-- ========================================================================
-- Tipo: construccion_hipotesis (NO causa_efecto - ese tipo no existe en ENUM)
-- Validador: validate_cause_effect_matching()
-- Frontend DTO: { "causes": { "c1": ["cons1", "cons2"], "c2": ["cons3"] } }
-- DB Solution: { "causes": { "cause-1": ["consequence-a", ...], ... } }
-- ========================================================================
INSERT INTO educational_content.exercises (
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, enable_hints, hint_cost_ml_coins,
xp_reward, ml_coins_reward,
is_active, version
) VALUES (
mod_id,
'[TEST] Causa-Efecto: Impacto de los Descubrimientos de Curie',
'Asocia Cada Causa con sus Consecuencias',
'Identifica las consecuencias (efectos) que resultaron de cada causa relacionada con la vida y trabajo de Marie Curie.',
'Lee cada causa en la columna izquierda. Arrastra las consecuencias correspondientes desde la columna derecha. Una causa puede tener 1-3 consecuencias.',
'construccion_hipotesis', 103,
'{
"allowMultiple": true,
"dragAndDrop": true,
"showFeedback": true,
"visualStyle": "columns",
"validationFunction": "validate_cause_effect_matching"
}'::jsonb,
'{
"causes": [
{
"id": "cause-1",
"text": "Descubrimiento del radio y sus propiedades radiactivas (1898)",
"category": "scientific_discovery"
},
{
"id": "cause-2",
"text": "Decisión de no patentar el proceso de aislamiento del radio",
"category": "ethical_decision"
},
{
"id": "cause-3",
"text": "Exposición prolongada a radiación sin medidas de protección",
"category": "health_risk"
},
{
"id": "cause-4",
"text": "Ser la primera mujer profesora en la Universidad de la Sorbona (1906)",
"category": "social_milestone"
},
{
"id": "cause-5",
"text": "Ganar dos Premios Nobel en diferentes disciplinas (1903 y 1911)",
"category": "recognition"
}
],
"consequences": [
{"id": "consequence-a", "text": "Desarrollo de tratamientos de radioterapia contra el cáncer"},
{"id": "consequence-b", "text": "Fundación de la física nuclear como disciplina"},
{"id": "consequence-c", "text": "Descubrimiento de la fisión nuclear por otros científicos"},
{"id": "consequence-d", "text": "Otros científicos pudieron replicar la investigación libremente"},
{"id": "consequence-e", "text": "No obtuvieron beneficios económicos significativos"},
{"id": "consequence-f", "text": "La medicina nuclear se desarrolló más rápidamente"},
{"id": "consequence-g", "text": "Marie desarrolló anemia aplásica y problemas de salud graves"},
{"id": "consequence-h", "text": "Se establecieron protocolos de seguridad radiológica"},
{"id": "consequence-i", "text": "Sus cuadernos siguen siendo radiactivos hoy en día"},
{"id": "consequence-j", "text": "Inspiró a generaciones de mujeres a estudiar ciencia"},
{"id": "consequence-k", "text": "Rompió barreras de género en la academia francesa"},
{"id": "consequence-l", "text": "Estableció que las mujeres podían alcanzar el más alto nivel científico"},
{"id": "consequence-m", "text": "Se convirtió en un icono científico reconocido mundialmente"},
{"id": "consequence-n", "text": "Demostró que era posible ganar Nobel en más de una disciplina"},
{"id": "consequence-o", "text": "Incrementó el prestigio de Francia en la comunidad científica"}
]
}'::jsonb,
'{
"causes": {
"cause-1": ["consequence-a", "consequence-b", "consequence-c"],
"cause-2": ["consequence-d", "consequence-e", "consequence-f"],
"cause-3": ["consequence-g", "consequence-h", "consequence-i"],
"cause-4": ["consequence-j", "consequence-k", "consequence-l"],
"cause-5": ["consequence-m", "consequence-n", "consequence-o"]
}
}'::jsonb,
'advanced', 100, 70,
25, 3,
ARRAY[
'Piensa en tres tipos de efectos: inmediatos, a largo plazo, y en otros',
'Una causa científica puede tener efectos médicos, sociales y en futuras investigaciones',
'Las decisiones éticas tienen consecuencias tanto positivas como negativas'
]::text[],
true, 20,
150, 30,
true, 1
) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET
content = EXCLUDED.content,
solution = EXCLUDED.solution,
config = EXCLUDED.config,
updated_at = NOW();
RAISE NOTICE '✅ Seeds de testing para nuevos validadores DB-117 creados exitosamente';
RAISE NOTICE '📝 Ejercicios creados con order_index 101, 102, 103';
RAISE NOTICE '🔧 Formato ajustado para compatibilidad con validadores implementados';
END $$;

View File

@ -0,0 +1,501 @@
-- =====================================================
-- Seed Data: Testing para Nuevos Validadores FE-059
-- =====================================================
-- Description: Ejercicios de prueba para validar los 3 nuevos validadores
-- Validators: validate_detective_connections, validate_prediction_scenarios, validate_causa_efecto_matching
-- Created by: Frontend Agent - FE-059
-- Date: 2025-11-19
-- Status: TESTING
-- Purpose: Proveer datos de prueba para validación de nuevos formatos DTO
-- =====================================================
SET search_path TO educational_content, public;
DO $$
DECLARE
mod_id UUID;
BEGIN
-- Usar Módulo 2 para testing
SELECT id INTO mod_id FROM educational_content.modules WHERE module_code = 'MOD-02-INFERENCIAL';
IF mod_id IS NULL THEN
RAISE EXCEPTION 'Módulo MOD-02-INFERENCIAL no encontrado';
END IF;
-- ========================================================================
-- TEST 1: DETECTIVE TEXTUAL - Conexión de Evidencias (NUEVO FORMATO)
-- ========================================================================
-- Tipo: detective_textual
-- Subtipo: connections
-- Frontend DTO: { "connections": [{"from": "...", "to": "...", "relationship": "..."}] }
-- ========================================================================
INSERT INTO educational_content.exercises (
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, enable_hints, hint_cost_ml_coins,
xp_reward, ml_coins_reward,
is_active, version
) VALUES (
mod_id,
'[TEST] Detective Textual: Investigación de los Descubrimientos de Curie',
'Conecta las Evidencias para Resolver el Caso',
'Analiza los documentos históricos y conecta las evidencias que están relacionadas. Explica la relación entre cada par de documentos.',
'Lee cada evidencia cuidadosamente. Arrastra desde un documento hacia otro para crear una conexión. Escribe una breve descripción de la relación que identificas.',
'detective_textual', 101, -- order_index alto para distinguir de producción
'{
"allowMultipleConnections": true,
"minConnectionsRequired": 3,
"showEvidenceDetails": true
}'::jsonb,
'{
"evidences": [
{
"id": "evidence-1",
"title": "Artículo Científico: Comptes Rendus (1898)",
"type": "publication",
"content": "Presentamos en esta comunicación el descubrimiento de dos nuevos elementos altamente radiactivos. El primero, al que denominamos POLONIO en honor a mi país natal, y el segundo, el RADIO, presenta una radiactividad extraordinaria, aproximadamente un millón de veces mayor que la del uranio. Estos descubrimientos abren nuevas fronteras en la comprensión de la materia.",
"date": "1898-12-26",
"author": "Pierre y Marie Curie"
},
{
"id": "evidence-2",
"title": "Cuaderno de Laboratorio Personal de Marie",
"type": "notes",
"content": "15 de diciembre, 1898: Después de procesar toneladas de pechblenda, finalmente hemos aislado una muestra pura. La radiación que emite es asombrosa. Pierre me advierte sobre el tiempo de exposición, pero la emoción del descubrimiento es abrumadora. Los experimentos del 10 al 14 de diciembre confirmaron que el nuevo elemento mantiene una actividad constante incluso después de varios días.",
"date": "1898-12-15",
"author": "Marie Curie"
},
{
"id": "evidence-3",
"title": "Carta a la Academia de Ciencias",
"type": "correspondence",
"content": "Distinguidos miembros de la Academia: Me dirijo a ustedes para informar formalmente sobre el descubrimiento que hemos anunciado en Comptes Rendus. Adjunto muestras del nuevo elemento radiactivo. Solicitamos respetuosamente que se considere nuestro trabajo para una presentación formal ante la Academia. Los experimentos documentados en nuestros cuadernos de laboratorio desde septiembre confirman la naturaleza extraordinaria de este hallazgo.",
"date": "1899-01-03",
"author": "Marie Curie"
},
{
"id": "evidence-4",
"title": "Acta de Reunión de la Academia",
"type": "official_record",
"content": "En la sesión del 18 de enero de 1899, se recibió la comunicación de Madame Curie sobre sus descubrimientos. Tras examinar las muestras presentadas y revisar la documentación experimental, la Academia reconoce la importancia de este trabajo. Se aprueba la publicación oficial y se recomienda para consideración del premio científico anual.",
"date": "1899-01-18",
"author": "Academia de Ciencias de Francia"
}
],
"instructions": {
"step1": "Lee cada evidencia cuidadosamente",
"step2": "Identifica relaciones entre los documentos (referencias cruzadas, cronología, confirmaciones)",
"step3": "Crea conexiones arrastrando desde un documento hacia otro",
"step4": "Describe la relación que identificaste"
}
}'::jsonb,
'{
"connections": [
{
"from": "evidence-1",
"to": "evidence-2",
"relationship": "documentation",
"requiredKeywords": ["experimento", "descubrimiento", "radiación", "diciembre"],
"explanation": "El artículo científico del 26 de diciembre describe los descubrimientos que fueron documentados en el cuaderno de laboratorio del 15 de diciembre. Los experimentos del cuaderno precedieron y fundamentaron la publicación."
},
{
"from": "evidence-2",
"to": "evidence-3",
"relationship": "reference",
"requiredKeywords": ["experimentos", "cuadernos", "laboratorio", "documentados"],
"explanation": "La carta menciona explícitamente los experimentos documentados en los cuadernos de laboratorio desde septiembre, haciendo referencia directa a las notas de Marie."
},
{
"from": "evidence-1",
"to": "evidence-3",
"relationship": "announcement",
"requiredKeywords": ["descubrimiento", "anunciado", "Comptes Rendus", "publicación"],
"explanation": "La carta a la Academia menciona el descubrimiento que fue anunciado en Comptes Rendus (el artículo científico), estableciendo una conexión directa entre ambos documentos."
},
{
"from": "evidence-3",
"to": "evidence-4",
"relationship": "response",
"requiredKeywords": ["comunicación", "muestras", "documentación", "trabajo"],
"explanation": "El acta de la Academia responde directamente a la carta de Marie, mencionando la recepción de su comunicación, el examen de las muestras, y la revisión de la documentación."
}
],
"minCorrectConnections": 3,
"allowPartialCredit": true,
"scoringRules": {
"perfectScore": 100,
"minPassingConnections": 3,
"keywordWeight": 0.4,
"relationshipWeight": 0.6
}
}'::jsonb,
'intermediate', 100, 70,
25, 3,
ARRAY[
'Busca fechas y referencias cruzadas entre documentos',
'El cuaderno de laboratorio suele ser la fuente primaria de los artículos científicos',
'Las cartas oficiales típicamente hacen referencia a publicaciones previas',
'Los actas de reuniones responden a comunicaciones formales'
]::text[],
true, 20,
150, 30,
true, 1
) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET
content = EXCLUDED.content,
solution = EXCLUDED.solution,
updated_at = NOW();
-- ========================================================================
-- TEST 2: PREDICCIÓN NARRATIVA - Escenarios Múltiples (NUEVO FORMATO)
-- ========================================================================
-- Tipo: prediccion_narrativa
-- Subtipo: scenarios
-- Frontend DTO: { "scenarios": { "s1": "pred_a", "s2": "pred_b" } }
-- ========================================================================
INSERT INTO educational_content.exercises (
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, enable_hints, hint_cost_ml_coins,
xp_reward, ml_coins_reward,
is_active, version
) VALUES (
mod_id,
'[TEST] Predicción Narrativa: Las Decisiones de Marie Curie',
'Predice las Acciones más Probables en Diferentes Escenarios',
'Basándote en el contexto histórico y el carácter de Marie Curie, predice qué decisión tomaría en cada situación hipotética.',
'Lee cada escenario cuidadosamente. Considera el contexto histórico, los valores de Marie, y sus motivaciones. Selecciona la predicción más probable.',
'prediccion_narrativa', 102,
'{
"showContext": true,
"allowExplanations": true,
"multipleScenarios": true
}'::jsonb,
'{
"introduction": "Marie Curie fue una científica pionera conocida por su dedicación a la ciencia, su altruismo, y su determinación ante la adversidad. A continuación, varios escenarios hipotéticos basados en momentos reales de su vida.",
"scenarios": [
{
"id": "scenario-1",
"title": "La Oferta de Patente (1902)",
"context": "Tras descubrir el proceso de aislamiento del radio, Marie y Pierre reciben una oferta millonaria de industriales estadounidenses para patentar el proceso. La patente les haría inmensamente ricos, pero limitaría el acceso de otros científicos al conocimiento.",
"question": "¿Qué decisión tomaría Marie?",
"predictions": [
{
"id": "prediction-a",
"text": "Rechazar la patente para que la ciencia sea libre y accesible",
"reasoning": "Histórico: Marie y Pierre rechazaron patentar por principios altruistas"
},
{
"id": "prediction-b",
"text": "Aceptar la patente para financiar más investigación",
"reasoning": "Pragmático pero contradice sus valores documentados"
},
{
"id": "prediction-c",
"text": "Patentar pero licenciar gratuitamente a instituciones científicas",
"reasoning": "Solución de compromiso poco práctica en esa época"
}
]
},
{
"id": "scenario-2",
"title": "La Cátedra de la Sorbona (1906)",
"context": "Después de la muerte de Pierre en 1906, la Universidad de la Sorbona ofrece a Marie suceder a su esposo como profesora de física, convirtiéndose en la primera mujer profesora en 650 años de historia de la universidad. Sin embargo, aceptar significa dejar temporalmente su investigación activa.",
"question": "¿Qué haría Marie?",
"predictions": [
{
"id": "prediction-d",
"text": "Rechazar para continuar su investigación en el laboratorio",
"reasoning": "Prioriza investigación pero ignora oportunidad histórica"
},
{
"id": "prediction-e",
"text": "Aceptar para inspirar a futuras mujeres científicas",
"reasoning": "Histórico: Marie aceptó esta posición en 1906"
},
{
"id": "prediction-f",
"text": "Delegar la cátedra en un colega masculino",
"reasoning": "Contradice su lucha por la igualdad de género"
}
]
},
{
"id": "scenario-3",
"title": "El Segundo Nobel (1911)",
"context": "En 1911, Marie es nominada para un segundo Premio Nobel, esta vez en Química por el aislamiento del radio puro. Sin embargo, simultáneamente enfrenta un escándalo mediático por su relación con el científico Paul Langevin. Algunos le aconsejan rechazar el Nobel para evitar más controversia.",
"question": "¿Qué decisión tomaría Marie?",
"predictions": [
{
"id": "prediction-g",
"text": "Rechazar el Nobel para proteger su reputación",
"reasoning": "Defensivo pero contradice su valentía característica"
},
{
"id": "prediction-h",
"text": "Retirarse de la vida pública por completo",
"reasoning": "Rendirse no coincide con su personalidad perseverante"
},
{
"id": "prediction-i",
"text": "Aceptar el Nobel con dignidad, separando vida personal de logros científicos",
"reasoning": "Histórico: Marie asistió a la ceremonia con orgullo en Estocolmo"
}
]
},
{
"id": "scenario-4",
"title": "La Primera Guerra Mundial (1914)",
"context": "Cuando estalla la Primera Guerra Mundial en 1914, Marie tiene 47 años y es una científica consagrada con dos Premios Nobel. Francia moviliza a sus ciudadanos para el esfuerzo bélico. Marie podría continuar su investigación en seguridad o contribuir directamente.",
"question": "¿Cómo contribuiría Marie?",
"predictions": [
{
"id": "prediction-j",
"text": "Permanecer en su laboratorio investigando",
"reasoning": "Seguro pero no refleja su compromiso patriótico"
},
{
"id": "prediction-k",
"text": "Desarrollar unidades móviles de rayos X para hospitales de campaña",
"reasoning": "Histórico: Marie creó las petites Curies, ambulancias radiológicas"
},
{
"id": "prediction-l",
"text": "Emigrar a un país neutral para proteger su trabajo",
"reasoning": "Contradice su amor por Francia adoptiva"
}
]
}
]
}'::jsonb,
'{
"scenarios": {
"scenario-1": "prediction-a",
"scenario-2": "prediction-e",
"scenario-3": "prediction-i",
"scenario-4": "prediction-k"
},
"explanations": {
"scenario-1": "Marie y Pierre Curie rechazaron patentar el proceso de aislamiento del radio, creyendo que el conocimiento científico debía ser libre para el beneficio de la humanidad. Esta decisión altruista les costó una fortuna potencial.",
"scenario-2": "Marie Curie aceptó la cátedra en la Sorbona en 1906, convirtiéndose en la primera mujer profesora en su historia. Combinó la docencia con su investigación, inspirando a generaciones futuras.",
"scenario-3": "A pesar del escándalo mediático, Marie viajó a Estocolmo en diciembre de 1911 para recibir su segundo Nobel con dignidad, separando su vida privada de sus logros científicos. Se convirtió en la primera persona en ganar dos Premios Nobel.",
"scenario-4": "Durante la Primera Guerra Mundial, Marie desarrolló las petites Curies, unidades móviles de rayos X que salvaron miles de vidas. Incluso aprendió a conducir y reparar los vehículos ella misma, viajando al frente de batalla."
},
"scoringRules": {
"perfectScore": 100,
"partialCredit": true,
"pointsPerScenario": 25
}
}'::jsonb,
'advanced', 100, 75,
20, 3,
ARRAY[
'Considera los valores documentados de Marie: altruismo, dedicación científica, determinación',
'Marie históricamente eligió el beneficio de la humanidad sobre el beneficio personal',
'Nunca se rindió ante la adversidad o los prejuicios de género',
'Combinaba idealismo científico con pragmatismo cuando era necesario'
]::text[],
true, 20,
150, 30,
true, 1
) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET
content = EXCLUDED.content,
solution = EXCLUDED.solution,
updated_at = NOW();
-- ========================================================================
-- TEST 3: CAUSA-EFECTO - Asociación/Matching (NUEVO FORMATO)
-- ========================================================================
-- Tipo: construccion_hipotesis
-- Subtipo: matching
-- Frontend DTO: { "causes": { "c1": ["cons1", "cons2"], "c2": ["cons3"] } }
-- ========================================================================
INSERT INTO educational_content.exercises (
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, enable_hints, hint_cost_ml_coins,
xp_reward, ml_coins_reward,
is_active, version
) VALUES (
mod_id,
'[TEST] Causa-Efecto: Impacto de los Descubrimientos de Curie',
'Asocia Cada Causa con sus Consecuencias',
'Identifica las consecuencias (efectos) que resultaron de cada causa relacionada con la vida y trabajo de Marie Curie. Una causa puede tener múltiples efectos.',
'Lee cada causa en la columna izquierda. Arrastra las consecuencias correspondientes desde la columna derecha. Una causa puede tener 1-3 consecuencias. Piensa en efectos inmediatos, a largo plazo y en otros.',
'construccion_hipotesis', 103,
'{
"allowMultiple": true,
"dragAndDrop": true,
"showFeedback": true,
"visualStyle": "columns"
}'::jsonb,
'{
"causes": [
{
"id": "cause-1",
"text": "Descubrimiento del radio y sus propiedades radiactivas (1898)",
"category": "scientific_discovery",
"icon": "flask"
},
{
"id": "cause-2",
"text": "Decisión de no patentar el proceso de aislamiento del radio",
"category": "ethical_decision",
"icon": "handshake"
},
{
"id": "cause-3",
"text": "Exposición prolongada a radiación sin medidas de protección",
"category": "health_risk",
"icon": "alert-triangle"
},
{
"id": "cause-4",
"text": "Ser la primera mujer profesora en la Universidad de la Sorbona (1906)",
"category": "social_milestone",
"icon": "award"
},
{
"id": "cause-5",
"text": "Ganar dos Premios Nobel en diferentes disciplinas (1903 y 1911)",
"category": "recognition",
"icon": "trophy"
}
],
"consequences": [
{
"id": "consequence-a",
"text": "Desarrollo de tratamientos de radioterapia contra el cáncer",
"type": "medical_advancement"
},
{
"id": "consequence-b",
"text": "Fundación de la física nuclear como disciplina",
"type": "scientific_field"
},
{
"id": "consequence-c",
"text": "Descubrimiento de la fisión nuclear por otros científicos",
"type": "future_discoveries"
},
{
"id": "consequence-d",
"text": "Otros científicos pudieron replicar y continuar la investigación libremente",
"type": "knowledge_sharing"
},
{
"id": "consequence-e",
"text": "No obtuvieron beneficios económicos significativos de su descubrimiento",
"type": "financial_impact"
},
{
"id": "consequence-f",
"text": "La medicina nuclear se desarrolló más rápidamente",
"type": "medical_advancement"
},
{
"id": "consequence-g",
"text": "Marie desarrolló anemia aplásica y otros problemas de salud graves",
"type": "health_consequence"
},
{
"id": "consequence-h",
"text": "Se establecieron posteriormente protocolos de seguridad radiológica",
"type": "safety_regulations"
},
{
"id": "consequence-i",
"text": "Sus cuadernos de laboratorio siguen siendo radiactivos hoy en día",
"type": "lasting_effect"
},
{
"id": "consequence-j",
"text": "Inspiró a generaciones de mujeres a estudiar ciencia",
"type": "social_impact"
},
{
"id": "consequence-k",
"text": "Rompió barreras de género en la academia francesa",
"type": "social_change"
},
{
"id": "consequence-l",
"text": "Estableció que las mujeres podían alcanzar el más alto nivel científico",
"type": "gender_equality"
},
{
"id": "consequence-m",
"text": "Se convirtió en un icono científico reconocido mundialmente",
"type": "legacy"
},
{
"id": "consequence-n",
"text": "Demostró que era posible ganar Nobel en más de una disciplina",
"type": "achievement"
},
{
"id": "consequence-o",
"text": "Incrementó el prestigio de Francia en la comunidad científica internacional",
"type": "national_pride"
}
],
"layout": {
"causesColumn": "left",
"consequencesColumn": "right",
"showConnections": true
}
}'::jsonb,
'{
"causes": {
"cause-1": ["consequence-a", "consequence-b", "consequence-c"],
"cause-2": ["consequence-d", "consequence-e", "consequence-f"],
"cause-3": ["consequence-g", "consequence-h", "consequence-i"],
"cause-4": ["consequence-j", "consequence-k", "consequence-l"],
"cause-5": ["consequence-m", "consequence-n", "consequence-o"]
},
"allowPartialMatches": true,
"strictOrder": false,
"scoringRules": {
"perfectScore": 100,
"pointsPerCorrectMatch": 6.67,
"minPassingMatches": 11,
"allowPartialCredit": true
},
"explanations": {
"cause-1": "El descubrimiento del radio revolucionó la medicina (radioterapia), fundó una nueva rama de la física, y abrió el camino para descubrimientos futuros como la fisión nuclear.",
"cause-2": "Al no patentar, permitieron que otros científicos accedieran libremente al conocimiento, acelerando el desarrollo de la medicina nuclear, aunque sacrificando ganancias personales.",
"cause-3": "La exposición sin protección causó problemas de salud graves a Marie, evidenció la necesidad de seguridad radiológica, y contaminó permanentemente sus materiales de trabajo.",
"cause-4": "Su nombramiento histórico inspiró a mujeres científicas, rompió barreras institucionales de género, y estableció un precedente de igualdad en la academia.",
"cause-5": "Sus dos Nobel consolidaron su legado mundial, demostraron la posibilidad de excelencia multidisciplinaria, e incrementaron el prestigio científico de Francia."
}
}'::jsonb,
'advanced', 100, 70,
25, 3,
ARRAY[
'Piensa en tres tipos de efectos: inmediatos, a largo plazo, y en otros',
'Una causa científica puede tener efectos médicos, sociales y en futuras investigaciones',
'Las decisiones éticas tienen consecuencias tanto positivas como negativas',
'Los riesgos de salud pueden generar efectos duraderos y cambios en políticas de seguridad'
]::text[],
true, 20,
150, 30,
true, 1
) ON CONFLICT (module_id, exercise_type, order_index) DO UPDATE SET
content = EXCLUDED.content,
solution = EXCLUDED.solution,
updated_at = NOW();
RAISE NOTICE 'Seeds de testing para nuevos validadores FE-059 creados exitosamente';
RAISE NOTICE 'Ejercicios creados con order_index 101, 102, 103 para distinguirlos de producción';
END $$;

View File

@ -0,0 +1,162 @@
-- =====================================================
-- Seed Data: Initialize User Gamification (DEV ONLY)
-- =====================================================
-- Description: Inicializa user_stats y user_ranks para usuarios existentes
-- Environment: DEVELOPMENT ONLY (NO production/staging)
-- Date: 2025-11-02
-- Migrated by: SA-SEEDS-GAM-01
-- =====================================================
SET search_path TO gamification_system, auth_management, auth, public;
BEGIN;
-- =====================================================
-- INICIALIZAR USER_STATS
-- =====================================================
INSERT INTO gamification_system.user_stats (
user_id,
tenant_id,
level,
total_xp,
xp_to_next_level,
ml_coins,
ml_coins_earned_total,
ml_coins_spent_total,
current_streak,
max_streak,
days_active_total,
exercises_completed,
modules_completed,
total_score,
average_score,
achievements_earned,
certificates_earned,
sessions_count,
weekly_xp,
monthly_xp,
weekly_exercises,
created_at,
updated_at
)
SELECT
u.id,
p.tenant_id,
1,
0,
100,
100,
100,
0,
0,
0,
0,
0,
0,
0,
0.0,
0,
0,
0,
0,
0,
0,
NOW(),
NOW()
FROM auth.users u
LEFT JOIN auth_management.profiles p ON u.id = p.user_id
WHERE u.deleted_at IS NULL
AND NOT EXISTS (
SELECT 1 FROM gamification_system.user_stats us
WHERE us.user_id = u.id
);
-- =====================================================
-- INICIALIZAR USER_RANKS
-- =====================================================
INSERT INTO gamification_system.user_ranks (
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,
is_current,
achieved_at,
created_at,
updated_at
)
SELECT
u.id,
p.tenant_id,
'Ajaw', -- Rango inicial Maya (nivel 1)
NULL,
0,
2,
0,
500,
0,
0,
true,
NOW(),
NOW(),
NOW()
FROM auth.users u
LEFT JOIN auth_management.profiles p ON u.id = p.user_id
WHERE u.deleted_at IS NULL
AND NOT EXISTS (
SELECT 1 FROM gamification_system.user_ranks ur
WHERE ur.user_id = u.id AND ur.is_current = true
);
COMMIT;
-- =====================================================
-- VERIFICACIÓN
-- =====================================================
DO $$
DECLARE
stats_count INT;
ranks_count INT;
users_count INT;
BEGIN
SELECT COUNT(*) INTO users_count FROM auth.users WHERE deleted_at IS NULL;
SELECT COUNT(*) INTO stats_count FROM gamification_system.user_stats;
SELECT COUNT(*) INTO ranks_count FROM gamification_system.user_ranks WHERE is_current = true;
RAISE NOTICE '';
RAISE NOTICE '========================================';
RAISE NOTICE ' Inicialización de Gamificación (DEV)';
RAISE NOTICE '========================================';
RAISE NOTICE 'Usuarios totales: %', users_count;
RAISE NOTICE 'User stats creados: %', stats_count;
RAISE NOTICE 'User ranks creados: %', ranks_count;
RAISE NOTICE '';
IF stats_count >= users_count AND ranks_count >= users_count THEN
RAISE NOTICE '✅ Todos los usuarios tienen stats y ranks inicializados';
ELSE
RAISE WARNING '⚠️ Algunos usuarios no tienen stats o ranks completos';
END IF;
RAISE NOTICE '========================================';
RAISE NOTICE '';
END $$;
-- =====================================================
-- MIGRATION NOTES
-- =====================================================
-- ENVIRONMENT: DEV ONLY
-- CORRECCIONES APLICADAS:
-- 1. Cambiado 'MERCENARIO' a 'mercenario' (lowercase para compatibilidad ENUM)
-- 2. Cambiado gamilit.now_mexico() a NOW() (función puede no existir aún)
-- 3. Agregado SET search_path para seguridad
-- 4. Envuelto en BEGIN/COMMIT para atomicidad
-- 5. Este script NO debe ejecutarse en production/staging
-- =====================================================

View File

@ -0,0 +1,360 @@
-- =====================================================================================
-- SEED: Demo Progress Data for Progress Tracking Schema
-- =====================================================================================
-- Description: Module progress and learning sessions for demo students
-- Dependencies: auth.users, educational_content.modules, educational_content.exercises
-- Idempotency: Uses ON CONFLICT to handle re-runs safely
-- =====================================================================================
SET search_path TO progress_tracking, educational_content, auth, public;
-- =====================================================================================
-- MODULE PROGRESS & LEARNING SESSIONS
-- =====================================================================================
DO $$
DECLARE
student1_id UUID;
student2_id UUID;
student3_id UUID;
module1_id UUID;
module2_id UUID;
module3_id UUID;
BEGIN
-- ==================================================================================
-- GET USER IDS (Demo Students)
-- ==================================================================================
SELECT id INTO student1_id
FROM auth.users
WHERE email = 'estudiante1@demo.glit.edu.mx';
SELECT id INTO student2_id
FROM auth.users
WHERE email = 'estudiante2@demo.glit.edu.mx';
SELECT id INTO student3_id
FROM auth.users
WHERE email = 'estudiante3@demo.glit.edu.mx';
-- ==================================================================================
-- GET MODULE IDS (Marie Curie Modules)
-- ==================================================================================
SELECT id INTO module1_id
FROM educational_content.modules
WHERE module_code = 'MOD-01-LITERAL';
SELECT id INTO module2_id
FROM educational_content.modules
WHERE module_code = 'MOD-02-INFERENCIAL';
SELECT id INTO module3_id
FROM educational_content.modules
WHERE module_code = 'MOD-03-CRITICA';
-- ==================================================================================
-- MODULE PROGRESS: Student Progress Tracking
-- ==================================================================================
RAISE NOTICE 'Inserting module progress data...';
INSERT INTO progress_tracking.module_progress (
user_id, module_id,
status, progress_percentage,
completed_exercises, total_exercises,
total_score, max_possible_score,
time_spent, attempts_count,
started_at, last_accessed_at, completed_at,
metadata, created_at, updated_at
) VALUES
-- ================================================================================
-- ESTUDIANTE 1: Advanced Student (2 modules active)
-- ================================================================================
-- Module 1: COMPLETED (Perfect Performance)
(
student1_id, module1_id,
'completed', 100,
5, 5,
480, 500,
45, 8,
NOW() - INTERVAL '10 days',
NOW() - INTERVAL '3 days',
NOW() - INTERVAL '3 days',
'{
"average_score_percentage": 96,
"completion_time_days": 7,
"streak_days": 5,
"comodines_used": ["pistas", "vision_lectora"],
"performance_trend": "improving",
"mastery_level": "advanced"
}'::jsonb,
NOW() - INTERVAL '10 days',
NOW() - INTERVAL '3 days'
),
-- Module 2: IN PROGRESS (Good Progress)
(
student1_id, module2_id,
'in_progress', 60,
3, 5,
285, 500,
30, 5,
NOW() - INTERVAL '2 days',
NOW() - INTERVAL '1 hour',
NULL,
'{
"average_score_percentage": 95,
"current_exercise": 4,
"comodines_used": ["pistas"],
"estimated_completion_days": 2,
"difficulty_level": "intermediate"
}'::jsonb,
NOW() - INTERVAL '2 days',
NOW() - INTERVAL '1 hour'
),
-- ================================================================================
-- ESTUDIANTE 2: Intermediate Student (Completed Module 1, Advanced Module 3)
-- ================================================================================
-- Module 1: COMPLETED (Good Performance)
(
student2_id, module1_id,
'completed', 100,
5, 5,
425, 500,
60, 12,
NOW() - INTERVAL '15 days',
NOW() - INTERVAL '5 days',
NOW() - INTERVAL '5 days',
'{
"average_score_percentage": 85,
"completion_time_days": 10,
"streak_days": 3,
"comodines_used": ["pistas", "segunda_oportunidad"],
"performance_trend": "stable",
"retry_rate": 40
}'::jsonb,
NOW() - INTERVAL '15 days',
NOW() - INTERVAL '5 days'
),
-- Module 3: IN PROGRESS (Challenge Level - Advanced)
(
student2_id, module3_id,
'in_progress', 40,
2, 5,
160, 500,
25, 4,
NOW() - INTERVAL '1 day',
NOW() - INTERVAL '2 hours',
NULL,
'{
"average_score_percentage": 80,
"current_exercise": 3,
"difficulty": "advanced",
"skipped_module_2": false,
"teacher_recommendation": "review inferential reading first"
}'::jsonb,
NOW() - INTERVAL '1 day',
NOW() - INTERVAL '2 hours'
),
-- ================================================================================
-- ESTUDIANTE 3: Foundational Student (Needs Support)
-- ================================================================================
-- Module 1: IN PROGRESS (Slower Pace, More Support Needed)
(
student3_id, module1_id,
'in_progress', 40,
2, 5,
150, 500,
35, 6,
NOW() - INTERVAL '5 days',
NOW() - INTERVAL '3 hours',
NULL,
'{
"average_score_percentage": 75,
"current_exercise": 3,
"comodines_used": ["pistas", "pistas", "vision_lectora"],
"support_level": "foundational",
"hints_per_exercise_avg": 2.5,
"teacher_intervention_suggested": true
}'::jsonb,
NOW() - INTERVAL '5 days',
NOW() - INTERVAL '3 hours'
)
ON CONFLICT (user_id, module_id) DO UPDATE SET
status = EXCLUDED.status,
progress_percentage = EXCLUDED.progress_percentage,
completed_exercises = EXCLUDED.completed_exercises,
total_score = EXCLUDED.total_score,
time_spent = EXCLUDED.time_spent,
attempts_count = EXCLUDED.attempts_count,
last_accessed_at = EXCLUDED.last_accessed_at,
completed_at = EXCLUDED.completed_at,
metadata = EXCLUDED.metadata,
updated_at = NOW();
RAISE NOTICE 'Module progress data inserted successfully';
-- ==================================================================================
-- LEARNING SESSIONS: Study Session Tracking
-- ==================================================================================
RAISE NOTICE 'Inserting learning sessions data...';
INSERT INTO progress_tracking.learning_sessions (
user_id, module_id,
started_at, ended_at, duration,
exercises_attempted, exercises_completed,
metadata, created_at
) VALUES
-- ================================================================================
-- ESTUDIANTE 1: Recent Active Sessions
-- ================================================================================
-- Session 1: Recent Module 2 Study (Focused & Productive)
(
student1_id, module2_id,
NOW() - INTERVAL '1 hour',
NOW() - INTERVAL '30 minutes',
30,
1, 1,
95, ARRAY[]::text[],
ARRAY['pistas']::text[],
'study',
'{
"device": "laptop",
"location": "home",
"focus_score": 85,
"interruptions": 0,
"browser": "chrome",
"screen_time_active_percentage": 95
}'::jsonb,
NOW() - INTERVAL '30 minutes'
),
-- Session 2: Yesterday Module 2 Study
(
student1_id, module2_id,
NOW() - INTERVAL '25 hours',
NOW() - INTERVAL '24 hours',
45,
2, 2,
190, ARRAY['quick_learner']::text[],
ARRAY['vision_lectora']::text[],
'study',
'{
"device": "laptop",
"location": "library",
"focus_score": 92,
"interruptions": 0,
"achievement_earned": "quick_learner"
}'::jsonb,
NOW() - INTERVAL '24 hours'
),
-- ================================================================================
-- ESTUDIANTE 2: Module 3 Sessions (Challenging Content)
-- ================================================================================
-- Session 1: Module 3 Study (Moderate Focus)
(
student2_id, module3_id,
NOW() - INTERVAL '3 hours',
NOW() - INTERVAL '2 hours',
60,
2, 1,
80, ARRAY[]::text[],
ARRAY['segunda_oportunidad']::text[],
'study',
'{
"device": "tablet",
"location": "school",
"focus_score": 75,
"interruptions": 2,
"retry_used": true,
"difficulty_feedback": "challenging but manageable"
}'::jsonb,
NOW() - INTERVAL '2 hours'
),
-- Session 2: Module 3 Review Session
(
student2_id, module3_id,
NOW() - INTERVAL '27 hours',
NOW() - INTERVAL '26 hours',
40,
1, 0,
0, ARRAY[]::text[],
ARRAY['pistas', 'pistas']::text[],
'review',
'{
"device": "tablet",
"location": "home",
"focus_score": 70,
"interruptions": 1,
"review_mode": true,
"notes": "struggling with critical analysis concepts"
}'::jsonb,
NOW() - INTERVAL '26 hours'
),
-- ================================================================================
-- ESTUDIANTE 3: Module 1 Sessions (Needs Support)
-- ================================================================================
-- Session 1: Recent Study with Interruptions
(
student3_id, module1_id,
NOW() - INTERVAL '4 hours',
NOW() - INTERVAL '3 hours',
45,
2, 1,
70, ARRAY[]::text[],
ARRAY['pistas', 'vision_lectora']::text[],
'study',
'{
"device": "mobile",
"location": "home",
"focus_score": 60,
"interruptions": 3,
"hints_requested": 2,
"vocabulary_support_needed": true
}'::jsonb,
NOW() - INTERVAL '3 hours'
),
-- Session 2: Previous Study Session (Low Completion)
(
student3_id, module1_id,
NOW() - INTERVAL '2 days',
NOW() - INTERVAL '2 days' + INTERVAL '25 minutes',
25,
1, 0,
0, ARRAY[]::text[],
ARRAY['pistas', 'pistas']::text[],
'study',
'{
"device": "mobile",
"location": "bus",
"focus_score": 45,
"interruptions": 5,
"session_abandoned": true,
"reason": "connectivity issues"
}'::jsonb,
NOW() - INTERVAL '2 days' + INTERVAL '25 minutes'
)
ON CONFLICT DO NOTHING;
RAISE NOTICE 'Learning sessions data inserted successfully';
RAISE NOTICE '✓ Progress tracking seeds completed';
RAISE NOTICE ' - Module progress entries: 5';
RAISE NOTICE ' - Learning sessions: 6';
END $$;

View File

@ -0,0 +1,596 @@
-- =====================================================================================
-- SEED: Exercise Attempts and Submissions for Progress Tracking
-- =====================================================================================
-- Description: Exercise attempts, submissions and detailed performance tracking
-- Dependencies: auth.users, educational_content.exercises, module_progress
-- Idempotency: Uses ON CONFLICT to handle re-runs safely
-- =====================================================================================
SET search_path TO progress_tracking, educational_content, auth, public;
-- =====================================================================================
-- EXERCISE ATTEMPTS & SUBMISSIONS
-- =====================================================================================
DO $$
DECLARE
student1_id UUID;
student2_id UUID;
student3_id UUID;
exercise_1_1_id UUID; -- Crucigrama Científico
exercise_1_2_id UUID; -- Línea de Tiempo
exercise_1_3_id UUID; -- Completar Biografía
exercise_2_1_id UUID; -- Detective de Motivaciones
exercise_2_2_id UUID; -- Predictor de Consecuencias
exercise_3_1_id UUID; -- Juez de Argumentos
BEGIN
-- ==================================================================================
-- GET USER IDS (Demo Students)
-- ==================================================================================
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';
-- ==================================================================================
-- GET EXERCISE IDS (by title patterns)
-- ==================================================================================
-- Module 1 Exercises (Literal)
SELECT exercise_id INTO exercise_1_1_id
FROM educational_content.exercises
WHERE title ILIKE '%Crucigrama%'
ORDER BY created_at LIMIT 1;
SELECT exercise_id INTO exercise_1_2_id
FROM educational_content.exercises
WHERE title ILIKE '%Línea de Tiempo%'
ORDER BY created_at LIMIT 1;
SELECT exercise_id INTO exercise_1_3_id
FROM educational_content.exercises
WHERE title ILIKE '%Biografía%'
ORDER BY created_at LIMIT 1;
-- Module 2 Exercises (Inferencial)
SELECT exercise_id INTO exercise_2_1_id
FROM educational_content.exercises
WHERE title ILIKE '%Detective%'
ORDER BY created_at LIMIT 1;
SELECT exercise_id INTO exercise_2_2_id
FROM educational_content.exercises
WHERE title ILIKE '%Predictor%'
ORDER BY created_at LIMIT 1;
-- Module 3 Exercises (Crítica)
SELECT exercise_id INTO exercise_3_1_id
FROM educational_content.exercises
WHERE title ILIKE '%Juez%'
ORDER BY created_at LIMIT 1;
-- ==================================================================================
-- EXERCISE ATTEMPTS: Detailed Attempt Tracking
-- ==================================================================================
RAISE NOTICE 'Inserting exercise attempts data...';
INSERT INTO progress_tracking.exercise_attempts (
user_id, exercise_id,
attempt_number, status,
score, max_score, score_percentage,
time_spent_seconds, started_at, completed_at,
hints_used, comodines_used,
metadata, created_at, updated_at
) VALUES
-- ================================================================================
-- ESTUDIANTE 1: Crucigrama Científico (Progressive Improvement - 3 attempts)
-- ================================================================================
-- Attempt 1: First Try (Good but not perfect)
(
student1_id, exercise_1_1_id,
1, 'completed',
75, 100, 75,
420, -- 7 minutes
NOW() - INTERVAL '9 days',
NOW() - INTERVAL '9 days' + INTERVAL '7 minutes',
1, ARRAY[]::text[],
'{
"mistakes": 5,
"corrections": 3,
"completion_path": "sequential",
"words_completed": ["RADIO", "NOBEL", "SORBONA"],
"words_struggled": ["PECHBLENDA", "POLONIO"],
"hint_words": ["PECHBLENDA"]
}'::jsonb,
NOW() - INTERVAL '9 days',
NOW() - INTERVAL '9 days' + INTERVAL '7 minutes'
),
-- Attempt 2: Second Try (Significant Improvement)
(
student1_id, exercise_1_1_id,
2, 'completed',
90, 100, 90,
360, -- 6 minutes
NOW() - INTERVAL '8 days',
NOW() - INTERVAL '8 days' + INTERVAL '6 minutes',
0, ARRAY['vision_lectora']::text[],
'{
"mistakes": 2,
"corrections": 1,
"improvements": ["mejor tiempo", "menos pistas", "mejor vocabulario"],
"words_completed": ["RADIO", "POLONIO", "NOBEL", "SORBONA"],
"words_struggled": ["RADIOACTIVIDAD"],
"power_up_impact": "high"
}'::jsonb,
NOW() - INTERVAL '8 days',
NOW() - INTERVAL '8 days' + INTERVAL '6 minutes'
),
-- Attempt 3: Mastery Achievement (Perfect Score)
(
student1_id, exercise_1_1_id,
3, 'completed',
100, 100, 100,
300, -- 5 minutes
NOW() - INTERVAL '7 days',
NOW() - INTERVAL '7 days' + INTERVAL '5 minutes',
0, ARRAY[]::text[],
'{
"mistakes": 0,
"perfect_score": true,
"mastery_achieved": true,
"all_words_correct": ["RADIO", "POLONIO", "RADIOACTIVIDAD", "NOBEL", "SORBONA", "PECHBLENDA"],
"speed_improvement": "40%",
"bonus_points": 25
}'::jsonb,
NOW() - INTERVAL '7 days',
NOW() - INTERVAL '7 days' + INTERVAL '5 minutes'
),
-- ================================================================================
-- ESTUDIANTE 1: Línea de Tiempo (1 attempt - High Performance)
-- ================================================================================
(
student1_id, exercise_1_2_id,
1, 'completed',
95, 100, 95,
480, -- 8 minutes
NOW() - INTERVAL '6 days',
NOW() - INTERVAL '6 days' + INTERVAL '8 minutes',
0, ARRAY[]::text[],
'{
"date_errors": 0,
"sequence_errors": 1,
"events_placed": 8,
"chronological_accuracy": "excellent",
"minor_error": "Nobel Química date slightly off"
}'::jsonb,
NOW() - INTERVAL '6 days',
NOW() - INTERVAL '6 days' + INTERVAL '8 minutes'
),
-- ================================================================================
-- ESTUDIANTE 1: Detective de Motivaciones (Module 2 - Current)
-- ================================================================================
(
student1_id, exercise_2_1_id,
1, 'completed',
95, 100, 95,
540, -- 9 minutes
NOW() - INTERVAL '1 hour',
NOW() - INTERVAL '1 hour' + INTERVAL '9 minutes',
1, ARRAY['pistas']::text[],
'{
"clues_found": 8,
"clues_total": 8,
"inference_accuracy": "excellent",
"motivation_identified": "pasión por la ciencia",
"supporting_evidence": ["sacrificios personales", "dedicación", "persistencia"]
}'::jsonb,
NOW() - INTERVAL '1 hour',
NOW() - INTERVAL '1 hour' + INTERVAL '9 minutes'
),
-- ================================================================================
-- ESTUDIANTE 2: Línea de Tiempo (2 attempts - Improvement Pattern)
-- ================================================================================
-- Attempt 1: First Try (Needs Improvement)
(
student2_id, exercise_1_2_id,
1, 'completed',
70, 100, 70,
540, -- 9 minutes
NOW() - INTERVAL '12 days',
NOW() - INTERVAL '12 days' + INTERVAL '9 minutes',
2, ARRAY['pistas']::text[],
'{
"date_errors": 3,
"sequence_errors": 2,
"events_placed": 8,
"common_mistakes": ["confusión entre Nobels", "fecha matrimonio incorrecta"],
"hints_used_for": ["Nobel Física", "Descubrimiento Polonio"]
}'::jsonb,
NOW() - INTERVAL '12 days',
NOW() - INTERVAL '12 days' + INTERVAL '9 minutes'
),
-- Attempt 2: Retry with Power-up (Good Improvement)
(
student2_id, exercise_1_2_id,
2, 'completed',
85, 100, 85,
480, -- 8 minutes
NOW() - INTERVAL '11 days',
NOW() - INTERVAL '11 days' + INTERVAL '8 minutes',
1, ARRAY['segunda_oportunidad']::text[],
'{
"date_errors": 1,
"sequence_errors": 0,
"improvement": "significant",
"events_placed": 8,
"corrected_mistakes": ["Nobels diferenciados", "matrimonio correcto"],
"remaining_issue": "fecha descubrimiento Radio"
}'::jsonb,
NOW() - INTERVAL '11 days',
NOW() - INTERVAL '11 days' + INTERVAL '8 minutes'
),
-- ================================================================================
-- ESTUDIANTE 2: Completar Biografía (1 attempt - Good)
-- ================================================================================
(
student2_id, exercise_1_3_id,
1, 'completed',
80, 100, 80,
600, -- 10 minutes
NOW() - INTERVAL '10 days',
NOW() - INTERVAL '10 days' + INTERVAL '10 minutes',
1, ARRAY[]::text[],
'{
"blanks_total": 10,
"blanks_correct": 8,
"vocabulary_accuracy": "good",
"context_understanding": "solid",
"errors": ["confusión: Pechblenda/Uranio", "sintaxis menor"]
}'::jsonb,
NOW() - INTERVAL '10 days',
NOW() - INTERVAL '10 days' + INTERVAL '10 minutes'
),
-- ================================================================================
-- ESTUDIANTE 2: Juez de Argumentos (Module 3 - Challenge Level)
-- ================================================================================
-- Attempt 1: First Try (Struggling with Critical Analysis)
(
student2_id, exercise_3_1_id,
1, 'completed',
70, 100, 70,
720, -- 12 minutes
NOW() - INTERVAL '3 hours',
NOW() - INTERVAL '3 hours' + INTERVAL '12 minutes',
2, ARRAY['segunda_oportunidad']::text[],
'{
"arguments_analyzed": 5,
"correct_evaluations": 3,
"critical_thinking_level": "developing",
"struggles": ["identificar falacias", "diferenciar hecho/opinión"],
"strengths": ["comprensión literal", "identificar evidencia"]
}'::jsonb,
NOW() - INTERVAL '3 hours',
NOW() - INTERVAL '3 hours' + INTERVAL '12 minutes'
),
-- ================================================================================
-- ESTUDIANTE 3: Crucigrama Científico (2 attempts - Foundational Level)
-- ================================================================================
-- Attempt 1: First Try (Significant Vocabulary Challenges)
(
student3_id, exercise_1_1_id,
1, 'completed',
60, 100, 60,
600, -- 10 minutes
NOW() - INTERVAL '4 days',
NOW() - INTERVAL '4 days' + INTERVAL '10 minutes',
3, ARRAY['pistas', 'vision_lectora']::text[],
'{
"mistakes": 8,
"corrections": 5,
"vocabulary_struggles": ["PECHBLENDA", "RADIOACTIVIDAD", "POLONIO"],
"words_completed": ["RADIO", "NOBEL"],
"words_incomplete": ["PECHBLENDA", "RADIOACTIVIDAD"],
"hints_used_for": ["PECHBLENDA", "POLONIO", "RADIOACTIVIDAD"],
"reading_support_needed": true
}'::jsonb,
NOW() - INTERVAL '4 days',
NOW() - INTERVAL '4 days' + INTERVAL '10 minutes'
),
-- Attempt 2: Retry (Currently In Progress)
(
student3_id, exercise_1_1_id,
2, 'in_progress',
0, 100, 0,
180, -- 3 minutes so far
NOW() - INTERVAL '3 hours',
NULL,
1, ARRAY['pistas']::text[],
'{
"current_progress": 40,
"words_completed": ["RADIO", "NOBEL"],
"current_word": "SORBONA",
"pause_reason": "break",
"session_active": true
}'::jsonb,
NOW() - INTERVAL '3 hours',
NOW()
),
-- ================================================================================
-- ESTUDIANTE 3: Línea de Tiempo (1 attempt - Needs Support)
-- ================================================================================
(
student3_id, exercise_1_2_id,
1, 'completed',
65, 100, 65,
720, -- 12 minutes
NOW() - INTERVAL '3 days',
NOW() - INTERVAL '3 days' + INTERVAL '12 minutes',
2, ARRAY['pistas', 'vision_lectora']::text[],
'{
"date_errors": 4,
"sequence_errors": 3,
"events_placed": 8,
"chronological_confusion": "high",
"hints_used_for": ["Nobel Física fecha", "Descubrimiento Radio"],
"support_recommendation": "review timeline concepts"
}'::jsonb,
NOW() - INTERVAL '3 days',
NOW() - INTERVAL '3 days' + INTERVAL '12 minutes'
)
ON CONFLICT (user_id, exercise_id, attempt_number) DO UPDATE SET
status = EXCLUDED.status,
score = EXCLUDED.score,
score_percentage = EXCLUDED.score_percentage,
time_spent_seconds = EXCLUDED.time_spent_seconds,
completed_at = EXCLUDED.completed_at,
hints_used = EXCLUDED.hints_used,
comodines_used = EXCLUDED.comodines_used,
metadata = EXCLUDED.metadata,
updated_at = NOW();
RAISE NOTICE 'Exercise attempts data inserted successfully';
-- ==================================================================================
-- EXERCISE SUBMISSIONS: Detailed Submission Data
-- ==================================================================================
RAISE NOTICE 'Inserting exercise submissions data...';
INSERT INTO progress_tracking.exercise_submissions (
user_id, exercise_id, attempt_number,
submission_type, answer_data, submission_files,
auto_graded, score, feedback,
submitted_at, graded_at, graded_by,
metadata, created_at, updated_at
) VALUES
-- ================================================================================
-- ESTUDIANTE 1: Crucigrama Perfect Score Submission
-- ================================================================================
(
student1_id, exercise_1_1_id, 3,
'interactive',
'{
"exercise_type": "crossword",
"answers": {
"1_across": "RADIO",
"2_across": "POLONIO",
"3_down": "RADIOACTIVIDAD",
"4_down": "NOBEL",
"5_across": "SORBONA",
"6_down": "PECHBLENDA"
},
"completion_time_seconds": 300,
"all_correct": true
}'::jsonb,
ARRAY[]::text[],
true, 100,
'{
"correct_answers": 6,
"total_answers": 6,
"feedback": "¡Perfecto! Dominas el vocabulario científico de Marie Curie.",
"feedback_details": {
"vocabulary": "excelente",
"spelling": "perfecto",
"context_understanding": "avanzado"
},
"encouragement": "Has demostrado un dominio completo del tema."
}'::jsonb,
NOW() - INTERVAL '7 days' + INTERVAL '5 minutes',
NOW() - INTERVAL '7 days' + INTERVAL '5 minutes',
NULL,
'{
"auto_grade_confidence": 100,
"perfect_score_bonus": 25,
"mastery_badge_earned": true,
"grading_algorithm": "exact_match_v2"
}'::jsonb,
NOW() - INTERVAL '7 days',
NOW() - INTERVAL '7 days'
),
-- ================================================================================
-- ESTUDIANTE 1: Línea de Tiempo High Performance Submission
-- ================================================================================
(
student1_id, exercise_1_2_id, 1,
'interactive',
'{
"exercise_type": "timeline",
"timeline": [
{"year": 1867, "event": "Nacimiento en Varsovia", "correct": true},
{"year": 1891, "event": "Llegada a París", "correct": true},
{"year": 1895, "event": "Matrimonio con Pierre", "correct": true},
{"year": 1898, "event": "Descubrimientos Radio y Polonio", "correct": true},
{"year": 1903, "event": "Premio Nobel de Física", "correct": true},
{"year": 1906, "event": "Muerte de Pierre", "correct": true},
{"year": 1911, "event": "Premio Nobel de Química", "correct": false, "student_answer": 1910},
{"year": 1934, "event": "Fallecimiento", "correct": true}
],
"sequence_accuracy": "perfect"
}'::jsonb,
ARRAY[]::text[],
true, 95,
'{
"correct_events": 7,
"total_events": 8,
"date_precision": "high",
"sequence_accuracy": "perfect",
"feedback": "Excelente trabajo. Solo un pequeño error en la fecha del segundo Nobel (1911, no 1910).",
"feedback_details": {
"chronological_thinking": "excelente",
"attention_to_detail": "muy bueno",
"historical_context": "sólido"
}
}'::jsonb,
NOW() - INTERVAL '6 days' + INTERVAL '8 minutes',
NOW() - INTERVAL '6 days' + INTERVAL '8 minutes',
NULL,
'{
"auto_grade_confidence": 98,
"grading_algorithm": "timeline_matcher_v1"
}'::jsonb,
NOW() - INTERVAL '6 days',
NOW() - INTERVAL '6 days'
),
-- ================================================================================
-- ESTUDIANTE 2: Línea de Tiempo Retry Submission
-- ================================================================================
(
student2_id, exercise_1_2_id, 2,
'interactive',
'{
"exercise_type": "timeline",
"timeline": [
{"year": 1867, "event": "Nacimiento en Varsovia", "correct": true},
{"year": 1891, "event": "Llegada a París", "correct": true},
{"year": 1895, "event": "Matrimonio con Pierre", "correct": true},
{"year": 1898, "event": "Descubrimientos Radio y Polonio", "correct": false, "student_answer": 1897},
{"year": 1903, "event": "Premio Nobel de Física", "correct": true},
{"year": 1906, "event": "Muerte de Pierre", "correct": true},
{"year": 1911, "event": "Premio Nobel de Química", "correct": true},
{"year": 1934, "event": "Fallecimiento", "correct": true}
],
"sequence_accuracy": "perfect"
}'::jsonb,
ARRAY[]::text[],
true, 85,
'{
"correct_events": 7,
"total_events": 8,
"date_precision": "good",
"sequence_accuracy": "perfect",
"feedback": "Muy bien. Has mejorado significativamente. Pequeño error en fecha descubrimiento Radio (1898, no 1897).",
"feedback_details": {
"improvement": "notable",
"nobel_dates_mastered": true,
"minor_date_confusion": "discovery dates"
},
"encouragement": "Has corregido los errores principales. ¡Buen trabajo!"
}'::jsonb,
NOW() - INTERVAL '11 days' + INTERVAL '8 minutes',
NOW() - INTERVAL '11 days' + INTERVAL '8 minutes',
NULL,
'{
"auto_grade_confidence": 95,
"improvement_from_attempt_1": 15,
"grading_algorithm": "timeline_matcher_v1"
}'::jsonb,
NOW() - INTERVAL '11 days',
NOW() - INTERVAL '11 days'
),
-- ================================================================================
-- ESTUDIANTE 2: Completar Biografía Submission
-- ================================================================================
(
student2_id, exercise_1_3_id, 1,
'fill_in_blank',
'{
"exercise_type": "fill_in_blank",
"blanks": [
{"position": 1, "correct_answer": "Varsovia", "student_answer": "Varsovia", "correct": true},
{"position": 2, "correct_answer": "Polonia", "student_answer": "Polonia", "correct": true},
{"position": 3, "correct_answer": "física", "student_answer": "física", "correct": true},
{"position": 4, "correct_answer": "Pierre Curie", "student_answer": "Pierre Curie", "correct": true},
{"position": 5, "correct_answer": "radio", "student_answer": "radio", "correct": true},
{"position": 6, "correct_answer": "polonio", "student_answer": "polonio", "correct": true},
{"position": 7, "correct_answer": "pechblenda", "student_answer": "uranio", "correct": false},
{"position": 8, "correct_answer": "Nobel", "student_answer": "Nobel", "correct": true},
{"position": 9, "correct_answer": "radioactividad", "student_answer": "radioactividad", "correct": true},
{"position": 10, "correct_answer": "Sorbona", "student_answer": "Sorbona", "correct": true}
]
}'::jsonb,
ARRAY[]::text[],
true, 80,
'{
"correct_answers": 8,
"total_answers": 10,
"vocabulary_accuracy": "good",
"feedback": "Buen trabajo. Solo un error: el mineral es pechblenda, no uranio (aunque uranio está relacionado).",
"feedback_details": {
"names_accuracy": "perfecto",
"scientific_terms": "muy bueno",
"context_understanding": "sólido",
"area_for_improvement": "vocabulario mineralógico"
}
}'::jsonb,
NOW() - INTERVAL '10 days' + INTERVAL '10 minutes',
NOW() - INTERVAL '10 days' + INTERVAL '10 minutes',
NULL,
'{
"auto_grade_confidence": 95,
"partial_credit_given": false,
"grading_algorithm": "exact_match_with_synonyms_v1"
}'::jsonb,
NOW() - INTERVAL '10 days',
NOW() - INTERVAL '10 days'
)
ON CONFLICT (user_id, exercise_id, attempt_number) DO UPDATE SET
answer_data = EXCLUDED.answer_data,
score = EXCLUDED.score,
feedback = EXCLUDED.feedback,
metadata = EXCLUDED.metadata,
updated_at = NOW();
RAISE NOTICE 'Exercise submissions data inserted successfully';
RAISE NOTICE '✓ Exercise attempts and submissions seeds completed';
RAISE NOTICE ' - Exercise attempts: 11';
RAISE NOTICE ' - Exercise submissions: 4';
RAISE NOTICE ' - Students tracked: 3';
RAISE NOTICE ' - Exercises covered: 6';
END $$;

View File

@ -0,0 +1,448 @@
-- =====================================================================
-- Archivo: 04-teams.sql
-- Schema: social_features
-- Descripción: Seeds de equipos colaborativos y sus membresías
-- Dependencias: 02-classrooms.sql, 03-classroom-members.sql
-- Autor: SA-SEEDS-SOCIAL
-- Fecha: 2025-11-02
-- =====================================================================
SET search_path TO social_features, auth, public;
-- =====================================================================
-- TEAMS: Equipos colaborativos dentro de aulas
-- =====================================================================
DO $$
DECLARE
classroom_2a UUID;
classroom_3b UUID;
classroom_1a UUID;
classroom_2st UUID;
student1_id UUID;
student2_id UUID;
student3_id UUID;
team_cientificos UUID;
team_exploradores UUID;
team_pioneros UUID;
team_innovadores UUID;
team_count INTEGER;
member_count INTEGER;
BEGIN
-- =====================================================================
-- Obtener classroom IDs
-- =====================================================================
SELECT classroom_id INTO classroom_2a
FROM social_features.classrooms
WHERE classroom_code = '2A-LECT-2025';
SELECT classroom_id INTO classroom_3b
FROM social_features.classrooms
WHERE classroom_code = '3B-DIGI-2025';
SELECT classroom_id INTO classroom_1a
FROM social_features.classrooms
WHERE classroom_code = '1A-INTRO-2025';
SELECT classroom_id INTO classroom_2st
FROM social_features.classrooms
WHERE classroom_code = '2ST-LITC-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 recursos necesarios
IF classroom_2a IS NULL THEN
RAISE EXCEPTION 'No se encontró el aula 2A-LECT-2025. Ejecutar seeds de classrooms primero.';
END IF;
IF student1_id IS NULL THEN
RAISE EXCEPTION 'No se encontraron estudiantes demo. Ejecutar seeds de auth primero.';
END IF;
-- =====================================================================
-- EQUIPO 1: Los Científicos (Aula 2° A - Secundaria Federal 15)
-- Enfoque: Biografías de científicos
-- =====================================================================
INSERT INTO social_features.teams (
classroom_id, name, code, description,
capacity, current_members_count,
is_active, is_active,
settings, metadata,
created_at, updated_at
) VALUES
(
classroom_2a,
'Los Científicos',
'TEAM-CIENT-2A',
'Equipo enfocado en explorar biografías de científicos famosos y sus contribuciones a la humanidad. Proyecto: "Mujeres en la Ciencia".',
5,
0,
'active',
true,
'{
"allow_public_join": false,
"require_approval": true,
"enable_team_chat": true,
"collaboration_tools": ["shared_documents", "video_calls", "task_board"],
"meeting_schedule": {
"frequency": "weekly",
"day": "Viernes",
"time": "15:00-16:00"
}
}'::jsonb,
'{
"color": "#3498db",
"icon": "flask",
"motto": "Explorando la ciencia juntos",
"current_project": "Biografías de Mujeres Científicas",
"achievements": [],
"team_goals": [
"Completar 5 biografías científicas",
"Presentar en Feria de Ciencias",
"Crear podcast educativo"
]
}'::jsonb,
NOW(),
NOW()
)
RETURNING team_id INTO team_cientificos;
-- =====================================================================
-- EQUIPO 2: Exploradores Digitales (Aula 3° B - Secundaria Federal 15)
-- Enfoque: Lectura digital y fact-checking
-- =====================================================================
INSERT INTO social_features.teams (
classroom_id, name, code, description,
capacity, current_members_count,
is_active, is_active,
settings, metadata,
created_at, updated_at
) VALUES
(
classroom_3b,
'Exploradores Digitales',
'TEAM-EXPLO-3B',
'Equipo dedicado a dominar la lectura digital, fact-checking y análisis crítico de medios. Proyecto: "Cazadores de Fake News".',
5,
0,
'active',
true,
'{
"allow_public_join": false,
"require_approval": true,
"enable_team_chat": true,
"collaboration_tools": ["shared_documents", "video_calls", "annotation_tools"],
"meeting_schedule": {
"frequency": "biweekly",
"day": "Jueves",
"time": "16:00-17:00"
}
}'::jsonb,
'{
"color": "#2ecc71",
"icon": "compass",
"motto": "Navegando el mundo digital con criterio",
"current_project": "Cazadores de Fake News",
"achievements": ["Primera verificación exitosa"],
"team_goals": [
"Verificar 10 noticias virales",
"Crear guía de fact-checking",
"Workshop para la comunidad"
],
"tools_mastered": ["Google Fact Check", "TinEye", "Wayback Machine"]
}'::jsonb,
NOW(),
NOW()
)
RETURNING team_id INTO team_exploradores;
-- =====================================================================
-- EQUIPO 3: Pioneros Técnicos (Aula 1° A - Secundaria Técnica 42)
-- Enfoque: Lectura de manuales técnicos
-- =====================================================================
INSERT INTO social_features.teams (
classroom_id, name, code, description,
capacity, current_members_count,
is_active, is_active,
settings, metadata,
created_at, updated_at
) VALUES
(
classroom_1a,
'Pioneros Técnicos',
'TEAM-PION-1A',
'Equipo enfocado en comprensión de manuales técnicos, documentación de software y estándares industriales.',
6,
0,
'active',
true,
'{
"allow_public_join": false,
"require_approval": true,
"enable_team_chat": true,
"collaboration_tools": ["shared_documents", "code_repository"],
"meeting_schedule": {
"frequency": "weekly",
"day": "Miércoles",
"time": "17:00-18:00"
}
}'::jsonb,
'{
"color": "#e74c3c",
"icon": "wrench",
"motto": "Leyendo el futuro técnico",
"current_project": "Manual de Arduino para Principiantes",
"achievements": [],
"team_goals": [
"Completar manual Arduino",
"Construir proyecto electrónico",
"Documentar proceso técnico"
]
}'::jsonb,
NOW(),
NOW()
)
RETURNING team_id INTO team_pioneros;
-- =====================================================================
-- EQUIPO 4: Innovadores STEAM (Aula 2° STEAM - Colegio Einstein)
-- Enfoque: Literatura científica bilingüe
-- =====================================================================
INSERT INTO social_features.teams (
classroom_id, name, code, description,
capacity, current_members_count,
is_active, is_active,
settings, metadata,
created_at, updated_at
) VALUES
(
classroom_2st,
'Innovadores STEAM',
'TEAM-INNOV-2ST',
'Equipo bilingüe enfocado en integrar literatura científica con proyectos STEAM. Proyecto: "Scientists Who Changed the World".',
4,
0,
'active',
true,
'{
"allow_public_join": false,
"require_approval": true,
"enable_team_chat": true,
"bilingual_communication": true,
"collaboration_tools": ["shared_documents", "video_calls", "design_tools"],
"meeting_schedule": {
"frequency": "weekly",
"day": "Viernes",
"time": "14:00-15:30"
}
}'::jsonb,
'{
"color": "#9b59b6",
"icon": "lightbulb",
"motto": "Innovation through knowledge / Innovación a través del conocimiento",
"current_project": "Scientists Who Changed the World",
"bilingual": true,
"achievements": ["STEAM Fair Participation"],
"team_goals": [
"Complete 3 bilingual biographies",
"Create interactive exhibition",
"Present at International STEAM Fair"
],
"partnership": "MIT Pre-Collegiate Program"
}'::jsonb,
NOW(),
NOW()
)
RETURNING team_id INTO team_innovadores;
-- =====================================================================
-- MEMBRESÍAS DE EQUIPOS
-- =====================================================================
-- Equipo 1: Los Científicos
INSERT INTO social_features.team_members (
team_id, user_id, role,
joined_date, is_active, is_active,
metadata, created_at, updated_at
) VALUES
(
team_cientificos,
student1_id,
'leader',
NOW(),
'active',
true,
'{
"responsibilities": ["Coordinar reuniones", "Asignar tareas", "Revisar entregas"],
"expertise": ["Investigación", "Organización"],
"contribution_score": 95
}'::jsonb,
NOW(),
NOW()
),
(
team_cientificos,
student3_id,
'member',
NOW(),
'active',
true,
'{
"responsibilities": ["Investigar biografías", "Editar contenido"],
"expertise": ["Escritura creativa", "Investigación"],
"contribution_score": 88
}'::jsonb,
NOW(),
NOW()
),
-- Equipo 2: Exploradores Digitales
(
team_exploradores,
student2_id,
'leader',
NOW(),
'active',
true,
'{
"responsibilities": ["Coordinar verificaciones", "Gestionar herramientas", "Moderar discusiones"],
"expertise": ["Fact-checking", "Alfabetización digital"],
"contribution_score": 92,
"certifications": ["Digital Literacy Basic"]
}'::jsonb,
NOW(),
NOW()
),
(
team_exploradores,
student1_id,
'member',
NOW(),
'active',
true,
'{
"responsibilities": ["Verificar noticias", "Documentar hallazgos"],
"expertise": ["Análisis crítico", "Búsqueda avanzada"],
"contribution_score": 90
}'::jsonb,
NOW(),
NOW()
),
-- Equipo 3: Pioneros Técnicos
(
team_pioneros,
student3_id,
'leader',
NOW(),
'active',
true,
'{
"responsibilities": ["Coordinar proyectos técnicos", "Revisar documentación"],
"expertise": ["Electrónica básica", "Lectura técnica"],
"contribution_score": 85
}'::jsonb,
NOW(),
NOW()
),
-- Equipo 4: Innovadores STEAM
(
team_innovadores,
student1_id,
'co-leader',
NOW(),
'active',
true,
'{
"responsibilities": ["Traducción bilingüe", "Diseño de presentaciones"],
"expertise": ["Bilingüismo", "Diseño gráfico"],
"contribution_score": 94,
"languages": ["Español", "English"]
}'::jsonb,
NOW(),
NOW()
),
(
team_innovadores,
student2_id,
'member',
NOW(),
'active',
true,
'{
"responsibilities": ["Investigación científica", "Redacción técnica"],
"expertise": ["Investigación", "STEAM projects"],
"contribution_score": 89,
"languages": ["Español", "English"]
}'::jsonb,
NOW(),
NOW()
)
ON CONFLICT (team_id, user_id) DO UPDATE SET
status = EXCLUDED.is_active,
role = EXCLUDED.role,
metadata = EXCLUDED.metadata,
updated_at = NOW();
-- =====================================================================
-- Actualizar member counts en teams
-- =====================================================================
UPDATE social_features.teams
SET current_members_count = (
SELECT COUNT(*)
FROM social_features.team_members
WHERE team_members.team_id = teams.team_id
AND status = 'active'
)
WHERE team_id IN (
team_cientificos, team_exploradores,
team_pioneros, team_innovadores
);
-- =====================================================================
-- Verificación de inserción
-- =====================================================================
SELECT COUNT(*) INTO team_count
FROM social_features.teams
WHERE status = 'active';
SELECT COUNT(*) INTO member_count
FROM social_features.team_members
WHERE status = 'active';
RAISE NOTICE '================================================';
RAISE NOTICE 'TEAMS SEEDS - RESUMEN DE INSERCIÓN';
RAISE NOTICE '================================================';
RAISE NOTICE 'Total de equipos creados: %', team_count;
RAISE NOTICE 'Total de membresías activas: %', member_count;
RAISE NOTICE '';
RAISE NOTICE 'Equipos por aula:';
RAISE NOTICE '- Los Científicos (2A): % miembros', (
SELECT current_members_count FROM social_features.teams WHERE team_id = team_cientificos
);
RAISE NOTICE '- Exploradores Digitales (3B): % miembros', (
SELECT current_members_count FROM social_features.teams WHERE team_id = team_exploradores
);
RAISE NOTICE '- Pioneros Técnicos (1A): % miembros', (
SELECT current_members_count FROM social_features.teams WHERE team_id = team_pioneros
);
RAISE NOTICE '- Innovadores STEAM (2ST): % miembros', (
SELECT current_members_count FROM social_features.teams WHERE team_id = team_innovadores
);
RAISE NOTICE '================================================';
END $$;

View File

@ -0,0 +1,145 @@
-- =====================================================
-- Seed Data: Feature Flags (DEV)
-- Description: Feature flags para control de funcionalidades GLIT
-- Schema: system_configuration
-- Table: feature_flags
-- Records: 5 feature flags
-- Created: 2025-11-02
-- =====================================================
SET search_path TO system_configuration, public;
-- =====================================================
-- INSERT: Feature Flags
-- =====================================================
INSERT INTO system_configuration.feature_flags (
feature_name,
feature_key,
description,
is_enabled,
rollout_percentage,
target_roles,
created_at,
updated_at
) VALUES
-- =====================================================
-- FEATURE FLAG 1: Sistema de Misiones
-- Status: ENABLED - Rollout: 100%
-- =====================================================
(
'Sistema de Misiones',
'missions_system',
'Habilitar sistema de misiones diarias y semanales para estudiantes',
true,
100,
NULL,
NOW(),
NOW()
),
-- =====================================================
-- FEATURE FLAG 2: Leaderboards Globales
-- Status: ENABLED - Rollout: 100%
-- =====================================================
(
'Leaderboards Globales',
'global_leaderboards',
'Habilitar rankings globales públicos visibles para todos los usuarios',
true,
100,
NULL,
NOW(),
NOW()
),
-- =====================================================
-- FEATURE FLAG 3: Equipos Colaborativos
-- Status: ENABLED - Rollout: 100%
-- =====================================================
(
'Equipos Colaborativos',
'collaborative_teams',
'Permitir creación y participación en equipos de estudiantes para desafíos grupales',
true,
100,
NULL,
NOW(),
NOW()
),
-- =====================================================
-- FEATURE FLAG 4: Chat en Vivo
-- Status: DISABLED - Rollout: 0%
-- =====================================================
(
'Chat en Vivo',
'live_chat',
'Chat en tiempo real entre estudiantes para colaboración y soporte peer-to-peer',
false,
0,
NULL,
NOW(),
NOW()
),
-- =====================================================
-- FEATURE FLAG 5: Modo Competitivo
-- Status: DISABLED - Rollout: 10% (A/B Testing)
-- Target: Solo estudiantes
-- =====================================================
(
'Modo Competitivo',
'competitive_mode',
'Desafíos 1v1 entre estudiantes con sistema de matchmaking y recompensas especiales',
false,
10,
ARRAY['student'::public.gamilit_role],
NOW(),
NOW()
)
ON CONFLICT (feature_key) DO UPDATE SET
feature_name = EXCLUDED.feature_name,
description = EXCLUDED.description,
is_enabled = EXCLUDED.is_enabled,
rollout_percentage = EXCLUDED.rollout_percentage,
target_roles = EXCLUDED.target_roles,
updated_at = NOW();
-- =====================================================
-- VERIFICACIÓN
-- =====================================================
-- Ver feature flags activos
-- SELECT feature_key, feature_name, is_enabled, rollout_percentage, target_roles
-- FROM system_configuration.feature_flags
-- WHERE is_enabled = true;
-- Ver feature flags en testing/desarrollo
-- SELECT feature_key, feature_name, is_enabled, rollout_percentage, target_roles
-- FROM system_configuration.feature_flags
-- WHERE is_enabled = false OR rollout_percentage < 100;
-- =====================================================
-- RESUMEN DE FEATURE FLAGS
-- =====================================================
-- Total Feature Flags: 5
--
-- HABILITADOS (100% Rollout):
-- 1. missions_system - Sistema de Misiones
-- 2. global_leaderboards - Leaderboards Globales
-- 3. collaborative_teams - Equipos Colaborativos
--
-- DESHABILITADOS:
-- 4. live_chat - Chat en Vivo (0% rollout)
--
-- EN A/B TESTING:
-- 5. competitive_mode - Modo Competitivo (10% rollout, solo students)
--
-- ESTRATEGIA DE ROLLOUT:
-- - Features core del sistema: 100% habilitadas
-- - Chat en vivo: Pendiente de activación
-- - Modo competitivo: Testing gradual con 10% de estudiantes
-- =====================================================
-- FIN DEL ARCHIVO
-- =====================================================

View File

@ -0,0 +1,273 @@
# VALIDACION DE DDL Y SEEDS - PRE-INIT-DATABASE
**Proyecto:** GAMILIT - Plataforma Educativa Gamificada
**Fecha:** 2025-12-26
**Auditor:** Requirements-Analyst (Claude Code)
**Script Objetivo:** init-database.sh v3.0
---
## RESUMEN EJECUTIVO
| Area | Estado | Problemas |
|------|--------|-----------|
| Estructura DDL | VALIDO | 3 schemas no referenciados |
| Archivos Base | VALIDO | 00-prerequisites.sql y 99-post-ddl-permissions.sql existen |
| Seeds DEV | PARCIAL | 2 archivos faltantes |
| Seeds PROD | INCOMPLETO | 12 archivos faltantes |
| Referencias Cruzadas | VALIDO | Sin FKs huerfanas detectadas |
**Resultado Global: PUEDE EJECUTARSE CON ADVERTENCIAS**
---
## 1. VALIDACION DE ESTRUCTURA DDL
### Schemas en init-database.sh (13)
| Schema | DDL Existe | Tablas | Funciones | Views | Triggers | RLS | Estado |
|--------|------------|--------|-----------|-------|----------|-----|--------|
| auth | SI | 1 | 0 | 1 | 0 | 0 | OK |
| auth_management | SI | 16 | 6 | 0 | 9 | 2 | OK |
| gamilit | SI | 0 | 32 | 1 | 0 | 0 | OK |
| storage | SI | 0 | 0 | 0 | 0 | 0 | OK |
| admin_dashboard | SI | 3 | 1 | 7 | 0 | 0 | OK |
| system_configuration | SI | 9 | 2 | 0 | 2 | 1 | OK |
| gamification_system | SI | 20 | 24 | 4 | 12 | 8 | OK |
| educational_content | SI | 21 | 28 | 1 | 4 | 2 | OK |
| content_management | SI | 9 | 4 | 0 | 4 | 1 | OK |
| social_features | SI | 18 | 2 | 1 | 6 | 11 | OK |
| progress_tracking | SI | 18 | 10 | 2 | 12 | 3 | OK |
| audit_logging | SI | 7 | 4 | 0 | 1 | 1 | OK |
| public | SI | 0 | 0 | 0 | 0 | 0 | OK |
### Schemas NO Referenciados en Script (3)
| Schema | Existe en DDL | Objetos | Riesgo |
|--------|---------------|---------|--------|
| communication | SI | 1 tabla, 1 RLS | MEDIO - No se creara |
| lti_integration | SI | 3 tablas | MEDIO - No se creara |
| notifications | SI | 6 tablas, 3 funciones | ALTO - No se creara |
### Totales DDL
| Tipo Objeto | Cantidad |
|-------------|----------|
| Tablas | 122 |
| Funciones | 113 |
| Views | 17 |
| Triggers | 62 |
| RLS Policies | 29 |
| Indexes | 23 |
| Materialized Views | 4 |
| **TOTAL** | **370** |
---
## 2. VALIDACION DE SEEDS
### Seeds DEV - Estado por Directorio
| Directorio | Archivos | Estado | Faltantes |
|------------|----------|--------|-----------|
| educational_content | 6 | OK | Ninguno |
| gamification_system | 6 | OK | Ninguno |
| system_configuration | 3 | OK | Ninguno |
| content_management | 1 | OK | Ninguno |
| auth | 1 | OK | Ninguno |
| auth_management | PARCIAL | ADVERTENCIA | 2 archivos |
| progress_tracking | 1 | OPCIONAL | N/A |
### Archivos Faltantes en DEV (2)
```
seeds/dev/auth_management/04-profiles-testing.sql <- NO EXISTE
seeds/dev/auth_management/05-profiles-demo.sql <- NO EXISTE
```
**Nota:** Existen archivos alternativos con nombres diferentes:
- `04-profiles-complete.sql`
- `06-profiles-production.sql`
- `07-profiles-production-additional.sql`
El script puede necesitar actualizacion de nombres.
### Seeds PROD - Estado por Directorio
| Directorio | Archivos | Estado | Faltantes |
|------------|----------|--------|-----------|
| auth | 1 | OK | Ninguno |
| auth_management | PARCIAL | ADVERTENCIA | 2 archivos |
| educational_content | 6 | OK | Ninguno |
| gamification_system | PARCIAL | ADVERTENCIA | 1 archivo |
| social_features | PARCIAL | ADVERTENCIA | 1 archivo |
| content_management | INCOMPLETO | CRITICO | 3 archivos |
| progress_tracking | INCOMPLETO | CRITICO | 2 archivos |
| audit_logging | INCOMPLETO | CRITICO | 2 archivos |
| system_configuration | PARCIAL | ADVERTENCIA | 1 archivo |
### Archivos Faltantes en PROD (12)
```
seeds/prod/auth_management/04-profiles-testing.sql
seeds/prod/auth_management/05-profiles-demo.sql
seeds/prod/gamification_system/04-initialize_user_gamification.sql
seeds/prod/content_management/01-marie-curie-bio.sql
seeds/prod/content_management/02-media-files.sql
seeds/prod/content_management/03-tags.sql
seeds/prod/social_features/04-teams.sql
seeds/prod/progress_tracking/01-demo-progress.sql
seeds/prod/progress_tracking/02-exercise-attempts.sql
seeds/prod/audit_logging/01-audit-logs.sql
seeds/prod/audit_logging/02-system-metrics.sql
seeds/prod/system_configuration/02-feature_flags.sql
```
---
## 3. ANALISIS DE IMPACTO
### Riesgo por Falta de Seeds
| Archivo Faltante | Impacto | Severidad |
|------------------|---------|-----------|
| profiles-testing.sql | Sin usuarios de prueba | BAJO (dev) |
| profiles-demo.sql | Sin usuarios demo | BAJO (dev) |
| initialize_user_gamification.sql | Gamificacion no inicializada | MEDIO |
| marie-curie-bio.sql | Sin contenido de ejemplo | BAJO |
| teams.sql | Sin equipos iniciales | BAJO |
| demo-progress.sql | Sin progreso de prueba | BAJO |
| audit-logs.sql | Sin logs iniciales | BAJO |
### El Script NO Fallara
El script `init-database.sh` tiene validacion (linea 866):
```bash
if [ -f "$seed_file" ]; then
# Solo ejecuta si el archivo existe
fi
```
Los archivos faltantes seran omitidos silenciosamente pero la BD funcionara.
---
## 4. SCHEMAS NO PROCESADOS
### communication
**Ubicacion:** `ddl/schemas/communication/`
| Tipo | Cantidad | Archivos |
|------|----------|----------|
| Tablas | 1 | messages.sql |
| RLS | 1 | messages-policy.sql |
**Impacto:** Sistema de mensajeria no disponible.
### lti_integration
**Ubicacion:** `ddl/schemas/lti_integration/`
| Tipo | Cantidad | Archivos |
|------|----------|----------|
| Tablas | 3 | lti_consumers.sql, lti_contexts.sql, lti_users.sql |
**Impacto:** Integracion LTI con LMS no disponible.
### notifications
**Ubicacion:** `ddl/schemas/notifications/`
| Tipo | Cantidad | Archivos |
|------|----------|----------|
| Tablas | 6 | notification_types.sql, user_notifications.sql, etc. |
| Funciones | 3 | send_notification.sql, mark_read.sql, etc. |
| RLS | 1 | notifications-policy.sql |
**Impacto:** Sistema de notificaciones no disponible.
---
## 5. RECOMENDACIONES
### Prioridad ALTA
1. **Agregar schemas faltantes a init-database.sh:**
```bash
local schemas=(
# ... existentes ...
"communication" # AGREGAR
"lti_integration" # AGREGAR
"notifications" # AGREGAR
)
```
2. **Actualizar referencias de seeds en load_seeds():**
- Cambiar `04-profiles-testing.sql` -> `04-profiles-complete.sql`
- O crear archivos con nombres correctos
### Prioridad MEDIA
3. **Crear seeds faltantes para PROD:**
- Los 12 archivos listados deben crearse o eliminarse del script
4. **Verificar seeds opcionales:**
- `progress_tracking/01-demo-progress.sql` puede no ser necesario en PROD
### Prioridad BAJA
5. **Documentar schemas opcionales:**
- Si communication, lti_integration, notifications son features futuras, documentarlo
---
## 6. VERIFICACION PRE-EJECUCION
### Checklist
| Verificacion | Estado | Accion |
|--------------|--------|--------|
| 00-prerequisites.sql existe | OK | Ninguna |
| 99-post-ddl-permissions.sql existe | OK | Ninguna |
| Todos los schemas tienen tables/ | OK | Ninguna |
| Seeds DEV basicos existen | OK | Advertencia menor |
| Seeds PROD basicos existen | PARCIAL | Revisar antes de PROD |
| Sin archivos corruptos | OK | Ninguna |
### Comando de Ejecucion Segura (DEV)
```bash
# El script funcionara correctamente en DEV
cd /home/isem/workspace/projects/gamilit/apps/database/scripts
./init-database.sh --env dev
```
### Advertencia para PROD
```bash
# ADVERTENCIA: 12 seeds faltantes en PROD
# Revisar lista de archivos antes de ejecutar
./init-database.sh --env prod
```
---
## 7. CONCLUSION
**Estado Final: APROBADO PARA DEV CON OBSERVACIONES**
| Criterio | Resultado |
|----------|-----------|
| DDL Estructura | VALIDO |
| Seeds Minimos | VALIDO |
| Referencias | VALIDO |
| Schemas completos | PARCIAL (3 no incluidos) |
**Recomendacion:** Ejecutar en DEV es seguro. Para PROD, crear seeds faltantes o actualizar el script para reflejar los nombres actuales de archivos.
---
**Generado por:** Requirements-Analyst (Claude Code)
**Fecha:** 2025-12-26
**Version:** 1.0