-- ============================================================================== -- GAMILIT - SCHEMA PROGRESS -- ============================================================================== -- Tablas de progreso, matriculas y estadisticas -- Mantenido por: Database-Agent -- Actualizado: 2025-12-18 -- ============================================================================== -- ------------------------------------------------------------------------------ -- TABLA: ENROLLMENTS (Matriculas) -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS enrollments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE, status VARCHAR(50) DEFAULT 'active', -- active, completed, dropped enrolled_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMPTZ, progress_percentage DECIMAL(5,2) DEFAULT 0, last_accessed_at TIMESTAMPTZ, 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 -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS lesson_progress ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, lesson_id UUID NOT NULL REFERENCES lessons(id) ON DELETE CASCADE, status VARCHAR(50) DEFAULT 'not_started', -- not_started, in_progress, completed started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, time_spent_seconds INTEGER DEFAULT 0, score DECIMAL(5,2), attempts INTEGER DEFAULT 0, 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_status ON lesson_progress(status); -- ------------------------------------------------------------------------------ -- TABLA: QUIZ_ATTEMPTS -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS quiz_attempts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, quiz_id UUID NOT NULL REFERENCES quizzes(id) ON DELETE CASCADE, score DECIMAL(5,2) NOT NULL, passed BOOLEAN NOT NULL, answers JSONB NOT NULL, time_taken_seconds INTEGER, started_at TIMESTAMPTZ NOT NULL, completed_at TIMESTAMPTZ NOT NULL, 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: USER_ACHIEVEMENTS -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS user_achievements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, achievement_id UUID NOT NULL REFERENCES achievements(id) ON DELETE CASCADE, earned_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, progress_data JSONB DEFAULT '{}', UNIQUE(user_id, achievement_id) ); CREATE INDEX idx_user_achievements_user ON user_achievements(user_id); CREATE INDEX idx_user_achievements_achievement ON user_achievements(achievement_id); -- ------------------------------------------------------------------------------ -- TABLA: USER_POINTS -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS user_points ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, points_type VARCHAR(50) NOT NULL, -- course, quiz, achievement, bonus points INTEGER NOT NULL, source_type VARCHAR(50), -- course, lesson, quiz, achievement source_id UUID, description TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_user_points_user ON user_points(user_id); CREATE INDEX idx_user_points_type ON user_points(points_type); CREATE INDEX idx_user_points_date ON user_points(created_at); -- ------------------------------------------------------------------------------ -- TABLA: USER_XP (Experiencia) -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS user_xp ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, total_xp INTEGER DEFAULT 0, level INTEGER DEFAULT 1, xp_to_next_level INTEGER DEFAULT 100, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id) ); CREATE INDEX idx_user_xp_user ON user_xp(user_id); CREATE INDEX idx_user_xp_level ON user_xp(level); -- ------------------------------------------------------------------------------ -- TABLA: STREAKS (Rachas) -- ------------------------------------------------------------------------------ CREATE TABLE IF NOT EXISTS streaks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, current_streak INTEGER DEFAULT 0, longest_streak INTEGER DEFAULT 0, last_activity_date DATE, streak_type VARCHAR(50) DEFAULT 'daily', -- daily, weekly updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, streak_type) ); CREATE INDEX idx_streaks_user ON streaks(user_id); -- ------------------------------------------------------------------------------ -- VISTA: LEADERBOARD -- ------------------------------------------------------------------------------ CREATE OR REPLACE VIEW leaderboard AS SELECT u.id as user_id, u.first_name, u.last_name, u.avatar_url, u.tenant_id, COALESCE(SUM(up.points), 0) as total_points, COALESCE(ux.level, 1) as level, COALESCE(ux.total_xp, 0) as total_xp, COUNT(DISTINCT ua.achievement_id) as achievements_count, COALESCE(s.current_streak, 0) as current_streak FROM users u LEFT JOIN user_points up ON u.id = up.user_id LEFT JOIN user_xp ux ON u.id = ux.user_id LEFT JOIN user_achievements ua ON u.id = ua.user_id LEFT JOIN streaks s ON u.id = s.user_id AND s.streak_type = 'daily' WHERE u.deleted_at IS NULL GROUP BY u.id, u.first_name, u.last_name, u.avatar_url, u.tenant_id, ux.level, ux.total_xp, s.current_streak ORDER BY total_points DESC;