Skip to main content

Introduction

The Domain layer contains all entity models that represent the business domain. These entities are mapped to PostgreSQL database tables using Entity Framework Core annotations.
Entities in the Domain layer are pure POCO (Plain Old CLR Objects) classes with minimal dependencies, ensuring the business logic remains independent of infrastructure concerns.

Entity Overview

ApiTickets has five core entity models:

TicketE

Main ticket entity with tracking information

UsuarioE

User entity with authentication details

AreaE

Department/area catalog for ticket classification

PrioridadE

Priority levels for tickets

EstadoTicketE

Ticket status/state definitions

TicketE Entity

The main entity representing support tickets in the system.

Location

Domain/Entities/TicketE/TicketE.cs

Entity Definition

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Domain.Entities.TicketE
{
    [Table("tickets")]
    public class TicketE
    {
        [Key]
        [Column("id_ticket")]
        public Guid? IdTicket { get; set; } = Guid.NewGuid();

        [Column("codigo_seguimiento")]
        public required string Codigo { get; set; }

        [Column("titulo")]
        public required string Titulo { get; set; }

        [Column("descripcion")]
        public required string Descripcion { get; set; }

        [Column("id_usuario")]
        public required Guid UsuarioId { get; set; }

        [Column("id_area")]
        public required int AreaId { get; set; }

        [Column("id_prioridad")]
        public required int PrioridadId { get; set; }

        [Column("id_estado")]
        public required int EstadoId { get; set; }

        [Column("fecha_creacion")]
        public required DateTime FechaCreacion { get; set; }

        [Column("fecha_actualizacion")]
        public required DateTime FechaActualizacion { get; set; }
    }
}

Properties

PropertyTypeDescription
IdTicketGuid?Unique identifier (primary key), auto-generated
CodigostringTracking code for public reference (e.g., “001”, “002”)
TitulostringTicket title/subject
DescripcionstringDetailed ticket description
UsuarioIdGuidForeign key to UsuarioE (ticket creator)
AreaIdintForeign key to AreaE (department)
PrioridadIdintForeign key to PrioridadE (priority level)
EstadoIdintForeign key to EstadoTicketE (current status)
FechaCreacionDateTimeTimestamp when ticket was created
FechaActualizacionDateTimeTimestamp of last update
The Codigo field is generated using a PostgreSQL sequence (ticket_numero_seq) and formatted as a zero-padded string.

UsuarioE Entity

Represents system users with authentication and role information.

Location

Domain/Entities/UsuarioE/UsuarioE.cs

Entity Definition

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Domain.Entities.UsuarioE
{
    [Table("usuarios")]
    public class UsuarioE
    {
        [Key]
        [Column("id_usuario")]
        public required Guid IdUsuario { get; set; }

        [Column("nombre")]
        public required string Nombre { get; set; } = null!;

        [Column("correo")]
        public required string Correo { get; set; } = null!;

        [Column("password_hash")]
        public required string Password { get; set; } = null!;

        [Column("rol")]
        public required string Rol { get; set; } = null!;

        [Column("activo")]
        public required bool Activo { get; set; } = true;

        [Column("fecha_creacion")]
        public DateTime FechaCreacion { get; set; }
    }
}

Properties

PropertyTypeDescription
IdUsuarioGuidUnique identifier (primary key)
NombrestringUser’s full name
CorreostringUser’s email address (used for login)
PasswordstringHashed password (never stored in plain text)
RolstringUser role (e.g., “Admin”, “User”, “Support”)
ActivoboolWhether the user account is active
FechaCreacionDateTimeAccount creation timestamp
Passwords are hashed using the IPassword utility from Domain.Utilidades before storage. The Password field stores the hash, never the plain text password.

Catalog Entities

Catalog entities provide reference data for ticket classification and management.

AreaE (Department)

namespace Domain.Entities.Catalogos
{
    [Table("areas")]
    public class AreaE
    {
        [Key]
        [Column("id_area")]
        public required int IdArea { get; set; }

        [Column("nombre")]
        public required string Nombre { get; set; }

