diff --git a/src/api/SIGCM2.Domain/Entities/PuntoDeVenta.cs b/src/api/SIGCM2.Domain/Entities/PuntoDeVenta.cs
new file mode 100644
index 0000000..87ec486
--- /dev/null
+++ b/src/api/SIGCM2.Domain/Entities/PuntoDeVenta.cs
@@ -0,0 +1,77 @@
+namespace SIGCM2.Domain.Entities;
+
+///
+/// Punto de Venta AFIP vinculado a un Medio. Gestiona comprobantes fiscales.
+/// Identidad por Id (IDENTITY). NumeroAFIP único por Medio (enforced por UNIQUE(MedioId,NumeroAFIP) en BD).
+///
+public sealed class PuntoDeVenta
+{
+ public int Id { get; }
+ public int MedioId { get; }
+ public short NumeroAFIP { get; }
+ public string Nombre { get; }
+ public string? Descripcion { get; }
+ public bool Activo { get; }
+ public DateTime FechaCreacion { get; }
+ public DateTime? FechaModificacion { get; }
+
+ public PuntoDeVenta(
+ int id,
+ int medioId,
+ short numeroAFIP,
+ string nombre,
+ string? descripcion,
+ bool activo,
+ DateTime fechaCreacion,
+ DateTime? fechaModificacion)
+ {
+ Id = id;
+ MedioId = medioId;
+ NumeroAFIP = numeroAFIP;
+ Nombre = nombre;
+ Descripcion = descripcion;
+ Activo = activo;
+ FechaCreacion = fechaCreacion;
+ FechaModificacion = fechaModificacion;
+ }
+
+ ///
+ /// Factory para crear un nuevo PdV (Id=0 — BD asigna via IDENTITY; Activo=true; FechaCreacion por DF de BD).
+ ///
+ public static PuntoDeVenta ForCreation(int medioId, short numeroAFIP, string nombre, string? descripcion)
+ => new(
+ id: 0,
+ medioId: medioId,
+ numeroAFIP: numeroAFIP,
+ nombre: nombre,
+ descripcion: descripcion,
+ activo: true,
+ fechaCreacion: default,
+ fechaModificacion: null);
+
+ ///
+ /// Retorna una nueva instancia con nombre, numeroAFIP y descripcion actualizados.
+ /// MedioId es inmutable (enforce en BD).
+ ///
+ public PuntoDeVenta WithUpdatedProfile(string nombre, short numeroAFIP, string? descripcion)
+ => new(
+ id: Id,
+ medioId: MedioId,
+ numeroAFIP: numeroAFIP,
+ nombre: nombre,
+ descripcion: descripcion,
+ activo: Activo,
+ fechaCreacion: FechaCreacion,
+ fechaModificacion: DateTime.UtcNow);
+
+ public PuntoDeVenta WithActivo(bool activo)
+ => new(
+ id: Id,
+ medioId: MedioId,
+ numeroAFIP: NumeroAFIP,
+ nombre: Nombre,
+ descripcion: Descripcion,
+ activo: activo,
+ fechaCreacion: FechaCreacion,
+ fechaModificacion: DateTime.UtcNow);
+}
diff --git a/src/api/SIGCM2.Domain/Entities/SecuenciaComprobante.cs b/src/api/SIGCM2.Domain/Entities/SecuenciaComprobante.cs
new file mode 100644
index 0000000..c936c67
--- /dev/null
+++ b/src/api/SIGCM2.Domain/Entities/SecuenciaComprobante.cs
@@ -0,0 +1,34 @@
+using SIGCM2.Domain.Enums;
+
+namespace SIGCM2.Domain.Entities;
+
+///
+/// Lleva el correlativo de números de comprobante por (PuntoDeVentaId × TipoComprobante).
+/// La reserva atómica la ejecuta usp_ReservarNumeroComprobante directamente en BD.
+/// Este objeto es un helper de lectura/proyección.
+///
+public sealed class SecuenciaComprobante
+{
+ public int PuntoDeVentaId { get; }
+ public TipoComprobante TipoComprobante { get; }
+ public int UltimoNumero { get; }
+ public DateTime FechaCreacion { get; }
+ public DateTime? FechaModificacion { get; }
+
+ /// El próximo número disponible (read-only, sin modificar el estado).
+ public int ProximoNumero => UltimoNumero + 1;
+
+ public SecuenciaComprobante(
+ int puntoDeVentaId,
+ TipoComprobante tipoComprobante,
+ int ultimoNumero,
+ DateTime fechaCreacion,
+ DateTime? fechaModificacion)
+ {
+ PuntoDeVentaId = puntoDeVentaId;
+ TipoComprobante = tipoComprobante;
+ UltimoNumero = ultimoNumero;
+ FechaCreacion = fechaCreacion;
+ FechaModificacion = fechaModificacion;
+ }
+}
diff --git a/src/api/SIGCM2.Domain/Enums/TipoComprobante.cs b/src/api/SIGCM2.Domain/Enums/TipoComprobante.cs
new file mode 100644
index 0000000..e1be111
--- /dev/null
+++ b/src/api/SIGCM2.Domain/Enums/TipoComprobante.cs
@@ -0,0 +1,16 @@
+namespace SIGCM2.Domain.Enums;
+
+///
+/// Tipos de comprobante AFIP soportados por ADM-008.
+/// Valor TINYINT persistido en BD (CHECK TipoComprobante BETWEEN 1 AND 6).
+/// Migración a tabla maestra diferida a FAC-001.
+///
+public enum TipoComprobante : byte
+{
+ FacturaA = 1,
+ FacturaB = 2,
+ FacturaC = 3,
+ NotaCreditoA = 4,
+ NotaCreditoB = 5,
+ NotaCreditoC = 6,
+}
diff --git a/src/api/SIGCM2.Domain/Exceptions/NumeroAFIPDuplicadoException.cs b/src/api/SIGCM2.Domain/Exceptions/NumeroAFIPDuplicadoException.cs
new file mode 100644
index 0000000..01c57c2
--- /dev/null
+++ b/src/api/SIGCM2.Domain/Exceptions/NumeroAFIPDuplicadoException.cs
@@ -0,0 +1,18 @@
+namespace SIGCM2.Domain.Exceptions;
+
+///
+/// Thrown when a (MedioId, NumeroAFIP) combination already exists in the system.
+/// Enforced by UNIQUE(MedioId, NumeroAFIP) in DB as safety net (REQ-PDV-003).
+///
+public sealed class NumeroAFIPDuplicadoException : DomainException
+{
+ public int MedioId { get; }
+ public short NumeroAFIP { get; }
+
+ public NumeroAFIPDuplicadoException(int medioId, short numeroAFIP)
+ : base($"El número AFIP '{numeroAFIP}' ya existe para el medio {medioId}.")
+ {
+ MedioId = medioId;
+ NumeroAFIP = numeroAFIP;
+ }
+}
diff --git a/src/api/SIGCM2.Domain/Exceptions/PuntoDeVentaInactivoException.cs b/src/api/SIGCM2.Domain/Exceptions/PuntoDeVentaInactivoException.cs
new file mode 100644
index 0000000..3405365
--- /dev/null
+++ b/src/api/SIGCM2.Domain/Exceptions/PuntoDeVentaInactivoException.cs
@@ -0,0 +1,15 @@
+namespace SIGCM2.Domain.Exceptions;
+
+///
+/// Thrown when a mutation (reserva) is attempted on an inactive PuntoDeVenta.
+///
+public sealed class PuntoDeVentaInactivoException : DomainException
+{
+ public int PuntoDeVentaId { get; }
+
+ public PuntoDeVentaInactivoException(int puntoDeVentaId)
+ : base($"El punto de venta {puntoDeVentaId} está inactivo. No se pueden realizar operaciones hasta reactivarlo.")
+ {
+ PuntoDeVentaId = puntoDeVentaId;
+ }
+}
diff --git a/src/api/SIGCM2.Domain/Exceptions/PuntoDeVentaNotFoundException.cs b/src/api/SIGCM2.Domain/Exceptions/PuntoDeVentaNotFoundException.cs
new file mode 100644
index 0000000..c8578e1
--- /dev/null
+++ b/src/api/SIGCM2.Domain/Exceptions/PuntoDeVentaNotFoundException.cs
@@ -0,0 +1,15 @@
+namespace SIGCM2.Domain.Exceptions;
+
+///
+/// Thrown when a requested PuntoDeVenta does not exist in the system.
+///
+public sealed class PuntoDeVentaNotFoundException : DomainException
+{
+ public int Id { get; }
+
+ public PuntoDeVentaNotFoundException(int id)
+ : base($"El punto de venta con id '{id}' no existe.")
+ {
+ Id = id;
+ }
+}
diff --git a/tests/SIGCM2.Application.Tests/Domain/PuntoDeVentaTests.cs b/tests/SIGCM2.Application.Tests/Domain/PuntoDeVentaTests.cs
new file mode 100644
index 0000000..639e66d
--- /dev/null
+++ b/tests/SIGCM2.Application.Tests/Domain/PuntoDeVentaTests.cs
@@ -0,0 +1,139 @@
+using SIGCM2.Domain.Entities;
+using SIGCM2.Domain.Enums;
+
+namespace SIGCM2.Application.Tests.Domain;
+
+public class PuntoDeVentaTests
+{
+ private static PuntoDeVenta MakePdv(
+ int id = 1,
+ int medioId = 5,
+ short numeroAFIP = 1,
+ string nombre = "PdV Central",
+ string? descripcion = null,
+ bool activo = true)
+ => new(id, medioId, numeroAFIP, nombre, descripcion, activo, DateTime.UtcNow, null);
+
+ // ── ForCreation ───────────────────────────────────────────────────────────
+
+ [Fact]
+ public void ForCreation_SetsCorrectValues()
+ {
+ var pdv = PuntoDeVenta.ForCreation(medioId: 5, numeroAFIP: 3, nombre: "PdV Sur", descripcion: null);
+
+ Assert.Equal(0, pdv.Id);
+ Assert.Equal(5, pdv.MedioId);
+ Assert.Equal(3, pdv.NumeroAFIP);
+ Assert.Equal("PdV Sur", pdv.Nombre);
+ Assert.Null(pdv.Descripcion);
+ Assert.True(pdv.Activo);
+ }
+
+ [Fact]
+ public void ForCreation_WithDescripcion_SetsDescripcion()
+ {
+ var pdv = PuntoDeVenta.ForCreation(5, 1, "PdV Norte", "Descripcion larga");
+
+ Assert.Equal("Descripcion larga", pdv.Descripcion);
+ }
+
+ // ── WithUpdatedProfile ────────────────────────────────────────────────────
+
+ [Fact]
+ public void WithUpdatedProfile_ReturnsNewInstanceWithUpdatedFields()
+ {
+ var original = MakePdv(id: 10, nombre: "Original");
+
+ var updated = original.WithUpdatedProfile(nombre: "Actualizado", numeroAFIP: 7, descripcion: "Desc");
+
+ Assert.NotSame(original, updated);
+ Assert.Equal("Actualizado", updated.Nombre);
+ Assert.Equal(7, updated.NumeroAFIP);
+ Assert.Equal("Desc", updated.Descripcion);
+ }
+
+ [Fact]
+ public void WithUpdatedProfile_ImmutableFields_Preserved()
+ {
+ var original = MakePdv(id: 10, medioId: 5);
+
+ var updated = original.WithUpdatedProfile("Nuevo", 2, null);
+
+ Assert.Equal(10, updated.Id);
+ Assert.Equal(5, updated.MedioId);
+ Assert.Equal(original.Activo, updated.Activo);
+ Assert.Equal(original.FechaCreacion, updated.FechaCreacion);
+ }
+
+ [Fact]
+ public void WithUpdatedProfile_SetsFechaModificacion()
+ {
+ var original = MakePdv();
+
+ var updated = original.WithUpdatedProfile("Nuevo", 2, null);
+
+ Assert.NotNull(updated.FechaModificacion);
+ }
+
+ // ── WithActivo ────────────────────────────────────────────────────────────
+
+ [Fact]
+ public void WithActivo_False_ReturnsDomainObjectWithActivoFalse()
+ {
+ var pdv = MakePdv(activo: true);
+
+ var deactivated = pdv.WithActivo(false);
+
+ Assert.False(deactivated.Activo);
+ Assert.NotSame(pdv, deactivated);
+ }
+
+ [Fact]
+ public void WithActivo_True_ReturnsDomainObjectWithActivoTrue()
+ {
+ var pdv = MakePdv(activo: false);
+
+ var reactivated = pdv.WithActivo(true);
+
+ Assert.True(reactivated.Activo);
+ }
+
+ [Fact]
+ public void WithActivo_ImmutableFields_Preserved()
+ {
+ var pdv = MakePdv(id: 99, medioId: 3);
+
+ var toggled = pdv.WithActivo(false);
+
+ Assert.Equal(99, toggled.Id);
+ Assert.Equal(3, toggled.MedioId);
+ Assert.Equal(pdv.NumeroAFIP, toggled.NumeroAFIP);
+ Assert.Equal(pdv.Nombre, toggled.Nombre);
+ }
+
+ // ── Constructor sets all properties ───────────────────────────────────────
+
+ [Fact]
+ public void Constructor_SetsAllProperties()
+ {
+ var now = DateTime.UtcNow;
+ var pdv = new PuntoDeVenta(
+ id: 7,
+ medioId: 3,
+ numeroAFIP: 4,
+ nombre: "PdV Test",
+ descripcion: "Desc Test",
+ activo: true,
+ fechaCreacion: now,
+ fechaModificacion: null);
+
+ Assert.Equal(7, pdv.Id);
+ Assert.Equal(3, pdv.MedioId);
+ Assert.Equal(4, pdv.NumeroAFIP);
+ Assert.Equal("PdV Test", pdv.Nombre);
+ Assert.Equal("Desc Test", pdv.Descripcion);
+ Assert.True(pdv.Activo);
+ Assert.Equal(now, pdv.FechaCreacion);
+ Assert.Null(pdv.FechaModificacion);
+ }
+}
diff --git a/tests/SIGCM2.Application.Tests/Domain/SecuenciaComprobanteTests.cs b/tests/SIGCM2.Application.Tests/Domain/SecuenciaComprobanteTests.cs
new file mode 100644
index 0000000..39c2fa7
--- /dev/null
+++ b/tests/SIGCM2.Application.Tests/Domain/SecuenciaComprobanteTests.cs
@@ -0,0 +1,57 @@
+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);
+ }
+ }
+}