trading-platform-database-v2/ddl/schemas/education/TECHNICAL.md
rckrdmrd 45e77e9a9c feat: Initial commit - Database schemas and scripts
DDL schemas for Trading Platform:
- User management
- Authentication
- Payments
- Education
- ML predictions
- Trading data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:23 -06:00

13 KiB

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_idauth.users(id) ON DELETE RESTRICT
  • enrollments.user_idauth.users(id) ON DELETE CASCADE
  • progress.user_idauth.users(id) ON DELETE CASCADE
  • quiz_attempts.user_idauth.users(id) ON DELETE CASCADE
  • certificates.user_idauth.users(id) ON DELETE CASCADE
  • user_achievements.user_idauth.users(id) ON DELETE CASCADE
  • user_gamification_profile.user_idauth.users(id) ON DELETE CASCADE
  • user_activity_log.user_idauth.users(id) ON DELETE CASCADE
  • course_reviews.user_idauth.users(id) ON DELETE CASCADE
  • course_reviews.approved_byauth.users(id)

Relaciones internas

  • categories.parent_idcategories(id) ON DELETE SET NULL
  • courses.category_idcategories(id) ON DELETE RESTRICT
  • modules.course_idcourses(id) ON DELETE CASCADE
  • modules.unlock_after_module_idmodules(id) ON DELETE SET NULL
  • lessons.module_idmodules(id) ON DELETE CASCADE
  • enrollments.course_idcourses(id) ON DELETE RESTRICT
  • progress.lesson_idlessons(id) ON DELETE CASCADE
  • progress.enrollment_idenrollments(id) ON DELETE CASCADE
  • quizzes.module_idmodules(id) ON DELETE CASCADE
  • quizzes.lesson_idlessons(id) ON DELETE CASCADE
  • quiz_questions.quiz_idquizzes(id) ON DELETE CASCADE
  • quiz_attempts.quiz_idquizzes(id) ON DELETE RESTRICT
  • quiz_attempts.enrollment_idenrollments(id) ON DELETE SET NULL
  • certificates.course_idcourses(id) ON DELETE RESTRICT
  • certificates.enrollment_idenrollments(id) ON DELETE RESTRICT
  • user_achievements.course_idcourses(id) ON DELETE SET NULL
  • user_achievements.quiz_idquizzes(id) ON DELETE SET NULL
  • user_activity_log.course_idcourses(id) ON DELETE SET NULL
  • user_activity_log.lesson_idlessons(id) ON DELETE SET NULL
  • user_activity_log.quiz_idquizzes(id) ON DELETE SET NULL
  • course_reviews.course_idcourses(id) ON DELETE CASCADE
  • course_reviews.enrollment_idenrollments(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:

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:

SELECT education.update_user_streak(
  '00000000-0000-0000-0000-000000000001'
);

Vistas Materializadas Recomendadas

Para mejorar performance en queries frecuentes:

-- 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:

-- 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

-- 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

-- 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:

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:

SELECT
  schemaname,
  tablename,
  indexname,
  idx_scan
FROM pg_stat_user_indexes
WHERE schemaname = 'education' AND idx_scan = 0;

Actividad de enrollments hoy:

SELECT COUNT(*)
FROM education.enrollments
WHERE enrolled_at::date = CURRENT_DATE;

Cursos más populares (últimos 7 días):

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

pg_dump -h localhost -U postgres -n education trading_platform > education_backup.sql

Restore

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