-- ============================================================================ -- OrbiQuant IA - Esquema EDUCATION -- ============================================================================ -- Archivo: 02_education_schema.sql -- Descripción: Cursos, lecciones, inscripciones y contenido educativo -- Fecha: 2025-12-05 -- ============================================================================ SET search_path TO education; -- ============================================================================ -- TIPOS ENUMERADOS -- ============================================================================ CREATE TYPE course_level_enum AS ENUM ('beginner', 'intermediate', 'advanced', 'expert'); CREATE TYPE course_status_enum AS ENUM ('draft', 'published', 'archived'); CREATE TYPE content_type_enum AS ENUM ('video', 'text', 'quiz', 'exercise', 'resource'); CREATE TYPE enrollment_status_enum AS ENUM ('active', 'completed', 'expired', 'cancelled'); -- ============================================================================ -- TABLA: categories -- Descripción: Categorías de cursos -- ============================================================================ CREATE TABLE IF NOT EXISTS categories ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name VARCHAR(100) NOT NULL, slug VARCHAR(100) NOT NULL UNIQUE, description TEXT, icon VARCHAR(50), parent_id UUID REFERENCES categories(id), sort_order INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_categories_parent ON categories(parent_id); CREATE INDEX idx_categories_slug ON categories(slug); -- ============================================================================ -- TABLA: courses -- Descripción: Cursos de trading -- ============================================================================ CREATE TABLE IF NOT EXISTS courses ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Información básica title VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL UNIQUE, description TEXT, short_description VARCHAR(500), thumbnail_url TEXT, preview_video_url TEXT, -- Categorización category_id UUID REFERENCES categories(id), level course_level_enum DEFAULT 'beginner', tags TEXT[], -- Pricing is_free BOOLEAN DEFAULT FALSE, price DECIMAL(10,2) DEFAULT 0, currency CHAR(3) DEFAULT 'USD', -- Acceso requires_subscription BOOLEAN DEFAULT FALSE, min_subscription_tier VARCHAR(20), -- 'basic', 'pro', 'elite' -- Metadata duration_minutes INT, lessons_count INT DEFAULT 0, enrolled_count INT DEFAULT 0, -- Rating average_rating DECIMAL(3,2) DEFAULT 0, ratings_count INT DEFAULT 0, -- Estado status course_status_enum DEFAULT 'draft', published_at TIMESTAMPTZ, -- Autor instructor_id UUID REFERENCES public.users(id), -- AI Generation ai_generated BOOLEAN DEFAULT FALSE, ai_generation_prompt TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_courses_slug ON courses(slug); CREATE INDEX idx_courses_category ON courses(category_id); CREATE INDEX idx_courses_status ON courses(status); CREATE INDEX idx_courses_level ON courses(level); CREATE INDEX idx_courses_instructor ON courses(instructor_id); -- ============================================================================ -- TABLA: modules -- Descripción: Módulos/secciones de un curso -- ============================================================================ CREATE TABLE IF NOT EXISTS modules ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, sort_order INT DEFAULT 0, -- Progreso requerido del módulo anterior para desbloquear unlock_after_module_id UUID REFERENCES modules(id), created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_modules_course ON modules(course_id); -- ============================================================================ -- TABLA: lessons -- Descripción: Lecciones individuales -- ============================================================================ CREATE TABLE IF NOT EXISTS lessons ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), module_id UUID NOT NULL REFERENCES modules(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, -- Contenido title VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, content_type content_type_enum DEFAULT 'video', -- Video video_url TEXT, video_duration_seconds INT, video_provider VARCHAR(50), -- 'youtube', 'vimeo', 'internal' -- Texto content_markdown TEXT, content_html TEXT, -- Recursos adicionales resources JSONB DEFAULT '[]', -- [{name, url, type}] -- Orden sort_order INT DEFAULT 0, -- Acceso is_preview BOOLEAN DEFAULT FALSE, -- Disponible sin inscripción -- AI ai_generated BOOLEAN DEFAULT FALSE, ai_summary TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(course_id, slug) ); CREATE INDEX idx_lessons_module ON lessons(module_id); CREATE INDEX idx_lessons_course ON lessons(course_id); -- ============================================================================ -- TABLA: quizzes -- Descripción: Cuestionarios y evaluaciones -- ============================================================================ CREATE TABLE IF NOT EXISTS quizzes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), lesson_id UUID REFERENCES lessons(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, description TEXT, -- Configuración passing_score INT DEFAULT 70, -- Porcentaje mínimo max_attempts INT, -- NULL = ilimitado time_limit_minutes INT, shuffle_questions BOOLEAN DEFAULT FALSE, show_correct_answers BOOLEAN DEFAULT TRUE, -- AI ai_generated BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_quizzes_lesson ON quizzes(lesson_id); CREATE INDEX idx_quizzes_course ON quizzes(course_id); -- ============================================================================ -- TABLA: quiz_questions -- Descripción: Preguntas de cuestionarios -- ============================================================================ CREATE TYPE question_type_enum AS ENUM ('multiple_choice', 'true_false', 'multiple_answer', 'short_answer'); CREATE TABLE IF NOT EXISTS quiz_questions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), quiz_id UUID NOT NULL REFERENCES quizzes(id) ON DELETE CASCADE, question_type question_type_enum DEFAULT 'multiple_choice', question_text TEXT NOT NULL, explanation TEXT, -- Explicación mostrada después de responder -- Opciones (para multiple_choice y multiple_answer) options JSONB, -- [{id, text, is_correct}] -- Para short_answer correct_answers TEXT[], -- Respuestas aceptadas points INT DEFAULT 1, sort_order INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_quiz_questions_quiz ON quiz_questions(quiz_id); -- ============================================================================ -- TABLA: enrollments -- Descripción: Inscripciones de usuarios a cursos -- ============================================================================ CREATE TABLE IF NOT EXISTS enrollments ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, -- Estado status enrollment_status_enum DEFAULT 'active', -- Progreso progress_percentage DECIMAL(5,2) DEFAULT 0, lessons_completed INT DEFAULT 0, -- Acceso enrolled_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMPTZ, -- NULL = acceso permanente completed_at TIMESTAMPTZ, -- Pago (si aplica) payment_id UUID, -- Referencia a financial.payments -- Certificado certificate_issued BOOLEAN DEFAULT FALSE, certificate_url TEXT, certificate_issued_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, course_id) ); CREATE INDEX idx_enrollments_user ON enrollments(user_id); CREATE INDEX idx_enrollments_course ON enrollments(course_id); CREATE INDEX idx_enrollments_status ON enrollments(status); -- ============================================================================ -- TABLA: lesson_progress -- Descripción: Progreso por lección -- ============================================================================ CREATE TABLE IF NOT EXISTS lesson_progress ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, lesson_id UUID NOT NULL REFERENCES lessons(id) ON DELETE CASCADE, enrollment_id UUID NOT NULL REFERENCES enrollments(id) ON DELETE CASCADE, -- Progreso de video video_watched_seconds INT DEFAULT 0, video_completed BOOLEAN DEFAULT FALSE, -- Estado started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, -- Notas del usuario user_notes TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, lesson_id) ); CREATE INDEX idx_lesson_progress_user ON lesson_progress(user_id); CREATE INDEX idx_lesson_progress_lesson ON lesson_progress(lesson_id); CREATE INDEX idx_lesson_progress_enrollment ON lesson_progress(enrollment_id); -- ============================================================================ -- TABLA: quiz_attempts -- Descripción: Intentos de cuestionarios -- ============================================================================ CREATE TABLE IF NOT EXISTS quiz_attempts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, quiz_id UUID NOT NULL REFERENCES quizzes(id) ON DELETE CASCADE, enrollment_id UUID REFERENCES enrollments(id) ON DELETE SET NULL, -- Resultado score DECIMAL(5,2), passed BOOLEAN, -- Respuestas answers JSONB, -- [{question_id, answer, is_correct}] -- Tiempo started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, submitted_at TIMESTAMPTZ, time_spent_seconds INT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_quiz_attempts_user ON quiz_attempts(user_id); CREATE INDEX idx_quiz_attempts_quiz ON quiz_attempts(quiz_id); -- ============================================================================ -- TABLA: course_reviews -- Descripción: Reseñas de cursos -- ============================================================================ CREATE TABLE IF NOT EXISTS course_reviews ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5), review_text TEXT, -- Moderación is_approved BOOLEAN DEFAULT TRUE, is_featured BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, course_id) ); CREATE INDEX idx_course_reviews_course ON course_reviews(course_id); -- ============================================================================ -- TABLA: ai_content_generations -- Descripción: Registro de contenido generado por IA -- ============================================================================ CREATE TABLE IF NOT EXISTS ai_content_generations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Referencia entity_type VARCHAR(50) NOT NULL, -- 'course', 'lesson', 'quiz' entity_id UUID NOT NULL, -- Generación prompt TEXT NOT NULL, model_used VARCHAR(100), generated_content TEXT NOT NULL, tokens_used INT, -- Estado approved BOOLEAN DEFAULT FALSE, approved_by UUID REFERENCES public.users(id), approved_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_ai_generations_entity ON ai_content_generations(entity_type, entity_id); -- ============================================================================ -- TRIGGERS -- ============================================================================ CREATE TRIGGER update_courses_updated_at BEFORE UPDATE ON courses FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); CREATE TRIGGER update_modules_updated_at BEFORE UPDATE ON modules FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); CREATE TRIGGER update_lessons_updated_at BEFORE UPDATE ON lessons FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); CREATE TRIGGER update_enrollments_updated_at BEFORE UPDATE ON enrollments FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); CREATE TRIGGER update_lesson_progress_updated_at BEFORE UPDATE ON lesson_progress FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();