From 7d2190c37e129e1ab1dec499a09a63500b587e2a Mon Sep 17 00:00:00 2001 From: dmolinari Date: Wed, 15 Apr 2026 15:26:22 -0300 Subject: [PATCH] feat(db): BATCH 1 - V005/V006 Permiso y RolPermiso + seed [UDT-005] --- database/migrations/V005__create_permiso.sql | 65 +++++++++++++ .../migrations/V006__create_rol_permiso.sql | 96 +++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 database/migrations/V005__create_permiso.sql create mode 100644 database/migrations/V006__create_rol_permiso.sql diff --git a/database/migrations/V005__create_permiso.sql b/database/migrations/V005__create_permiso.sql new file mode 100644 index 0000000..b2b19e2 --- /dev/null +++ b/database/migrations/V005__create_permiso.sql @@ -0,0 +1,65 @@ +-- V005__create_permiso.sql +-- Tabla catálogo de permisos atómicos RBAC (18 permisos iniciales §2.4.2). +-- Requerimiento: ejecutar ANTES de V006 (FK PermisoId). +-- Run on: SIGCM2 (prod) and SIGCM2_Test (integration tests) + +SET QUOTED_IDENTIFIER ON; +SET ANSI_NULLS ON; +SET NOCOUNT ON; +GO + +IF OBJECT_ID(N'dbo.Permiso', N'U') IS NULL +BEGIN + CREATE TABLE dbo.Permiso ( + Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Permiso PRIMARY KEY, + Codigo VARCHAR(60) NOT NULL, + Nombre NVARCHAR(100) NOT NULL, + Descripcion NVARCHAR(500) NULL, + Modulo VARCHAR(30) NOT NULL, + Activo BIT NOT NULL CONSTRAINT DF_Permiso_Activo DEFAULT(1), + FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Permiso_FC DEFAULT(SYSUTCDATETIME()), + CONSTRAINT UQ_Permiso_Codigo UNIQUE (Codigo), + -- Formato: segmentos en minúsculas separados por ':', p.ej. ventas:contado:crear + -- Usa collation binaria para forzar case-sensitivity (igual que CK_Rol_Codigo_Format). + CONSTRAINT CK_Permiso_Codigo_Format CHECK ( + PATINDEX('[a-z]%', Codigo COLLATE Latin1_General_BIN2) = 1 + AND PATINDEX('%[^a-z0-9_:]%', Codigo COLLATE Latin1_General_BIN2) = 0 + ) + ); + PRINT 'Table dbo.Permiso created.'; +END +ELSE + PRINT 'Table dbo.Permiso already exists — skip.'; +GO + +-- Seed 18 permisos canónicos (idempotente via MERGE). +-- Convención RBAC: cada permiso nuevo → asignar a admin en la misma migración (V006+). +MERGE dbo.Permiso AS t +USING (VALUES + ('ventas:contado:crear', N'Cargar orden contado', NULL, 'ventas'), + ('ventas:contado:modificar', N'Modificar orden contado', NULL, 'ventas'), + ('ventas:contado:cobrar', N'Cobrar orden contado', NULL, 'ventas'), + ('ventas:contado:facturar', N'Facturar orden contado', NULL, 'ventas'), + ('ventas:ctacte:crear', N'Cargar orden cuenta corriente', NULL, 'ventas'), + ('ventas:ctacte:facturar', N'Facturar lote cuenta corriente', NULL, 'ventas'), + ('textos:editar', N'Editar textos', NULL, 'textos'), + ('textos:reclamos:ver', N'Ver reclamos de textos', NULL, 'textos'), + ('pauta:azanu:ver', N'Ver AZANU en pauta', NULL, 'pauta'), + ('pauta:limpiar', N'Limpieza de pauta', NULL, 'pauta'), + ('pauta:recursos:fueradehora', N'Recursos fuera de hora', NULL, 'pauta'), + ('productores:deuda:ver', N'Ver deuda propia de productores', NULL, 'productores'), + ('productores:pendientes:crear', N'Cargar pendientes de productores', NULL, 'productores'), + ('productores:deuda:bypass', N'Bypass de deuda de productores', NULL, 'productores'), + ('administracion:usuarios:gestionar', N'Gestionar usuarios del sistema', N'Crear, editar y desactivar usuarios', 'administracion'), + ('administracion:tarifarios:gestionar', N'Gestionar tarifarios', N'Crear y modificar tarifarios de publicidad', 'administracion'), + ('administracion:medios:gestionar', N'Gestionar medios publicitarios', N'Alta y configuración de medios', 'administracion'), + ('administracion:auditoria:ver', N'Ver logs de auditoría', N'Acceso al dashboard de auditoría', 'administracion') +) AS s (Codigo, Nombre, Descripcion, Modulo) +ON t.Codigo = s.Codigo +WHEN NOT MATCHED BY TARGET THEN + INSERT (Codigo, Nombre, Descripcion, Modulo) + VALUES (s.Codigo, s.Nombre, s.Descripcion, s.Modulo); +GO + +PRINT 'Permiso seeds applied (18 permisos).'; +GO diff --git a/database/migrations/V006__create_rol_permiso.sql b/database/migrations/V006__create_rol_permiso.sql new file mode 100644 index 0000000..803c360 --- /dev/null +++ b/database/migrations/V006__create_rol_permiso.sql @@ -0,0 +1,96 @@ +-- V006__create_rol_permiso.sql +-- Tabla M:N Rol ↔ Permiso + seed inicial según matriz §2.4.2. +-- Requiere: V003 (dbo.Rol), V005 (dbo.Permiso). +-- Convención RBAC: cada permiso nuevo → asignar explícitamente a admin en la misma migración. +-- Run on: SIGCM2 (prod) and SIGCM2_Test (integration tests) + +SET QUOTED_IDENTIFIER ON; +SET ANSI_NULLS ON; +SET NOCOUNT ON; +GO + +IF OBJECT_ID(N'dbo.RolPermiso', N'U') IS NULL +BEGIN + CREATE TABLE dbo.RolPermiso ( + Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_RolPermiso PRIMARY KEY, + RolId INT NOT NULL CONSTRAINT FK_RolPermiso_Rol REFERENCES dbo.Rol(Id) ON DELETE CASCADE, + PermisoId INT NOT NULL CONSTRAINT FK_RolPermiso_Permiso REFERENCES dbo.Permiso(Id) ON DELETE CASCADE, + FechaAsignacion DATETIME2(3) NOT NULL CONSTRAINT DF_RolPermiso_FA DEFAULT(SYSUTCDATETIME()), + CONSTRAINT UQ_RolPermiso UNIQUE (RolId, PermisoId) + ); + CREATE INDEX IX_RolPermiso_RolId ON dbo.RolPermiso (RolId); + CREATE INDEX IX_RolPermiso_PermisoId ON dbo.RolPermiso (PermisoId); + PRINT 'Table dbo.RolPermiso created.'; +END +ELSE + PRINT 'Table dbo.RolPermiso already exists — skip.'; +GO + +-- Seed: mapeo rol → permisos según matriz §2.4.2 +-- admin: 18 permisos (explícito — sin wildcard, convención RBAC) +-- cajero: 4 permisos (ventas contado) +-- operador_ctacte: 2 permisos (ventas ctacte) +-- picadora: 2 permisos (textos) +-- jefe_publicidad: 7 permisos (textos + pauta + productores) +-- productor: 2 permisos (productores) +-- diagramacion: 1 permiso (pauta:azanu:ver) +-- reportes: 0 permisos (solo lectura reportes — sin permisos en este catálogo) +-- Total rows: 36 +MERGE dbo.RolPermiso AS t +USING ( + SELECT r.Id AS RolId, p.Id AS PermisoId + FROM (VALUES + -- admin (18 permisos) + ('admin', 'ventas:contado:crear'), + ('admin', 'ventas:contado:modificar'), + ('admin', 'ventas:contado:cobrar'), + ('admin', 'ventas:contado:facturar'), + ('admin', 'ventas:ctacte:crear'), + ('admin', 'ventas:ctacte:facturar'), + ('admin', 'textos:editar'), + ('admin', 'textos:reclamos:ver'), + ('admin', 'pauta:azanu:ver'), + ('admin', 'pauta:limpiar'), + ('admin', 'pauta:recursos:fueradehora'), + ('admin', 'productores:deuda:ver'), + ('admin', 'productores:pendientes:crear'), + ('admin', 'productores:deuda:bypass'), + ('admin', 'administracion:usuarios:gestionar'), + ('admin', 'administracion:tarifarios:gestionar'), + ('admin', 'administracion:medios:gestionar'), + ('admin', 'administracion:auditoria:ver'), + -- cajero (4 permisos) + ('cajero', 'ventas:contado:crear'), + ('cajero', 'ventas:contado:modificar'), + ('cajero', 'ventas:contado:cobrar'), + ('cajero', 'ventas:contado:facturar'), + -- operador_ctacte (2 permisos) + ('operador_ctacte', 'ventas:ctacte:crear'), + ('operador_ctacte', 'ventas:ctacte:facturar'), + -- picadora (2 permisos) + ('picadora', 'textos:editar'), + ('picadora', 'textos:reclamos:ver'), + -- jefe_publicidad (7 permisos) + ('jefe_publicidad', 'textos:editar'), + ('jefe_publicidad', 'textos:reclamos:ver'), + ('jefe_publicidad', 'pauta:azanu:ver'), + ('jefe_publicidad', 'pauta:limpiar'), + ('jefe_publicidad', 'pauta:recursos:fueradehora'), + ('jefe_publicidad', 'productores:deuda:ver'), + ('jefe_publicidad', 'productores:deuda:bypass'), + -- productor (2 permisos) + ('productor', 'productores:deuda:ver'), + ('productor', 'productores:pendientes:crear'), + -- diagramacion (1 permiso) + ('diagramacion', 'pauta:azanu:ver') + -- reportes: 0 permisos — no filas + ) AS x (RolCodigo, PermisoCodigo) + JOIN dbo.Rol r ON r.Codigo = x.RolCodigo + JOIN dbo.Permiso p ON p.Codigo = x.PermisoCodigo +) AS s ON t.RolId = s.RolId AND t.PermisoId = s.PermisoId +WHEN NOT MATCHED BY TARGET THEN + INSERT (RolId, PermisoId) VALUES (s.RolId, s.PermisoId); +GO + +PRINT 'RolPermiso seeds applied (36 rows: admin×18 + cajero×4 + operador_ctacte×2 + picadora×2 + jefe_publicidad×7 + productor×2 + diagramacion×1).'; +GO