Skip to content

Archivos de la Carpeta controllers

La carpeta src/controllers contiene los manejadores de solicitudes HTTP que orquestan el flujo de las peticiones. Estos archivos son esenciales para la operativa de la aplicación en producción, ya que actúan como el punto de entrada para todas las interacciones del usuario con la API, delegando la lógica de negocio a los servicios y gestionando las respuestas.

auth.controllers.ts

Este controlador gestiona las operaciones de autenticación de usuarios, como el inicio de sesión y el registro de nuevas cuentas.

  • Propósito: Manejar las peticiones de login y register.
  • Funcionalidad:
    • Validación de datos: Utiliza esquemas (userLoginSchema, userRegisterSchema) para validar los datos de entrada (email, contraseña, rol).
    • Delegación a servicios: Invoca a authService para la lógica de autenticación y creación de usuarios.
    • Respuesta: Retorna un token de autenticación (JWT) si la operación es exitosa.
  • Rol en la aplicación: Es el punto clave donde los usuarios se identifican y obtienen acceso a los recursos protegidos de la API.
ts
// Importación de tipos y servicios necesarios
import { NextFunction, Request, Response } from "express";
import { authService } from "../services/auth.service";
import { HttpError } from "../utils/httpError.util";
import { userLoginSchema, userRegisterSchema } from "../schemas/auth.schemas";
/* 
Recibe tres parámetros estándar:
- req: la solicitud HTTP.
- res: la respuesta HTTP.
- next: función para pasar el control al siguiente middleware si hay errores.
*/
const handleLogin = async (req: Request, res: Response, next: NextFunction) => {
  /*
  Usa un esquema de validación (userLoginSchema, posiblemente con Joi o Zod) para validar los datos que el cliente envió en req.body (por ejemplo: { email, password }):
  - value contiene los datos validados.
  - error contiene detalles si hay errores en la validación.
  */
  try {
    const { error, value } = userLoginSchema.validate(req.body);
    /* 
    - Si la validación falla, lanza un error personalizado con un código de estado 400 (Bad Request).
    - HttpError es una clase personalizada para enviar errores con mensaje y código.
    */
    if (error) {
      throw new HttpError(error.message, 400);
    }
    /* 
    -Extrae el email y password ya validados.
    - Llama al servicio authService.authenticateUser(...):
      - Este método busca al usuario por email.
      - Compara contraseñas.
      - Si todo es correcto, devuelve un token (probablemente JWT).
    */
    const { email, password } = value;
    const token = await authService.authenticateUser(email, password);
    /* Si todo fue exitoso, responde con un JSON que contiene el token. */
    res.json({ token });
    /* Si ocurre cualquier error en el proceso, lo pasa al middleware de manejo de errores de Express (next(error)), que lo puede registrar o enviar una respuesta de error. */
  } catch (error) {
    next(error);
  }
};

/* 
Recibe tres parámetros estándar:
- req: la solicitud HTTP.
- res: la respuesta HTTP.
- next: función para pasar el control al siguiente middleware si hay errores.
*/
const handleRegister = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  /*
  Usa un esquema de validación (userRegisterSchema) para validar los datos enviados en req.body:
  - value contiene los datos validados.
  - error contiene detalles si hay errores en la validación.
  */
  try {
    const { error, value } = userRegisterSchema.validate(req.body);
    /*
    Si la validación falla, lanza un error personalizado con código 400 (Bad Request).
    - HttpError es una clase personalizada para manejar errores con mensaje y estado.
    */
    if (error) {
      throw new HttpError(error.message, 400);
    }
    /*
    Extrae los datos validados del body: email, password y role.
    Llama al servicio authService.createUserAccount(...):
    - Este método crea un nuevo usuario en la base de datos.
    - Genera y devuelve un token (probablemente JWT) tras crear el usuario.
    */
    const { email, password, role } = value;
    const token = await authService.createUserAccount(email, password, role);
    /* Si el registro fue exitoso, responde con un JSON que contiene el token de autenticación. */
    res.json({ token });
    /*
  Si ocurre cualquier error en el proceso, lo pasa al middleware de manejo de errores.
  Esto incluye errores de validación, de creación de usuario o errores internos del servidor.
  */
  } catch (error) {
    next(error);
  }
};
/*
Exporta el controlador completo, agrupando las funciones relacionadas con la autenticación.
- handleLogin: para inicio de sesión.
- handleRegister: para registro de nuevos usuarios.
*/
export const authController = {
  handleLogin,
  handleRegister,
};

user.controllers.ts

