diff --git a/tests/SIGCM2.Api.Tests/Admin/PuntosDeVentaControllerTests.cs b/tests/SIGCM2.Api.Tests/Admin/PuntosDeVentaControllerTests.cs
index 2ec816c..5bffba0 100644
--- a/tests/SIGCM2.Api.Tests/Admin/PuntosDeVentaControllerTests.cs
+++ b/tests/SIGCM2.Api.Tests/Admin/PuntosDeVentaControllerTests.cs
@@ -125,13 +125,6 @@ public sealed class PuntosDeVentaControllerTests : IAsyncLifetime
"SELECT Id FROM dbo.Medio WHERE Codigo = @Codigo", new { Codigo = codigo });
if (id is null) return;
- // Delete SecuenciaComprobante for PuntosDeVenta of this Medio (no versioning)
- await conn.ExecuteAsync("""
- DELETE sc FROM dbo.SecuenciaComprobante sc
- INNER JOIN dbo.PuntoDeVenta pdv ON pdv.Id = sc.PuntoDeVentaId
- WHERE pdv.MedioId = @id
- """, new { id });
-
// Delete dependent PuntosDeVenta (disable versioning to also clear history)
await conn.ExecuteAsync("ALTER TABLE dbo.PuntoDeVenta SET (SYSTEM_VERSIONING = OFF)");
await conn.ExecuteAsync("DELETE FROM dbo.PuntoDeVenta_History WHERE MedioId = @id", new { id });
@@ -604,238 +597,4 @@ public sealed class PuntosDeVentaControllerTests : IAsyncLifetime
}
}
- // ── SECUENCIAS: RESERVAR ──────────────────────────────────────────────────
-
- /// T5.3 — Primera reserva inicializa en 1.
- [Fact]
- public async Task ReservarNumero_FirstReservation_Returns1()
- {
- const string medioCodigo = "ADMS08_MED_RSV1";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Reservar 1", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Reservar First", token);
-
- using var req = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/secuencias/FacturaA/reservar", bearerToken: token);
- var resp = await _client.SendAsync(req);
-
- Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
- var json = await resp.Content.ReadFromJsonAsync();
- Assert.Equal(1, json.GetProperty("numeroReservado").GetInt32());
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
-
- /// T5.3 — 409 punto_de_venta_inactivo al reservar en PdV inactivo.
- [Fact]
- public async Task ReservarNumero_WhenPdvInactive_Returns409PdvInactivo()
- {
- const string medioCodigo = "ADMS08_MED_RSVI";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Reservar Inactivo", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Reservar Inactivo", token);
-
- // Deactivate PdV
- using var deactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/deactivate", bearerToken: token);
- await _client.SendAsync(deactReq);
-
- using var req = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/secuencias/FacturaA/reservar", bearerToken: token);
- var resp = await _client.SendAsync(req);
-
- Assert.Equal(HttpStatusCode.Conflict, resp.StatusCode);
- var json = await resp.Content.ReadFromJsonAsync();
- Assert.Equal("punto_de_venta_inactivo", json.GetProperty("error").GetString());
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
-
- /// T5.3 — 409 medio_inactivo al reservar con Medio inactivo.
- [Fact]
- public async Task ReservarNumero_WhenMedioInactive_Returns409MedioInactivo()
- {
- const string medioCodigo = "ADMS08_MED_RSVMI";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Reservar MedioInactivo", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Reservar MedioInact", token);
-
- // Deactivate medio
- using var deactMedioReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
- await _client.SendAsync(deactMedioReq);
-
- using var req = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/secuencias/FacturaA/reservar", bearerToken: token);
- var resp = await _client.SendAsync(req);
-
- Assert.Equal(HttpStatusCode.Conflict, resp.StatusCode);
- var json = await resp.Content.ReadFromJsonAsync();
- Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
-
- // ── SECUENCIAS: PROXIMO ───────────────────────────────────────────────────
-
- /// T5.3 — GetProximo es read-only: no modifica UltimoNumero.
- [Fact]
- public async Task GetProximoNumero_DoesNotChangeState()
- {
- const string medioCodigo = "ADMS08_MED_PROX";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Proximo", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Proximo", token);
-
- // Reserve once to establish state
- using var rsv = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/secuencias/FacturaA/reservar", bearerToken: token);
- await _client.SendAsync(rsv);
-
- // GetProximo twice — should return 2 both times
- using var req1 = BuildRequest(HttpMethod.Get, $"{Endpoint}/{pdvId}/secuencias/FacturaA/proximo", bearerToken: token);
- var resp1 = await _client.SendAsync(req1);
- Assert.Equal(HttpStatusCode.OK, resp1.StatusCode);
- var json1 = await resp1.Content.ReadFromJsonAsync();
- Assert.Equal(2, json1.GetProperty("proximoNumero").GetInt32());
-
- using var req2 = BuildRequest(HttpMethod.Get, $"{Endpoint}/{pdvId}/secuencias/FacturaA/proximo", bearerToken: token);
- var resp2 = await _client.SendAsync(req2);
- var json2 = await resp2.Content.ReadFromJsonAsync();
- Assert.Equal(2, json2.GetProperty("proximoNumero").GetInt32());
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
-
- /// T5.3 — GetProximo para fila inexistente devuelve 1.
- [Fact]
- public async Task GetProximoNumero_WhenNoSequenceExists_Returns1()
- {
- const string medioCodigo = "ADMS08_MED_PROX1";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Proximo 1", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Proximo First", token);
-
- using var req = BuildRequest(HttpMethod.Get, $"{Endpoint}/{pdvId}/secuencias/FacturaB/proximo", bearerToken: token);
- var resp = await _client.SendAsync(req);
-
- Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
- var json = await resp.Content.ReadFromJsonAsync();
- Assert.Equal(1, json.GetProperty("proximoNumero").GetInt32());
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
-
- // ── T5.4 — Concurrencia ───────────────────────────────────────────────────
-
- ///
- /// T5.4 — 50 tasks paralelas reservando para mismo PdV + TipoComprobante
- /// deben producir 50 números distintos cubriendo {1..50}.
- ///
- [Fact]
- public async Task ReservarNumero_50ConcurrentReservations_ProducesNoDuplicates()
- {
- const string medioCodigo = "ADMS08_CONC50";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Concurrencia 50", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Concurrencia 50", token);
-
- const int taskCount = 50;
- var tasks = Enumerable.Range(0, taskCount)
- .Select(_ => Task.Run(async () =>
- {
- // Each task creates its own HttpClient to avoid sharing
- // the shared _client which is not thread-safe for concurrent requests.
- // Use BuildRequest on a shared client IS safe since HttpClient is thread-safe
- // for concurrent operations as long as each message is distinct.
- using var req = new HttpRequestMessage(HttpMethod.Post, $"{Endpoint}/{pdvId}/secuencias/FacturaA/reservar");
- req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
- var resp = await _client.SendAsync(req);
- resp.EnsureSuccessStatusCode();
- var json = await resp.Content.ReadFromJsonAsync();
- return json.GetProperty("numeroReservado").GetInt32();
- }))
- .ToList();
-
- var results = await Task.WhenAll(tasks);
-
- // All 50 numbers must be present exactly once
- Assert.Equal(taskCount, results.Length);
- Assert.Equal(taskCount, results.Distinct().Count());
-
- var expected = Enumerable.Range(1, taskCount).ToHashSet();
- var actual = results.ToHashSet();
- Assert.Equal(expected, actual);
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
-
- // ── T5.5 — Secuencialidad ─────────────────────────────────────────────────
-
- ///
- /// T5.5 — 100 reservas en serie para mismo PdV + TipoComprobante
- /// deben devolver {1, 2, 3, ..., 100} en orden.
- ///
- [Fact]
- public async Task ReservarNumero_100SerialReservations_ProducesSequentialNumbers()
- {
- const string medioCodigo = "ADMS08_SEQ100";
- var token = await GetAdminTokenAsync();
-
- try
- {
- var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Secuencial 100", token);
- var pdvId = await CreatePdvAsync(medioId, 1, "PdV Secuencial 100", token);
-
- const int count = 100;
- var results = new List(count);
-
- for (int i = 0; i < count; i++)
- {
- using var req = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/secuencias/FacturaA/reservar", bearerToken: token);
- var resp = await _client.SendAsync(req);
- resp.EnsureSuccessStatusCode();
- var json = await resp.Content.ReadFromJsonAsync();
- results.Add(json.GetProperty("numeroReservado").GetInt32());
- }
-
- // Verify sequential: {1, 2, 3, ..., 100}
- var expected = Enumerable.Range(1, count).ToList();
- Assert.Equal(expected, results);
- }
- finally
- {
- await DeleteMedioIfExistsAsync(medioCodigo);
- }
- }
}
diff --git a/tests/SIGCM2.Application.Tests/Domain/SecuenciaComprobanteTests.cs b/tests/SIGCM2.Application.Tests/Domain/SecuenciaComprobanteTests.cs
deleted file mode 100644
index 39c2fa7..0000000
--- a/tests/SIGCM2.Application.Tests/Domain/SecuenciaComprobanteTests.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using SIGCM2.Domain.Entities;
-using SIGCM2.Domain.Enums;
-
-namespace SIGCM2.Application.Tests.Domain;
-
-public class SecuenciaComprobanteTests
-{
- private static SecuenciaComprobante Make(
- int puntoDeVentaId = 1,
- TipoComprobante tipo = TipoComprobante.FacturaA,
- int ultimoNumero = 0)
- => new(puntoDeVentaId, tipo, ultimoNumero, DateTime.UtcNow, null);
-
- [Fact]
- public void Constructor_SetsAllProperties()
- {
- var now = DateTime.UtcNow;
- var seq = new SecuenciaComprobante(
- puntoDeVentaId: 3,
- tipoComprobante: TipoComprobante.FacturaB,
- ultimoNumero: 42,
- fechaCreacion: now,
- fechaModificacion: null);
-
- Assert.Equal(3, seq.PuntoDeVentaId);
- Assert.Equal(TipoComprobante.FacturaB, seq.TipoComprobante);
- Assert.Equal(42, seq.UltimoNumero);
- Assert.Equal(now, seq.FechaCreacion);
- Assert.Null(seq.FechaModificacion);
- }
-
- [Fact]
- public void ProximoNumero_WhenUltimoNumeroZero_ReturnsOne()
- {
- var seq = Make(ultimoNumero: 0);
-
- Assert.Equal(1, seq.ProximoNumero);
- }
-
- [Fact]
- public void ProximoNumero_WhenUltimoNumeroN_ReturnsNPlusOne()
- {
- var seq = Make(ultimoNumero: 7);
-
- Assert.Equal(8, seq.ProximoNumero);
- }
-
- [Fact]
- public void AllTipoComprobanteValues_CanBeUsedInConstructor()
- {
- foreach (TipoComprobante tipo in Enum.GetValues())
- {
- var seq = Make(tipo: tipo);
- Assert.Equal(tipo, seq.TipoComprobante);
- }
- }
-}
diff --git a/tests/SIGCM2.Application.Tests/PuntosDeVenta/ProximoNumero/GetProximoNumeroQueryHandlerTests.cs b/tests/SIGCM2.Application.Tests/PuntosDeVenta/ProximoNumero/GetProximoNumeroQueryHandlerTests.cs
deleted file mode 100644
index 4adc03b..0000000
--- a/tests/SIGCM2.Application.Tests/PuntosDeVenta/ProximoNumero/GetProximoNumeroQueryHandlerTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using NSubstitute;
-using SIGCM2.Application.Abstractions.Persistence;
-using SIGCM2.Application.PuntosDeVenta.ProximoNumero;
-using SIGCM2.Domain.Enums;
-
-namespace SIGCM2.Application.Tests.PuntosDeVenta.ProximoNumero;
-
-public class GetProximoNumeroQueryHandlerTests
-{
- private readonly IPuntoDeVentaRepository _repo = Substitute.For();
- private readonly GetProximoNumeroQueryHandler _handler;
-
- public GetProximoNumeroQueryHandlerTests()
- {
- _handler = new GetProximoNumeroQueryHandler(_repo);
- }
-
- [Fact]
- public async Task Handle_ExistingSequence_ReturnsUltimoNumeroMasUno()
- {
- _repo.GetUltimoNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Returns(7);
-
- var result = await _handler.Handle(new GetProximoNumeroQuery(10, TipoComprobante.FacturaA));
-
- Assert.Equal(TipoComprobante.FacturaA, result.TipoComprobante);
- Assert.Equal(8, result.ProximoNumero);
- }
-
- [Fact]
- public async Task Handle_NoExistingSequence_ReturnsOne()
- {
- _repo.GetUltimoNumeroAsync(10, TipoComprobante.FacturaB, Arg.Any())
- .Returns((int?)null);
-
- var result = await _handler.Handle(new GetProximoNumeroQuery(10, TipoComprobante.FacturaB));
-
- Assert.Equal(1, result.ProximoNumero);
- }
-
- [Fact]
- public async Task Handle_DoesNotCallReservarNumero()
- {
- _repo.GetUltimoNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Returns(5);
-
- await _handler.Handle(new GetProximoNumeroQuery(10, TipoComprobante.FacturaA));
-
- await _repo.DidNotReceive().ReservarNumeroAsync(
- Arg.Any(), Arg.Any(), Arg.Any());
- }
-}
diff --git a/tests/SIGCM2.Application.Tests/PuntosDeVenta/Reservar/ReservarNumeroCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/PuntosDeVenta/Reservar/ReservarNumeroCommandHandlerTests.cs
deleted file mode 100644
index a3cf99f..0000000
--- a/tests/SIGCM2.Application.Tests/PuntosDeVenta/Reservar/ReservarNumeroCommandHandlerTests.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using NSubstitute;
-using NSubstitute.ExceptionExtensions;
-using SIGCM2.Application.Abstractions.Persistence;
-using SIGCM2.Application.PuntosDeVenta.Reservar;
-using SIGCM2.Domain.Enums;
-using SIGCM2.Domain.Exceptions;
-
-namespace SIGCM2.Application.Tests.PuntosDeVenta.Reservar;
-
-public class ReservarNumeroCommandHandlerTests
-{
- private readonly IPuntoDeVentaRepository _repo = Substitute.For();
- private readonly ReservarNumeroCommandHandler _handler;
-
- private static readonly ReservarNumeroCommand ValidCommand =
- new(PuntoDeVentaId: 10, TipoComprobante: TipoComprobante.FacturaA);
-
- public ReservarNumeroCommandHandlerTests()
- {
- // Use delay = 0 for fast tests
- _handler = new ReservarNumeroCommandHandler(_repo, deadlockBackoffMs: [0, 0, 0]);
- }
-
- // ── happy path ───────────────────────────────────────────────────────────
-
- [Fact]
- public async Task Handle_HappyPath_ReturnsNumeroReservado()
- {
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Returns(7);
-
- var result = await _handler.Handle(ValidCommand);
-
- Assert.Equal(TipoComprobante.FacturaA, result.TipoComprobante);
- Assert.Equal(7, result.NumeroReservado);
- }
-
- // ── retry deadlock ────────────────────────────────────────────────────────
-
- [Fact]
- public async Task Handle_DeadlockTwiceThenSucceeds_ReturnsResult()
- {
- var deadlock = new DeadlockTransientException();
-
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Returns(
- _ => Task.FromException(deadlock),
- _ => Task.FromException(deadlock),
- _ => Task.FromResult(3));
-
- var result = await _handler.Handle(ValidCommand);
-
- Assert.Equal(3, result.NumeroReservado);
- }
-
- [Fact]
- public async Task Handle_DeadlockThreeTimes_BubblesUpDeadlockException()
- {
- var deadlock = new DeadlockTransientException();
-
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Returns(
- _ => Task.FromException(deadlock),
- _ => Task.FromException(deadlock),
- _ => Task.FromException(deadlock));
-
- await Assert.ThrowsAsync(
- () => _handler.Handle(ValidCommand));
- }
-
- [Fact]
- public async Task Handle_DeadlockExhaustsBackoff_TriedFourTimesTotal()
- {
- // backoff = [0,0,0] → 3 retries → 4 total attempts (1 initial + 3 retries)
- var deadlock = new DeadlockTransientException();
-
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Returns(_ => Task.FromException(deadlock));
-
- try { await _handler.Handle(ValidCommand); } catch (DeadlockTransientException) { }
-
- await _repo.Received(4).ReservarNumeroAsync(
- Arg.Any(), Arg.Any(), Arg.Any());
- }
-
- // ── domain exceptions bubble up without retry ─────────────────────────────
-
- [Fact]
- public async Task Handle_PuntoDeVentaInactivo_BubblesUpImmediately()
- {
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Throws(new PuntoDeVentaInactivoException(10));
-
- await Assert.ThrowsAsync(
- () => _handler.Handle(ValidCommand));
-
- await _repo.Received(1).ReservarNumeroAsync(
- Arg.Any(), Arg.Any(), Arg.Any());
- }
-
- [Fact]
- public async Task Handle_MedioInactivo_BubblesUpImmediately()
- {
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Throws(new MedioInactivoException(5));
-
- await Assert.ThrowsAsync(
- () => _handler.Handle(ValidCommand));
-
- await _repo.Received(1).ReservarNumeroAsync(
- Arg.Any(), Arg.Any(), Arg.Any());
- }
-
- [Fact]
- public async Task Handle_PdvNotFound_BubblesUpImmediately()
- {
- _repo.ReservarNumeroAsync(10, TipoComprobante.FacturaA, Arg.Any())
- .Throws(new PuntoDeVentaNotFoundException(10));
-
- await Assert.ThrowsAsync(
- () => _handler.Handle(ValidCommand));
-
- await _repo.Received(1).ReservarNumeroAsync(
- Arg.Any(), Arg.Any(), Arg.Any());
- }
-}
diff --git a/tests/SIGCM2.TestSupport/SqlTestFixture.cs b/tests/SIGCM2.TestSupport/SqlTestFixture.cs
index 54c698d..45dbb9c 100644
--- a/tests/SIGCM2.TestSupport/SqlTestFixture.cs
+++ b/tests/SIGCM2.TestSupport/SqlTestFixture.cs
@@ -171,7 +171,7 @@ public sealed class SqlTestFixture : IAsyncLifetime
-- 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'),
-- V013 (ADM-008): permiso para CRUD de Puntos de Venta
- ('administracion:puntos_de_venta:gestionar', N'Gestionar puntos de venta', N'Crear, editar y desactivar puntos de venta y reservar numeros','administracion')
+ ('administracion:puntos_de_venta:gestionar', N'Gestionar puntos de venta', N'Crear, editar y desactivar puntos de venta AFIP','administracion')
) AS s (Codigo, Nombre, Descripcion, Modulo)
ON t.Codigo = s.Codigo
WHEN NOT MATCHED BY TARGET THEN
@@ -382,13 +382,38 @@ public sealed class SqlTestFixture : IAsyncLifetime
}
///
- /// ADM-008 (V013): applies PuntoDeVenta / SecuenciaComprobante schema + temporal tables +
- /// permiso 'administracion:puntos_de_venta:gestionar' + SP usp_ReservarNumeroComprobante.
- /// Idempotent — mirrors V013__create_puntos_de_venta.sql. Permiso y asignación se siembran
- /// desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync (post-respawn).
+ /// ADM-008 (V013): applies dbo.PuntoDeVenta schema + temporal table.
+ /// NOTE: SecuenciaComprobante y SP usp_ReservarNumeroComprobante fueron eliminados
+ /// post-smoke (Batch 9) — IMAC/Infogestión asigna los números AFIP externamente.
+ /// Este método también hace DROP idempotente de esos artefactos en caso de que
+ /// SIGCM2_Test los tuviera de una versión previa de la migración V013.
+ /// Permiso y asignación se siembran desde SeedPermisosCanonicalAsync / SeedRolPermisosCanonicalAsync.
///
private async Task EnsureV013SchemaAsync()
{
+ // ── Drops idempotentes de artefactos eliminados (cirugía post-smoke) ──
+ // Si SIGCM2_Test tiene SecuenciaComprobante o el SP de una versión previa, se limpian.
+ const string dropSp = """
+ IF OBJECT_ID(N'dbo.usp_ReservarNumeroComprobante', N'P') IS NOT NULL
+ DROP PROCEDURE dbo.usp_ReservarNumeroComprobante;
+ """;
+
+ const string disableSecuenciaVersioning = """
+ IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.SecuenciaComprobante') AND temporal_type = 2)
+ ALTER TABLE dbo.SecuenciaComprobante SET (SYSTEM_VERSIONING = OFF);
+ """;
+
+ const string dropSecuenciaHistory = """
+ IF OBJECT_ID(N'dbo.SecuenciaComprobante_History', N'U') IS NOT NULL
+ DROP TABLE dbo.SecuenciaComprobante_History;
+ """;
+
+ const string dropSecuencia = """
+ IF OBJECT_ID(N'dbo.SecuenciaComprobante', N'U') IS NOT NULL
+ DROP TABLE dbo.SecuenciaComprobante;
+ """;
+
+ // ── PuntoDeVenta: crear si no existe ──────────────────────────────────
const string createPdv = """
IF OBJECT_ID(N'dbo.PuntoDeVenta', N'U') IS NULL
BEGIN
@@ -417,23 +442,6 @@ public sealed class SqlTestFixture : IAsyncLifetime
END
""";
- const string createSecuencia = """
- IF OBJECT_ID(N'dbo.SecuenciaComprobante', N'U') IS NULL
- BEGIN
- CREATE TABLE dbo.SecuenciaComprobante (
- PuntoDeVentaId INT NOT NULL,
- TipoComprobante TINYINT NOT NULL,
- UltimoNumero INT NOT NULL CONSTRAINT DF_SecuenciaComprobante_UltimoNumero DEFAULT(0),
- FechaCreacion DATETIME2(3) NOT NULL CONSTRAINT DF_SecuenciaComprobante_FechaCreacion DEFAULT(SYSUTCDATETIME()),
- FechaModificacion DATETIME2(3) NULL,
- CONSTRAINT PK_SecuenciaComprobante PRIMARY KEY (PuntoDeVentaId, TipoComprobante),
- CONSTRAINT FK_SecuenciaComprobante_PuntoDeVenta FOREIGN KEY (PuntoDeVentaId) REFERENCES dbo.PuntoDeVenta(Id) ON DELETE NO ACTION,
- CONSTRAINT CK_SecuenciaComprobante_TipoComprobante CHECK (TipoComprobante BETWEEN 1 AND 6),
- CONSTRAINT CK_SecuenciaComprobante_UltimoNumero CHECK (UltimoNumero >= 0)
- );
- END
- """;
-
const string addPdvPeriod = """
IF COL_LENGTH('dbo.PuntoDeVenta', 'ValidFrom') IS NULL
BEGIN
@@ -458,122 +466,17 @@ public sealed class SqlTestFixture : IAsyncLifetime
END
""";
- // SecuenciaComprobante: sin SYSTEM_VERSIONING (AD8 revisitado — ver comentario en V013).
- // Si una version previa del fixture activo SYSTEM_VERSIONING, lo desactiva + drop history.
- const string disableSecuenciaVersioning = """
- IF EXISTS (SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('dbo.SecuenciaComprobante') AND temporal_type = 2)
- BEGIN
- ALTER TABLE dbo.SecuenciaComprobante SET (SYSTEM_VERSIONING = OFF);
- END
- """;
-
- const string dropSecuenciaHistory = """
- IF OBJECT_ID(N'dbo.SecuenciaComprobante_History', N'U') IS NOT NULL
- BEGIN
- DROP TABLE dbo.SecuenciaComprobante_History;
- END
- """;
-
- const string dropSecuenciaPeriod = """
- IF EXISTS (SELECT 1 FROM sys.periods WHERE object_id = OBJECT_ID('dbo.SecuenciaComprobante'))
- BEGIN
- ALTER TABLE dbo.SecuenciaComprobante DROP PERIOD FOR SYSTEM_TIME;
- END
- """;
-
- const string dropSecuenciaValidCols = """
- IF COL_LENGTH('dbo.SecuenciaComprobante', 'ValidFrom') IS NOT NULL
- BEGIN
- IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_SecuenciaComprobante_ValidFrom' AND parent_object_id = OBJECT_ID('dbo.SecuenciaComprobante'))
- ALTER TABLE dbo.SecuenciaComprobante DROP CONSTRAINT DF_SecuenciaComprobante_ValidFrom;
- IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_SecuenciaComprobante_ValidTo' AND parent_object_id = OBJECT_ID('dbo.SecuenciaComprobante'))
- ALTER TABLE dbo.SecuenciaComprobante DROP CONSTRAINT DF_SecuenciaComprobante_ValidTo;
- ALTER TABLE dbo.SecuenciaComprobante DROP COLUMN ValidFrom, ValidTo;
- END
- """;
-
- const string dropSp = """
- IF OBJECT_ID(N'dbo.usp_ReservarNumeroComprobante', N'P') IS NOT NULL
- DROP PROCEDURE dbo.usp_ReservarNumeroComprobante;
- """;
-
- const string createSp = """
- CREATE PROCEDURE dbo.usp_ReservarNumeroComprobante
- @PuntoDeVentaId INT,
- @TipoComprobante TINYINT,
- @NumeroReservado INT OUTPUT
- AS
- BEGIN
- SET NOCOUNT ON;
- SET XACT_ABORT ON;
- SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-
- BEGIN TRAN;
-
- DECLARE @PdvActivo BIT;
- DECLARE @MedioActivo BIT;
-
- SELECT
- @PdvActivo = p.Activo,
- @MedioActivo = m.Activo
- FROM dbo.PuntoDeVenta p
- JOIN dbo.Medio m ON m.Id = p.MedioId
- WHERE p.Id = @PuntoDeVentaId;
-
- IF @PdvActivo IS NULL
- BEGIN
- ROLLBACK;
- THROW 50003, 'punto_de_venta_not_found', 1;
- END
-
- IF @PdvActivo = 0
- BEGIN
- ROLLBACK;
- THROW 50001, 'punto_de_venta_inactivo', 1;
- END
-
- IF @MedioActivo = 0
- BEGIN
- ROLLBACK;
- THROW 50002, 'medio_inactivo', 1;
- END
-
- DECLARE @_out TABLE (n INT NOT NULL);
-
- UPDATE dbo.SecuenciaComprobante
- SET
- UltimoNumero = UltimoNumero + 1,
- FechaModificacion = SYSUTCDATETIME()
- OUTPUT inserted.UltimoNumero INTO @_out(n)
- WHERE PuntoDeVentaId = @PuntoDeVentaId
- AND TipoComprobante = @TipoComprobante;
-
- IF @@ROWCOUNT = 0
- BEGIN
- INSERT INTO dbo.SecuenciaComprobante (PuntoDeVentaId, TipoComprobante, UltimoNumero)
- VALUES (@PuntoDeVentaId, @TipoComprobante, 1);
- SET @NumeroReservado = 1;
- END
- ELSE
- BEGIN
- SELECT @NumeroReservado = n FROM @_out;
- END
-
- COMMIT;
- END
- """;
-
- await _connection.ExecuteAsync(createPdv);
- await _connection.ExecuteAsync(createPdvIndex);
- await _connection.ExecuteAsync(createSecuencia);
- await _connection.ExecuteAsync(addPdvPeriod);
- await _connection.ExecuteAsync(setPdvVersioning);
+ // Drops primero (limpieza de versión previa)
+ await _connection.ExecuteAsync(dropSp);
await _connection.ExecuteAsync(disableSecuenciaVersioning);
await _connection.ExecuteAsync(dropSecuenciaHistory);
- await _connection.ExecuteAsync(dropSecuenciaPeriod);
- await _connection.ExecuteAsync(dropSecuenciaValidCols);
- await _connection.ExecuteAsync(dropSp);
- await _connection.ExecuteAsync(createSp);
+ await _connection.ExecuteAsync(dropSecuencia);
+
+ // Luego crear PuntoDeVenta + Temporal Table
+ await _connection.ExecuteAsync(createPdv);
+ await _connection.ExecuteAsync(createPdvIndex);
+ await _connection.ExecuteAsync(addPdvPeriod);
+ await _connection.ExecuteAsync(setPdvVersioning);
}
///