feat(db): Medio + Seccion (temporal tables + seed) — ADM-001 B1
V011 crea dbo.Medio y dbo.Seccion con SYSTEM_VERSIONING ON (retention 10 anios) y PAGE compression en history; siembra el permiso 'administracion:secciones:gestionar' y lo asigna a rol admin. El permiso 'administracion:medios:gestionar' ya existia desde V005. V012 siembra Medios fundacionales ELDIA y ELPLATA (MERGE idempotente). Rollbacks V011/V012 validados estructuralmente; aplicacion y reaplicacion verificadas en SIGCM2_Test y SIGCM2. Fixture de tests actualizado: EnsureV011SchemaAsync, SeedMediosCanonicalAsync, ignora Medio_History y Seccion_History en Respawner.
This commit is contained in:
@@ -27,6 +27,8 @@ database/
|
|||||||
| V008 | `V008__add_mustchangepassword_and_indexes.sql` | UDT-008 | Usuario.MustChangePassword + IX_Usuario_Activo_Rol |
|
| V008 | `V008__add_mustchangepassword_and_indexes.sql` | UDT-008 | Usuario.MustChangePassword + IX_Usuario_Activo_Rol |
|
||||||
| V009 | `V009__activate_permisos_overrides.sql` | UDT-009 | Migración shape `PermisosJson` `{grant, deny}` |
|
| V009 | `V009__activate_permisos_overrides.sql` | UDT-009 | Migración shape `PermisosJson` `{grant, deny}` |
|
||||||
| **V010** | **`V010__audit_infrastructure.sql`** | **UDT-010** | **Infra de auditoría + Temporal Tables. Ver nota abajo.** |
|
| **V010** | **`V010__audit_infrastructure.sql`** | **UDT-010** | **Infra de auditoría + Temporal Tables. Ver nota abajo.** |
|
||||||
|
| V011 | `V011__create_medio_seccion.sql` | ADM-001 | Medio + Seccion (temporal, retention 10y) + permiso `administracion:secciones:gestionar` |
|
||||||
|
| V012 | `V012__seed_medios.sql` | ADM-001 | Seed idempotente de Medios ELDIA y ELPLATA |
|
||||||
|
|
||||||
## Convenciones
|
## Convenciones
|
||||||
|
|
||||||
@@ -78,6 +80,16 @@ O desde SSMS: abrir el archivo, conectar a cada base, F5.
|
|||||||
|
|
||||||
**Catálogo de entidades auditables** (source of truth): `Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.5 📋 Auditoría.md`. Cada UDT nueva que introduzca entidades de negocio debe agregar esas tablas al catálogo y activar `SYSTEM_VERSIONING` en su migración.
|
**Catálogo de entidades auditables** (source of truth): `Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.5 📋 Auditoría.md`. Cada UDT nueva que introduzca entidades de negocio debe agregar esas tablas al catálogo y activar `SYSTEM_VERSIONING` en su migración.
|
||||||
|
|
||||||
|
### V011/V012 — ADM-001 Medios y Secciones
|
||||||
|
|
||||||
|
**Alcance**: crea `dbo.Medio` y `dbo.Seccion` con Temporal Tables (retention 10 años), el permiso `administracion:secciones:gestionar` (y lo asigna a rol `admin`), y siembra los dos Medios fundacionales `ELDIA` y `ELPLATA`.
|
||||||
|
|
||||||
|
**Notas**:
|
||||||
|
- `administracion:medios:gestionar` ya existía desde V005 — no se toca.
|
||||||
|
- `PlataformaEmpresaId` es `INT NULL` sin FK; la FK se agrega en INT-003 cuando se cree la tabla `PlataformaEmpresa`.
|
||||||
|
- `Seccion.Tipo` acepta `'clasificados' | 'notables' | 'suplementos'` (CHECK constraint). Config avanzada (par/impar, suplementos, cupos) se introduce en ADM-003.
|
||||||
|
- Rollback: `V012_ROLLBACK.sql` (falla si hay Secciones vivas) → `V011_ROLLBACK.sql`. Rollback en prod NO soportado si ADM-008/009 o PRC-* ya aplicados.
|
||||||
|
|
||||||
## Recursos
|
## Recursos
|
||||||
|
|
||||||
- Design autoritativo: `Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.5 📋 Auditoría.md`
|
- Design autoritativo: `Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.5 📋 Auditoría.md`
|
||||||
|
|||||||
118
database/migrations/V011_ROLLBACK.sql
Normal file
118
database/migrations/V011_ROLLBACK.sql
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
-- V011_ROLLBACK.sql
|
||||||
|
-- Reversa de V011__create_medio_seccion.sql.
|
||||||
|
--
|
||||||
|
-- ⚠️ ADVERTENCIA: ejecutar ELIMINA Medio, Seccion, su historia temporal,
|
||||||
|
-- el permiso 'administracion:secciones:gestionar' y sus asignaciones.
|
||||||
|
-- ('administracion:medios:gestionar' NO se toca — es pre-existente de V005.)
|
||||||
|
--
|
||||||
|
-- Uso intended: ROLLBACK en entornos NO-productivos.
|
||||||
|
-- Prerequisito: no deben existir FKs vivas apuntando a Medio (p.ej., Punto de Venta, Tarifario).
|
||||||
|
-- Si ADM-008, ADM-009 o PRC-* ya están aplicados, este rollback falla — usar backup.
|
||||||
|
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
SET ANSI_NULLS ON;
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 1. Apagar SYSTEM_VERSIONING + remover PERIOD en Seccion y Medio
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
-- Seccion primero (FK al Medio)
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Seccion') AND temporal_type = 2)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion SET (SYSTEM_VERSIONING = OFF);
|
||||||
|
PRINT 'Seccion: SYSTEM_VERSIONING OFF.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.Seccion'))
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion DROP PERIOD FOR SYSTEM_TIME;
|
||||||
|
PRINT 'Seccion: PERIOD FOR SYSTEM_TIME dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH('dbo.Seccion', 'ValidFrom') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion DROP CONSTRAINT IF EXISTS DF_Seccion_ValidFrom;
|
||||||
|
ALTER TABLE dbo.Seccion DROP CONSTRAINT IF EXISTS DF_Seccion_ValidTo;
|
||||||
|
ALTER TABLE dbo.Seccion DROP COLUMN ValidFrom, ValidTo;
|
||||||
|
PRINT 'Seccion: ValidFrom/ValidTo dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Seccion_History', N'U') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP TABLE dbo.Seccion_History;
|
||||||
|
PRINT 'Seccion_History dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Medio
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Medio') AND temporal_type = 2)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio SET (SYSTEM_VERSIONING = OFF);
|
||||||
|
PRINT 'Medio: SYSTEM_VERSIONING OFF.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.Medio'))
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio DROP PERIOD FOR SYSTEM_TIME;
|
||||||
|
PRINT 'Medio: PERIOD FOR SYSTEM_TIME dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH('dbo.Medio', 'ValidFrom') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio DROP CONSTRAINT IF EXISTS DF_Medio_ValidFrom;
|
||||||
|
ALTER TABLE dbo.Medio DROP CONSTRAINT IF EXISTS DF_Medio_ValidTo;
|
||||||
|
ALTER TABLE dbo.Medio DROP COLUMN ValidFrom, ValidTo;
|
||||||
|
PRINT 'Medio: ValidFrom/ValidTo dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Medio_History', N'U') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP TABLE dbo.Medio_History;
|
||||||
|
PRINT 'Medio_History dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 2. Drop Seccion y Medio (Seccion primero por FK)
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Seccion', N'U') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP TABLE dbo.Seccion;
|
||||||
|
PRINT 'Table dbo.Seccion dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Medio', N'U') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP TABLE dbo.Medio;
|
||||||
|
PRINT 'Table dbo.Medio dropped.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 3. Remover permiso 'administracion:secciones:gestionar' + RolPermiso
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
DELETE rp
|
||||||
|
FROM dbo.RolPermiso rp
|
||||||
|
JOIN dbo.Permiso p ON p.Id = rp.PermisoId
|
||||||
|
WHERE p.Codigo = 'administracion:secciones:gestionar';
|
||||||
|
GO
|
||||||
|
|
||||||
|
DELETE FROM dbo.Permiso
|
||||||
|
WHERE Codigo = 'administracion:secciones:gestionar';
|
||||||
|
GO
|
||||||
|
|
||||||
|
PRINT '';
|
||||||
|
PRINT 'V011 rolled back. dbo.Medio, dbo.Seccion and their history removed.';
|
||||||
|
PRINT 'administracion:medios:gestionar preserved (pre-existing from V005).';
|
||||||
|
GO
|
||||||
206
database/migrations/V011__create_medio_seccion.sql
Normal file
206
database/migrations/V011__create_medio_seccion.sql
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
-- V011__create_medio_seccion.sql
|
||||||
|
-- ADM-001 (Fase 1 CRITICAL PATH): Medios y Secciones — catálogo fundacional.
|
||||||
|
--
|
||||||
|
-- Cambios:
|
||||||
|
-- 1. dbo.Medio (Codigo UQ global, TipoMedio enum 1..4, PlataformaEmpresaId NULL, SYSTEM_VERSIONING ON).
|
||||||
|
-- 2. dbo.Seccion (FK MedioId, Codigo UQ por Medio, Tipo CHECK, SYSTEM_VERSIONING ON).
|
||||||
|
-- 3. Permiso 'administracion:secciones:gestionar' + asignación a rol 'admin'.
|
||||||
|
-- El permiso 'administracion:medios:gestionar' ya existía desde V005.
|
||||||
|
--
|
||||||
|
-- Patrón: V007 (permisos MERGE) + V010 (Temporal Tables con retention 10 años + PAGE compression en history).
|
||||||
|
-- Idempotente: seguro para re-ejecutar.
|
||||||
|
-- Reversa: V011_ROLLBACK.sql.
|
||||||
|
-- Run on: SIGCM2 (dev) y SIGCM2_Test (integration tests).
|
||||||
|
--
|
||||||
|
-- Source of truth: Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.10 📋 UDTs Módulo Administración.md (ADM-001)
|
||||||
|
-- Entidades: Obsidian/03-MODELO-de-DATOS/3.2 Entidades Core/3.2.1 🏢 Medio.md
|
||||||
|
-- Auditoría: Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.5 📋 Auditoría.md
|
||||||
|
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
SET ANSI_NULLS ON;
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 1. dbo.Medio
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Medio', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Medio (
|
||||||
|
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Medio PRIMARY KEY,
|
||||||
|
Codigo VARCHAR(30) NOT NULL,
|
||||||
|
Nombre NVARCHAR(100) NOT NULL,
|
||||||
|
Tipo TINYINT NOT NULL, -- TipoMedio: 1=Diario, 2=Radio, 3=Web, 4=Poster
|
||||||
|
PlataformaEmpresaId INT NULL, -- FK futura a INT-003 (IMAC mapping)
|
||||||
|
Activo BIT NOT NULL CONSTRAINT DF_Medio_Activo DEFAULT(1),
|
||||||
|
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Medio_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||||
|
FechaModificacion DATETIME2(3) NULL,
|
||||||
|
CONSTRAINT UQ_Medio_Codigo UNIQUE (Codigo),
|
||||||
|
CONSTRAINT CK_Medio_Tipo CHECK (Tipo BETWEEN 1 AND 4)
|
||||||
|
);
|
||||||
|
PRINT 'Table dbo.Medio created.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
PRINT 'Table dbo.Medio already exists — skip.';
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Medio_Activo_Tipo' AND object_id = OBJECT_ID('dbo.Medio'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Medio_Activo_Tipo
|
||||||
|
ON dbo.Medio(Activo, Tipo)
|
||||||
|
INCLUDE (Codigo, Nombre, PlataformaEmpresaId);
|
||||||
|
PRINT 'Index IX_Medio_Activo_Tipo created.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 2. dbo.Seccion
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.Seccion', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Seccion (
|
||||||
|
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Seccion PRIMARY KEY,
|
||||||
|
MedioId INT NOT NULL,
|
||||||
|
Codigo VARCHAR(30) NOT NULL,
|
||||||
|
Nombre NVARCHAR(100) NOT NULL,
|
||||||
|
Tipo VARCHAR(20) NOT NULL, -- 'clasificados' | 'notables' | 'suplementos'
|
||||||
|
Activo BIT NOT NULL CONSTRAINT DF_Seccion_Activo DEFAULT(1),
|
||||||
|
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Seccion_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||||
|
FechaModificacion DATETIME2(3) NULL,
|
||||||
|
CONSTRAINT FK_Seccion_Medio FOREIGN KEY (MedioId) REFERENCES dbo.Medio(Id) ON DELETE NO ACTION,
|
||||||
|
CONSTRAINT UQ_Seccion_MedioId_Codigo UNIQUE (MedioId, Codigo),
|
||||||
|
CONSTRAINT CK_Seccion_Tipo CHECK (Tipo IN ('clasificados','notables','suplementos'))
|
||||||
|
);
|
||||||
|
PRINT 'Table dbo.Seccion created.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
PRINT 'Table dbo.Seccion already exists — skip.';
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Seccion_MedioId_Activo' AND object_id = OBJECT_ID('dbo.Seccion'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Seccion_MedioId_Activo
|
||||||
|
ON dbo.Seccion(MedioId, Activo)
|
||||||
|
INCLUDE (Codigo, Nombre, Tipo);
|
||||||
|
PRINT 'Index IX_Seccion_MedioId_Activo created.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 3. SYSTEM_VERSIONING — Medio
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
IF COL_LENGTH('dbo.Medio', 'ValidFrom') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio
|
||||||
|
ADD
|
||||||
|
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Medio_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||||
|
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Medio_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||||
|
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||||
|
PRINT 'Medio: PERIOD FOR SYSTEM_TIME added.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Medio') AND temporal_type = 2)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio
|
||||||
|
SET (SYSTEM_VERSIONING = ON (
|
||||||
|
HISTORY_TABLE = dbo.Medio_History,
|
||||||
|
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||||
|
));
|
||||||
|
PRINT 'Medio: SYSTEM_VERSIONING = ON (history: dbo.Medio_History, retention: 10 years).';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
PRINT 'Medio: SYSTEM_VERSIONING already ON — skip.';
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'Medio_History' AND schema_id = SCHEMA_ID('dbo'))
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys.partitions p
|
||||||
|
JOIN sys.tables t ON t.object_id = p.object_id
|
||||||
|
WHERE t.name = 'Medio_History' AND p.data_compression = 2
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio_History REBUILD WITH (DATA_COMPRESSION = PAGE);
|
||||||
|
PRINT 'Medio_History: rebuilt with PAGE compression.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 4. SYSTEM_VERSIONING — Seccion
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
IF COL_LENGTH('dbo.Seccion', 'ValidFrom') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion
|
||||||
|
ADD
|
||||||
|
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Seccion_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||||
|
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Seccion_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||||
|
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||||
|
PRINT 'Seccion: PERIOD FOR SYSTEM_TIME added.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Seccion') AND temporal_type = 2)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion
|
||||||
|
SET (SYSTEM_VERSIONING = ON (
|
||||||
|
HISTORY_TABLE = dbo.Seccion_History,
|
||||||
|
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||||
|
));
|
||||||
|
PRINT 'Seccion: SYSTEM_VERSIONING = ON.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
PRINT 'Seccion: SYSTEM_VERSIONING already ON — skip.';
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'Seccion_History' AND schema_id = SCHEMA_ID('dbo'))
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys.partitions p
|
||||||
|
JOIN sys.tables t ON t.object_id = p.object_id
|
||||||
|
WHERE t.name = 'Seccion_History' AND p.data_compression = 2
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion_History REBUILD WITH (DATA_COMPRESSION = PAGE);
|
||||||
|
PRINT 'Seccion_History: rebuilt with PAGE compression.';
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
-- 5. Permiso nuevo: administracion:secciones:gestionar
|
||||||
|
-- ('administracion:medios:gestionar' ya fue sembrado en V005 — no se toca).
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
MERGE dbo.Permiso AS t
|
||||||
|
USING (VALUES
|
||||||
|
('administracion:secciones:gestionar', N'Gestionar secciones por medio', N'Crear, editar y desactivar secciones de un medio', '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
|
||||||
|
|
||||||
|
MERGE dbo.RolPermiso AS t
|
||||||
|
USING (
|
||||||
|
SELECT r.Id AS RolId, p.Id AS PermisoId
|
||||||
|
FROM (VALUES
|
||||||
|
('admin', 'administracion:secciones:gestionar')
|
||||||
|
) 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 '';
|
||||||
|
PRINT 'V011 applied successfully — dbo.Medio + dbo.Seccion (temporal, retention 10y) + permiso secciones.';
|
||||||
|
PRINT 'Next: V012__seed_medios.sql (seed ELDIA, ELPLATA).';
|
||||||
|
GO
|
||||||
30
database/migrations/V012_ROLLBACK.sql
Normal file
30
database/migrations/V012_ROLLBACK.sql
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-- V012_ROLLBACK.sql
|
||||||
|
-- Reversa de V012__seed_medios.sql.
|
||||||
|
--
|
||||||
|
-- Elimina los seed rows ELDIA y ELPLATA solo si NO tienen Secciones asociadas.
|
||||||
|
-- Si alguna sección depende de un seed Medio, el DELETE falla por FK ON DELETE NO ACTION.
|
||||||
|
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
SET ANSI_NULLS ON;
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Falla temprano si hay secciones vivas apuntando a estos Medios.
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.Seccion s
|
||||||
|
JOIN dbo.Medio m ON m.Id = s.MedioId
|
||||||
|
WHERE m.Codigo IN ('ELDIA', 'ELPLATA')
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
RAISERROR('Cannot rollback V012: existen Secciones vinculadas a ELDIA/ELPLATA. Rollback ADM-001 completo con V011_ROLLBACK.sql.', 16, 1);
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
DELETE FROM dbo.Medio
|
||||||
|
WHERE Codigo IN ('ELDIA', 'ELPLATA');
|
||||||
|
GO
|
||||||
|
|
||||||
|
PRINT 'V012 rolled back — seed Medios ELDIA y ELPLATA removed.';
|
||||||
|
GO
|
||||||
27
database/migrations/V012__seed_medios.sql
Normal file
27
database/migrations/V012__seed_medios.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- V012__seed_medios.sql
|
||||||
|
-- ADM-001: seed inicial de Medios ELDIA y ELPLATA.
|
||||||
|
--
|
||||||
|
-- Idempotente via MERGE por Codigo.
|
||||||
|
-- Tipo = 1 (Diario) per enum TipoMedio.
|
||||||
|
-- PlataformaEmpresaId = NULL (INT-003 lo poblará cuando exista el mapeo IMAC).
|
||||||
|
--
|
||||||
|
-- Run on: SIGCM2 (dev) y SIGCM2_Test (integration tests).
|
||||||
|
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
SET ANSI_NULLS ON;
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
MERGE dbo.Medio AS t
|
||||||
|
USING (VALUES
|
||||||
|
('ELDIA', N'El Día', 1),
|
||||||
|
('ELPLATA', N'El Plata', 1)
|
||||||
|
) AS s (Codigo, Nombre, Tipo)
|
||||||
|
ON t.Codigo = s.Codigo
|
||||||
|
WHEN NOT MATCHED BY TARGET THEN
|
||||||
|
INSERT (Codigo, Nombre, Tipo, PlataformaEmpresaId, Activo)
|
||||||
|
VALUES (s.Codigo, s.Nombre, s.Tipo, NULL, 1);
|
||||||
|
GO
|
||||||
|
|
||||||
|
PRINT 'V012 applied — Medios ELDIA y ELPLATA seeded (idempotent).';
|
||||||
|
GO
|
||||||
@@ -36,12 +36,15 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
// Applied manually via: sqlcmd ... -i database/migrations/V010__audit_infrastructure.sql
|
// Applied manually via: sqlcmd ... -i database/migrations/V010__audit_infrastructure.sql
|
||||||
await EnsureV010SchemaAsync();
|
await EnsureV010SchemaAsync();
|
||||||
|
|
||||||
|
// V011 (ADM-001): ensure dbo.Medio, dbo.Seccion + temporal tables + permiso 'administracion:secciones:gestionar'.
|
||||||
|
await EnsureV011SchemaAsync();
|
||||||
|
|
||||||
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
||||||
{
|
{
|
||||||
DbAdapter = DbAdapter.SqlServer,
|
DbAdapter = DbAdapter.SqlServer,
|
||||||
// Rol is a lookup table seeded by migration V003 — never wipe or Usuario FK breaks.
|
// Rol is a lookup table seeded by migration V003 — never wipe or Usuario FK breaks.
|
||||||
// Permiso and RolPermiso are seeded by V005/V006 — never wipe or integration tests lose the permission catalog.
|
// Permiso and RolPermiso are seeded by V005/V006 — never wipe or integration tests lose the permission catalog.
|
||||||
// *_History tables: UDT-010 system-versioned — Respawn cannot DELETE them directly (engine rejects).
|
// *_History tables: UDT-010/ADM-001 system-versioned — Respawn cannot DELETE them directly (engine rejects).
|
||||||
TablesToIgnore =
|
TablesToIgnore =
|
||||||
[
|
[
|
||||||
new Respawn.Graph.Table("dbo", "Rol"),
|
new Respawn.Graph.Table("dbo", "Rol"),
|
||||||
@@ -51,6 +54,8 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
new Respawn.Graph.Table("dbo", "Rol_History"),
|
new Respawn.Graph.Table("dbo", "Rol_History"),
|
||||||
new Respawn.Graph.Table("dbo", "Permiso_History"),
|
new Respawn.Graph.Table("dbo", "Permiso_History"),
|
||||||
new Respawn.Graph.Table("dbo", "RolPermiso_History"),
|
new Respawn.Graph.Table("dbo", "RolPermiso_History"),
|
||||||
|
new Respawn.Graph.Table("dbo", "Medio_History"),
|
||||||
|
new Respawn.Graph.Table("dbo", "Seccion_History"),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,6 +69,7 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
await SeedPermisosCanonicalAsync();
|
await SeedPermisosCanonicalAsync();
|
||||||
await SeedRolPermisosCanonicalAsync();
|
await SeedRolPermisosCanonicalAsync();
|
||||||
await SeedAdminAsync();
|
await SeedAdminAsync();
|
||||||
|
await SeedMediosCanonicalAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SeedRolCanonicalAsync()
|
private async Task SeedRolCanonicalAsync()
|
||||||
@@ -157,7 +163,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
-- V007 (UDT-006): permisos administrativos RBAC
|
-- V007 (UDT-006): permisos administrativos RBAC
|
||||||
('administracion:roles:gestionar', N'Gestionar roles del sistema', N'Crear, editar y desactivar roles RBAC', 'administracion'),
|
('administracion:roles:gestionar', N'Gestionar roles del sistema', N'Crear, editar y desactivar roles RBAC', 'administracion'),
|
||||||
('administracion:roles_permisos:gestionar', N'Gestionar asignacion de permisos', N'Asignar y revocar permisos por rol', 'administracion'),
|
('administracion:roles_permisos:gestionar', N'Gestionar asignacion de permisos', N'Asignar y revocar permisos por rol', 'administracion'),
|
||||||
('administracion:permisos:ver', N'Ver catalogo de permisos', N'Consultar el listado de permisos del sistema', 'administracion')
|
('administracion:permisos:ver', N'Ver catalogo de permisos', N'Consultar el listado de permisos del sistema', 'administracion'),
|
||||||
|
-- V011 (ADM-001): permiso para CRUD de Secciones
|
||||||
|
('administracion:secciones:gestionar', N'Gestionar secciones por medio', N'Crear, editar y desactivar secciones de un medio','administracion')
|
||||||
) AS s (Codigo, Nombre, Descripcion, Modulo)
|
) AS s (Codigo, Nombre, Descripcion, Modulo)
|
||||||
ON t.Codigo = s.Codigo
|
ON t.Codigo = s.Codigo
|
||||||
WHEN NOT MATCHED BY TARGET THEN
|
WHEN NOT MATCHED BY TARGET THEN
|
||||||
@@ -197,6 +205,8 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
('admin', 'administracion:roles:gestionar'),
|
('admin', 'administracion:roles:gestionar'),
|
||||||
('admin', 'administracion:roles_permisos:gestionar'),
|
('admin', 'administracion:roles_permisos:gestionar'),
|
||||||
('admin', 'administracion:permisos:ver'),
|
('admin', 'administracion:permisos:ver'),
|
||||||
|
-- V011 (ADM-001)
|
||||||
|
('admin', 'administracion:secciones:gestionar'),
|
||||||
('cajero', 'ventas:contado:crear'),
|
('cajero', 'ventas:contado:crear'),
|
||||||
('cajero', 'ventas:contado:modificar'),
|
('cajero', 'ventas:contado:modificar'),
|
||||||
('cajero', 'ventas:contado:cobrar'),
|
('cajero', 'ventas:contado:cobrar'),
|
||||||
@@ -241,6 +251,148 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
await _connection.ExecuteAsync(sql);
|
await _connection.ExecuteAsync(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ADM-001 (V011): applies Medio/Seccion schema + temporal + permiso 'administracion:secciones:gestionar'
|
||||||
|
/// idempotently to the test database. Mirrors V011__create_medio_seccion.sql.
|
||||||
|
/// Canonical seed (ELDIA, ELPLATA) vive en SeedMediosCanonicalAsync — se reaplica tras cada Respawn.
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnsureV011SchemaAsync()
|
||||||
|
{
|
||||||
|
const string createMedio = """
|
||||||
|
IF OBJECT_ID(N'dbo.Medio', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Medio (
|
||||||
|
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Medio PRIMARY KEY,
|
||||||
|
Codigo VARCHAR(30) NOT NULL,
|
||||||
|
Nombre NVARCHAR(100) NOT NULL,
|
||||||
|
Tipo TINYINT NOT NULL,
|
||||||
|
PlataformaEmpresaId INT NULL,
|
||||||
|
Activo BIT NOT NULL CONSTRAINT DF_Medio_Activo DEFAULT(1),
|
||||||
|
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Medio_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||||
|
FechaModificacion DATETIME2(3) NULL,
|
||||||
|
CONSTRAINT UQ_Medio_Codigo UNIQUE (Codigo),
|
||||||
|
CONSTRAINT CK_Medio_Tipo CHECK (Tipo BETWEEN 1 AND 4)
|
||||||
|
);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string createMedioIndex = """
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Medio_Activo_Tipo' AND object_id = OBJECT_ID('dbo.Medio'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Medio_Activo_Tipo
|
||||||
|
ON dbo.Medio(Activo, Tipo)
|
||||||
|
INCLUDE (Codigo, Nombre, PlataformaEmpresaId);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string createSeccion = """
|
||||||
|
IF OBJECT_ID(N'dbo.Seccion', N'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Seccion (
|
||||||
|
Id INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Seccion PRIMARY KEY,
|
||||||
|
MedioId INT NOT NULL,
|
||||||
|
Codigo VARCHAR(30) NOT NULL,
|
||||||
|
Nombre NVARCHAR(100) NOT NULL,
|
||||||
|
Tipo VARCHAR(20) NOT NULL,
|
||||||
|
Activo BIT NOT NULL CONSTRAINT DF_Seccion_Activo DEFAULT(1),
|
||||||
|
FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_Seccion_FechaCreacion DEFAULT(SYSUTCDATETIME()),
|
||||||
|
FechaModificacion DATETIME2(3) NULL,
|
||||||
|
CONSTRAINT FK_Seccion_Medio FOREIGN KEY (MedioId) REFERENCES dbo.Medio(Id) ON DELETE NO ACTION,
|
||||||
|
CONSTRAINT UQ_Seccion_MedioId_Codigo UNIQUE (MedioId, Codigo),
|
||||||
|
CONSTRAINT CK_Seccion_Tipo CHECK (Tipo IN ('clasificados','notables','suplementos'))
|
||||||
|
);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string createSeccionIndex = """
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_Seccion_MedioId_Activo' AND object_id = OBJECT_ID('dbo.Seccion'))
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Seccion_MedioId_Activo
|
||||||
|
ON dbo.Seccion(MedioId, Activo)
|
||||||
|
INCLUDE (Codigo, Nombre, Tipo);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string addMedioPeriod = """
|
||||||
|
IF COL_LENGTH('dbo.Medio', 'ValidFrom') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio
|
||||||
|
ADD
|
||||||
|
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Medio_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||||
|
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Medio_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||||
|
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string setMedioVersioning = """
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Medio') AND temporal_type = 2)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Medio
|
||||||
|
SET (SYSTEM_VERSIONING = ON (
|
||||||
|
HISTORY_TABLE = dbo.Medio_History,
|
||||||
|
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||||
|
));
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string addSeccionPeriod = """
|
||||||
|
IF COL_LENGTH('dbo.Seccion', 'ValidFrom') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion
|
||||||
|
ADD
|
||||||
|
ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Seccion_ValidFrom DEFAULT(SYSUTCDATETIME()),
|
||||||
|
ValidTo DATETIME2(3) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
|
||||||
|
CONSTRAINT DF_Seccion_ValidTo DEFAULT(CONVERT(DATETIME2(3), '9999-12-31 23:59:59.999')),
|
||||||
|
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string setSeccionVersioning = """
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.Seccion') AND temporal_type = 2)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Seccion
|
||||||
|
SET (SYSTEM_VERSIONING = ON (
|
||||||
|
HISTORY_TABLE = dbo.Seccion_History,
|
||||||
|
HISTORY_RETENTION_PERIOD = 10 YEARS
|
||||||
|
));
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
await _connection.ExecuteAsync(createMedio);
|
||||||
|
await _connection.ExecuteAsync(createMedioIndex);
|
||||||
|
await _connection.ExecuteAsync(createSeccion);
|
||||||
|
await _connection.ExecuteAsync(createSeccionIndex);
|
||||||
|
await _connection.ExecuteAsync(addMedioPeriod);
|
||||||
|
await _connection.ExecuteAsync(setMedioVersioning);
|
||||||
|
await _connection.ExecuteAsync(addSeccionPeriod);
|
||||||
|
await _connection.ExecuteAsync(setSeccionVersioning);
|
||||||
|
// Permiso 'administracion:secciones:gestionar' + asignación a admin se siembran
|
||||||
|
// desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync (post-respawn).
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ADM-001 (V012): MERGE seed ELDIA + ELPLATA. Re-seeded on every Respawn reset.
|
||||||
|
/// </summary>
|
||||||
|
private async Task SeedMediosCanonicalAsync()
|
||||||
|
{
|
||||||
|
const string sql = """
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
MERGE dbo.Medio AS t
|
||||||
|
USING (VALUES
|
||||||
|
('ELDIA', N'El Día', 1),
|
||||||
|
('ELPLATA', N'El Plata', 1)
|
||||||
|
) AS s (Codigo, Nombre, Tipo)
|
||||||
|
ON t.Codigo = s.Codigo
|
||||||
|
WHEN NOT MATCHED BY TARGET THEN
|
||||||
|
INSERT (Codigo, Nombre, Tipo, PlataformaEmpresaId, Activo)
|
||||||
|
VALUES (s.Codigo, s.Nombre, s.Tipo, NULL, 1);
|
||||||
|
""";
|
||||||
|
await _connection.ExecuteAsync(sql);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UDT-010 (V010): verifies that the audit infrastructure is present.
|
/// UDT-010 (V010): verifies that the audit infrastructure is present.
|
||||||
/// Does NOT re-apply the migration (the ALTER DATABASE ADD FILEGROUP/FILE + partition
|
/// Does NOT re-apply the migration (the ALTER DATABASE ADD FILEGROUP/FILE + partition
|
||||||
|
|||||||
Reference in New Issue
Block a user