# DI-007: Contratos y DTOs

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

---

## 1. DTOs Backend (C#)

### Request DTOs (Inputs)

```csharp
// Students
public record CreateStudentInput(string Name, string Email);
public record UpdateStudentInput(string? Name, string? Email);

// Enrollments
public record EnrollInput(int StudentId, int SubjectId);
```

### Response DTOs

```csharp
// Student
public record StudentDto(
    int Id,
    string Name,
    string Email,
    int TotalCredits,
    IEnumerable<EnrollmentDto> Enrollments);

// Subject
public record SubjectDto(
    int Id,
    string Name,
    int Credits,
    ProfessorDto Professor);

// Professor
public record ProfessorDto(
    int Id,
    string Name);

// Enrollment
public record EnrollmentDto(
    int Id,
    SubjectDto Subject,
    DateTime EnrolledAt);

// Available Subject (con disponibilidad)
public record AvailableSubjectDto(
    SubjectDto Subject,
    bool IsAvailable,
    string? UnavailableReason);

// Classmates
public record ClassmateDto(
    string SubjectName,
    IEnumerable<string> StudentNames);
```

### Payloads (Respuestas con errores)

```csharp
public record StudentPayload(StudentDto? Student, IEnumerable<string>? Errors);
public record EnrollmentPayload(EnrollmentDto? Enrollment, IEnumerable<string>? Errors);
public record DeletePayload(bool Success, IEnumerable<string>? Errors);
```

---

## 2. DTOs Frontend (TypeScript)

### Tipos Generados (graphql-codegen)

```typescript
// Student
export interface Student {
  id: number;
  name: string;
  email: string;
  totalCredits: number;
  enrollments: Enrollment[];
}

// Subject
export interface Subject {
  id: number;
  name: string;
  credits: number;
  professor: Professor;
}

// Professor
export interface Professor {
  id: number;
  name: string;
  subjects?: Subject[];
}

// Enrollment
export interface Enrollment {
  id: number;
  subject: Subject;
  enrolledAt: string;
}

// Available Subject
export interface AvailableSubject {
  subject: Subject;
  isAvailable: boolean;
  unavailableReason?: string;
}

// Classmate
export interface Classmate {
  subjectName: string;
  students: string[];
}
```

### Inputs

```typescript
export interface CreateStudentInput {
  name: string;
  email: string;
}

export interface UpdateStudentInput {
  name?: string;
  email?: string;
}

export interface EnrollInput {
  studentId: number;
  subjectId: number;
}
```

### Payloads

```typescript
export interface StudentPayload {
  student?: Student;
  errors?: string[];
}

export interface EnrollmentPayload {
  enrollment?: Enrollment;
  errors?: string[];
}

export interface DeletePayload {
  success: boolean;
  errors?: string[];
}
```

---

## 3. Mapeos (Mapster)

```csharp
public class MappingConfig : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.NewConfig<Student, StudentDto>()
            .Map(dest => dest.TotalCredits, src => src.Enrollments.Count * 3);

        config.NewConfig<Subject, SubjectDto>();
        config.NewConfig<Professor, ProfessorDto>();
        config.NewConfig<Enrollment, EnrollmentDto>();
    }
}
```

---

## 4. Validadores (FluentValidation)

```csharp
public class CreateStudentInputValidator : AbstractValidator<CreateStudentInput>
{
    public CreateStudentInputValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Nombre requerido")
            .MaximumLength(100).WithMessage("Máximo 100 caracteres");

        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("Email requerido")
            .EmailAddress().WithMessage("Formato de email inválido")
            .MaximumLength(255).WithMessage("Máximo 255 caracteres");
    }
}

public class EnrollInputValidator : AbstractValidator<EnrollInput>
{
    public EnrollInputValidator()
    {
        RuleFor(x => x.StudentId)
            .GreaterThan(0).WithMessage("StudentId inválido");

        RuleFor(x => x.SubjectId)
            .GreaterThan(0).WithMessage("SubjectId inválido");
    }
}
```

---

## 5. Contratos de Repositorio (Ports)

```csharp
public interface IStudentRepository
{
    Task<Student?> GetByIdAsync(int id, CancellationToken ct = default);
    Task<Student?> GetByIdWithEnrollmentsAsync(int id, CancellationToken ct = default);
    Task<IEnumerable<Student>> GetAllAsync(CancellationToken ct = default);
    Task<bool> EmailExistsAsync(string email, int? excludeId = null, CancellationToken ct = default);
    Task AddAsync(Student student, CancellationToken ct = default);
    void Update(Student student);
    void Delete(Student student);
}

public interface ISubjectRepository
{
    Task<Subject?> GetByIdAsync(int id, CancellationToken ct = default);
    Task<IEnumerable<Subject>> GetAllAsync(CancellationToken ct = default);
    Task<IEnumerable<Subject>> GetByProfessorIdAsync(int professorId, CancellationToken ct = default);
}

public interface IEnrollmentRepository
{
    Task<IEnumerable<Enrollment>> GetByStudentIdAsync(int studentId, CancellationToken ct = default);
    Task<IEnumerable<Student>> GetClassmatesAsync(int studentId, int subjectId, CancellationToken ct = default);
}

public interface IUnitOfWork
{
    Task<int> SaveChangesAsync(CancellationToken ct = default);
}
```

---

## 6. Resumen de Contratos por Operación

| Operación | Input | Output | Errores Posibles |
|-----------|-------|--------|------------------|
| createStudent | CreateStudentInput | StudentPayload | DUPLICATE_EMAIL, VALIDATION |
| updateStudent | UpdateStudentInput | StudentPayload | NOT_FOUND, DUPLICATE_EMAIL |
| deleteStudent | id: Int | DeletePayload | NOT_FOUND |
| enrollStudent | EnrollInput | EnrollmentPayload | MAX_ENROLLMENTS, SAME_PROFESSOR |
| unenrollStudent | enrollmentId: Int | DeletePayload | NOT_FOUND |
