# DI-002: Modelo de Dominio

**Proyecto:** Sistema de Registro de Estudiantes
**Fecha:** 2026-01-07

---

## 1. Diagrama de Entidades

```
┌─────────────────┐       ┌─────────────────┐
│    PROFESSOR    │       │     STUDENT     │
├─────────────────┤       ├─────────────────┤
│ Id: int (PK)    │       │ Id: int (PK)    │
│ Name: string    │       │ Name: string    │
└────────┬────────┘       │ Email: Email    │
         │                │ RowVersion      │
         │ 1:2            └────────┬────────┘
         ▼                         │
┌─────────────────┐                │ 0..3
│     SUBJECT     │                ▼
├─────────────────┤       ┌─────────────────┐
│ Id: int (PK)    │◄──────│   ENROLLMENT    │
│ Name: string    │  1:N  ├─────────────────┤
│ Credits: 3      │       │ Id: int (PK)    │
│ ProfessorId: FK │       │ StudentId: FK   │
└─────────────────┘       │ SubjectId: FK   │
                          │ EnrolledAt      │
                          └─────────────────┘
```

---

## 2. Entidades

### Student (Aggregate Root)

```csharp
public class Student
{
    public int Id { get; private set; }
    public string Name { get; private set; }
    public Email Email { get; private set; }
    public byte[] RowVersion { get; private set; }

    private readonly List<Enrollment> _enrollments = new();
    public IReadOnlyCollection<Enrollment> Enrollments => _enrollments;

    public int TotalCredits => _enrollments.Count * 3;

    public void Enroll(Subject subject, IEnrollmentPolicy policy)
    {
        policy.Validate(this, subject);
        _enrollments.Add(new Enrollment(this, subject));
    }

    public void Unenroll(int subjectId)
    {
        var enrollment = _enrollments.FirstOrDefault(e => e.SubjectId == subjectId);
        if (enrollment != null) _enrollments.Remove(enrollment);
    }
}
```

### Subject

```csharp
public class Subject
{
    public int Id { get; private set; }
    public string Name { get; private set; }
    public int Credits { get; } = 3;  // Constante: RN-002
    public int ProfessorId { get; private set; }
    public Professor Professor { get; private set; }
}
```

### Professor

```csharp
public class Professor
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    private readonly List<Subject> _subjects = new();
    public IReadOnlyCollection<Subject> Subjects => _subjects;
}
```

### Enrollment

```csharp
public class Enrollment
{
    public int Id { get; private set; }
    public int StudentId { get; private set; }
    public int SubjectId { get; private set; }
    public DateTime EnrolledAt { get; private set; }

    public Student Student { get; private set; }
    public Subject Subject { get; private set; }
}
```

---

## 3. Value Objects

### Email

```csharp
public record Email
{
    public string Value { get; }

    private Email(string value) => Value = value;

    public static Result<Email> Create(string email)
    {
        if (string.IsNullOrWhiteSpace(email))
            return Result.Failure<Email>("Email requerido");

        if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
            return Result.Failure<Email>("Formato de email inválido");

        return Result.Success(new Email(email.ToLowerInvariant()));
    }
}
```

---

## 4. Domain Service

### EnrollmentDomainService (Implementa RN-003 y RN-005)

```csharp
public class EnrollmentDomainService : IEnrollmentPolicy
{
    public void Validate(Student student, Subject newSubject)
    {
        // RN-003: Máximo 3 materias
        if (student.Enrollments.Count >= 3)
            throw new MaxEnrollmentsExceededException(student.Id);

        // RN-005: No repetir profesor
        var professorIds = student.Enrollments
            .Select(e => e.Subject.ProfessorId)
            .ToHashSet();

        if (professorIds.Contains(newSubject.ProfessorId))
            throw new SameProfessorConstraintException(
                student.Id,
                newSubject.ProfessorId);
    }
}
```

---

## 5. Excepciones de Dominio

```csharp
public class MaxEnrollmentsExceededException : DomainException
{
    public MaxEnrollmentsExceededException(int studentId)
        : base($"Estudiante {studentId} ya tiene 3 materias inscritas") { }
}

public class SameProfessorConstraintException : DomainException
{
    public SameProfessorConstraintException(int studentId, int professorId)
        : base($"Estudiante {studentId} ya tiene materia con profesor {professorId}") { }
}
```

---

## 6. Invariantes del Dominio

| Invariante | Entidad | Validación |
|------------|---------|------------|
| Email único | Student | DB Constraint + Validator |
| Email válido | Student | Value Object |
| Max 3 inscripciones | Student | Domain Service |
| No repetir profesor | Student | Domain Service |
| 3 créditos/materia | Subject | Constante |
| 2 materias/profesor | Professor | Seed Data |
