Modificación de la función insertar_empleado() del modelo y creación de insertar_empleado.html
Fecha: domingo 22 de abril de 2026
Hora de inicio: 12:10 p.m.
Hora de finalización: 7:27 p.m.
Horas trabajadas: 5 h 30 min
El día anterior había quedado pendiente corregir insertar_empleado(), pues no funcionaba. El objetivo del día era resolverlo. Además, tenía que construir el template HTML del formulario de inserción de empleados, lo que implicaba aprender HTML, CSS y Bootstrap.
Actividades realizadas
12:10 p.m. – 12:43 p.m.: Corrección de las consecuencias del merge del día anterior
Al revisar el repositorio me di cuenta que lo que había pasado con el merge automático de Git del día anterior fue que se sobrescribió la versión actualizada de dbo.ListarEmpleados con la versión vieja, la que todavía tenía el prefijo sp_ y no usaba alias de tabla. Esto lo corregí.
Además, en este mismo commit aproveché para agregar en ambos SPs la siguiente línea al final, justo antes del END: SELECT @resultado AS resultado;
| Línea agregada en dbo.ListarEmpleados |
| Línea agregada en dbo.InsertarEmpleado |
La idea era que si el SP retornaba su propio valor de resultado como un recordset de una sola fila y columna, Python podría leerlo con fetchone() en ambas funciones, sin depender del parámetro OUTPUT. También aproveché para dividir en varias líneas los comentarios que estaban demasiado largos en una sola línea, por estilo y legibilidad.
1:30 p.m. – 3:01 p.m.: Corrigiendo insertar_empleado() y ajustando obtener_empleados()
Con el SP modificado, proseguí a corregir el modelo en Python.
Cambios en obtener_empleados():
El cambio principal fue simplificar cómo se pasa el parámetro de salida. Antes usaba una variable "parametros = [None]" y se la pasaba al cursor.execute(). Al eliminar esa variable y pasar None directamente, el código quedó más limpio. Luego, como el SP también retorna el resultado como recordset con "SELECT @resultado AS resultado", hay que hacer que el cursor avance al segundo resultado con cursor.nextset() y leerlo con fetchone().
Uso de None y eliminación de variable Uso de cursor.nextset() y fetchone()
Cambios en insertar_empleado():
La forma en que intenté manejar el parámetro OUTPUT del SP en la versión anterior no funcionó porque, como descubrí el día anterior, cursor.var() no existe en pyodbc.
Así que creé una segunda versión manejando el parámetro OUTPUT de la misma forma en que lo hice cuando modifiqué la función obtener_empleados().
| Versión que implementa "SELECT @resultado AS resultado" en el SP y pasa None al parámetro de salida |
Pero incluso realizando estas modificaciones que sí funcionaron en obtener_empleados(), para esta función no, y el error obtenido en la prueba fue el siguiente:
"Prueba 2: Insertar empleado nuevo Error al insertar empleado: No results. Previous SQL was not a query. Resultado del SP: -2 Error inesperado al insertar empleado Prueba 3: Insertar empleado con nombre duplicado Error al insertar empleado: No results. Previous SQL was not a query. Resultado del SP: -2 Error inesperado al insertar empleado".
Esto básicamente indica que pyodbc intentaba leer un resultado de una operación que no devolvía ninguno. Este video me ayudó a entender mejor el error: Understanding the No results. Previous SQL was not a query Error in Python SQL Server Scripts.
Procedí a revisar que el archivo stored_procedures.sql y la BD no estuvieran desincronizados, aunque lo veía poco probable, ya que anteriormente actualicé el SP en el SSMS. Como se ve en esta imagen, confirmé que el "SELECT @resul AS resultado" fue agregado al SP en la BD.
| Revisión de sincronización entre el código de stored_procedures.sql y la BD |
Esta información la encontré en documentación de pyodbc que indica que los parámetros de salida con {CALL} no están completamente soportados con los drivers de Microsoft, y la solución recomendada es usar un bloque anónimo. Por lo tanto, lo implementé como se logra apreciar en la imagen:
| Implementación de bloque anónimo |
Sin embargo, esta solución me generó una gran duda: ¿esto se considera SQL incrustado en capa lógica?. Las instrucciones de la tarea indican que todo código SQL debe estar en procedimientos almacenados y que en capa lógica solo se puede invocar un SP. Un bloque anónimo es técnicamente SQL que vive en el código Python, no en un SP. Se lo comenté a Johana por WhatsApp y ella propuso una solución más limpia. Esta consistía en modificar el SP dbo.InsertarEmpleado para que en lugar de retornar el resultado como parámetro OUTPUT, se utilice una variable que almacene el valor del resultado del SP y lo retorne como recordset con un "SELECT". Así Python podría leerlo con fetchone() sin necesidad de ningún bloque anónimo. Estuve de acuerdo y ella hizo ese cambio y lo subió al repositorio.
4:00 p.m. – 7:27 p.m.: Construcción del formulario de inserción
Esta parte del día también fue fuerte, ya que implicó aprender tecnologías que no había usado antes como HTML, CSS y Bootstrap. Johana se encargó de la pantalla principal (index.html) y yo del formulario de inserción (insertar_empleado.html), este fue un cambio que coordinamos porque los errores en insertar_empleado() me habían retrasado y ella necesitaba avanzar.
Para construir el formulario partí de cero en HTML. Consulté los fundamentos básicos como estructura de una página, atributos, estilos, formularios, elementos div, y luego pasé a Bootstrap para darle el mismo estilo visual que tiene index.html, incluyendo mismos colores, navbar y estructura general. También usé Jinja2 para el bloque de mensajes flash que muestra errores de validación y confirmaciones.
Al probar en el navegador, el botón "Regresar" mostraba el HTML como texto plano en lugar de renderizarse como botón. Resultó ser un error muy pequeño, pero visible. La causa fue una etiqueta de apertura faltante. La corregí y el botón apareció correctamente.
- Campos vacíos
- Nombre con números
- Salario no numérico
- Salario negativo
- Botón Regresar
Todos mostraron el mensaje de error esperado. Al final de la sesión el formulario de inserción estaba funcionando correctamente en el navegador.
Forma de trabajo del equipo
Nos coordinamos por WhatsApp durante el día. La comunicación fue especialmente importante en el momento de decidir qué hacer con el bloque anónimo. Johana propuso la solución que terminó siendo la escogida y la implementó ella misma en el SP y en la función obtener_empleados(). También, cuando intercambiamos los templates, ella hizo la pantalla principal y yo el formulario de inserción.
Buenas prácticas descubiertas
- Siempre hacer pull antes de empezar a trabajar, especialmente si la compañera también estuvo activa en el repositorio el día anterior.
- Cuando hay una limitación técnica de una librería, es recomendado buscar la documentación oficial antes de intentar soluciones creativas, en este caso, la wiki de pyodbc explicaba exactamente el problema y la alternativa recomendada.
- Si una solución genera dudas sobre si cumple con las instrucciones del proyecto, consultarlo con el equipo antes de darlo por definitivo.
- Al construir interfaces web, mantener consistencia visual entre las pantallas desde el inicio ahorra tiempo de ajustes después.
Moraleja del día
A veces la solución funcional no es la correcta para el contexto del proyecto. El bloque anónimo resolvía el problema de pyodbc, pero introducía SQL en capa lógica, que es exactamente lo que las instrucciones de la tarea piden no hacer.
Referencias
Modelo y conexión a BD:
- Calling Stored Procedures — mssql-python wiki
- Python ODBC example — Easysoft
- Passing parameters to SQL queries — psycopg3
- Connecting to and querying SQL Server with Python — Hex
Comentarios
Publicar un comentario