feat(domain): Medio + Seccion entities + 4 exceptions — ADM-001 B2

Entities sealed immutable con factory ForCreation + copy-with methods
WithUpdatedProfile/WithActivo (Codigo inmutable en Medio; MedioId y
Codigo inmutables en Seccion — enforzado en Validators en B4).

Exceptions: MedioCodigoDuplicado (UQ global), SeccionCodigoDuplicadoEnMedio
(UQ compuesto), MedioNotFound, SeccionNotFound. Todas heredan de
DomainException.
This commit is contained in:
2026-04-16 18:45:46 -03:00
parent ff7d8986fd
commit bb98dbf217
7 changed files with 219 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
namespace SIGCM2.Domain.Entities;
public sealed class Medio
{
public int Id { get; }
public string Codigo { get; }
public string Nombre { get; }
public TipoMedio Tipo { get; }
public int? PlataformaEmpresaId { get; }
public bool Activo { get; }
public DateTime FechaCreacion { get; }
public DateTime? FechaModificacion { get; }
public Medio(
int id,
string codigo,
string nombre,
TipoMedio tipo,
int? plataformaEmpresaId,
bool activo,
DateTime fechaCreacion,
DateTime? fechaModificacion)
{
Id = id;
Codigo = codigo;
Nombre = nombre;
Tipo = tipo;
PlataformaEmpresaId = plataformaEmpresaId;
Activo = activo;
FechaCreacion = fechaCreacion;
FechaModificacion = fechaModificacion;
}
/// <summary>
/// Factory for creating a new Medio (Id=0 — DB assigns via IDENTITY; Activo=true; FechaCreacion set by DB default).
/// </summary>
public static Medio ForCreation(string codigo, string nombre, TipoMedio tipo, int? plataformaEmpresaId)
{
return new Medio(
id: 0,
codigo: codigo,
nombre: nombre,
tipo: tipo,
plataformaEmpresaId: plataformaEmpresaId,
activo: true,
fechaCreacion: default,
fechaModificacion: null);
}
/// <summary>
/// Returns a new instance with updated fields. Codigo is immutable (use BD UQ to enforce).
/// Sets FechaModificacion = UtcNow.
/// </summary>
public Medio WithUpdatedProfile(string nombre, TipoMedio tipo, int? plataformaEmpresaId)
=> new(
id: Id,
codigo: Codigo,
nombre: nombre,
tipo: tipo,
plataformaEmpresaId: plataformaEmpresaId,
activo: Activo,
fechaCreacion: FechaCreacion,
fechaModificacion: DateTime.UtcNow);
public Medio WithActivo(bool activo)
=> new(
id: Id,
codigo: Codigo,
nombre: Nombre,
tipo: Tipo,
plataformaEmpresaId: PlataformaEmpresaId,
activo: activo,
fechaCreacion: FechaCreacion,
fechaModificacion: DateTime.UtcNow);
}

View File

@@ -0,0 +1,72 @@
namespace SIGCM2.Domain.Entities;
public sealed class Seccion
{
public int Id { get; }
public int MedioId { get; }
public string Codigo { get; }
public string Nombre { get; }
public string Tipo { get; } // 'clasificados' | 'notables' | 'suplementos' — enforzado por CHECK en BD
public bool Activo { get; }
public DateTime FechaCreacion { get; }
public DateTime? FechaModificacion { get; }
public Seccion(
int id,
int medioId,
string codigo,
string nombre,
string tipo,
bool activo,
DateTime fechaCreacion,
DateTime? fechaModificacion)
{
Id = id;
MedioId = medioId;
Codigo = codigo;
Nombre = nombre;
Tipo = tipo;
Activo = activo;
FechaCreacion = fechaCreacion;
FechaModificacion = fechaModificacion;
}
public static Seccion ForCreation(int medioId, string codigo, string nombre, string tipo)
{
return new Seccion(
id: 0,
medioId: medioId,
codigo: codigo,
nombre: nombre,
tipo: tipo,
activo: true,
fechaCreacion: default,
fechaModificacion: null);
}
/// <summary>
/// Returns a new instance with updated fields. MedioId and Codigo are immutable.
/// Sets FechaModificacion = UtcNow.
/// </summary>
public Seccion WithUpdatedProfile(string nombre, string tipo)
=> new(
id: Id,
medioId: MedioId,
codigo: Codigo,
nombre: nombre,
tipo: tipo,
activo: Activo,
fechaCreacion: FechaCreacion,
fechaModificacion: DateTime.UtcNow);
public Seccion WithActivo(bool activo)
=> new(
id: Id,
medioId: MedioId,
codigo: Codigo,
nombre: Nombre,
tipo: Tipo,
activo: activo,
fechaCreacion: FechaCreacion,
fechaModificacion: DateTime.UtcNow);
}

View File

@@ -0,0 +1,9 @@
namespace SIGCM2.Domain.Entities;
public enum TipoMedio
{
Diario = 1,
Radio = 2,
Web = 3,
Poster = 4,
}

View File

@@ -0,0 +1,15 @@
namespace SIGCM2.Domain.Exceptions;
/// <summary>
/// Thrown when attempting to create a Medio with a Codigo that already exists (global UQ).
/// </summary>
public sealed class MedioCodigoDuplicadoException : DomainException
{
public string Codigo { get; }
public MedioCodigoDuplicadoException(string codigo)
: base($"El medio con código '{codigo}' ya existe.")
{
Codigo = codigo;
}
}

View File

@@ -0,0 +1,15 @@
namespace SIGCM2.Domain.Exceptions;
/// <summary>
/// Thrown when a requested Medio does not exist in the system.
/// </summary>
public sealed class MedioNotFoundException : DomainException
{
public int Id { get; }
public MedioNotFoundException(int id)
: base($"El medio con id '{id}' no existe.")
{
Id = id;
}
}

View File

@@ -0,0 +1,18 @@
namespace SIGCM2.Domain.Exceptions;
/// <summary>
/// Thrown when attempting to create a Seccion with a Codigo that already exists within the same Medio
/// (composite UQ on MedioId + Codigo).
/// </summary>
public sealed class SeccionCodigoDuplicadoEnMedioException : DomainException
{
public int MedioId { get; }
public string Codigo { get; }
public SeccionCodigoDuplicadoEnMedioException(int medioId, string codigo)
: base($"La sección con código '{codigo}' ya existe para el medio {medioId}.")
{
MedioId = medioId;
Codigo = codigo;
}
}

View File

@@ -0,0 +1,15 @@
namespace SIGCM2.Domain.Exceptions;
/// <summary>
/// Thrown when a requested Seccion does not exist in the system.
/// </summary>
public sealed class SeccionNotFoundException : DomainException
{
public int Id { get; }
public SeccionNotFoundException(int id)
: base($"La sección con id '{id}' no existe.")
{
Id = id;
}
}