academia/docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md

8.7 KiB

DI-004: Esquema GraphQL

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


1. Schema Completo

# ═══════════════════════════════════════════════════════════════
# TYPES
# ═══════════════════════════════════════════════════════════════

type Student {
  id: Int!
  name: String!
  email: String!
  totalCredits: Int!
  isActivated: Boolean!
  activationExpiresAt: DateTime
  enrollments: [Enrollment!]!
}

type User {
  id: Int!
  username: String!
  role: String!           # "Admin" | "Student"
  studentId: Int
  studentName: String
}

type Subject {
  id: Int!
  name: String!
  credits: Int!
  professor: Professor!
  enrolledStudents: [Student!]!
}

type Professor {
  id: Int!
  name: String!
  subjects: [Subject!]!
}

type Enrollment {
  id: Int!
  student: Student!
  subject: Subject!
  enrolledAt: DateTime!
}

type AvailableSubject {
  subject: Subject!
  isAvailable: Boolean!
  unavailableReason: String
}

type Classmate {
  subjectName: String!
  students: [String!]!
}

# ═══════════════════════════════════════════════════════════════
# QUERIES
# ═══════════════════════════════════════════════════════════════

type Query {
  # Autenticación
  me: User                    # Usuario autenticado actual
  validateActivationCode(code: String!): ActivationValidation!

  # Estudiantes
  students: [Student!]!
  student(id: Int!): Student

  # Materias
  subjects: [Subject!]!
  subject(id: Int!): Subject
  availableSubjects(studentId: Int!): [AvailableSubject!]!

  # Profesores
  professors: [Professor!]!
  professor(id: Int!): Professor

  # Compañeros de clase
  classmates(studentId: Int!): [Classmate!]!
}

type ActivationValidation {
  isValid: Boolean!
  studentName: String
  error: String
}

# ═══════════════════════════════════════════════════════════════
# MUTATIONS
# ═══════════════════════════════════════════════════════════════

type Mutation {
  # Autenticación
  login(input: LoginInput!): AuthPayload!
  activateAccount(input: ActivateAccountInput!): AuthPayload!
  resetPassword(input: ResetPasswordInput!): ResetPayload!

  # Estudiantes (Admin crea con código de activación)
  createStudent(input: CreateStudentInput!): CreateStudentPayload!
  updateStudent(id: Int!, input: UpdateStudentInput!): StudentPayload!
  deleteStudent(id: Int!): DeletePayload!
  regenerateActivationCode(studentId: Int!): ActivationCodePayload!

  # Inscripciones
  enrollStudent(input: EnrollInput!): EnrollmentPayload!
  unenrollStudent(enrollmentId: Int!): DeletePayload!
}

# ═══════════════════════════════════════════════════════════════
# INPUTS
# ═══════════════════════════════════════════════════════════════

# Autenticación
input LoginInput {
  username: String!
  password: String!
}

input ActivateAccountInput {
  activationCode: String!
  username: String!
  password: String!
}

input ResetPasswordInput {
  username: String!
  recoveryCode: String!
  newPassword: String!
}

# Estudiantes
input CreateStudentInput {
  name: String!
  email: String!
}

input UpdateStudentInput {
  name: String
  email: String
}

input EnrollInput {
  studentId: Int!
  subjectId: Int!
}

# ═══════════════════════════════════════════════════════════════
# PAYLOADS (Union para errores)
# ═══════════════════════════════════════════════════════════════

# Autenticación
type AuthPayload {
  success: Boolean!
  token: String
  recoveryCode: String    # Solo en activación (se muestra una vez)
  user: User
  error: String
}

type ResetPayload {
  success: Boolean!
  error: String
}

# Estudiantes
type CreateStudentPayload {
  student: Student
  activationCode: String      # Código para activar cuenta
  activationUrl: String       # URL completa de activación
  expiresAt: DateTime         # Cuándo expira el código
  errors: [String!]
}

type ActivationCodePayload {
  activationCode: String!
  activationUrl: String!
  expiresAt: DateTime!
}

type StudentPayload {
  student: Student
  errors: [String!]
}

type EnrollmentPayload {
  enrollment: Enrollment
  errors: [String!]
}

type DeletePayload {
  success: Boolean!
  errors: [String!]
}

# ═══════════════════════════════════════════════════════════════
# SCALARS
# ═══════════════════════════════════════════════════════════════

scalar DateTime

2. Ejemplos de Queries

Listar estudiantes con inscripciones

query GetStudents {
  students {
    id
    name
    email
    totalCredits
    enrollments {
      subject { name professor { name } }
    }
  }
}

Materias disponibles para inscripción

query GetAvailableSubjects($studentId: Int!) {
  availableSubjects(studentId: $studentId) {
    subject { id name credits professor { name } }
    isAvailable
    unavailableReason
  }
}

Compañeros de clase

query GetClassmates($studentId: Int!) {
  classmates(studentId: $studentId) {
    subjectName
    students
  }
}

3. Ejemplos de Mutations

Crear estudiante

mutation CreateStudent {
  createStudent(input: { name: "Juan Pérez", email: "juan@email.com" }) {
    student { id name email }
    errors
  }
}

Inscribir en materia

mutation Enroll {
  enrollStudent(input: { studentId: 1, subjectId: 3 }) {
    enrollment {
      id
      subject { name }
      enrolledAt
    }
    errors
  }
}

4. DataLoaders (Evitar N+1)

DataLoader Carga Batch
StudentByIdDataLoader Estudiantes por IDs
SubjectByIdDataLoader Materias por IDs
ProfessorByIdDataLoader Profesores por IDs
EnrollmentsByStudentDataLoader Inscripciones por estudiante
SubjectsByProfessorDataLoader Materias por profesor

5. Errores de Negocio

Código Mensaje Contexto
MAX_ENROLLMENTS "Máximo 3 materias permitidas" enrollStudent
SAME_PROFESSOR "Ya tienes materia con este profesor" enrollStudent
DUPLICATE_EMAIL "Email ya registrado" createStudent
NOT_FOUND "Estudiante no encontrado" updateStudent
INVALID_CREDENTIALS "Usuario o contraseña incorrectos" login
USERNAME_EXISTS "El nombre de usuario ya está en uso" activateAccount
INVALID_ACTIVATION_CODE "Código de activación inválido o expirado" activateAccount
INVALID_RECOVERY_CODE "Código de recuperación inválido" resetPassword
UNAUTHORIZED "No tienes permiso para esta acción" Operaciones protegidas

6. Ejemplos de Autenticación

Login

mutation Login {
  login(input: { username: "admin", password: "Admin123!" }) {
    success
    token
    user { id username role studentId studentName }
    error
  }
}

Activar Cuenta

mutation ActivateAccount {
  activateAccount(input: {
    activationCode: "MSAGDM5DNLAF"
    username: "juan.perez"
    password: "MiPassword123"
  }) {
    success
    token
    recoveryCode   # Solo se muestra UNA vez
    user { id username role studentId studentName }
    error
  }
}

Crear Estudiante (Admin)

mutation CreateStudent {
  createStudent(input: { name: "Juan Pérez", email: "juan@email.com" }) {
    student { id name email isActivated }
    activationCode     # "MSAGDM5DNLAF"
    activationUrl      # "https://app.com/activate?code=MSAGDM5DNLAF"
    expiresAt          # "2026-01-11T06:00:00Z"
    errors
  }
}