# DI-004: Esquema GraphQL

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

---

## 1. Schema Completo

```graphql
# ═══════════════════════════════════════════════════════════════
# 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
```graphql
query GetStudents {
  students {
    id
    name
    email
    totalCredits
    enrollments {
      subject { name professor { name } }
    }
  }
}
```

### Materias disponibles para inscripción
```graphql
query GetAvailableSubjects($studentId: Int!) {
  availableSubjects(studentId: $studentId) {
    subject { id name credits professor { name } }
    isAvailable
    unavailableReason
  }
}
```

### Compañeros de clase
```graphql
query GetClassmates($studentId: Int!) {
  classmates(studentId: $studentId) {
    subjectName
    students
  }
}
```

---

## 3. Ejemplos de Mutations

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

### Inscribir en materia
```graphql
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
```graphql
mutation Login {
  login(input: { username: "admin", password: "Admin123!" }) {
    success
    token
    user { id username role studentId studentName }
    error
  }
}
```

### Activar Cuenta
```graphql
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)
```graphql
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
  }
}
```
