Séptima Entrada de la Bitácora (6/16/26)
Hora de inicio: 6:59 pm (6/16/26)
Hora de fin: 2:53 am (6/17/26)
Cantidad de horas trabajadas: 8 horas muy dedicadas
En esta entrada de la bitácora principalmente me dediqué a trabajar finalmente en el SP para simulaciones, ya que antes no teníamos el XML de operaciones, pero apenas vi que lo subieron ya me puse a programar este SP, anteriormente ya había hecho SPs para hacer algunas partes, como asociar/desasociar deducciones de forma individual, pero eso realmente no es algo que se pida que se haga en la GUI, entonces no tenía sentido subirlo, lo había hecho más que todo para ir adelantando esta parte.
A día de hoy seguimos sin tener el XML de catálogos que realmente refleje los catálogos de esta tarea programada para Error y TipoEvento, entonces bueno, realmente la información de TipoEvento es la más problemática, porque no tienen sentido esos TipoEvento del XML para la trazabilidad de esta tarea, entonces POR AHORA, tentativamente, decidí incluir estos directamente en el SP que carga el XML de forma alambrada, porque si no no se me ocurre cómo hacer una trazabilidad decente, y necesito esa trazabilidad para realmente darme cuenta si las cosas funcionan. Entonces esto fue lo primero, hice un SP relativamente sencillo para cargar los catálogos, pero TipoEvento en realidad lo cargo de forma alambrada de forma NO trivial ni arbitraria, ya que me baso totalmente en el requerimiento de trazabilidad y en el XML parcial que daba el profe ahí, que es lo que no se siguió en el XML de datos/catálogos. Mientras hacía esto me di cuenta que no había creado la tabla Error, entonces la agregué.
Ahora, para el XML de simulaciones realmente tuve que investigar mucho porque lo que más se me complicó fue el tema de las fechas, y el cómo estructurar las transacciones, realmente no estoy 100% seguro de que sea la forma correcta, usualmente cuando un archivo de código me queda tan grande mi instinto es pensar que hay alguna forma más eficiente, pero realmente no la conozco.
Para las fechas vi muchas fuentes, por ejemplo:
https://stackoverflow.com/questions/1051488/get-the-last-day-of-the-month-in-sql
https://stackoverflow.com/questions/7255595/problem-when-using-dateadd-and-datediff-to-get-the-start-day-monday-of-the-wee
https://stackoverflow.com/questions/1110998/get-day-of-week-in-sql-server-2005-2008
https://learn.microsoft.com/en-us/sql/t-sql/functions/datepart-transact-sql?view=sql-server-ver17
https://learn.microsoft.com/en-us/sql/t-sql/statements/set-datefirst-transact-sql?view=sql-server-ver17
https://learn.microsoft.com/en-us/sql/t-sql/functions/datediff-transact-sql?view=sql-server-ver17
https://learn.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql?view=sql-server-ver17
Y de toda mi investigación lo que intuyo es que realmente hay muchas maneras de hacerlo, así que la que decidí quizás no sea la mejor, pero bueno, fue un poco como escoger al azar en el momento para probar si funcionaba, y de hecho no logro encontrar la fuente exacta que utilicé para mi fórmula base, pero era básicamente este patrón:
SET @diaSemana = DATEDIFF(DAY, 0, @fechaOperacion) % 7;
También otro que leí le hacía +1 para que fuera en base 1 entonces lunes fuera 1, martes 2, miércoles 3, etc., pero al fin y al cabo la lógica es la misma con o sin +1.
Luego también usé otras funciones como estas:
https://learn.microsoft.com/en-us/sql/t-sql/functions/datefromparts-transact-sql?view=sql-server-ver17
https://learn.microsoft.com/en-us/sql/t-sql/functions/eomonth-transact-sql?view=sql-server-ver17
Para algunos cálculos como el último día del mes.
Todo este calendario se crea con un loop comparando cada fecha de operación, calculando cuál sería el inicio de la semana o mes, y los cierres respectivos.
La lógica tal vez se entiende más fácil viendo el código, aunque los cálculos realmente no son tan intuitivos de leer, por ejemplo:
SET @inicioSemanaActual = DATEADD(DAY, -(((@diaSemana - @VIERNES) + 7) % 7), @fechaOperacion);
SET @inicioSemanaSiguiente = DATEADD(DAY, 1, @fechaOperacion);
SET @proximoInicioSemana = DATEADD(DAY, 7, @inicioSemanaActual);
El propósito es básicamente: inicioSemanaActual siempre retrocede al viernes anterior según el día de la fecha de operación, inicioSemanaSiguiente suma un día porque solo se usa cuando el día de operación es un jueves, para eso se usa el flag de @esJueves.
Luego el cálculo de los meses es un poco más complejo, ya que el profe en el contexto el proyecto dice que el salario mensual va desde la semana que empieza el último viernes del mes anterior, y termina el último jueves del mes actual.
Por poner una imagen más explicativa, la idea sería entonces que el salario mensual se calcularía según un calendario como así, poniendo de ejemplo junio 2026:
Comienza el último viernes del mes anterior, que sería el 29 de mayo, y termina el último jueves del mes actual, que sería el 25 de junio, entonces este mes por ejemplo tendría 4 semanas.
Comparándolo con abril por ejemplo:
Podemos ver que abril tendría 5 semanas, y abril es un caso interesante porque justo termina un jueves.
Entonces bueno, para explicarlo mejor con el código, sería así:
SET @fechaInicioMesActual = DATEADD(DAY, 1, @ultimoJuevesAnterior);
Para calcular el inicio lo que hacemos es sumarle un día al último jueves del mes anterior (esta es la lógica que se me ocurrió para que no haya solapamiento entre semanas de ciertos meses, porque téeeecnicamente, bajo las instrucciones, mayo 2026 debería empezar el último viernes del mes anterior, pero abril terminó un jueves, entonces creo yo que tiene más sentido que mayo empiece el día siguiente al último jueves del mes anterior)
SET @fechaFinMesActual = DATEADD(DAY, -((((DATEDIFF(DAY, 0, @ultimoDiaMes) % 7) - @JUEVES) + 7) % 7), @ultimoDiaMes);
Por explicar un poco mejor cómo funciona ese cálculo y esas funciones, básicamente DATEDIFF(DAY, 0, @ultimoDiaMes) calcula la diferencia de días entre dos fechas, la fecha 0 en SQL Server es 1900-01-01, que fue un lunes, por eso se usa como referencia, y la diferencia de días en sí no es lo importante, lo que hacemos es usar el patrón que mencioné antes con % 7 para averiguar el residuo, y de ahí sacamos el día, entonces por ejemplo, con abril, esto sería básicamente la diferencia entre 1900-01-01 y 2026-04-30, y al sacar el % 7 obtenemos "3" que sería jueves, recordando que en la lógica del SP se tiene:
0 ::= lunes
1::= martes
2 ::= miercoles
3 ::= jueves
4 ::= viernes
5 ::= sabado
6 ::= domingo
Entonces se sabe que el ultimo dia es jueves, y al restarle la constante @JUEVES (3) lo que buscamos es encontrar que tantos dias estamos "más allá" del último jueves, por ejemplo con abril, el resultado sería 3 - 3 = 0 porque pues ya estamos en el último jueves. El propósito de sumarle 7 es para evitar negativos, porque por ejemplo si el último día del mes fuera un martes, como en junio 2026, entonces tendríamos 1 - 3 = -2, luego -2 + 7 = 5, y finalmente para abril tendriamos 7 % 7 = 0, y -(0) = 0, entonces al hacer el DATEADD no se resta nada porque ya estamos en el último jueves del mes, o en el caso de junio tendríamos 5 % 7 = 5, -(5) = -5, entonces el dateadd se devuelve 5 dias, así:
Después para el XML lo que se hace es usar algunas funciones de XQuery (nodes, value, query) junto con XPath en algunas ocasiones como:
SET @xmlDia = @inXmlOperaciones.query('/Operaciones/FechaOperacion[@Fecha=sql:variable("@fechaStr")]');
Esto de usar variables de SQL lo vi durante mi investigación y facilita bastante el proceso, aquí dejo una referencia de las tantas que vi al investigar sobre esto:
https://stackoverflow.com/questions/39390942/use-variable-in-xpath-for-sql-server
Okay estoy tomando mucho tiempo anotando y explicando todo esto, entonces ire mas resumidamente a partir de ahora, pero igual creo que es importante explicar todo el proceso porque es el SP mas importante de la tarea.
Por resumir un poco la estructura general del SP, seria algo asi:
- try
- declaracion de variables y tablas variable
- cargar las fechas de los XML
- loop con las fechas
- calcular fechas
- limpiar y agregar valores a las tablas variables con las que se haran las siguientes transacciones
- loop para la transaccion de insertar empleados
- pre procesado
- begin tInsertar
- commit tInsertar
- loop para la transaccion de asociar deducciones
- pre proceso
- begin tAsociar
- commit tAsociar
- loop para la transaccion de desasociar deducciones
- pre proceso
- begin tDesasociar
- commit tDesasociar
- pre proceso para la transaccion mas grande, que es la que procesa asistencias, nuevas jornadas, y hace cierre y apertura de mes/semana
- agregar valores a las tablas variable @Marcas, @Jornadas y @Empleaados
- loop para la transaccion tEmpleado (que es la que procesa todo eso)
- pre proceso
- begin tEmpleado (aqui voy a poner mas detalles ya que es la parte mas importante)
- validacion sobre la fecha de contratacion del empleado (para saltarlo si no ha empezado)
- crear MesPlanilla/SemanaPlanilla cuando es la primera vez que cualquier empleado toca ese mes/semana
- crear PMxE/PSxE cuando es la primera vez que un empleado dado toca ese mes/semana
- procesar asistencias
- se valida que el empleado tenga marca para la fecha, y que no se este registrando otra vez su MarcaAsistencia
- se calculan las horas trabajadas con respecto a las horas que le corresponde segun su jornada
- loop pequeño para tener una tabla con cada hora trabajada
- calcular la cantidad de horas ordinarias, extra normales y extra dobles
- se calculan los montos y saldos para hacer el insert de MarcaAsistencia
- se hacen los inserts de los movimientos adecuados (MovPlanilla, MovHoras) para cada tipo de horas respectivamente
- se inserta en la bitacora
- cierre de semana/mes
- si es jueves y no se ha cerrado la semana entonces se cierra la semana y se procesan las deducciones activas que tenga cada empleado para esa fecha (mediante un loop, que tambien inserta MovPlanilla y MovDeduccion)
- se actualiza cada PSxE/PMxE y las deducciones por mes
- se actualiza SemanaPlanilla para cerrarla (y tambien MesPlanilla en caso de que sea la ultima semana)
- apertura de semana/mes y nuevas jornadas
- si es jueves, y el empleado ya estaba contratado para la semana siguiente y no lo eliminaron, entonces se abren los siguientes MesPlanilla, PMxE, SemanaPlanilla y PSxE, y se le asigna la siguiente jornada (JornadaXEmpleadoXSemana)
- commit tEmpleado
- loop para eliminar empleados
- pre proceso
- begin tEliminar
- update
- commit tEliminar
- termina
- catch
Esa es la explicación general de cómo funciona todo el SP. Un bug interesante que encontré en su momento es a la hora de hacer los loops, por ejemplo voy a usar el de eliminar de referencia, antes hacía:
SELECT @lo = 1;
SELECT @hi = MAX([E].[Sec])
FROM @Elimina AS [E];
Pero esto por alguna razon terminaba eliminando empleados que no se debian eliminar, la idea original era usar 1 porque segun yo estaba limpiando la tabla variable en cada iteracion, pero luego investigue que es porque al hacer DELETE no se resetea el identity, entonces eso por alguna razon causaba que se eliminaran mas empleados de los que se deberian eliminar, entonces lo que hice fue usar:
SELECT @lo = MIN([E].[Sec])
FROM @Elimina AS [E];
SELECT @hi = MAX([E].[Sec])
FROM @Elimina AS [E];
Y ya esto lo solucionó.
Terminado el SP, hice un commit, y me dediqué a escribir esta entrada de la bitácora.
Por último, creé las rutas del backend y sus respectivas funciones para llamar a los SPs, que ahora son muy muy simples, ya que ahora basicamente todo se hace en los SP.
Hice algunas pruebas, y terminé de conectar todo.
Comments
Post a Comment