# Documentación Técnica - Schema Education **Proyecto:** Trading Platform (Trading Platform) **Schema:** education **PostgreSQL:** 15+ **Versión:** 1.0.0 --- ## Estadísticas del Schema - **ENUMs:** 6 tipos - **Tablas:** 14 tablas - **Funciones:** 8 funciones - **Triggers:** 15+ triggers - **Vistas:** 7 vistas - **Índices:** 60+ índices - **Total líneas SQL:** ~1,350 líneas --- ## Índices por Tabla ### categories - `idx_categories_parent` - parent_id - `idx_categories_slug` - slug - `idx_categories_active` - is_active (WHERE is_active = true) ### courses - `idx_courses_category` - category_id - `idx_courses_slug` - slug - `idx_courses_status` - status - `idx_courses_difficulty` - difficulty_level - `idx_courses_instructor` - instructor_id - `idx_courses_published` - published_at (WHERE status = 'published') ### modules - `idx_modules_course` - course_id - `idx_modules_order` - course_id, display_order ### lessons - `idx_lessons_module` - module_id - `idx_lessons_order` - module_id, display_order - `idx_lessons_type` - content_type - `idx_lessons_preview` - is_preview (WHERE is_preview = true) ### enrollments - `idx_enrollments_user` - user_id - `idx_enrollments_course` - course_id - `idx_enrollments_status` - status - `idx_enrollments_user_active` - user_id, status (WHERE status = 'active') ### progress - `idx_progress_user` - user_id - `idx_progress_lesson` - lesson_id - `idx_progress_enrollment` - enrollment_id - `idx_progress_completed` - is_completed (WHERE is_completed = true) - `idx_progress_user_enrollment` - user_id, enrollment_id ### quizzes - `idx_quizzes_module` - module_id - `idx_quizzes_lesson` - lesson_id - `idx_quizzes_active` - is_active (WHERE is_active = true) ### quiz_questions - `idx_quiz_questions_quiz` - quiz_id - `idx_quiz_questions_order` - quiz_id, display_order ### quiz_attempts - `idx_quiz_attempts_user` - user_id - `idx_quiz_attempts_quiz` - quiz_id - `idx_quiz_attempts_enrollment` - enrollment_id - `idx_quiz_attempts_user_quiz` - user_id, quiz_id - `idx_quiz_attempts_completed` - is_completed, completed_at ### certificates - `idx_certificates_user` - user_id - `idx_certificates_course` - course_id - `idx_certificates_number` - certificate_number - `idx_certificates_verification` - verification_code ### user_achievements - `idx_user_achievements_user` - user_id - `idx_user_achievements_type` - achievement_type - `idx_user_achievements_earned` - earned_at DESC - `idx_user_achievements_course` - course_id ### user_gamification_profile - `idx_gamification_user` - user_id - `idx_gamification_level` - current_level DESC - `idx_gamification_xp` - total_xp DESC - `idx_gamification_weekly` - weekly_xp DESC - `idx_gamification_monthly` - monthly_xp DESC ### user_activity_log - `idx_activity_user` - user_id - `idx_activity_type` - activity_type - `idx_activity_created` - created_at DESC - `idx_activity_user_date` - user_id, created_at DESC - `idx_activity_course` - course_id (WHERE course_id IS NOT NULL) ### course_reviews - `idx_reviews_course` - course_id - `idx_reviews_user` - user_id - `idx_reviews_rating` - rating - `idx_reviews_approved` - is_approved (WHERE is_approved = true) - `idx_reviews_featured` - is_featured (WHERE is_featured = true) - `idx_reviews_helpful` - helpful_votes DESC --- ## Constraints ### CHECK Constraints **categories:** - `valid_color_format` - Color debe ser formato #RRGGBB **courses:** - `valid_rating` - avg_rating >= 0 AND <= 5 - `valid_price` - price_usd >= 0 **lessons:** - `video_fields_required` - Si content_type='video', video_url y video_duration_seconds requeridos **enrollments:** - `valid_progress` - progress_percentage >= 0 AND <= 100 - `valid_completion` - Si status='completed', completed_at y progress=100 requeridos **progress:** - `valid_watch_percentage` - watch_percentage >= 0 AND <= 100 - `completion_requires_date` - Si is_completed=true, completed_at requerido **quizzes:** - `valid_passing_score` - passing_score_percentage > 0 AND <= 100 - `quiz_association` - Debe tener module_id O lesson_id (no ambos) **quiz_questions:** - `valid_options` - Si question_type requiere options, options no puede ser NULL **quiz_attempts:** - `valid_score_percentage` - score_percentage >= 0 AND <= 100 **user_gamification_profile:** - `valid_level` - current_level >= 1 - `valid_xp` - total_xp >= 0 - `valid_streak` - current_streak_days >= 0 AND longest_streak_days >= 0 - `valid_avg_score` - average_quiz_score >= 0 AND <= 100 **course_reviews:** - `rating` - rating >= 1 AND <= 5 ### UNIQUE Constraints - `categories.slug` - UNIQUE - `courses.slug` - UNIQUE - `modules.unique_course_order` - UNIQUE(course_id, display_order) - `lessons.unique_module_order` - UNIQUE(module_id, display_order) - `enrollments.unique_user_course` - UNIQUE(user_id, course_id) - `progress.unique_user_lesson` - UNIQUE(user_id, lesson_id) - `certificates.certificate_number` - UNIQUE - `certificates.verification_code` - UNIQUE - `certificates.unique_user_course_cert` - UNIQUE(user_id, course_id) - `user_gamification_profile.unique_user_gamification` - UNIQUE(user_id) - `course_reviews.unique_user_course_review` - UNIQUE(user_id, course_id) --- ## Foreign Keys ### Relaciones con auth.users - `courses.instructor_id` → `auth.users(id)` ON DELETE RESTRICT - `enrollments.user_id` → `auth.users(id)` ON DELETE CASCADE - `progress.user_id` → `auth.users(id)` ON DELETE CASCADE - `quiz_attempts.user_id` → `auth.users(id)` ON DELETE CASCADE - `certificates.user_id` → `auth.users(id)` ON DELETE CASCADE - `user_achievements.user_id` → `auth.users(id)` ON DELETE CASCADE - `user_gamification_profile.user_id` → `auth.users(id)` ON DELETE CASCADE - `user_activity_log.user_id` → `auth.users(id)` ON DELETE CASCADE - `course_reviews.user_id` → `auth.users(id)` ON DELETE CASCADE - `course_reviews.approved_by` → `auth.users(id)` ### Relaciones internas - `categories.parent_id` → `categories(id)` ON DELETE SET NULL - `courses.category_id` → `categories(id)` ON DELETE RESTRICT - `modules.course_id` → `courses(id)` ON DELETE CASCADE - `modules.unlock_after_module_id` → `modules(id)` ON DELETE SET NULL - `lessons.module_id` → `modules(id)` ON DELETE CASCADE - `enrollments.course_id` → `courses(id)` ON DELETE RESTRICT - `progress.lesson_id` → `lessons(id)` ON DELETE CASCADE - `progress.enrollment_id` → `enrollments(id)` ON DELETE CASCADE - `quizzes.module_id` → `modules(id)` ON DELETE CASCADE - `quizzes.lesson_id` → `lessons(id)` ON DELETE CASCADE - `quiz_questions.quiz_id` → `quizzes(id)` ON DELETE CASCADE - `quiz_attempts.quiz_id` → `quizzes(id)` ON DELETE RESTRICT - `quiz_attempts.enrollment_id` → `enrollments(id)` ON DELETE SET NULL - `certificates.course_id` → `courses(id)` ON DELETE RESTRICT - `certificates.enrollment_id` → `enrollments(id)` ON DELETE RESTRICT - `user_achievements.course_id` → `courses(id)` ON DELETE SET NULL - `user_achievements.quiz_id` → `quizzes(id)` ON DELETE SET NULL - `user_activity_log.course_id` → `courses(id)` ON DELETE SET NULL - `user_activity_log.lesson_id` → `lessons(id)` ON DELETE SET NULL - `user_activity_log.quiz_id` → `quizzes(id)` ON DELETE SET NULL - `course_reviews.course_id` → `courses(id)` ON DELETE CASCADE - `course_reviews.enrollment_id` → `enrollments(id)` ON DELETE CASCADE --- ## Triggers ### Triggers de updated_at Aplica a: categories, courses, modules, lessons, enrollments, progress, quizzes, quiz_questions, user_gamification_profile, course_reviews **Función:** `education.update_updated_at_column()` **Trigger:** `update_{table}_updated_at` **Evento:** BEFORE UPDATE **Acción:** Actualiza `updated_at = NOW()` ### Triggers de lógica de negocio **update_enrollment_on_progress** - Tabla: progress - Función: `education.update_enrollment_progress()` - Evento: AFTER INSERT OR UPDATE - Condición: WHEN (NEW.is_completed = true) - Acción: Recalcula progreso del enrollment **auto_complete_enrollment_trigger** - Tabla: enrollments - Función: `education.auto_complete_enrollment()` - Evento: BEFORE UPDATE - Acción: Completa enrollment si progress >= 100% **generate_certificate_number_trigger** - Tabla: certificates - Función: `education.generate_certificate_number()` - Evento: BEFORE INSERT - Acción: Genera certificate_number y verification_code **update_course_rating_on_review_insert** - Tabla: course_reviews - Función: `education.update_course_rating_stats()` - Evento: AFTER INSERT - Acción: Actualiza avg_rating del curso **update_course_rating_on_review_update** - Tabla: course_reviews - Función: `education.update_course_rating_stats()` - Evento: AFTER UPDATE - Condición: rating o is_approved cambió - Acción: Actualiza avg_rating del curso **update_course_rating_on_review_delete** - Tabla: course_reviews - Función: `education.update_course_rating_stats()` - Evento: AFTER DELETE - Acción: Actualiza avg_rating del curso **update_enrollment_count_on_insert** - Tabla: enrollments - Función: `education.update_enrollment_count()` - Evento: AFTER INSERT - Acción: Incrementa contador en courses **update_enrollment_count_on_delete** - Tabla: enrollments - Función: `education.update_enrollment_count()` - Evento: AFTER DELETE - Acción: Decrementa contador en courses **update_streak_on_activity** - Tabla: user_activity_log - Función: `education.trigger_update_streak()` - Evento: AFTER INSERT - Acción: Actualiza streak del usuario --- ## Funciones Públicas ### education.update_user_xp(user_id UUID, xp_to_add INTEGER) Actualiza XP del usuario y recalcula nivel. **Parámetros:** - `user_id`: UUID del usuario - `xp_to_add`: Cantidad de XP a agregar **Lógica:** - Suma XP al total - Calcula nuevo nivel basado en fórmula cuadrática - Actualiza weekly_xp y monthly_xp - Crea achievement si subió de nivel **Ejemplo:** ```sql SELECT education.update_user_xp( '00000000-0000-0000-0000-000000000001', 100 ); ``` ### education.update_user_streak(user_id UUID) Actualiza streak del usuario basado en actividad diaria. **Parámetros:** - `user_id`: UUID del usuario **Lógica:** - Verifica última actividad - Incrementa streak si es día consecutivo - Resetea streak si se rompió - Crea achievement en milestones (7, 30, 100 días) **Ejemplo:** ```sql SELECT education.update_user_streak( '00000000-0000-0000-0000-000000000001' ); ``` --- ## Vistas Materializadas Recomendadas Para mejorar performance en queries frecuentes: ```sql -- Top cursos por enrollments (actualizar diariamente) CREATE MATERIALIZED VIEW education.mv_top_courses AS SELECT * FROM education.v_popular_courses; CREATE UNIQUE INDEX ON education.mv_top_courses(id); -- Leaderboards (actualizar cada hora) CREATE MATERIALIZED VIEW education.mv_leaderboard_weekly AS SELECT * FROM education.v_leaderboard_weekly; CREATE UNIQUE INDEX ON education.mv_leaderboard_weekly(user_id); ``` --- ## Optimizaciones Recomendadas ### 1. Particionamiento de user_activity_log Para logs con alto volumen: ```sql -- Particionar por mes CREATE TABLE education.user_activity_log_2025_12 PARTITION OF education.user_activity_log FOR VALUES FROM ('2025-12-01') TO ('2026-01-01'); ``` ### 2. Índices adicionales según uso ```sql -- Si hay muchas búsquedas por título de curso CREATE INDEX idx_courses_title_trgm ON education.courses USING gin(title gin_trgm_ops); -- Requiere extension pg_trgm CREATE EXTENSION IF NOT EXISTS pg_trgm; ``` ### 3. Vacuum y Analyze automático ```sql -- Configurar autovacuum para tablas con alta escritura ALTER TABLE education.user_activity_log SET (autovacuum_vacuum_scale_factor = 0.01); ALTER TABLE education.progress SET (autovacuum_vacuum_scale_factor = 0.02); ``` --- ## Monitoreo ### Queries útiles para monitoreo **Tamaño de tablas:** ```sql SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size FROM pg_tables WHERE schemaname = 'education' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; ``` **Índices no usados:** ```sql SELECT schemaname, tablename, indexname, idx_scan FROM pg_stat_user_indexes WHERE schemaname = 'education' AND idx_scan = 0; ``` **Actividad de enrollments hoy:** ```sql SELECT COUNT(*) FROM education.enrollments WHERE enrolled_at::date = CURRENT_DATE; ``` **Cursos más populares (últimos 7 días):** ```sql SELECT c.title, COUNT(e.id) as new_enrollments FROM education.courses c LEFT JOIN education.enrollments e ON c.id = e.course_id AND e.enrolled_at >= NOW() - INTERVAL '7 days' GROUP BY c.id, c.title ORDER BY new_enrollments DESC LIMIT 10; ``` --- ## Backup y Restore ### Backup solo del schema education ```bash pg_dump -h localhost -U postgres -n education trading_platform > education_backup.sql ``` ### Restore ```bash psql -h localhost -U postgres trading_platform < education_backup.sql ``` --- ## Versión y Changelog **v1.0.0** (2025-12-06) - Implementación inicial completa - 14 tablas - 8 funciones - 7 vistas - Sistema de gamificación completo - Reviews de cursos - Activity logging --- **Documentación generada:** 2025-12-06 **Última revisión:** 2025-12-06