# 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 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 StudentNames); ``` ### Payloads (Respuestas con errores) ```csharp public record StudentPayload(StudentDto? Student, IEnumerable? Errors); public record EnrollmentPayload(EnrollmentDto? Enrollment, IEnumerable? Errors); public record DeletePayload(bool Success, IEnumerable? 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() .Map(dest => dest.TotalCredits, src => src.Enrollments.Count * 3); config.NewConfig(); config.NewConfig(); config.NewConfig(); } } ``` --- ## 4. Validadores (FluentValidation) ```csharp public class CreateStudentInputValidator : AbstractValidator { 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 { 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 GetByIdAsync(int id, CancellationToken ct = default); Task GetByIdWithEnrollmentsAsync(int id, CancellationToken ct = default); Task> GetAllAsync(CancellationToken ct = default); Task 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 GetByIdAsync(int id, CancellationToken ct = default); Task> GetAllAsync(CancellationToken ct = default); Task> GetByProfessorIdAsync(int professorId, CancellationToken ct = default); } public interface IEnrollmentRepository { Task> GetByStudentIdAsync(int studentId, CancellationToken ct = default); Task> GetClassmatesAsync(int studentId, int subjectId, CancellationToken ct = default); } public interface IUnitOfWork { Task 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 |