Imported from MatiasBS027/Tarea3-BD (
AGENTS.md). Install upstream withnpx skills add MatiasBS027/Tarea3-BD. Copyright stays with the author.
AGENTS.md — Guía operativa (Planilla / Control de Asistencia)
Fuente única de verdad:
Especificacion.pdf(PDF del profe fquiros, Mayo 2026). Este archivo no redefine requisitos; documenta decisiones de implementación, convenciones y workflow del agente.
1. Modelo de datos (resumen)
1.1 Origen del modelo
- Se parte del modelo de Sebas (su diagrama ER,
ModeloSebas.jpeg+ModeloSebas1/2/3.jpeg) usado como guía estructural. - Se aplican 8 correcciones justificadas contra
Especificacion.pdf(verSQL/SCRIPTS/Tablas.sqlpor tabla y la sección §1.3 aquí para el resumen). - Convenciones de Tarea2-BD:
iden minúscula (PK IDENTITY), tablasDBErroryErrorpara trazabilidad,inXxx/@outResultCode INT OUTPUTen SPs,SET XACT_ABORT ON; SET NOCOUNT ON;por SP. - Convenciones de SSMS en
SQL/SCRIPTS/Tablas.sql: sinIF OBJECT_IDniDROP(esquema único, se asume base preexistente creada conVaciarDB.sql). - Los SPs individuales (
SQL/SPs/*.sql) SÍ usanIF OBJECT_ID DROPporque se deployan incrementalmente. - Convenciones Tarea2-BD para
BitacoraEvento:IpPostIn(noip),PostTime(noFechaHora). - El backend de Tarea2-BD (
src/controllers/*.ts) NO es de este proyecto — no basarse en él.
1.2 Modelo final (21 tablas, 23 FKs, 1 trigger)
| Tabla | Columnas | Notas |
|---|---|---|
BitacoraEvento |
id, idTipoEvento, idUsuario, PostTime, IpPostIn, Descripcion | R07: User.Id, IP, estampa de tiempo. NVARCHAR(512) en Descripcion. |
DBError |
id, UserName, Number, State, Severity, Line, [Procedure], Message, DateTime | Bitácora de errores no controlados. NVARCHAR en UserName/Procedure/Message. |
DeduccionEmpleado |
id, idEmpleado, idTipoDeduccion, MontoFijo, FechaInicio, FechaFin | Una fila por (empleado, tipo de deducción). MontoFijo carga el Valor del TipoDeduccion (sea % o monto); el SP decide cómo aplicarlo. FechaFin='9999-12-31' = vigente. |
DeduccionXMes |
id, idPlanillaMensual, idEmpleado, idTipoDeduccion, MontoTotal | Resumen mensual de deducciones por empleado (PDF §6: "DeduccionesXEmpleadoxMes"). idEmpleado agregado para evitar join transitivo a PlanillaMensual. |
Empleado |
id, idPuesto, idUsuario, ValorDocumento, Nombre, CuentaBancaria, FechaContratacion, Activo | Sin Departamento/TipoDocIdentidad (no aportan a planilla, ver §1.3). |
Feriado |
id, Nombre, Fecha | Tabla obligatoria: el SP de cierre verifica Fecha contra MarcaAsistencia.Fecha para extras dobles (PDF p.1, p.8 §4.4.5). |
HorarioJornada |
id, idEmpleado, idSemana, idTipoJornada | Asignación de jornada por semana. |
MarcaAsistencia |
id, idEmpleado, Fecha, HoraEntrada, HoraSalida | Sin FK a HorarioJornada ni Semana (ver §1.3.6). Jornada nocturna puede cruzar medianoche. |
Mes |
id, FechaInicio, FechaFin, NumJueves TINYINT | Encabezado del ciclo mensual. NumJueves precalculado por el SP. |
MovHoras |
id, QHoras INT, Monto DECIMAL(10,2), idAsistencia, idTipoMov | Tabla nueva. PDF §4.4.5: cada asistencia → hasta 3 movimientos (1 ord, 1 ext-normal, 1 ext-doble). QHoras siempre entero (PDF: "Solo se pagan horas completas, si el empleado trabajo 7.5 horas se pagan 7 horas"). Monto = QHoras × SalarioXHora × factor (1.0/1.5/2.0). |
MovPlanilla |
id, idPlanillaSemanal, idTipoMovimiento, Monto, NuevoSaldo | Líneas monetarias de la planilla. NuevoSaldo = saldo acumulado después de aplicar el movimiento. |
PlanillaMensual |
id, idEmpleado, idMes, SalarioBruto, TotalDeducciones, SalarioNeto | Suma de las semanales del mes (puede no ser mes natural). |
PlanillaSemanal |
id, idEmpleado, idSemana, SalarioBruto, TotalDeducciones, SalarioNeto, Comprobante VARBINARY(MAX) NULL | Sin columnas HorasOrd/ExtraNormal/ExtraDoble (denormalización, ver §1.3.5). Comprobante = PDF/image del recibo. |
Puesto |
id, Nombre, SalarioXHora | Catálogo. Mapeo por Nombre desde el XML (PDF p.6). |
Semana |
id, idMes, FechaInicio, FechaFin | Ciclo semanal viernes → jueves. |
TipoDeduccion |
id, Nombre, EsObligatoria BIT, EsPorcentual BIT, Valor DECIMAL(8,4), idTipoMovimiento | Modelo unificado: si EsPorcentual=1 el SP aplica Valor * SalarioBruto; si EsPorcentual=0 el SP divide Valor / NumJueves y aplica por semana. |
TipoEvento |
id, Nombre | Catálogo para BitacoraEvento. |
TipoJornada |
id, Nombre, HoraInicio TIME(0), HoraFin TIME(0) | Diurno, Vespertino, Nocturno. |
TipoMovimiento |
id, Nombre, Accion CHAR(1) | Crédito ('C') o Débito ('D'). Catálogo para MovHoras y MovPlanilla. |
Usuario |
id, Username, PasswordHash, Tipo VARCHAR(2) | Tipo='1' admin, Tipo='2' empleado (PDF p.6, comentario XML). |
| Trigger | trg_Empleado_Insert_AssignMandatoryDeductions |
AFTER INSERT ON Empleado, cross-join con TipoDeduccion WHERE EsObligatoria=1, inserta DeduccionEmpleado con MontoFijo=td.Valor, FechaInicio=i.FechaContratacion, FechaFin='9999-12-31'. |
Total: 21 tablas, 23 FKs, 1 trigger.
1.3 Las 8 correcciones al modelo de Sebas (con cita al PDF)
| # | Cambio | Justificación (PDF) |
|---|---|---|
| 1 | Quitar Departamento, TipoDocIdentidad |
Mencionados solo en el XML de catálogos (p.7 §4); no aparecen en R01-R07 ni en §2 (cálculo de planilla). "Control de asistencia y Planilla Obrera" no las necesita. |
| 2 | Agregar MovHoras |
p.8 §4.4.5: "en un caso extremo la asistencia del empleado a una fecha puede generar 3 movimientos". p.8: "debe generarse movimientos en la planilla semanal". R04 (p.3): "se visualizan, en un grid, para cada día de la semana … los movimientos que generó esa asistencia". Sebas solo guarda sumas — rompe R04. |
| 3 | Comprobante de tabla a columna VARBINARY(MAX) NULL en PlanillaSemanal |
El PDF no menciona Comprobante como entidad. R04 (p.3) enumera las columnas del grid y Comprobante no está. |
| 4 | Quitar ComprobanteHora |
Consecuencia de 3. |
| 5 | Quitar PlanillaSemanal.HorasOrdinarias/ExtraNormal/ExtraDoble |
Denormalización. p.8: "debe generarse movimientos". p.10 §6: "insertar movimientos por horas". Con MovHoras se obtiene por SQL agregado. |
| 6 | MarcaAsistencia solo idEmpleado (sin idSemana/idHorarioJornada) |
p.8: "NO puede ser que inicien al siguiente día (a menos que se inserten jueves), pues aún no tendrán asignado una jornada de trabajo." Si la FK fuera obligatoria, no podrían existir marcas sin horario. |
| 7 | BitacoraEvento: ip→IpPostIn, FechaHora→PostTime |
Cosmético, convención Tarea2-BD. |
| 8 | Feriado.Fecha consultable al cierre |
p.1: "el valor de la hora extra es 1.5 … siempre que la hora trabajada no sea en domingo ni feriado, en cuyo caso es 2.0". p.8 §4.4.5: "si la fecha es domingo o feriado, son horas extras dobles". El SP hace MarcaAsistencia.Fecha IN (SELECT Fecha FROM Feriado). |
2. Convenciones
2.1 Naming
- Tablas:
PascalCasesingular. - Columnas:
PascalCase. PK =id(minúscula, IDENTITY). FK =idEntidad(minúscula, ej.idPuesto,idEmpleado). - SPs:
sp_ActionEntity(sp_InsertarEmpleado,sp_ProcessWeeklyPayroll). - Triggers:
trg_Tabla_Evento(trg_Empleado_Insert_AssignMandatoryDeductions). - Cursores explícitos: prohibido. Usar
OUTPUT INTOpara capturar IDs nuevos.
2.2 Tipos de datos
- Montos:
decimal(10,2)(equivalente amoneypara nuestros rangos). - Porcentajes:
decimal(8,4)(rango 0–1). - Fechas:
date. Fechas+hora:datetime. Horas de jornada:time(0). - IDs:
int IDENTITY(1,1)para todas las tablas (catálogos incluidos). Tarea2-BD lo hace así para uniformidad; el SP de carga de XML usaWHERE NOT EXISTSpara idempotencia. - PDFs/images de comprobante:
varbinary(max).
2.3 Constraints
- Toda FK:
FK_Origen_Destino. - UNIQUE:
UQ_Tabla_Cols. - CHECK:
CK_Tabla_Restriccion. - Identificadores que se buscan por nombre (no por PK):
Puesto.Nombre,Usuario.Username,TipoEvento.Nombre,TipoMovimiento.Nombre,TipoJornada.Nombre.
2.4 Plantilla de SP
USE [PlanillaDB];
GO
IF OBJECT_ID(N'dbo.sp_AccionEntidad', N'P') IS NOT NULL
DROP PROCEDURE [dbo].[sp_AccionEntidad];
GO
CREATE PROCEDURE [dbo].[sp_AccionEntidad]
@inParam1 VARCHAR(128),
@inIpPostIn VARCHAR(64),
@inPostTime DATETIME,
@outResultCode INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET @outResultCode = 0;
BEGIN TRY
-- BEGIN TRANSACTION
-- logica de negocio
-- INSERT en BitacoraEvento
-- COMMIT
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
INSERT INTO [dbo].[DBError] ([UserName], [Number], [State], [Severity], [Line], [Procedure], [Message], [DateTime])
VALUES (SYSTEM_USER, ERROR_NUMBER(), ERROR_STATE(), ERROR_SEVERITY(),
ERROR_LINE(), ISNULL(ERROR_PROCEDURE(), 'sp_AccionEntidad'),
ERROR_MESSAGE(), GETDATE());
SET @outResultCode = 50008;
END CATCH
END;
GO
3. Reglas de negocio (resumen para SPs)
3.1 Semana y mes planilla
- Semana: viernes → jueves. Cierre: jueves 00:00 (medianoche).
- Mes planilla: último viernes del mes anterior → último jueves del mes en curso. 4 o 5 semanas según cuántos jueves caigan.
- Deducciones porcentuales:
SalarioBruto × Valor(semanal) — el SP veEsPorcentual=1. - Deducciones fijas:
Valor / NumJueves— el SP veEsPorcentual=0.
3.2 Horas extra (PDF p.8 §4.4.5)
- Ordinarias: horas trabajadas dentro de la jornada ×
SalarioXHora→ fila enMovHoras(conMonto) + fila enMovPlanilla(crédito). - Extras normales: horas trabajadas después del fin de jornada, no domingo/feriado ×
SalarioXHora × 1.5→ fila enMovHoras(conMonto) + fila enMovPlanilla. - Extras dobles: horas extras en domingo/feriado ×
SalarioXHora × 2.0→ fila enMovHoras(conMonto) + fila enMovPlanilla. - Solo horas completas (7.5 → 7). QHoras = INT en
MovHoras. - Una asistencia puede generar hasta 3 movimientos distintos (ej: salida 3am del día siguiente siendo feriado).
3.3 Salario neto
SalarioNeto = SalarioBruto - TotalDeducciones.- Planilla semanal y mensual: ambas con
SalarioBruto,TotalDeducciones,SalarioNeto.
3.4 Cierre mensual
Cuando el jueves es el último jueves del mes calendario:
- Cerrar planillas semanales del mes (asignar
idPlanillaMensualen su tabla mensual). - Acumular a
PlanillaMensualpor empleado. - Crear encabezado del siguiente mes planilla (4 o 5 semanas).
3.5 Trigger
trg_Empleado_Insert_AssignMandatoryDeductions (en SQL/SCRIPTS/Trigger.sql):
- Al insertar un empleado, crea una fila en
DeduccionEmpleadopor cadaTipoDeduccionconEsObligatoria=1. - Copia
TipoDeduccion.ValoraDeduccionEmpleado.MontoFijo(sea % o monto fijo). FechaInicio = Empleado.FechaContratacion,FechaFin = '9999-12-31'(sentinela de vigente).
4. Arquitectura
- DB: SQL Server ≥ 2014, base
PlanillaDB(se crea conVaciarDB.sql, esquema conTablas.sql, trigger conTrigger.sql). - Capa lógica:
src/(Node.js + Express +mssql). Se adaptó del código de Tarea2-BD pero se reescribió significativamente para este proyecto (SP execution patterns, middleware, validación, etc.). - Auth:
Usuario.Tipo='1'admin,Tipo='2'empleado (PDF p.6). Middleware ensrc/middleware/authMiddleware.ts. - Validación:
express-validatorensrc/middleware/validation.ts. Cada ruta usavalidateXxxarrays que sanitizan y validan antes de llegar al controller. - Trazabilidad: cada SP inserta en
BitacoraEventodentro de la misma transacción; enBEGIN CATCHinserta enDBError. - Scripts disponibles:
pnpm dev— hot-reload contsx watchpnpm start— producción (Node + dist/)pnpm build— compila backendpnpm build:frontend— compila frontend (TS → public/js/)pnpm build:all— ambospostinstall— compila automáticamente traspnpm install
- Health check:
GET /healthsin autenticación, devuelve{ status: "ok", timestamp }. - Tests:
pnpm test— corre todos los tests entests/*.test.tspnpm test:auth— solo tests de auth middleware (16 tests, no requiere BD)
- Auth middleware:
src/middleware/authMiddleware.tscontieneauthenticate(JWT decode) yrequireAdmin(tipo='1'). Cableado ensrc/index.tspor ruta; tests entests/authMiddleware.test.ts.
4.1 Conexión
La conexión real está en src/db/connection.ts (asume base ya creada y SPs ya desplegados). Credenciales dev-only — rotar antes de producción.
5. SPs a implementar (orden sugerido)
sp_Login/sp_Logout(con escritura aBitacoraEvento).sp_GetEmpleados/sp_GetEmpleadoById/sp_GetEmpleadoByIdInt(R01/R02).sp_InsertarEmpleado(cuenta el trigger).sp_UpdateEmpleado/sp_DeleteEmpleado.sp_GetTiposMovimiento(catálogo).sp_GetMovimientos/sp_InsertMovimiento(empleado consulta planilla).sp_GetError(helper).sp_GetBitacora/sp_GetTiposEvento(bitácora de eventos).sp_CrearCalendario(generaMesySemanacon regla viernes→jueves, cierre último jueves).sp_ProcesarAsistencia(genera hasta 3MovHoraspor asistencia + susMovPlanilla).sp_ProcesarPlanillaSemanal(cierre jueves: deducciones, SalarioNeto).sp_ProcesarPlanillaMensual(último jueves: cierre de mes, siguiente ciclo).sp_GetPlanillaSemanal/sp_GetPlanillaMensual(R04/R05).sp_ImpersonarEmpleado/sp_RegresarAdmin(R03/R06).
Todos con SET NOCOUNT ON; (NO usar SET XACT_ABORT ON — combinado con transacciones explícitas produce Msg 3930), BEGIN TRY / BEGIN CATCH con IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION en el CATCH antes de insertar en DBError, y INSERT INTO BitacoraEvento ... para SPs que escriben. SPs de solo lectura (catálogos, GETs) no abren transacción ni escriben bitácora.
6. Workflow del agente
- Antes de cambiar esquema: leer
SQL/SCRIPTS/Tablas.sqlySQL/SCRIPTS/Trigger.sql, y verificar contra el PDF (Especificacion.pdf). - Antes de cambiar SPs: leer este
AGENTS.mdy verificar el invariante conDBError/Error/BitacoraEventoenTablas.sql. - No añadir tablas que no estén en este AGENTS.md o el PDF, a menos que el usuario lo apruebe.
- No usar SQL embebido en TS: siempre
pool.request().execute('sp_...'). - Validación de entrada: usar arrays de
validateXxxensrc/middleware/validation.tspor ruta. No repetir validación manual en controllers. - Cada cambio al esquema requiere probar con el dataset de ejemplo (seed en
sp_CargarCatalogosXML+ INSERTs de prueba en scripts separados). - Mapeo XML → BD: el SP de carga (
sp_CargarCatalogosXML) es el único punto que lee el XML. Idempotente:WHERE NOT EXISTSoMERGE. - Primer
pnpm install: ejecutarpnpm approve-builds esbuildpara que el build script de esbuild pueda correr. Caso contrario,postinstallfalla y los test commands se rompen.
7. Riesgos identificados
- Asistencia sin horario: si el
SPde procesamiento de asistencia no encuentraHorarioJornadapara esa semana, debe tratarlo como fuera de jornada (probablemente todo el tiempo es "extra" o se rechaza). Documentar el comportamiento esperado antes de implementar. - Jornadas nocturnas: una asistencia puede cruzar medianoche. Calcular siempre con
datetime, no condatenitime. - Mes planilla ≠ mes natural: la apertura del siguiente mes ocurre cuando el jueves es el último jueves del mes calendario, no el último día.
- Carga inicial idempotente: el SP
sp_CargarCatalogosXMLdebe usarWHERE NOT EXISTSoMERGEcuando se implemente, para no duplicar si se corre 2 veces. - Subtipo único de
TipoDeduccion: el modelo simplificado (Sebas) funde obligatoria/porcentual/fija en una sola tabla con flags. El SP que aplica la deducción es el responsable de interpretarEsPorcentualyEsObligatoriacorrectamente. PlanillaSemanal.Comprobante: esVARBINARY(MAX) NULL. El SP que lo llena debe generar el PDF (o NULL si no se ha emitido). No hay tablaComprobanteque liste múltiples comprobantes por planilla.- Cierre semanal sin movimientos: si una semana no tuvo marcas para un empleado, el SP debe crear la
PlanillaSemanalcon saldos en 0 y aplicar deducciones igual (las deducciones porcentuales sobre 0 son 0; las fijas se dividen igual entre 4 ó 5).
8. Anexo: archivos del proyecto SQL
SQL/SCRIPTS/VaciarDB.sql— drop + create de la base vacía (enmaster).SQL/SCRIPTS/Tablas.sql— 21 tablas + 23 FKs, en formato SSMS,USE [PlanillaDB]. Asume base preexistente.SQL/SCRIPTS/Trigger.sql—trg_Empleado_Insert_AssignMandatoryDeductions.SQL/SCRIPTS/CargarDatosXML.sql— scaffold desp_CargarCatalogosXML(pendiente del XML final).ModeloSebas*.jpeg— diagrama ER del compañero, referencia visual. No se commitea (evidencia del compañero).