db.py, cargar_datos.py, Login y logout funcionales
Fecha: 21 de abril 2026
Hora de inicio: 11:00 a.m.
Hora de finalización: 4:30 p.m.
Horas trabajadas: 5h 30 min
Con todos los stored procedures ya implementados en la base de datos, el siguiente paso crítico era establecer el puente entre Python y Azure SQL Database, y luego implementar la funcionalidad de autenticación (Login y logout). Esta entrada documenta tanto la configuración técnica de la conexión como el desarrollo completo de las vistas, controladores y modelos necesarios para que un usuario pueda hacer login, mantener su sesión activa, y hacer logout correctamente.
Actividades realizadas
11:00 a.m. - 11:30 a.m.
Configuración de conexión a Azure SQL Database (db.py)
El primer paso fue crear un módulo centralizado de conexión db.py que encapsulara toda la lógica de conexión a la base de datos. Este módulo define una función get_db_connection()que todos los modelos (modelo_auth, modelo_empleado, modelo_movimiento) importan y llaman, evitando duplicación de código y facilitando el mantenimiento futuro.
11:30 a.m. - 4:30 p.m.
Img 1: función Login
El proceso interno del SP de Login es:
- Verifica si hay bloqueo por intentos fallidos (>5 en 20 minutos), si existe, devuelve error 50003
- Valida que el username exista, si no, error 50001
- Valida que el password sea correcto, si no, error 50002
- Si todo es correcto, devuelve id del usuario y username
Cada uno de estos
eventos (exitoso, no exitoso, bloqueado) se registra en BitacoraEvento mediante
una llamada interna a RegistrarBitacora.
Manejo crítico
de múltiples resultsets:
Cuando el SP
Login ejecuta EXEC dbo.RegistrarBitacora, ese SP devuelve su propio resultset
antes de que el SP principal devuelva el suyo. Esto significa que pyodbc
recibe:
- El resultset de RegistrarBitacora
(1 columna: resultCode)
- El resultset del SP Login (3
columnas: resultCode, idUsuario, username)
Sin una
estrategia de navegación, cursor.fetchone() lee el primer resultset (el de la
bitácora con 1 columna). Luego, el código intenta acceder a resultado[1] y
resultado[2], que no existen, resultando en que id_usuario quede en 0 (o
error).
La solución
implementada es un while que salta resultsets de 1 columna:
Img 2: while de función Login
Función
logout(id_usuario, ip):
Esta función es
conceptualmente más simple. Ejecuta el SP dbo.Logout, que solo registra el
evento de logout en BitacoraEvento y devuelve el código de resultado:
El módulo
controlador_auth.py se define un Blueprint de Flask llamado auth_bp que
contiene las rutas de autenticación. Un Blueprint es una forma de organizar
rutas en módulos, permitiendo que diferentes funcionalidades (auth, empleados,
movimientos) se desarrollen en carpetas separadas sin conflictos (esto fue algo
que ya vi en la Tarea 1).
Ruta GET/POST '/' (login_view):
Esta ruta maneja
tanto la visualización del formulario de login (GET) como el procesamiento del
login (POST).
Lógica GET:
- Intenta obtener el username de los
query parameters (útil si el usuario viene de un login fallido anterior)
- Obtiene la IP del cliente con
request.remote_addr
- Si existe username previo, verifica
si está bloqueado (lógica futura de verificar_bloqueo())
- Renderiza el template login.html
pasando la información de bloqueo y username previo.
Cuando el usuario
envía el formulario:
- Se extraen username y password del
formulario (request.form)
- Se obtiene la IP del cliente
- Se valida que ambos campos no estén
vacíos, pero si lo están, se muestra un mensaje y se redirige al login
- Se llama a login() del modelo para
ejecutar el SP
- Si
result_code == 0 (éxito):
-Se guardan id_usuario y username en la sesión de Flask (session es un
diccionario seguro que persiste en el navegador del usuario como una cookie
encriptada).
-Se redirige a la página principal de empleados
(empleado.listar_empleados_view).
- Si
result_code > 50000 (error):
-Se obtiene el mensaje de error de la BD mediante
obtener_mensaje_error(result_code) (que llama a sp_ObtenerMensajeError)
-Se muestra el mensaje con flash() (que lo guarda temporalmente en la
sesión)
-Se redirige al login, pasando el username como query parameter:
?username={username}
Implementación de la vista: login.html
El archivo
templates/login.html define la interfaz visual del formulario de login. Utiliza
HTML5 para estructura y Bootstrap (via CDN) para estilos base.
Luego creé estilos.css como archivo para centralizar los estilos de la aplicación. Se importa desde login.html y el resto de htmls creados por mí.
Errores Encontrados
Error 1:
Múltiples resultsets desde sp_RegistrarBitacora complicaban la lectura
Al
ejecutar login(), el valor de id_usuario siempre llegaba como 0 o generaba
errores, aunque la base de datos mostrara que el login fue exitoso en SSMS.
Causa: El SP
Login hace al menos una llamada a sp_RegistrarBitacora para registrar el
evento. Cada SP llamado desde otro devuelve su propio resultset. Así, pyodbc
recibe múltiples resultsets:
- El resultset de sp_RegistrarBitacora
(1 columna)
- El resultset del SP Login (3
columnas)
Sin una
estrategia clara de navegación, cursor.fetchone() leía el primer resultset y el
código intentaba acceder a columnas inexistentes.
Solución:
Implementar el patrón de salto de resultsets verificando el número de columnas. Lo que se muestra en la img3.
Error 2:
Username vacío en la sesión en algunos casos
En raras
ocasiones, el username guardado en session['username'] era una cadena vacía.
Causa: Si algo
fallaba internamente en el SP antes de asignar @usernameOut, la variable
quedaba vacía. Aunque esto no debería ocurrir en circunstancias normales, se
agregó una medida por si acaso:
Img 7: reacción defensiva ante posible error.
Si username_out
está vacío, se usa el username del formulario en su lugar. Esto garantiza que
siempre hay un username válido en la sesión.
Forma de trabajo del equipo
Yo (JJ) implementé db.py, modelo_auth.py, controlador_auth.py, login.html y estilos.css. La mayor parte del tiempo se invirtió en debugging del patrón de resultsets múltiples, que no era obvio al principio. En este caso la comunicación fue mediante whatsApp, con VB habíamos acordado que yo iba a iniciar antes que ella porque algunas partes de su trabajo dependíand e cierta manera de la implementación del mío.
Referencias consultadas
- Flask Documentation — Sessions: https://flask.palletsprojects.com/en/stable/
- Flask Documentation — Flash messages: https://flask.palletsprojects.com/en/stable/
- Flask Documentation — Blueprints: https://flask.palletsprojects.com/en/stable/blueprints/
- Flask Documentation — request.remote_addr: https://flask.palletsprojects.com/en/stable/api/#flask.request.remote_addr
- pyodbc Documentation — Multiple result sets: https://github.com/mkleehammer/pyodbc/wiki
- Bootstrap Documentation — Forms: https://getbootstrap.com/docs/5.0/forms/overview/
- Jinja2 Template Engine: https://jinja.palletsprojects.com/en/3.0.x/
- OWASP Security Best Practices — Session Management: https://owasp.org/www-project-top-ten/
- Tarea programada 1.
Comentarios
Publicar un comentario