academia/docs/entregables/02-diseno/esquema-graphql/DI-007-contratos-dtos.md

5.7 KiB

DI-007: Contratos y DTOs

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


1. DTOs Backend (C#)

Request DTOs (Inputs)

// 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

// 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)

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)

// 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

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

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

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

Payloads

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

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

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

3. Mapeos (Mapster)

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)

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)

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