        [Column("activo")]
        public required bool Activo { get; set; }
    }
}
Purpose: Defines departments or areas that handle tickets (e.g., IT, HR, Sales)
PropertyTypeDescription
IdAreaintUnique identifier
NombrestringDepartment name
ActivoboolWhether the area is active

PrioridadE (Priority)

namespace Domain.Entities.Catalogos
{
    [Table("prioridades")]
    public class PrioridadE
    {
        [Key]
        [Column("id_prioridad")]
        public required int IdPrioridad { get; set; }

        [Column("nombre")]
        public required string Nombre { get; set; }

        [Column("nivel")]
        public required int Nivel { get; set; }
    }
}
Purpose: Defines priority levels for tickets (e.g., Low, Medium, High, Critical)
PropertyTypeDescription
IdPrioridadintUnique identifier
NombrestringPriority name (e.g., “Alta”, “Media”, “Baja”)
NivelintNumeric priority level for sorting

EstadoTicketE (Ticket Status)

namespace Domain.Entities.Catalogos
{
    [Table("estados_ticket")]
    public class EstadoTicketE
    {
        [Key]
        [Column("id_estado")]
        public required int IdEstado { get; set; }

        [Column("nombre")]
        public required string Nombre { get; set; }

        [Column("es_final")]
        public required bool Final { get; set; }
    }
}
Purpose: Defines ticket states throughout their lifecycle
PropertyTypeDescription
IdEstadointUnique identifier
NombrestringStatus name (e.g., “Abierto”, “En Progreso”, “Cerrado”)
FinalboolWhether this is a terminal state (e.g., Closed, Resolved)

DTOs vs Entities

The Domain layer includes both Entities (database models) and DTOs (Data Transfer Objects) for clean separation.

Entities

  • Map directly to database tables
  • Include EF Core annotations
  • Used by Infrastructure layer

DTOs

  • Clean data structures for API
  • No database annotations
  • Used for serialization/deserialization

DTO Example: TicketDto

namespace Domain.DTOs.TicketD
{
    public class TicketDto
    {
        public Guid? IdTicket { get; set; }
        public string? Codigo { get; set; }
        public required string Titulo { get; set; }
        public required string Descripcion { get; set; }
        public required Guid UsuarioId { get; set; }
        public required int AreaId { get; set; }
        public required int PrioridadId { get; set; }
        public required int EstadoId { get; set; }
        public DateTime FechaCreacion { get; set; }
        public DateTime FechaActualizacion { get; set; }

        // Conversion method from Entity to DTO
        public static TicketDto CrearDTO(TicketE ticketE)
        {
            return new TicketDto
            {
                IdTicket = ticketE.IdTicket,
                Codigo = ticketE.Codigo,
                Titulo = ticketE.Titulo,
                Descripcion = ticketE.Descripcion,
                UsuarioId = ticketE.UsuarioId,
                AreaId = ticketE.AreaId,
                PrioridadId = ticketE.PrioridadId,
                EstadoId = ticketE.EstadoId,
                FechaCreacion = ticketE.FechaCreacion,
                FechaActualizacion = ticketE.FechaActualizacion,
            };
        }

        // Conversion method from DTO to Entity
        public static TicketE CrearE(TicketDto ticketDto)
        {
            return new TicketE
            {
                Codigo = ticketDto.Codigo,
                Titulo = ticketDto.Titulo,
                Descripcion = ticketDto.Descripcion,
                UsuarioId = ticketDto.UsuarioId,
                AreaId = ticketDto.AreaId,
                PrioridadId = ticketDto.PrioridadId,
                EstadoId = ticketDto.EstadoId,
                FechaCreacion = ticketDto.FechaCreacion,
                FechaActualizacion = ticketDto.FechaActualizacion,
            };
        }
    }
}

Why Use DTOs?

DTOs provide a stable API contract independent of database schema changes. You can modify entities without breaking API consumers.
DTOs prevent exposing sensitive entity properties (like password hashes) in API responses.
public class UsuarioDto
{
    public Guid IdUsuario { get; set; }
    public string Nombre { get; set; }
    public string Correo { get; set; }
    public string Rol { get; set; }
    public bool Activo { get; set; }
    // Password is NOT included!
    
