feat: gestion RBAC granular, importacion sheets, filtrado de aulas virtuales y pisos, capacidad infinita y traduccion
This commit is contained in:
+198
-11
@@ -1,40 +1,227 @@
|
||||
import sys
|
||||
|
||||
# Asegurar codificación UTF-8 en consola de Windows
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
if hasattr(sys.stderr, 'reconfigure'):
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
|
||||
from app import create_app, db
|
||||
from app.models.user import User
|
||||
from app.models.role import Role, Permission, SYSTEM_MODULES
|
||||
from sqlalchemy import text
|
||||
|
||||
def init_database():
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
try:
|
||||
print("⏳ Conectando a la base de datos y creando tablas...")
|
||||
db.create_all()
|
||||
print("✅ Tablas creadas en la base de datos con éxito.")
|
||||
print("[*] Conectando a la base de datos y sincronizando esquema...")
|
||||
|
||||
# Verificar o crear usuario administrador
|
||||
# 1. Crear tablas que no existan
|
||||
db.create_all()
|
||||
|
||||
# 2. Migraciones incrementales seguras para bases de datos existentes
|
||||
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;
|
||||
"""))
|
||||
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);
|
||||
"""))
|
||||
db.session.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS buildings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
code VARCHAR(50) UNIQUE,
|
||||
address VARCHAR(255),
|
||||
floors INTEGER DEFAULT 1 NOT NULL,
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
"""))
|
||||
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);
|
||||
"""))
|
||||
db.session.commit()
|
||||
print("[OK] Esquema y tablas actualizadas con éxito.")
|
||||
|
||||
# 3. Crear o sincronizar roles predeterminados y matriz de permisos
|
||||
print("[*] Configurando roles institucionales y permisos RBAC...")
|
||||
default_roles = [
|
||||
{
|
||||
'name': 'Admin',
|
||||
'description': 'Control total institucional, infraestructura, gestión académica y accesos RBAC',
|
||||
'is_system': True,
|
||||
'permissions': {
|
||||
'classrooms': 'read_write',
|
||||
'buildings': 'read_write',
|
||||
'reservations': 'read_write',
|
||||
'schedule': 'read_write',
|
||||
'academic': 'read_write',
|
||||
'import': 'read_write',
|
||||
'optimizer': 'read_write',
|
||||
'users': 'read_write'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Docente',
|
||||
'description': 'Gestión de reservas de cursada, consulta de cronograma y materias',
|
||||
'is_system': False,
|
||||
'permissions': {
|
||||
'classrooms': 'read',
|
||||
'buildings': 'read',
|
||||
'reservations': 'read_write',
|
||||
'schedule': 'read_write',
|
||||
'academic': 'read',
|
||||
'import': 'none',
|
||||
'optimizer': 'read',
|
||||
'users': 'none'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Operador',
|
||||
'description': 'Gestión operativa de aulas, sedes, reservas, importación y cartelera',
|
||||
'is_system': False,
|
||||
'permissions': {
|
||||
'classrooms': 'read_write',
|
||||
'buildings': 'read_write',
|
||||
'reservations': 'read_write',
|
||||
'schedule': 'read_write',
|
||||
'academic': 'read_write',
|
||||
'import': 'read_write',
|
||||
'optimizer': 'none',
|
||||
'users': 'none'
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Consulta',
|
||||
'description': 'Acceso de solo lectura al cronograma general, asignaturas y aulas',
|
||||
'is_system': False,
|
||||
'permissions': {
|
||||
'classrooms': 'read',
|
||||
'buildings': 'read',
|
||||
'reservations': 'read',
|
||||
'schedule': 'read',
|
||||
'academic': 'read',
|
||||
'import': 'none',
|
||||
'optimizer': 'none',
|
||||
'users': 'none'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
module_ids = [m['id'] for m in SYSTEM_MODULES]
|
||||
admin_role = None
|
||||
|
||||
for r_data in default_roles:
|
||||
role = Role.query.filter_by(name=r_data['name']).first()
|
||||
if not role:
|
||||
role = Role(
|
||||
name=r_data['name'],
|
||||
description=r_data['description'],
|
||||
is_system=r_data['is_system']
|
||||
)
|
||||
db.session.add(role)
|
||||
db.session.flush()
|
||||
print(f" [+] Rol '{role.name}' creado.")
|
||||
else:
|
||||
role.description = r_data['description']
|
||||
role.is_system = r_data['is_system']
|
||||
|
||||
if role.name == 'Admin':
|
||||
admin_role = role
|
||||
|
||||
# Sincronizar permisos del rol
|
||||
for mod in module_ids:
|
||||
level = r_data['permissions'].get(mod, 'none')
|
||||
role.set_permission(mod, level)
|
||||
|
||||
db.session.commit()
|
||||
print("[OK] Roles y matriz de permisos configurados correctamente.")
|
||||
|
||||
# 4. Verificar o crear usuario administrador inicial
|
||||
print("[*] Verificando usuario administrador inicial...")
|
||||
admin = User.query.filter_by(email="admin@edu-space.com").first()
|
||||
if not admin:
|
||||
print("⏳ Creando usuario administrador inicial...")
|
||||
admin = User(
|
||||
email="admin@edu-space.com",
|
||||
name="System Administrator",
|
||||
role="ADMIN",
|
||||
is_active=True
|
||||
role_id=admin_role.id if admin_role else None,
|
||||
is_active=True,
|
||||
preferred_language="es",
|
||||
theme_preference="auto"
|
||||
)
|
||||
admin.set_password("admin123")
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
print("✅ Usuario inicial creado exitosamente:")
|
||||
print("[OK] Usuario inicial creado exitosamente:")
|
||||
print(" Email: admin@edu-space.com")
|
||||
print(" Password: admin123")
|
||||
print(f" Rol: {admin_role.name if admin_role else 'ADMIN'}")
|
||||
else:
|
||||
print("ℹ️ El usuario admin (admin@edu-space.com) ya existe en la base de datos.")
|
||||
# Sincronizar rol y atributos del admin existente si faltan
|
||||
if admin_role and admin.role_id != admin_role.id:
|
||||
admin.role_id = admin_role.id
|
||||
admin.role = "ADMIN"
|
||||
if not admin.preferred_language:
|
||||
admin.preferred_language = "es"
|
||||
if not admin.theme_preference:
|
||||
admin.theme_preference = "auto"
|
||||
db.session.commit()
|
||||
print(f"[INFO] El usuario admin (admin@edu-space.com) está activo y vinculado al rol '{admin_role.name if admin_role else 'ADMIN'}'.")
|
||||
|
||||
# 5. Inicializar edificios institucionales oficiales
|
||||
print("[*] Verificando edificios institucionales...")
|
||||
from app.models.building import Building
|
||||
b_central = Building.query.filter_by(name='Edificio Central').first()
|
||||
if not b_central:
|
||||
b_central = Building(
|
||||
name='Edificio Central',
|
||||
code='CENTRAL',
|
||||
address='Tte. Gral. Juan Domingo Perón 802, CABA',
|
||||
floors=4,
|
||||
description='Sede central institucional de UniCABA. Aulas de grado, auditorio y laboratorios.'
|
||||
)
|
||||
db.session.add(b_central)
|
||||
else:
|
||||
b_central.address = 'Tte. Gral. Juan Domingo Perón 802, CABA'
|
||||
|
||||
b_virtual = Building.query.filter_by(name='Campus Virtual').first()
|
||||
if not b_virtual:
|
||||
b_virtual = Building(
|
||||
name='Campus Virtual',
|
||||
code='VIRTUAL',
|
||||
address='Entorno Digital / Microsoft Teams - Moodle',
|
||||
floors=0,
|
||||
description='Entorno de aulas virtuales y clases a distancia'
|
||||
)
|
||||
db.session.add(b_virtual)
|
||||
db.session.commit()
|
||||
print("[OK] Edificios institucionales configurados con dirección oficial.")
|
||||
|
||||
print("\n🚀 Base de datos inicializada y lista para usar.")
|
||||
print("\n[LISTO] Base de datos inicializada y lista para usar.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error al inicializar la base de datos: {str(e)}")
|
||||
print(f"\n[ERROR] Error al inicializar la base de datos: {str(e)}")
|
||||
print("\nVerifica que:")
|
||||
print("1. El servicio de PostgreSQL o MySQL esté corriendo.")
|
||||
print("1. El servicio de PostgreSQL esté corriendo.")
|
||||
print("2. La base de datos 'classrooms_db' haya sido creada.")
|
||||
print("3. La variable DATABASE_URL en el archivo .env tenga el usuario y contraseña correctos.")
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user