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.

El módulo modelo_auth.py ontiene la lógica de negocio de autenticación. Define dos funciones principales:

Función Login: Esta función es el corazón del sistema de autenticación. Recibe el username, password e IP del cliente, ejecuta el SP Login en la base de datos, y devuelve el código de resultado, id del usuario y username si el login fue exitoso.


                                    Img 1: función Login


El proceso interno del SP de Login es:

  1. Verifica si hay bloqueo por intentos fallidos (>5 en 20 minutos), si existe, devuelve error 50003
  2. Valida que el username exista, si no, error 50001
  3. Valida que el password sea correcto, si no, error 50002
  4. 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:

  1. El resultset de RegistrarBitacora (1 columna: resultCode)
  2. 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:


                                Img 3: función Logout

Implementación de autenticación: controlador_auth.py

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:


                                   Img 4: Lógica GET 

Cuando el usuario accede a GET /, el controlador:
  1. Intenta obtener el username de los query parameters (útil si el usuario viene de un login fallido anterior)
  2. Obtiene la IP del cliente con request.remote_addr
  3. Si existe username previo, verifica si está bloqueado (lógica futura de verificar_bloqueo())
  4. Renderiza el template login.html pasando la información de bloqueo y username previo.
Lógica POST:
                                            Img 5: Lógica POST

Cuando el usuario envía el formulario:

  1. Se extraen username y password del formulario (request.form)
  2. Se obtiene la IP del cliente
  3. Se valida que ambos campos no estén vacíos, pero si lo están, se muestra un mensaje y se redirige al login
  4. Se llama a login() del modelo para ejecutar el SP
  5. 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).

  1. 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}


Logout_view:


                                Img 6: logout_view

Aquí se obtiene le id del usuario de la session (si no existe usa 0), obtiene la IP, se llama a logout y se limpiar la sesión, redirigiéndose al login.


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:

  1. El resultset de sp_RegistrarBitacora (1 columna)
  2. 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


Comentarios

Entradas más populares de este blog

Creación del SP de Cargar Datos

Implementación de funcionalidades: Editar Empleado y Eliminar Empleado

Implementación de funcionalidad: Consultar Empleado