## Schema: education (Complete Learning Management System) ### Course Structure (3 tables): - categories: Hierarchical course categories with materialized paths - courses: Full course catalog with pricing, access models, gamification - modules: Course sections/modules with sequencing ### Content (3 tables): - lessons: Individual lessons (video, article, interactive) - quizzes: Assessments with configurable rules - quiz_questions: Question bank with multiple types ### Progress Tracking (3 tables): - enrollments: User enrollments with progress tracking - lesson_progress: Detailed per-lesson progress - quiz_attempts: Quiz attempt history with grading ### Completion (2 tables): - course_reviews: Student reviews with moderation - certificates: Verifiable completion certificates ## Features: - 8 custom ENUMs for education domain - Multi-tenancy with RLS policies - Automatic progress calculation triggers - Quiz grading and statistics - Certificate generation with verification codes - Rating aggregation for courses - Gamification support (XP, badges) Total: 11 tables, ~95KB of DDL Roadmap: orchestration/planes/ROADMAP-IMPLEMENTACION-DDL-2026-Q1.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
135 lines
4.1 KiB
PL/PgSQL
135 lines
4.1 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: education
|
|
-- TABLE: modules
|
|
-- DESCRIPTION: Modulos/secciones de un curso
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-16
|
|
-- SPRINT: Sprint 2 - DDL Implementation Roadmap Q1-2026
|
|
-- ============================================================================
|
|
|
|
-- Tabla de Modulos
|
|
CREATE TABLE IF NOT EXISTS education.modules (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
course_id UUID NOT NULL REFERENCES education.courses(id) ON DELETE CASCADE,
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Informacion basica
|
|
title VARCHAR(200) NOT NULL,
|
|
slug VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
|
|
-- Orden y estructura
|
|
sequence_number INTEGER NOT NULL DEFAULT 0,
|
|
|
|
-- Estado
|
|
status education.publish_status NOT NULL DEFAULT 'draft',
|
|
is_preview BOOLEAN NOT NULL DEFAULT FALSE, -- Disponible como preview gratuito
|
|
is_locked BOOLEAN NOT NULL DEFAULT FALSE, -- Bloqueado hasta cumplir requisitos
|
|
|
|
-- Requisitos para desbloquear
|
|
unlock_requirements JSONB DEFAULT '{}'::JSONB, -- { "modules": [], "min_score": 70 }
|
|
|
|
-- Duracion
|
|
estimated_duration_minutes INTEGER,
|
|
|
|
-- Estadisticas (cache)
|
|
lesson_count INTEGER NOT NULL DEFAULT 0,
|
|
quiz_count INTEGER NOT NULL DEFAULT 0,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
CONSTRAINT modules_unique_sequence UNIQUE (course_id, sequence_number),
|
|
CONSTRAINT modules_unique_slug UNIQUE (course_id, slug)
|
|
);
|
|
|
|
COMMENT ON TABLE education.modules IS
|
|
'Modulos o secciones que organizan las lecciones de un curso';
|
|
|
|
COMMENT ON COLUMN education.modules.is_preview IS
|
|
'Si TRUE, el modulo esta disponible como preview gratuito';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_modules_course
|
|
ON education.modules(course_id, sequence_number);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_modules_tenant
|
|
ON education.modules(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_modules_status
|
|
ON education.modules(status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_modules_preview
|
|
ON education.modules(course_id, is_preview)
|
|
WHERE is_preview = TRUE;
|
|
|
|
-- Trigger para updated_at
|
|
DROP TRIGGER IF EXISTS module_updated_at ON education.modules;
|
|
CREATE TRIGGER module_updated_at
|
|
BEFORE UPDATE ON education.modules
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION education.update_education_timestamp();
|
|
|
|
-- Trigger para actualizar conteo en curso
|
|
CREATE OR REPLACE FUNCTION education.update_course_module_count()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
UPDATE education.courses
|
|
SET total_modules = total_modules + 1,
|
|
last_content_update = NOW()
|
|
WHERE id = NEW.course_id;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
UPDATE education.courses
|
|
SET total_modules = total_modules - 1,
|
|
last_content_update = NOW()
|
|
WHERE id = OLD.course_id;
|
|
END IF;
|
|
|
|
RETURN COALESCE(NEW, OLD);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS module_course_count ON education.modules;
|
|
CREATE TRIGGER module_course_count
|
|
AFTER INSERT OR DELETE ON education.modules
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION education.update_course_module_count();
|
|
|
|
-- Vista de modulos con lecciones
|
|
CREATE OR REPLACE VIEW education.v_course_modules AS
|
|
SELECT
|
|
m.id,
|
|
m.course_id,
|
|
m.title,
|
|
m.slug,
|
|
m.description,
|
|
m.sequence_number,
|
|
m.status,
|
|
m.is_preview,
|
|
m.is_locked,
|
|
m.estimated_duration_minutes,
|
|
m.lesson_count,
|
|
m.quiz_count
|
|
FROM education.modules m
|
|
WHERE m.status = 'published'
|
|
ORDER BY m.course_id, m.sequence_number;
|
|
|
|
-- RLS Policy para multi-tenancy
|
|
ALTER TABLE education.modules ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY modules_tenant_isolation ON education.modules
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON education.modules TO trading_app;
|
|
GRANT SELECT ON education.modules TO trading_readonly;
|
|
GRANT SELECT ON education.v_course_modules TO trading_app;
|