Este controlador maneja las operaciones CRUD (Crear, Leer, Actualizar, Eliminar) para la gestión de usuarios, diferenciadas de la autenticación inicial.

  • Propósito: Gestionar las operaciones relacionadas con las cuentas de usuario (creación, lectura, actualización y eliminación).
  • Funcionalidad:
    • createUserHandler: Registra un nuevo usuario (posiblemente para uso interno o administración).
    • getUserByIdHandler: Obtiene los detalles de un usuario por su ID.
    • updateUserHandler: Modifica los datos de un usuario existente.
    • deleteUserHandler: Elimina un usuario de la base de datos.
    • getAllUsersHandler: Lista todos los usuarios registrados en el sistema.
  • Rol en la aplicación: Es el punto de interacción principal para la administración de usuarios una vez autenticados, o para la creación de usuarios con fines administrativos.
ts
// Importación de tipos y servicios necesarios
import { NextFunction, Request, Response } from "express";
import { userService } from "../services/user.service";
/*
Controlador para crear un nuevo usuario.
Recibe:
- req: la solicitud HTTP, con el cuerpo que contiene email, password y role.
- res: la respuesta HTTP.
- next: función para manejar errores.
*/
const createUserHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Extrae los datos del cuerpo de la solicitud
    const { email, password, role } = req.body;
    // Llama al servicio para registrar un nuevo usuario
    const newUser = await userService.registerUser(email, password, role);
    // Devuelve el usuario creado como JSON
    res.json({ newUser });
  } catch (error) {
    // Si hay error, lo pasa al middleware de errores
    next(error);
  }
};

/*
Controlador para obtener un usuario por ID.
Recibe:
- req: contiene el ID como parámetro en la URL.
- res: para responder con los datos.
- next: para manejar errores.
*/
const getUserByIdHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Extrae el ID desde los parámetros
    const { id } = req.params;
    // Llama al servicio para buscar el usuario
    const user = await userService.findUserById(id);
    // Devuelve el usuario encontrado
    res.json(user);
  } catch (error) {
    // Pasa cualquier error al middleware
    next(error);
  }
};

/*
Controlador para actualizar un usuario.
Recibe:
- req: con el ID en los parámetros y los nuevos datos en el body.
- res: para enviar el usuario actualizado.
- next: para manejo de errores.
*/
const updateUserHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Extrae el ID del usuario desde los parámetros
    const { id } = req.params;
    // Extrae los datos del cuerpo de la solicitud
    const { email, password, role } = req.body;
    // Llama al servicio para actualizar el usuario
    const user = await userService.modifyUserById(id, email, password, role);
    // Responde con el usuario actualizado
    res.json(user);
  } catch (error) {
    next(error);
  }
};

/*
Controlador para eliminar un usuario por ID.
Recibe:
- req: con el ID del usuario a eliminar.
- res: para responder con el resultado.
- next: para manejar errores.
*/
const deleteUserHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Extrae el ID del usuario desde los parámetros
    const { id } = req.params;
    // Llama al servicio para eliminar el usuario
    const user = await userService.removeUserById(id);
    // Devuelve el usuario eliminado (o info relacionada)
    res.json(user);
  } catch (error) {
    next(error);
  }
};

/*
Controlador para obtener todos los usuarios.
No requiere parámetros.
*/
const getAllUsersHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Llama al servicio para obtener todos los usuarios
    const users = await userService.listAllUsers();
    // Devuelve el arreglo de usuarios
    res.json(users);
  } catch (error) {
    next(error);
  }
};

/*
Exporta todos los controladores relacionados a usuarios agrupados en un objeto:
- getAllUsersHandler: obtener todos los usuarios.
- getUserByIdHandler: obtener un usuario por ID.
- createUserHandler: crear nuevo usuario.
- deleteUserHandler: eliminar usuario.
- updateUserHandler: actualizar usuario.
*/
export const userController = {
  getAllUsersHandler,
  getUserByIdHandler,
  createUserHandler,
  deleteUserHandler,
  updateUserHandler,
};

upload.controllers.ts

Este controlador maneja la subida genérica de archivos (como imágenes, certificados u otros documentos) a través de la API, y su integración con servicios de almacenamiento externo como Google Drive.

  • Propósito: Proporcionar un endpoint para recibir archivos del cliente, subirlos a Google Drive, hacerlos públicos y retornar su enlace.
  • Funcionalidad:
    • Procesamiento de archivos: Usa multer para recibir archivos desde peticiones multipart/form-data.
    • Integración con almacenamiento externo: Sube los archivos recibidos a Google Drive utilizando el servicio uploadFileToDrive(...).
    • Publicación del archivo: Usa setFilePublic(...) para que el archivo subido tenga un enlace accesible públicamente.
    • Limpieza: Elimina el archivo del almacenamiento local (uploads/) después de haber sido subido correctamente.
    • Respuesta: Retorna un mensaje de éxito con el enlace público y el ID del archivo en Google Drive.
  • Rol en la aplicación: Actúa como el punto central para la recepción y gestión de archivos subidos por los clientes, encargándose de su almacenamiento seguro en una plataforma externa (como Google Drive) y proporcionando un enlace público para su posterior referencia.
