fix: database initialization on sqlite and i18n logic
This commit is contained in:
@@ -565,3 +565,10 @@
|
|||||||
* **Validación Automatizada de la Suite de Pruebas:**
|
* **Validación Automatizada de la Suite de Pruebas:**
|
||||||
* Creación de `tests/test_sprint4_optimizer_and_concurrency.py` con 5 pruebas exhaustivas.
|
* Creación de `tests/test_sprint4_optimizer_and_concurrency.py` con 5 pruebas exhaustivas.
|
||||||
* Ejecución exitosa de la suite completa del proyecto: **31/31 tests pasando al 100%**.
|
* Ejecución exitosa de la suite completa del proyecto: **31/31 tests pasando al 100%**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Actualización de Lógica de Detección de Idioma (2026-09-10)
|
||||||
|
* **Preferencia de Idioma y Detección Automática (pp/__init__.py):**
|
||||||
|
* Se reestructuró la función get_locale() para garantizar que el idioma por defecto del sistema siempre sea español (es), a menos que el usuario configure un idioma diferente de manera explícita en su perfil.
|
||||||
|
* Para usuarios no autenticados o que ingresan por primera vez sin una sesión activa, el sistema respeta el idioma principal configurado en el navegador (␍equest.accept_languages), y guarda esta preferencia en la sesión para mantener la consistencia durante la visita inicial.
|
||||||
|
|||||||
+14
-29
@@ -24,8 +24,9 @@ def create_app(config_class=Config):
|
|||||||
cors.init_app(app)
|
cors.init_app(app)
|
||||||
|
|
||||||
# Login manager configuration
|
# Login manager configuration
|
||||||
|
from flask_babel import lazy_gettext
|
||||||
login_manager.login_view = 'auth.login'
|
login_manager.login_view = 'auth.login'
|
||||||
login_manager.login_message = 'Por favor inicia sesión para acceder a esta página.'
|
login_manager.login_message = lazy_gettext('Por favor inicia sesión para acceder a esta página.')
|
||||||
login_manager.login_message_category = 'info'
|
login_manager.login_message_category = 'info'
|
||||||
|
|
||||||
# Import models to ensure they are registered
|
# Import models to ensure they are registered
|
||||||
@@ -56,46 +57,30 @@ def create_app(config_class=Config):
|
|||||||
|
|
||||||
# Language selector function
|
# Language selector function
|
||||||
def get_locale():
|
def get_locale():
|
||||||
# 1. Parámetro explícito en la URL (?lang=es o ?lang=en)
|
# 1. Si el usuario está autenticado y tiene idioma configurado explícitamente en su perfil, prevalece.
|
||||||
|
from flask_login import current_user
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
if getattr(current_user, 'preferred_language', None) and current_user.preferred_language in app.config['LANGUAGES']:
|
||||||
|
return current_user.preferred_language
|
||||||
|
# El idioma por defecto siempre será español a menos que se configure a mano
|
||||||
|
return app.config['BABEL_DEFAULT_LOCALE']
|
||||||
|
|
||||||
|
# 2. Parámetro explícito en la URL (?lang=es o ?lang=en) (para usuarios no autenticados o login)
|
||||||
lang = request.args.get('lang')
|
lang = request.args.get('lang')
|
||||||
if lang and lang in app.config['LANGUAGES']:
|
if lang and lang in app.config['LANGUAGES']:
|
||||||
session['language'] = lang
|
session['language'] = lang
|
||||||
return lang
|
return lang
|
||||||
|
|
||||||
# 2. Si el usuario está autenticado y tiene idioma configurado en su perfil, prevalece en toda la sesión
|
# 3. Si ya hay un idioma guardado en la sesión
|
||||||
from flask_login import current_user
|
|
||||||
if current_user.is_authenticated and getattr(current_user, 'preferred_language', None):
|
|
||||||
if current_user.preferred_language in app.config['LANGUAGES']:
|
|
||||||
return current_user.preferred_language
|
|
||||||
|
|
||||||
# 3. En la pantalla de Login (usuario no autenticado):
|
|
||||||
# El idioma predeterminado del login debe ser español o respetar el idioma del navegador.
|
|
||||||
# No debe verse afectado por cookies o sesiones residuales de otros usuarios.
|
|
||||||
is_login = (request.endpoint == 'auth.login' or request.path.rstrip('/') == '/login')
|
|
||||||
if is_login:
|
|
||||||
# Solo si el usuario cambió el idioma explícitamente en el selector del login
|
|
||||||
if session.get('login_lang_explicit') in app.config['LANGUAGES']:
|
|
||||||
return session['login_lang_explicit']
|
|
||||||
|
|
||||||
# Detectar según el navegador del usuario (soporta es-419, es-AR, es-ES, etc.)
|
|
||||||
if request.accept_languages:
|
|
||||||
for accepted_lang, _quality in request.accept_languages:
|
|
||||||
primary = accepted_lang.split('-')[0].lower()
|
|
||||||
if primary in app.config['LANGUAGES']:
|
|
||||||
return primary
|
|
||||||
|
|
||||||
# Idioma predeterminado por defecto del login: Español
|
|
||||||
return 'es'
|
|
||||||
|
|
||||||
# 4. Para otras rutas: verificar si hay idioma en sesión (ej. selector manual)
|
|
||||||
if 'language' in session and session['language'] in app.config['LANGUAGES']:
|
if 'language' in session and session['language'] in app.config['LANGUAGES']:
|
||||||
return session['language']
|
return session['language']
|
||||||
|
|
||||||
# 5. Detección automática del navegador por defecto (soporta subtags regionales)
|
# 4. Primera vez que se accede a la web (sin sesión): respetar el idioma del navegador
|
||||||
if request.accept_languages:
|
if request.accept_languages:
|
||||||
for accepted_lang, _quality in request.accept_languages:
|
for accepted_lang, _quality in request.accept_languages:
|
||||||
primary = accepted_lang.split('-')[0].lower()
|
primary = accepted_lang.split('-')[0].lower()
|
||||||
if primary in app.config['LANGUAGES']:
|
if primary in app.config['LANGUAGES']:
|
||||||
|
session['language'] = primary # Se guarda para la sesión actual
|
||||||
return primary
|
return primary
|
||||||
|
|
||||||
# Fallback del sistema: Español
|
# Fallback del sistema: Español
|
||||||
|
|||||||
+69
-68
@@ -21,74 +21,75 @@ def init_database():
|
|||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
# 2. Migraciones incrementales seguras para bases de datos existentes
|
# 2. Migraciones incrementales seguras para bases de datos existentes
|
||||||
db.session.execute(text("""
|
if db.engine.url.drivername != 'sqlite':
|
||||||
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 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 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 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 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("""
|
||||||
"""))
|
ALTER TABLE subjects ALTER COLUMN code TYPE VARCHAR(50);
|
||||||
db.session.execute(text("""
|
"""))
|
||||||
CREATE TABLE IF NOT EXISTS buildings (
|
db.session.execute(text("""
|
||||||
id SERIAL PRIMARY KEY,
|
CREATE TABLE IF NOT EXISTS buildings (
|
||||||
name VARCHAR(100) UNIQUE NOT NULL,
|
id SERIAL PRIMARY KEY,
|
||||||
code VARCHAR(50) UNIQUE,
|
name VARCHAR(100) UNIQUE NOT NULL,
|
||||||
address VARCHAR(255),
|
code VARCHAR(50) UNIQUE,
|
||||||
floors INTEGER DEFAULT 1 NOT NULL,
|
address VARCHAR(255),
|
||||||
description TEXT,
|
floors INTEGER DEFAULT 1 NOT NULL,
|
||||||
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
description TEXT,
|
||||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
|
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
||||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
|
||||||
);
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
|
||||||
"""))
|
);
|
||||||
db.session.execute(text("""
|
"""))
|
||||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
db.session.execute(text("""
|
||||||
id SERIAL PRIMARY KEY,
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
id SERIAL PRIMARY KEY,
|
||||||
user_email VARCHAR(255),
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||||
action VARCHAR(50) NOT NULL,
|
user_email VARCHAR(255),
|
||||||
module VARCHAR(50) NOT NULL,
|
action VARCHAR(50) NOT NULL,
|
||||||
entity_id INTEGER,
|
module VARCHAR(50) NOT NULL,
|
||||||
details TEXT,
|
entity_id INTEGER,
|
||||||
ip_address VARCHAR(45),
|
details TEXT,
|
||||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
ip_address VARCHAR(45),
|
||||||
);
|
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
CREATE INDEX IF NOT EXISTS ix_audit_logs_action ON audit_logs (action);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS ix_audit_logs_module ON audit_logs (module);
|
CREATE INDEX IF NOT EXISTS ix_audit_logs_action ON audit_logs (action);
|
||||||
CREATE INDEX IF NOT EXISTS ix_audit_logs_created_at ON audit_logs (created_at);
|
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 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 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.execute(text("""
|
||||||
"""))
|
ALTER TABLE reservations ADD COLUMN IF NOT EXISTS shift VARCHAR(30);
|
||||||
db.session.execute(text("""
|
"""))
|
||||||
CREATE TABLE IF NOT EXISTS student_enrollments (
|
db.session.execute(text("""
|
||||||
id SERIAL PRIMARY KEY,
|
CREATE TABLE IF NOT EXISTS student_enrollments (
|
||||||
student_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
id SERIAL PRIMARY KEY,
|
||||||
commission_id INTEGER NOT NULL REFERENCES commissions(id) ON DELETE CASCADE,
|
student_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'activo',
|
commission_id INTEGER NOT NULL REFERENCES commissions(id) ON DELETE CASCADE,
|
||||||
enrolled_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
status VARCHAR(20) NOT NULL DEFAULT 'activo',
|
||||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
|
enrolled_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
|
||||||
notes TEXT,
|
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
|
||||||
CONSTRAINT uq_student_commission UNIQUE (student_id, commission_id)
|
notes TEXT,
|
||||||
);
|
CONSTRAINT uq_student_commission UNIQUE (student_id, commission_id)
|
||||||
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);
|
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);
|
||||||
db.session.commit()
|
"""))
|
||||||
|
db.session.commit()
|
||||||
print("[OK] Esquema y tablas actualizadas con éxito.")
|
print("[OK] Esquema y tablas actualizadas con éxito.")
|
||||||
|
|
||||||
# 3. Crear o sincronizar roles predeterminados y matriz de permisos
|
# 3. Crear o sincronizar roles predeterminados y matriz de permisos
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user