Entrada 18 (Cargador del XML)

# Bitácora de Sesión

Fecha: 28/04/2026

Inicio: [12:00] | Fin: [15:00] || Total: [3 horas]

 Presentes: Matías Benavides Sandoval

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

¿QUÉ HICIMOS HOY?

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 - Renombramos el script principal de creación de la base de datos para que quedara como `CrearBD.sql`.

- Consolidamos la carga inicial de datos en un único script: `SQL/Scripts/CargarDatosXML.sql`.

- Implementamos la carga usando `OPENXML` y `sp_xml_preparedocument` para leer el XML en memoria y transformar sus nodos en filas.

- Dejamos la carga como un procedimiento inicial ejecutable una sola vez, con protección para evitar duplicados.

- Eliminamos los scripts parciales de carga por tabla para que quede una sola fuente de verdad.

- Corregimos el cálculo de saldos: el procedimiento ahora calcula `NuevoSaldo` y actualiza `Empleado.SaldoVacaciones` tomando el último movimiento por empleado (no el MAX intermedio).

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PROBLEMAS ENCONTRADOS Y CÓMO SE RESOLVIERON

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- La carga por scripts individuales fragmentaba la inicialización y podía causar confusión; se resolvió unificando toda la carga en `CargarDatosXML.sql`.

- La carga inicial generó inconsistencias de saldo debido a dos causas que corregimos:

    1. `TipoMovimiento.TipoAccion` en los datos venía como `Credito`/`Debito`, pero la lógica interna esperaba códigos (`'A'`/`'R'`), por lo que los CASEs devolvían 0. Solución: normalizar en la carga.

    2. El cálculo del saldo final usaba `MAX(m.NuevoSaldo)` que devolvía valores intermedios en lugar del último saldo cronológico. Solución: actualizar para tomar el último movimiento por fecha/PostTime/id.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

DUDAS Y DIVERGENCIAS DE CRITERIO

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- Se confirmó que el XML se usará solo como carga inicial y no como fuente viva de cambios de la aplicación.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ERRORES Y SALIDAS RELEVANTES (literales / resumen)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- Error de build TypeScript relacionado con importación/casing en `src/utils/errorhelper.ts` que provocó un fallo de compilación hasta corregir el path (error reportado por `tsc`).

- Resultado de ejecución de limpieza y reseed en SSMS/`sqlcmd` (ejemplo de salida):

    - `(16 rows affected)` / `(14 rows affected)` ... (salidas de DELETE/INSERT durante limpieza y carga)

    - `Checking identity information: current identity value '16'.` (salida de `DBCC CHECKIDENT`)

- Discrepancia inicial detectada al verificar saldos: `SaldoCalculado` devolvía `0.00` para muchos empleados porque el CASE comprobaba `TipoAccion = 'A'/'R'` pero la tabla contenía `Credito`/`Debito`.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AVANCE DEL CÓDIGO

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

USE VacacionesDB;
GO

IF OBJECT_ID('dbo.sp_CargarDatosInicialesXML', 'P') IS NOT NULL
    DROP PROCEDURE dbo.sp_CargarDatosInicialesXML;
GO