ts
import { Request, Response, NextFunction } from "express";
import multer from "multer";
import path from "path";
import fs from "fs";
import {
  uploadFileToDrive,
  setFilePublic,
} from "../services/google.drive.service";
/*
Configuración de almacenamiento con multer:
- Los archivos se guardarán en la carpeta local `uploads/`.
- El nombre del archivo será el timestamp actual + nombre original, para evitar colisiones.
*/
const storage = multer.diskStorage({
  destination: "uploads/",
  filename: (req, file, cb) => {
    cb(null, `${Date.now()}-${file.originalname}`);
  },
});
/*
Instancia de multer usando la configuración de almacenamiento anterior.
Este `upload` puede usarse como middleware para manejar `multipart/form-data`.
*/
const upload = multer({ storage });
/*
Controlador para subir una imagen:
- Verifica si se recibió un archivo en la petición (`req.file`).
- Si no hay archivo, responde con un error 400.
- Si hay archivo:
  1. Obtiene la ruta local del archivo.
  2. Llama a uploadFileToDrive(...) para subirlo a Google Drive.
  3. Usa setFilePublic(...) para hacerlo accesible públicamente.
  4. Elimina el archivo local (`uploads/`) con `fs.unlinkSync(...)`.
  5. Responde con un mensaje de éxito, el link público de Drive y el ID del archivo.
*/
export const uploadImage = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    // Si no se subió ningún archivo, responde con error.
    if (!req.file) {
      return res
        .status(400)
        .json({ message: "No se ha subido ningún archivo." });
    }
    // Ruta local del archivo subido
    const filePath = path.join("uploads", req.file.filename);
    // Subir el archivo a Google Drive
    const uploadedFile = await uploadFileToDrive(filePath, req.file.filename);
    // Hacer el archivo público para obtener un enlace accesible
    await setFilePublic(uploadedFile.id ?? "");
    // Eliminar archivo local después de subir a Drive
    fs.unlinkSync(filePath);
    // Responder con mensaje de éxito y enlace de Drive
    res.json({
      message: "Archivo subido exitosamente",
      link: uploadedFile.webViewLink || uploadedFile.webContentLink,
      id: uploadedFile.id, // Devuelve el ID del archivo, útil para guardarlo en la base de datos
    });
  } catch (error) {
    // Manejo de errores inesperados
    res.status(500).json({ message: "Error al subir la imagen", error });
  }
};

student.controllers.ts

Este controlador maneja las operaciones CRUD para estudiantes dentro de la API, utilizando la lógica del servicio studentService y cumpliendo con la estructura y campos definidos en la interfaz Student.

  • Propósito: Es el punto principal de interacción para la administración completa de los datos de estudiantes, incluyendo la creación, lectura, actualización y eliminación de sus perfiles, así como la gestión de archivos adjuntos específicos de los estudiantes (como certificados o imágenes).
  • Funcionalidad:
    • createStudentHandler: Registra un nuevo estudiante.
    • getStudentByIdHandler: Obtiene los detalles de un usuario por su ID.
    • updateStudentHandler: Modifica los datos de un usuario existente.
    • deleteStudentHandler: Elimina un usuario de la base de datos.
    • getAllStudentsHandler: Lista todos los estudiantes registrados en el sistema
  • Rol en la aplicación: Facilita a los clientes la subida directa de archivos, que luego son gestionados y servidos a través de una plataforma externa.