    public static UsuarioDto CreateDTO(UsuarioE usuarioE)
    {
        return new UsuarioDto
        {
            IdUsuario = usuarioE.IdUsuario,
            Nombre = usuarioE.Nombre,
            Correo = usuarioE.Correo,
            Rol = usuarioE.Rol,
            Activo = usuarioE.Activo
            // Password intentionally excluded
        };
    }
}
Different DTOs for different use cases (create, update, read).
// For creating new tickets
public class CrearticketDto
{
    public required string Titulo { get; set; }
    public required string Descripcion { get; set; }
    public required Guid UsuarioId { get; set; }
    public required int AreaId { get; set; }
    public required int PrioridadId { get; set; }
    public required int EstadoId { get; set; }
    // No ID or dates - those are set by the system
}

// For updating ticket status
public class ActualizarEstadoTicketDto
{
    public Guid Id { get; set; }
    public int EstadoId { get; set; }
    // Only the fields that can be updated
}

// For querying with joined data
public class ConsultarTicketDto
{
    public Guid? IdTicket { get; set; }
    public required string Titulo { get; set; }
    public required string Codigo { get; set; }
    public required string Descripcion { get; set; }
    public required string Area { get; set; }        // Joined from AreaE
    public required string Prioridad { get; set; }   // Joined from PrioridadE
    public required string Estado { get; set; }      // Joined from EstadoTicketE
    public DateTime FechaCreacion { get; set; }
    public DateTime FechaActualizacion { get; set; }
}
DTOs can be optimized for specific queries without affecting the entity model.

Entity Relationships

While Entity Framework Core can handle relationships via navigation properties, this implementation uses a lightweight approach with foreign keys only.

Relationship Details

  • TicketE → UsuarioE: Many tickets can be created by one user
  • TicketE → AreaE: Many tickets can be assigned to one area/department
  • TicketE → PrioridadE: Many tickets can have the same priority level
  • TicketE → EstadoTicketE: Many tickets can be in the same status
Relationships are maintained through foreign keys rather than navigation properties, keeping entities lightweight and reducing query complexity.

Database Context Registration

Entities are registered with Entity Framework Core in the Infrastructure layer:
namespace Infrastructure
{
    public class DBContext : DbContext
    {
        // DbSets expose entities as queryable collections
        public virtual DbSet<UsuarioE> UsuarioEs { get; set; }
        public virtual DbSet<AreaE> AreaEs { get; set; }
        public virtual DbSet<PrioridadE> PrioridadEs { get; set; }
        public virtual DbSet<EstadoTicketE> EstadoTicketEs { get; set; }
        public virtual DbSet<TicketE> TicketEs { get; set; }
    }
}

Entity Conventions

Naming Conventions

1

Entity Classes

Entity classes end with E suffix (e.g., TicketE, UsuarioE)
2

DTO Classes

DTO classes end with Dto suffix (e.g., TicketDto, UsuarioDto)
3

Table Names

Database tables use lowercase with underscores (e.g., tickets, estados_ticket)
4

Column Names

Database columns use lowercase with underscores (e.g., id_ticket, fecha_creacion)

Required Modifier

C# 11’s required modifier ensures properties are initialized:
public required string Titulo { get; set; }
This provides compile-time safety, ensuring all required properties are set when creating instances.

Nullable Reference Types

public Guid? IdTicket { get; set; } = Guid.NewGuid();  // Nullable, auto-generated
public required string Nombre { get; set; } = null!;   // Required, non-nullable

Best Practices

Immutable IDs

Use Guid for entity IDs to prevent collisions and improve security

Audit Fields

Always include FechaCreacion and FechaActualizacion timestamps

Soft Deletes

Use Activo boolean flag instead of hard deletes where appropriate

Validation

Use Data Annotations for basic validation at the entity level
For complex business validation rules, implement them in the Application or Domain services layer rather than in entity classes.

Next Steps

Architecture Overview

Understand the overall Clean Architecture structure

Layer Details

Learn about each architectural layer

Database Setup

Set up PostgreSQL and run migrations

API Reference

Explore the REST API endpoints

Build docs developers (and LLMs) love