CREATE PROCEDURE dbo.sp_CargarDatosInicialesXML
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @xml NVARCHAR(MAX) = N'
<Datos>
    <Puestos>
        <Puesto Nombre="Cajero" SalarioxHora="11.00"/>
        <Puesto Nombre="Camarero" SalarioxHora="10.00"/>
        <Puesto Nombre="Cuidador" SalarioxHora="13.50"/>
        <Puesto Nombre="Conductor" SalarioxHora="15.00"/>
        <Puesto Nombre="Asistente" SalarioxHora="11.00"/>
        <Puesto Nombre="Recepcionista" SalarioxHora="12.00"/>
        <Puesto Nombre="Fontanero" SalarioxHora="13.00"/>
        <Puesto Nombre="Niñera" SalarioxHora="12.00"/>
        <Puesto Nombre="Conserje" SalarioxHora="11.00"/>
        <Puesto Nombre="Albañil" SalarioxHora="10.50"/>
    </Puestos>

    <TiposEvento>
        <TipoEvento Id="1" Nombre="Login Exitoso"/>
        <TipoEvento Id="2" Nombre="Login No Exitoso"/>
        <TipoEvento Id="3" Nombre="Login deshabilitado"/>
        <TipoEvento Id="4" Nombre="Logout"/>
        <TipoEvento Id="5" Nombre="Insercion no exitosa"/>
        <TipoEvento Id="6" Nombre="Insercion exitosa"/>
        <TipoEvento Id="7" Nombre="Update no exitoso"/>
        <TipoEvento Id="8" Nombre="Update exitoso"/>
        <TipoEvento Id="9" Nombre="Intento de borrado"/>
        <TipoEvento Id="10" Nombre="Borrado exitoso"/>
        <TipoEvento Id="11" Nombre="Consulta con filtro de nombre"/>
        <TipoEvento Id="12" Nombre="Consulta con filtro de cedula"/>
        <TipoEvento Id="13" Nombre="Intento de insertar movimiento"/>
        <TipoEvento Id="14" Nombre="Insertar movimiento exitoso"/>
    </TiposEvento>

    <TiposMovimientos>
        <TipoMovimiento Id="1" Nombre="Cumplir mes" TipoAccion="Credito"/>
        <TipoMovimiento Id="2" Nombre="Bono vacacional" TipoAccion="Credito"/>
        <TipoMovimiento Id="3" Nombre="Reversion de Credito" TipoAccion="Credito"/>
        <TipoMovimiento Id="4" Nombre="Disfrute de vacaciones" TipoAccion="Debito"/>
        <TipoMovimiento Id="5" Nombre="Venta de vacaciones" TipoAccion="Debito"/>
        <TipoMovimiento Id="6" Nombre="Reversion Debito" TipoAccion="Debito"/>
    </TiposMovimientos>

    <Empleados>
        <empleado Puesto="Camarero" ValorDocumentoIdentidad="6993943" Nombre="Kaitlyn Jensen" FechaContratacion="2017-12-07"/>
        <empleado Puesto="Albañil" ValorDocumentoIdentidad="1896802" Nombre="Robert Buchanan" FechaContratacion="2020-09-20"/>
        <empleado Puesto="Cajero" ValorDocumentoIdentidad="5095109" Nombre="Christina Ward" FechaContratacion="2015-09-13"/>
        <empleado Puesto="Fontanero" ValorDocumentoIdentidad="8403646" Nombre="Bradley Wright" FechaContratacion="2020-01-27"/>
        <empleado Puesto="Conserje" ValorDocumentoIdentidad="6019592" Nombre="Robert Singh" FechaContratacion="2017-02-01"/>
        <empleado Puesto="Asistente" ValorDocumentoIdentidad="4510358" Nombre="Ryan Mitchell" FechaContratacion="2018-06-08"/>
        <empleado Puesto="Asistente" ValorDocumentoIdentidad="7517662" Nombre="Candace Fox" FechaContratacion="2013-12-17"/>
        <empleado Puesto="Asistente" ValorDocumentoIdentidad="8326328" Nombre="Allison Murillo" FechaContratacion="2020-04-19"/>
        <empleado Puesto="Cuidador" ValorDocumentoIdentidad="2161775" Nombre="Jessica Murphy" FechaContratacion="2017-04-12"/>
        <empleado Puesto="Fontanero" ValorDocumentoIdentidad="2918773" Nombre="Nancy Newton PhD" FechaContratacion="2016-11-22"/>
        <empleado Puesto="Conductor" ValorDocumentoIdentidad="9772211" Nombre="Alicia Ortega" FechaContratacion="2021-05-14"/>
        <empleado Puesto="Recepcionista" ValorDocumentoIdentidad="6641189" Nombre="Pedro Salas" FechaContratacion="2019-03-21"/>
        <empleado Puesto="Niñera" ValorDocumentoIdentidad="3389054" Nombre="Sofía Herrera" FechaContratacion="2022-08-09"/>
    </Empleados>

    <Usuarios>
        <usuario Id="1" Nombre="UsuarioScripts" Pass="UsuarioScripts"/>
        <usuario Id="2" Nombre="mgarrison" Pass=")*2LnSr^lk"/>
        <usuario Id="3" Nombre="jgonzalez" Pass="3YSI0HtiXI"/>
        <usuario Id="4" Nombre="zkelly" Pass="X4US4aLam@"/>
        <usuario Id="5" Nombre="andersondeborah" Pass="732F34xo%S"/>
        <usuario Id="6" Nombre="hardingmicheal" Pass="himB9Dzd%_"/>
        <usuario Id="7" Nombre="martinezlisa" Pass="7Kp9vQ2mT1"/>
        <usuario Id="8" Nombre="floresdaniel" Pass="H4s8Nq3xL6"/>
        <usuario Id="9" Nombre="perezmaria" Pass="R2m7Bv5cZ8"/>
        <usuario Id="10" Nombre="torresluis" Pass="J9t6Wk4pS3"/>
    </Usuarios>

    <Movimientos>
        <movimiento ValorDocId="7517662" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-01-18" Monto="2" PostByUser="hardingmicheal" PostInIP="42.142.119.153" PostTime="2024-01-18 18:47:14"/>
        <movimiento ValorDocId="6993943" IdTipoMovimiento="Bono vacacional" Fecha="2024-10-31" Monto="1" PostByUser="mgarrison" PostInIP="156.92.82.57" PostTime="2024-10-31 12:43:18"/>
        <movimiento ValorDocId="8326328" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-11-22" Monto="7" PostByUser="andersondeborah" PostInIP="218.213.110.232" PostTime="2024-11-22 00:23:53"/>
        <movimiento ValorDocId="4510358" IdTipoMovimiento="Reversion de Credito" Fecha="2024-07-03" Monto="3" PostByUser="hardingmicheal" PostInIP="143.42.131.166" PostTime="2024-07-03 17:07:39"/>
        <movimiento ValorDocId="8403646" IdTipoMovimiento="Reversion de Credito" Fecha="2024-12-07" Monto="8" PostByUser="zkelly" PostInIP="155.44.100.105" PostTime="2024-12-07 15:44:30"/>
        <movimiento ValorDocId="8326328" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-11-26" Monto="10" PostByUser="hardingmicheal" PostInIP="141.163.255.56" PostTime="2024-11-26 09:33:41"/>
        <movimiento ValorDocId="6993943" IdTipoMovimiento="Disfrute de vacaciones" Fecha="2024-11-20" Monto="6" PostByUser="hardingmicheal" PostInIP="4.176.52.1" PostTime="2024-11-20 23:31:41"/>
        <movimiento ValorDocId="2918773" IdTipoMovimiento="Disfrute de vacaciones" Fecha="2024-10-30" Monto="10" PostByUser="zkelly" PostInIP="220.164.108.231" PostTime="2024-10-30 03:55:57"/>
        <movimiento ValorDocId="2161775" IdTipoMovimiento="Reversion Debito" Fecha="2024-06-13" Monto="2" PostByUser="hardingmicheal" PostInIP="135.223.57.22" PostTime="2024-06-13 13:28:39"/>
        <movimiento ValorDocId="8403646" IdTipoMovimiento="Bono vacacional" Fecha="2024-01-01" Monto="6" PostByUser="zkelly" PostInIP="150.250.94.62" PostTime="2024-01-01 05:17:10"/>
        <movimiento ValorDocId="2918773" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-07-12" Monto="6" PostByUser="hardingmicheal" PostInIP="218.191.123.15" PostTime="2024-07-12 09:10:16"/>
        <movimiento ValorDocId="5095109" IdTipoMovimiento="Reversion de Credito" Fecha="2024-12-27" Monto="14" PostByUser="hardingmicheal" PostInIP="136.103.23.170" PostTime="2024-12-27 12:59:03"/>
        <movimiento ValorDocId="6993943" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-04-08" Monto="1" PostByUser="jgonzalez" PostInIP="158.48.100.86" PostTime="2024-04-08 01:24:38"/>
        <movimiento ValorDocId="8403646" IdTipoMovimiento="Bono vacacional" Fecha="2024-08-25" Monto="8" PostByUser="jgonzalez" PostInIP="204.0.219.231" PostTime="2024-08-25 16:24:07"/>
        <movimiento ValorDocId="5095109" IdTipoMovimiento="Bono vacacional" Fecha="2024-03-07" Monto="7" PostByUser="andersondeborah" PostInIP="208.0.4.33" PostTime="2024-03-07 08:19:28"/>
        <movimiento ValorDocId="9772211" IdTipoMovimiento="Cumplir mes" Fecha="2024-02-14" Monto="4" PostByUser="martinezlisa" PostInIP="10.10.10.10" PostTime="2024-02-14 08:11:00"/>
        <movimiento ValorDocId="6641189" IdTipoMovimiento="Bono vacacional" Fecha="2024-02-28" Monto="3" PostByUser="floresdaniel" PostInIP="10.10.10.11" PostTime="2024-02-28 09:20:15"/>
        <movimiento ValorDocId="3389054" IdTipoMovimiento="Disfrute de vacaciones" Fecha="2024-03-12" Monto="5" PostByUser="perezmaria" PostInIP="10.10.10.12" PostTime="2024-03-12 14:05:45"/>
        <movimiento ValorDocId="9772211" IdTipoMovimiento="Reversion de Credito" Fecha="2024-04-03" Monto="2" PostByUser="torresluis" PostInIP="10.10.10.13" PostTime="2024-04-03 11:30:05"/>
        <movimiento ValorDocId="6641189" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-04-19" Monto="1" PostByUser="mgarrison" PostInIP="172.16.0.21" PostTime="2024-04-19 16:42:31"/>
        <movimiento ValorDocId="3389054" IdTipoMovimiento="Reversion Debito" Fecha="2024-05-02" Monto="3" PostByUser="jgonzalez" PostInIP="172.16.0.22" PostTime="2024-05-02 07:18:09"/>
        <movimiento ValorDocId="5095109" IdTipoMovimiento="Cumplir mes" Fecha="2024-05-18" Monto="6" PostByUser="andersondeborah" PostInIP="172.16.0.23" PostTime="2024-05-18 18:22:40"/>
        <movimiento ValorDocId="4510358" IdTipoMovimiento="Disfrute de vacaciones" Fecha="2024-06-09" Monto="4" PostByUser="hardingmicheal" PostInIP="172.16.0.24" PostTime="2024-06-09 12:10:55"/>
        <movimiento ValorDocId="6019592" IdTipoMovimiento="Bono vacacional" Fecha="2024-06-25" Monto="2" PostByUser="martinezlisa" PostInIP="172.16.0.25" PostTime="2024-06-25 09:44:03"/>
        <movimiento ValorDocId="7517662" IdTipoMovimiento="Reversion de Credito" Fecha="2024-07-11" Monto="5" PostByUser="floresdaniel" PostInIP="172.16.0.26" PostTime="2024-07-11 13:55:27"/>
        <movimiento ValorDocId="8403646" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-08-08" Monto="4" PostByUser="perezmaria" PostInIP="172.16.0.27" PostTime="2024-08-08 15:00:00"/>
        <movimiento ValorDocId="6993943" IdTipoMovimiento="Cumplir mes" Fecha="2024-09-14" Monto="7" PostByUser="torresluis" PostInIP="172.16.0.28" PostTime="2024-09-14 10:25:18"/>
        <movimiento ValorDocId="2161775" IdTipoMovimiento="Reversion Debito" Fecha="2024-10-05" Monto="1" PostByUser="zkelly" PostInIP="172.16.0.29" PostTime="2024-10-05 08:12:49"/>
        <movimiento ValorDocId="2918773" IdTipoMovimiento="Bono vacacional" Fecha="2024-11-03" Monto="2" PostByUser="martinezlisa" PostInIP="172.16.0.30" PostTime="2024-11-03 17:33:12"/>
        <movimiento ValorDocId="8326328" IdTipoMovimiento="Venta de vacaciones" Fecha="2024-12-18" Monto="8" PostByUser="floresdaniel" PostInIP="172.16.0.31" PostTime="2024-12-18 19:47:59"/>
    </Movimientos>

    <Error>
        <error Codigo="50001" Descripcion="Username no existe"/>
        <error Codigo="50002" Descripcion="Password no existe"/>
        <error Codigo="50003" Descripcion="Login deshabilitado"/>
        <error Codigo="50004" Descripcion="Empleado con ValorDocumentoIdentidad ya existe en inserción"/>
        <error Codigo="50005" Descripcion="Empleado con mismo nombre ya existe en inserción"/>
        <error Codigo="50006" Descripcion="Empleado con ValorDocumentoIdentidad ya existe en actualizacion"/>
        <error Codigo="50007" Descripcion="Empleado con mismo nombre ya existe en actualización"/>
        <error Codigo="50008" Descripcion="Error de base de datos"/>
        <error Codigo="50009" Descripcion="Nombre de empleado no alfabético"/>
        <error Codigo="50010" Descripcion="Valor de documento de identidad no alfabético"/>
        <error Codigo="50011" Descripcion="Monto del movimiento rechazado pues si se aplicar el saldo seria negativo."/>
    </Error>