ts
// Importaciones necesarias desde Express y otros módulos del proyecto.
import { NextFunction, Request, Response } from "express";
import { studentService } from "../services/student.service";
import { MulterRequest } from "../interfaces/express.interface";
import {
  processStudentFiles,
  normalizeStudentData,
} from "../utils/student.util";
import { Student as StudentInterface } from "../models/student.model";
/*
Controlador para crear un nuevo estudiante:
- Normaliza los datos del body (uso de mayúsculas, fechas, campos vacíos).
- Procesa los archivos subidos con Multer y los sube a Google Drive.
- Combina datos del formulario con enlaces e IDs de Google Drive.
- Llama a studentService.createStudent(...) para crear el estudiante.
- Devuelve el estudiante creado como JSON.
*/
const createStudentHandler = async (
  req: MulterRequest,
  res: Response,
  next: NextFunction
) => {
  try {
    // Normaliza los datos del cuerpo usando utilidades personalizadas.
    const studentData = normalizeStudentData(req.body);
    // Procesa los archivos subidos y devuelve los enlaces/IDs de Google Drive.
    const uploadedFileDetails = await processStudentFiles(req);
    // Combina datos del formulario con los enlaces de archivos subidos.
    const newStudentData = {
      ...studentData,
      studentImage: uploadedFileDetails.studentImage?.link,
      studentImageDriveId: uploadedFileDetails.studentImage?.id,
      birthCertificate: uploadedFileDetails.birthCertificate?.link,
      birthCertificateDriveId: uploadedFileDetails.birthCertificate?.id,
      studyCertificate: uploadedFileDetails.studyCertificate?.link,
      studyCertificateDriveId: uploadedFileDetails.studyCertificate?.id,
      linkDni: uploadedFileDetails.linkDni?.link,
      linkDniDriveId: uploadedFileDetails.linkDni?.id,
    };
    // Llama al servicio para crear el estudiante en la base de datos.
    const newStudent = await studentService.createStudent(newStudentData);
    // Devuelve el estudiante creado en la respuesta.
    res.json(newStudent);
  } catch (error) {
    // En caso de error, pasa el control al middleware de errores.
    next(error);
  }
};
/*
Controlador para obtener un estudiante por su ID:
- Extrae el ID desde los parámetros de la URL.
- Llama al servicio para buscar el estudiante.
- Devuelve el estudiante como JSON.
*/
const getStudentByIdHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const { id } = req.params;
    const student = await studentService.getStudentById(id);
    res.json(student);
  } catch (error) {
    next(error);
  }
};
/*
Controlador para actualizar un estudiante existente:
- Extrae el ID desde los parámetros.
- Verifica si el estudiante existe.
- Normaliza los datos del body.
- Procesa archivos nuevos y determina si hay que eliminar alguno.
- Llama al servicio para actualizar y devuelve el resultado.
*/
const updateStudentHandler = async (
  req: MulterRequest,
  res: Response,
  next: NextFunction
) => {
  try {
    const { id } = req.params;
    // Busca al estudiante para asegurarse que existe.
    const existingStudent = (await studentService.getStudentById(
      id
    )) as StudentInterface;
    if (!existingStudent) {
      return res.status(404).json({ message: "Estudiante no encontrado" });
    }
    // Normaliza los nuevos datos del body.
    const studentData = normalizeStudentData(req.body);
    // Procesa archivos subidos (si hay).
    const uploadedFileDetails = await processStudentFiles(req);
    // Inicializa datos a actualizar.
    const updatedData: any = { ...studentData };
    // Define los campos de archivos a procesar.
    const fileFields = [
      { field: "studentImage", driveIdField: "studentImageDriveId" },
      { field: "birthCertificate", driveIdField: "birthCertificateDriveId" },
      { field: "studyCertificate", driveIdField: "studyCertificateDriveId" },
      { field: "linkDni", driveIdField: "linkDniDriveId" },
    ];
    // Procesa cada archivo según si se subió nuevo, se elimina o se mantiene.
    for (const { field, driveIdField } of fileFields) {
      const fileDetails =
        uploadedFileDetails[field as keyof typeof uploadedFileDetails];

      if (req.body[`${field}_delete`] === "true") {
        // Si el usuario pidió eliminar el archivo.
        updatedData[field] = null;
        updatedData[driveIdField] = null;
      } else {
        // Si hay nuevo archivo, usa ese. Si no, conserva el anterior.
        updatedData[field] =
          fileDetails?.link ?? existingStudent[field as keyof StudentInterface];
        updatedData[driveIdField] =
          fileDetails?.id ??
          existingStudent[driveIdField as keyof StudentInterface];
      }
    }
    // Llama al servicio para guardar los cambios.
    const updatedStudent = await studentService.updateStudentById(
      id,
      updatedData
    );
    res.json(updatedStudent);
  } catch (error) {
    next(error);
  }
};
/*
Controlador para eliminar un estudiante por ID:
- Extrae el ID desde los parámetros.
- Llama al servicio para eliminarlo.
- Devuelve el estudiante eliminado.
*/
const deleteStudentHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const { id } = req.params;
    const deletedStudent = await studentService.deleteStudentById(id);
    res.json(deletedStudent);
  } catch (error) {
    next(error);
  }
};
/*
Controlador para obtener todos los estudiantes registrados:
- No requiere parámetros.
- Devuelve un arreglo con todos los estudiantes.
*/
const getAllStudentsHandler = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const students = await studentService.getAllStudents();
    res.json(students);
  } catch (error) {
    next(error);
  }
};
/*
Exporta todas las funciones del controlador agrupadas en un objeto:
- createStudentHandler: crear nuevo estudiante.
- getStudentByIdHandler: obtener estudiante por ID.
- updateStudentHandler: actualizar estudiante.
- deleteStudentHandler: eliminar estudiante.
- getAllStudentsHandler: listar todos los estudiantes.
*/
export const studentController = {
  createStudentHandler,
  getStudentByIdHandler,
  updateStudentHandler,
  deleteStudentHandler,
  getAllStudentsHandler,
};