From bddd382c96fa06de900d968192c757ab3f091a98 Mon Sep 17 00:00:00 2001 From: Carlos Tello Date: Wed, 23 Sep 2026 14:36:44 -0300 Subject: [PATCH] fix(db): anadir migraciones DDL para columnas extendidas de users, subjects y commissions --- backend/classrooms_db.sql | 18 +++++- backend/init_db.py | 122 +++++++++++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 18 deletions(-) diff --git a/backend/classrooms_db.sql b/backend/classrooms_db.sql index d6d5640..81344db 100644 --- a/backend/classrooms_db.sql +++ b/backend/classrooms_db.sql @@ -265,7 +265,11 @@ CREATE TABLE public.commissions ( created_at timestamp without time zone, updated_at timestamp without time zone, shift character varying(30), - virtual_link character varying(500) + virtual_link character varying(500), + grades_closed boolean DEFAULT false, + grades_closed_at timestamp without time zone, + grades_closed_by integer, + acta_number character varying(50) ); @@ -447,7 +451,8 @@ CREATE TABLE public.subjects ( active boolean NOT NULL, created_at timestamp without time zone, updated_at timestamp without time zone, - career_id integer + career_id integer, + is_short_course boolean DEFAULT false ); @@ -486,7 +491,14 @@ CREATE TABLE public.users ( last_login timestamp without time zone, preferred_language character varying(10), theme_preference character varying(10) DEFAULT 'auto'::character varying NOT NULL, - role_id integer + role_id integer, + first_name character varying(100), + last_name character varying(100), + phone character varying(50), + personal_email character varying(255), + address character varying(255), + document_type character varying(20) DEFAULT 'DNI'::character varying, + document_number character varying(30) ); diff --git a/backend/init_db.py b/backend/init_db.py index 7dff7bf..16aaf40 100644 --- a/backend/init_db.py +++ b/backend/init_db.py @@ -22,21 +22,29 @@ def init_database(): # 2. Migraciones incrementales seguras para bases de datos existentes if db.engine.url.drivername != 'sqlite': + # Columnas extendidas de users db.session.execute(text(""" ALTER TABLE users ADD COLUMN IF NOT EXISTS preferred_language VARCHAR(10) DEFAULT NULL; - """)) - db.session.execute(text(""" ALTER TABLE users ADD COLUMN IF NOT EXISTS theme_preference VARCHAR(10) DEFAULT 'auto' NOT NULL; - """)) - db.session.execute(text(""" ALTER TABLE users ADD COLUMN IF NOT EXISTS role_id INTEGER REFERENCES roles(id) ON DELETE SET NULL; + ALTER TABLE users ADD COLUMN IF NOT EXISTS first_name VARCHAR(100); + ALTER TABLE users ADD COLUMN IF NOT EXISTS last_name VARCHAR(100); + ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(50); + ALTER TABLE users ADD COLUMN IF NOT EXISTS personal_email VARCHAR(255); + ALTER TABLE users ADD COLUMN IF NOT EXISTS address VARCHAR(255); + ALTER TABLE users ADD COLUMN IF NOT EXISTS document_type VARCHAR(20) DEFAULT 'DNI'; + ALTER TABLE users ADD COLUMN IF NOT EXISTS document_number VARCHAR(30); + CREATE INDEX IF NOT EXISTS ix_users_document_number ON users (document_number); """)) + + # Columnas extendidas de subjects db.session.execute(text(""" ALTER TABLE subjects ADD COLUMN IF NOT EXISTS career_id INTEGER REFERENCES careers(id) ON DELETE SET NULL; - """)) - db.session.execute(text(""" ALTER TABLE subjects ALTER COLUMN code TYPE VARCHAR(50); + ALTER TABLE subjects ADD COLUMN IF NOT EXISTS is_short_course BOOLEAN DEFAULT FALSE; """)) + + # Infraestructura: buildings y classrooms db.session.execute(text(""" CREATE TABLE IF NOT EXISTS buildings ( id SERIAL PRIMARY KEY, @@ -49,7 +57,26 @@ def init_database(): created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() ); + ALTER TABLE classrooms ADD COLUMN IF NOT EXISTS building_id INTEGER REFERENCES buildings(id) ON DELETE SET NULL; """)) + + # Columnas extendidas de commissions + db.session.execute(text(""" + ALTER TABLE commissions ADD COLUMN IF NOT EXISTS shift VARCHAR(30); + ALTER TABLE commissions ADD COLUMN IF NOT EXISTS virtual_link VARCHAR(500); + ALTER TABLE commissions ADD COLUMN IF NOT EXISTS grades_closed BOOLEAN DEFAULT FALSE; + ALTER TABLE commissions ADD COLUMN IF NOT EXISTS grades_closed_at TIMESTAMP WITHOUT TIME ZONE; + ALTER TABLE commissions ADD COLUMN IF NOT EXISTS grades_closed_by INTEGER REFERENCES users(id) ON DELETE SET NULL; + ALTER TABLE commissions ADD COLUMN IF NOT EXISTS acta_number VARCHAR(50); + """)) + + # Columnas extendidas de reservations + db.session.execute(text(""" + ALTER TABLE reservations ADD COLUMN IF NOT EXISTS shift VARCHAR(30); + ALTER TABLE reservations ADD COLUMN IF NOT EXISTS virtual_link VARCHAR(500); + """)) + + # Tablas de soporte: audit_logs db.session.execute(text(""" CREATE TABLE IF NOT EXISTS audit_logs ( id SERIAL PRIMARY KEY, @@ -66,15 +93,8 @@ def init_database(): CREATE INDEX IF NOT EXISTS ix_audit_logs_module ON audit_logs (module); CREATE INDEX IF NOT EXISTS ix_audit_logs_created_at ON audit_logs (created_at); """)) - db.session.execute(text(""" - ALTER TABLE classrooms ADD COLUMN IF NOT EXISTS building_id INTEGER REFERENCES buildings(id) ON DELETE SET NULL; - """)) - db.session.execute(text(""" - ALTER TABLE commissions ADD COLUMN IF NOT EXISTS shift VARCHAR(30); - """)) - db.session.execute(text(""" - ALTER TABLE reservations ADD COLUMN IF NOT EXISTS shift VARCHAR(30); - """)) + + # Tablas de soporte: student_enrollments db.session.execute(text(""" CREATE TABLE IF NOT EXISTS student_enrollments ( id SERIAL PRIMARY KEY, @@ -89,7 +109,79 @@ def init_database(): CREATE INDEX IF NOT EXISTS ix_student_enrollments_student_id ON student_enrollments (student_id); CREATE INDEX IF NOT EXISTS ix_student_enrollments_commission_id ON student_enrollments (commission_id); """)) + + # Tablas de soporte: commission_teachers + db.session.execute(text(""" + CREATE TABLE IF NOT EXISTS commission_teachers ( + id SERIAL PRIMARY KEY, + commission_id INTEGER NOT NULL REFERENCES commissions(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role VARCHAR(50) DEFAULT 'Titular' NOT NULL, + is_primary BOOLEAN DEFAULT FALSE NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), + CONSTRAINT uq_commission_teacher UNIQUE (commission_id, user_id) + ); + CREATE INDEX IF NOT EXISTS ix_commission_teachers_commission_id ON commission_teachers (commission_id); + CREATE INDEX IF NOT EXISTS ix_commission_teachers_user_id ON commission_teachers (user_id); + """)) + + # Tablas de soporte: system_settings + db.session.execute(text(""" + CREATE TABLE IF NOT EXISTS system_settings ( + id SERIAL PRIMARY KEY, + key VARCHAR(100) UNIQUE NOT NULL, + value TEXT, + description VARCHAR(255), + category VARCHAR(50) DEFAULT 'system', + is_encrypted BOOLEAN DEFAULT FALSE, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() + ); + CREATE INDEX IF NOT EXISTS ix_system_settings_key ON system_settings (key); + """)) + + # Tablas de soporte: moodle_sync_tasks + db.session.execute(text(""" + CREATE TABLE IF NOT EXISTS moodle_sync_tasks ( + id SERIAL PRIMARY KEY, + action VARCHAR(50) NOT NULL, + entity_type VARCHAR(50) NOT NULL, + entity_id VARCHAR(100), + payload TEXT NOT NULL, + status VARCHAR(20) DEFAULT 'PENDING', + attempts INTEGER DEFAULT 0, + max_attempts INTEGER DEFAULT 5, + error_message TEXT, + next_retry_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() + ); + CREATE INDEX IF NOT EXISTS ix_moodle_sync_tasks_action ON moodle_sync_tasks (action); + CREATE INDEX IF NOT EXISTS ix_moodle_sync_tasks_status ON moodle_sync_tasks (status); + CREATE INDEX IF NOT EXISTS ix_moodle_sync_tasks_next_retry_at ON moodle_sync_tasks (next_retry_at); + """)) db.session.commit() + else: + try: + cursor = db.session.connection().connection.cursor() + cursor.execute("PRAGMA table_info(users)") + existing_cols = [row[1] for row in cursor.fetchall()] + for col_name, col_def in [ + ('preferred_language', 'VARCHAR(10)'), + ('theme_preference', "VARCHAR(10) DEFAULT 'auto'"), + ('role_id', 'INTEGER'), + ('first_name', 'VARCHAR(100)'), + ('last_name', 'VARCHAR(100)'), + ('phone', 'VARCHAR(50)'), + ('personal_email', 'VARCHAR(255)'), + ('address', 'VARCHAR(255)'), + ('document_type', "VARCHAR(20) DEFAULT 'DNI'"), + ('document_number', 'VARCHAR(30)') + ]: + if col_name not in existing_cols: + cursor.execute(f"ALTER TABLE users ADD COLUMN {col_name} {col_def}") + db.session.commit() + except Exception: + pass print("[OK] Esquema y tablas actualizadas con éxito.") # 3. Crear o sincronizar roles predeterminados y matriz de permisos