</Datos>';

    DECLARE @handle INT = NULL;

    BEGIN TRY
        EXEC sp_xml_preparedocument @handle OUTPUT, @xml;

        BEGIN TRANSACTION;

        -- Puestos: se cargan por nombre porque el resto de la app los usa como catálogo.
        INSERT INTO dbo.Puesto (Nombre, SalarioxHora)
        SELECT x.Nombre, x.SalarioxHora
        FROM OPENXML(@handle, '/Datos/Puestos/Puesto', 1)
        WITH (
            Nombre NVARCHAR(128) '@Nombre',
            SalarioxHora DECIMAL(10,2) '@SalarioxHora'
        ) AS x
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.Puesto p
            WHERE p.Nombre = x.Nombre
        );

        -- Tipos de evento: el Id del XML no se conserva literal, pero el catálogo sí queda completo.
        INSERT INTO dbo.TipoEvento (Nombre)
        SELECT x.Nombre
        FROM OPENXML(@handle, '/Datos/TiposEvento/TipoEvento', 1)
        WITH (
            Id INT '@Id',
            Nombre NVARCHAR(128) '@Nombre'
        ) AS x
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.TipoEvento t
            WHERE t.Nombre = x.Nombre
        );

        -- Tipos de movimiento: se cargan por nombre y tipo de acción normalizada.
        INSERT INTO dbo.TipoMovimiento (Nombre, TipoAccion)
        SELECT
            x.Nombre,
            CASE
                WHEN x.TipoAccion = 'Credito' THEN 'A'
                WHEN x.TipoAccion = 'Debito' THEN 'R'
                ELSE x.TipoAccion
            END AS TipoAccion
        FROM OPENXML(@handle, '/Datos/TiposMovimientos/TipoMovimiento', 1)
        WITH (
            Id INT '@Id',
            Nombre NVARCHAR(128) '@Nombre',
            TipoAccion NVARCHAR(32) '@TipoAccion'
        ) AS x
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.TipoMovimiento t
            WHERE t.Nombre = x.Nombre
        );

        -- Usuarios: se cargan por username y password.
        INSERT INTO dbo.Usuario (Username, Password)
        SELECT x.Nombre, x.Pass
        FROM OPENXML(@handle, '/Datos/Usuarios/usuario', 1)
        WITH (
            Id INT '@Id',
            Nombre NVARCHAR(128) '@Nombre',
            Pass NVARCHAR(128) '@Pass'
        ) AS x
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.Usuario u
            WHERE u.Username = x.Nombre
        );

        -- Códigos de error: el catálogo queda disponible para traducir mensajes en el backend.
        INSERT INTO dbo.Error (Codigo, Descripcion)
        SELECT x.Codigo, x.Descripcion
        FROM OPENXML(@handle, '/Datos/Error/error', 1)
        WITH (
            Codigo INT '@Codigo',
            Descripcion NVARCHAR(256) '@Descripcion'
        ) AS x
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.Error e
            WHERE e.Codigo = x.Codigo
        );

        -- Empleados: se enlazan al puesto cargado arriba por el nombre del puesto.
        INSERT INTO dbo.Empleado (idPuesto, ValorDocumentoIdentidad, Nombre, FechaContratación, SaldoVacaciones, EsActivo)
        SELECT p.id, x.ValorDocumentoIdentidad, x.Nombre, x.FechaContratacion, 0.00, 1
        FROM OPENXML(@handle, '/Datos/Empleados/empleado', 1)
        WITH (
            Puesto NVARCHAR(128) '@Puesto',
            ValorDocumentoIdentidad NVARCHAR(32) '@ValorDocumentoIdentidad',
            Nombre NVARCHAR(128) '@Nombre',
            FechaContratacion DATE '@FechaContratacion'
        ) AS x
        INNER JOIN dbo.Puesto p
            ON p.Nombre = x.Puesto
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.Empleado e
            WHERE e.ValorDocumentoIdentidad = x.ValorDocumentoIdentidad
        );

        -- Movimientos: primero se extraen a una tabla temporal para poder calcular el saldo acumulado.
        CREATE TABLE #MovimientosXml (
            ValorDocumentoIdentidad NVARCHAR(32) NOT NULL,
            NombreTipoMovimiento NVARCHAR(128) NOT NULL,
            Fecha DATE NOT NULL,
            Monto DECIMAL(10,2) NOT NULL,
            PostByUser NVARCHAR(128) NOT NULL,
            PostInIP NVARCHAR(32) NOT NULL,
            PostTime DATETIME NOT NULL
        );

        INSERT INTO #MovimientosXml (
            ValorDocumentoIdentidad,
            NombreTipoMovimiento,
            Fecha,
            Monto,
            PostByUser,
            PostInIP,
            PostTime
        )
        SELECT
            x.ValorDocumentoIdentidad,
            x.NombreTipoMovimiento,
            x.Fecha,
            x.Monto,
            x.PostByUser,
            x.PostInIP,
            x.PostTime
        FROM OPENXML(@handle, '/Datos/Movimientos/movimiento', 1)
        WITH (
            ValorDocumentoIdentidad NVARCHAR(32) '@ValorDocId',
            NombreTipoMovimiento NVARCHAR(128) '@IdTipoMovimiento',
            Fecha DATE '@Fecha',
            Monto DECIMAL(10,2) '@Monto',
            PostByUser NVARCHAR(128) '@PostByUser',
            PostInIP NVARCHAR(32) '@PostInIP',
            PostTime DATETIME '@PostTime'
        ) AS x;

        ;WITH MovimientosConSaldo AS (
            SELECT
                e.id AS idEmpleado,
                tm.id AS idTipoMovimiento,
                m.Fecha,
                m.Monto,
                CASE
                    WHEN tm.TipoAccion = 'A' THEN m.Monto
                    ELSE -m.Monto
                END AS MovimientoFirmado,
                SUM(
                    CASE
                        WHEN tm.TipoAccion = 'A' THEN m.Monto
                        ELSE -m.Monto
                    END
                ) OVER (
                    PARTITION BY e.id
                    ORDER BY m.Fecha, m.PostTime, tm.id, m.Monto
                    ROWS UNBOUNDED PRECEDING
                ) AS NuevoSaldo,
                u.id AS idUsuario,
                m.PostInIP,
                m.PostTime
            FROM #MovimientosXml m
            INNER JOIN dbo.Empleado e
                ON e.ValorDocumentoIdentidad = m.ValorDocumentoIdentidad
            INNER JOIN dbo.TipoMovimiento tm
                ON tm.Nombre = m.NombreTipoMovimiento
            INNER JOIN dbo.Usuario u
                ON u.Username = m.PostByUser
        )
        INSERT INTO dbo.Movimiento (idEmpleado, idTipoMovimiento, Fecha, Monto, NuevoSaldo, idUsuario, IpPostIn, PostTime)
        SELECT
            s.idEmpleado,
            s.idTipoMovimiento,
            s.Fecha,
            s.Monto,
            s.NuevoSaldo,
            s.idUsuario,
            s.PostInIP,
            s.PostTime
        FROM MovimientosConSaldo s
        WHERE NOT EXISTS (
            SELECT 1
            FROM dbo.Movimiento m
            WHERE m.idEmpleado = s.idEmpleado
                AND m.idTipoMovimiento = s.idTipoMovimiento
                AND m.Fecha = s.Fecha
                AND m.Monto = s.Monto
                AND m.idUsuario = s.idUsuario
                AND m.IpPostIn = s.PostInIP
                AND m.PostTime = s.PostTime
        );

        -- El saldo actual del empleado debe reflejar el último movimiento cargado.
        ;WITH UltimoMovimiento AS (
            SELECT
                m.idEmpleado,
                m.NuevoSaldo,
                ROW_NUMBER() OVER (
                    PARTITION BY m.idEmpleado
                    ORDER BY m.Fecha DESC, m.PostTime DESC, m.id DESC
                ) AS rn
            FROM dbo.Movimiento m
        ),
        SaldoFinal AS (
            SELECT
                idEmpleado,
                NuevoSaldo
            FROM UltimoMovimiento
            WHERE rn = 1
        )
        UPDATE e
        SET e.SaldoVacaciones = ISNULL(s.NuevoSaldo, 0.00)
        FROM dbo.Empleado e
        LEFT JOIN SaldoFinal s
            ON s.idEmpleado = e.id;

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        IF @handle IS NOT NULL
            EXEC sp_xml_removedocument @handle;

        THROW;
    END CATCH;

    IF @handle IS NOT NULL
        EXEC sp_xml_removedocument @handle;
END;
GO

EXEC dbo.sp_CargarDatosInicialesXML;
GO

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MORALEJAS / BUENAS PRÁCTICAS

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- Cuando una carga es inicial y estática, conviene tener un único punto de entrada en vez de varios scripts dispersos.

- `OPENXML` sirve bien cuando el XML se quiere tratar como documento en memoria y convertirlo en filas SQL.

- Mantener solo una fuente de verdad evita inconsistencias entre scripts que hacen la misma tarea.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PRÓXIMA SESIÓN: ¿QUÉ SIGUE?

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

- Validar todo, buscar errores, ver si falla algo, terminar el analisis de resultados, y basicamente, cerrar lo que queda


Comentarios

Entradas más populares de este blog

Entrada 16 (Controlador de movimientos)

Entrada 20 (Lógica de insertarMovimientos, conectarla y probarla)

Entrada 21 (Análisis de resultados)