Skip to content

Archivos de la Carpeta src/hooks

La carpeta src/hooks contiene hooks personalizados que encapsulan lógica de negocio, manejo de estado y efectos secundarios. Estos hooks son reutilizables y permiten mantener los componentes de React más limpios y enfocados en la UI.


use.login.ts

Este hook personalizado (useLogin) encapsula la lógica para gestionar el proceso de inicio de sesión después de que se ha obtenido un token de autenticación del backend. Su objetivo principal es validar y almacenar el token, y manejar los estados de carga y error asociados.

  • Descripción: useLogin proporciona una función login que toma un token, lo decodifica para validar su formato, y si es válido, lo guarda en el almacenamiento persistente (auth.storage). También maneja los estados de carga y posibles errores durante este proceso.
  • Propósito: Centralizar la lógica de procesamiento y almacenamiento del token de autenticación en el cliente. Esto desacopla el componente de login de los detalles de cómo se gestiona el token y cómo se manejan los errores de validación del token.
  • Funcionalidad clave:
    • Estado Interno: Gestiona los estados con useState:
      • isLoading: Booleano que indica si el proceso de login (validación y almacenamiento del token) está en curso.
      • loginError: Almacena cualquier mensaje de error si la validación o almacenamiento del token falla.
    • useAuthStorage(): Utiliza este hook (importado de ../components/auth/auth.storage.ts) para interactuar con el almacenamiento local/sesión del navegador. Proporciona funciones como updateToken para guardar el token y clearToken para eliminarlo si es inválido.
    • useDecodeToken(): Utiliza este hook (importado de ./use.decode.token.ts) para obtener la función decodeToken. Esta función es crucial para decodificar el token JWT y verificar su validez (por ejemplo, si está corrupto o si su estructura es incorrecta).
    • login(newToken: string):
      • Es la función principal expuesta por el hook. Es una función async y está envuelta en useCallback para optimizar su rendimiento.
      • Establece isLoading a true y limpia cualquier loginError anterior.
      • Decodificación y Validación del Token Mejorada: Llama a decodeToken(newToken). Si la decodificación falla (decoded es null o undefined), se asume que el token es inválido o corrupto. En este caso, se limpia el token existente, se establece un mensaje de error específico para la UI y se lanza un error para el manejo externo.
      • Almacenamiento del Token: Si el token es válido, updateToken(newToken) se llama para guardarlo en el almacenamiento.
      • Manejo de Errores General: Cualquier otra excepción durante el proceso se captura y reporta a Sentry (Sentry.captureException(error)). Se asegura que el token se limpie (clearToken()) y se establece un mensaje de error genérico (o el mensaje del error capturado si es una instancia de Error). El error se propaga (throw error) para que el componente que invocó login pueda manejarlo también.
      • Finalización: El bloque finally asegura que setIsLoading(false) siempre se ejecute, independientemente del éxito o fracaso de la operación.
    • Retorno: El hook retorna un objeto que contiene isLoading, loginError y la función login.
  • Rol en la aplicación: useLogin es un hook de lógica de autenticación post-API. Es el puente entre recibir un token del backend (después de una autenticación exitosa) y asegurar que ese token sea válido y se almacene correctamente en el frontend. Su integración con useAuthStorage y useDecodeToken lo hace robusto y centralizado.
tsx
// src/hooks/use.login.ts
import { useState, useCallback } from "react"; // Hooks de React
import { useAuthStorage } from "../components/auth/auth.storage"; // Hook para interactuar con el almacenamiento del token
import { useDecodeToken } from "./use.decode.token"; // Hook para decodificar tokens JWT
import * as Sentry from "@sentry/react"; // Librería para monitoreo de errores (Sentry)
import { UseLoginResult } from "../interface/hooks/login";

/**
 * Hook personalizado para gestionar la lógica de inicio de sesión después de obtener un token.
 * Se encarga de validar el token, almacenarlo y manejar los estados de carga y error.
 * @returns Un objeto con el estado de carga, el error de login y la función de login.
 */
export const useLogin = (): UseLoginResult => {
  // Obtiene funciones para actualizar y limpiar el token del almacenamiento
  const { updateToken, clearToken } = useAuthStorage();
  // Obtiene la función para decodificar tokens JWT
  const { decodeToken } = useDecodeToken(null); // No necesitamos el estado inicial aquí para useDecodeToken, solo la función

  // Estados locales del hook
  const [isLoading, setIsLoading] = useState(false); // Estado de carga del proceso de login
  const [loginError, setLoginError] = useState<string | null>(null); // Estado para errores específicos del login

  // Función principal de login, envuelta en useCallback para optimización
  const login = useCallback(
    async (newToken: string) => {
      setIsLoading(true); // Inicia el estado de carga
      setLoginError(null); // Limpia cualquier error previo
      try {
        const decoded = decodeToken(newToken);
        if (!decoded) {
          // Si la decodificación falla (el token es null/undefined o inválido)
          clearToken(); // Limpia cualquier token existente (por seguridad)
          const errorMessage =
            "El token recibido es inválido o está corrupto. Por favor, intente iniciar sesión nuevamente.";
          setLoginError(errorMessage); // Establece un mensaje de error más específico
          throw new Error(errorMessage); // Lanza un error para ser capturado externamente (y por Sentry)
        }

        // Si el token es válido, actualiza el token en el almacenamiento
        updateToken(newToken);
        // Aquí podrías devolver información adicional del token si es necesario al componente llamante
      } catch (error) {
        // Captura y reporta cualquier excepción a Sentry
        Sentry.captureException(error);
        clearToken(); // Asegura que el token se limpie en caso de cualquier error
        // Si el error ya es una instancia de Error, usa su mensaje, de lo contrario, un mensaje genérico
        setLoginError(
          error instanceof Error
            ? error.message
            : "Ocurrió un error inesperado durante el login."
        );
        throw error; // Propaga el error para que el componente llamante pueda manejarlo
      } finally {
        setIsLoading(false); // Finaliza el estado de carga, independientemente del resultado
      }
    },
    // Dependencias de useCallback: se re-crea si alguna de estas funciones cambia
    [updateToken, clearToken, decodeToken]
  );

  // Retorna los estados y la función de login
  return { isLoading, loginError, login };
};

use.logout.ts

Este hook personalizado (useLogout) encapsula la lógica para gestionar el cierre de sesión del usuario. Su objetivo principal es limpiar el token de autenticación del almacenamiento, finalizando así la sesión del usuario en el cliente.

  • Descripción: useLogout proporciona una función logout que se encarga de eliminar el token de autenticación del almacenamiento persistente de la aplicación (auth.storage).
  • Propósito: Centralizar y simplificar la acción de cerrar la sesión del usuario. Esto asegura que la lógica de limpieza del token se maneje de manera consistente en toda la aplicación, sin necesidad de duplicar el código en los componentes de la UI.
  • Funcionalidad clave:
    • useAuthStorage(): Utiliza este hook (importado de ../components/auth/auth.storage.ts) para acceder a la función clearToken. Esta función es la responsable de eliminar el token del almacenamiento local o de sesión del navegador.
    • logout(): Es la función principal expuesta por el hook. Está envuelta en useCallback para optimizar su rendimiento, asegurando que la función no se recree innecesariamente en cada render del componente que la utilice. Al ser invocada, simplemente llama a clearToken(), lo que efectivamente cierra la sesión del usuario en el lado del cliente.
    • Retorno: El hook retorna un objeto que contiene únicamente la función logout.
  • Rol en la aplicación: useLogout es un hook de utilidad para la autenticación. Es utilizado por cualquier parte de la aplicación (como un componente de barra de navegación o un botón de perfil de usuario) que necesite permitir al usuario cerrar su sesión, manteniendo la lógica limpia y desacoplada.
tsx
// src/hooks/use.logout.ts
import { useCallback } from "react"; // Hook de React para memoizar funciones
import { useAuthStorage } from "../components/auth/auth.storage"; // Hook para interactuar con el almacenamiento del token

// Define la interfaz para el valor de retorno del hook
interface UseLogoutResult {
  logout: () => void; // Función para cerrar la sesión del usuario
}

/**
 * Hook personalizado para gestionar la lógica de cierre de sesión.
 * Se encarga de limpiar el token de autenticación del almacenamiento.
 * @returns Un objeto con la función `logout`.
 */
export const useLogout = (): UseLogoutResult => {
  // Obtiene la función para limpiar el token del almacenamiento
  const { clearToken } = useAuthStorage();

  // La función de logout, envuelta en useCallback para optimización.
  // Cuando se llama a 'logout', simplemente se ejecuta 'clearToken'.
  const logout = useCallback(() => {
    clearToken();
  }, [clearToken]); // clearToken es una dependencia para useCallback

  // Retorna la función de logout
  return { logout };
};

use.profile.students.ts

Este hook personalizado (useStudentProfile) es fundamental para la página de perfil del estudiante. Se encarga de toda la lógica relacionada con la obtención, actualización y eliminación de los datos de un estudiante específico.

  • Descripción: useStudentProfile maneja el ciclo de vida de un perfil de estudiante. Obtiene los datos del estudiante de la API, gestiona el estado de edición, las actualizaciones del formulario, y las operaciones de eliminación (tanto del estudiante como de archivos individuales). También proporciona retroalimentación visual mediante notificaciones toast.
  • Propósito: Centralizar y encapsular toda la lógica de negocio y estado necesaria para la gestión de un perfil de estudiante en el frontend, haciendo que el componente de página (student.profile.tsx) sea más conciso y legible.
  • Funcionalidad clave:
    • Estados Internos: Gestiona varios estados con useState:
      • student: El objeto Student actual (los datos originales del estudiante).
      • error: Almacena cualquier mensaje de error durante las operaciones de la API.
      • isEditing: Un booleano que indica si el perfil está en modo de edición.
      • updatedData: Una copia editable de los datos del estudiante para el formulario de actualización.
      • isDeleted: Un booleano que indica si el estudiante ha sido eliminado con éxito.
      • isNotFound: Un nuevo booleano que indica específicamente si el estudiante no pudo ser encontrado (ej. un error 404 de la API).
    • Obtención de Datos (useEffect): Utiliza useEffect para llamar a la función fetchStudent (importada de ../utils/students.profile.ts) cuando el id del estudiante o el token de autenticación cambian. Esto asegura que los datos del estudiante se carguen al inicializar la página o si los identificadores cambian. Se incluye una validación inicial para asegurar que id y token estén presentes antes de intentar la llamada a la API. También maneja explícitamente el estado isNotFound si el estudiante no se encuentra.
    • Funciones Optimizadas con useCallback: La mayoría de las funciones manejadoras (handleEdit, handleChange, handleDeleteFile, handleSubmitEdit, handleDelete) están envueltas en useCallback. Esto memoiza las funciones, evitando que se re-creen en cada render y mejorando el rendimiento de los componentes hijos que las reciben como props.
      • handleEdit: Cambia el estado isEditing a true.
      • handleChange: Un manejador de cambios versátil que actualiza el estado updatedData. Es capaz de manejar eventos de cambio estándar de inputs HTML (incluyendo type="file") y actualizaciones programáticas (mediante un objeto { name: keyof Student; value: any }). Utiliza actualizaciones funcionales de useState para asegurar que siempre opera sobre el estado más reciente.
      • handleDeleteFile: Permite "marcar" un campo de archivo (como studentImage) como null en updatedData. Internamente, reutiliza handleChange para realizar esta actualización de estado.
      • handleSubmitEdit: Maneja el envío del formulario de actualización.
        • Utiliza la nueva función utilitaria mapStudentToFormData (importada de ../utils/student.form.mapper.ts) para construir un objeto FormData a partir de updatedData. Esto desacopla la lógica de preparación de FormData del hook.
        • Llama a la función updateStudent (que ahora acepta FormData) con los datos preparados.
        • Actualiza los estados student y updatedData con la respuesta exitosa del servidor y desactiva el modo de edición.
        • Muestra notificaciones toast de éxito o error.
      • handleDelete: Gestiona la eliminación de un estudiante. Solicita confirmación al usuario antes de llamar a la función deleteStudent. Si la eliminación es exitosa, establece isDeleted a true (lo que dispara una redirección en la página) y muestra un toast de éxito.
    • createDeleteFileEvent (Eliminada): La función auxiliar createDeleteFileEvent ha sido eliminada, ya que su lógica ahora se maneja de forma más limpia y directa mediante el uso de handleChange para actualizar campos a null.
    • Retorno: El hook retorna un objeto con todos los estados (student, error, isEditing, updatedData, isDeleted, isNotFound) y las funciones manejadoras para que el componente de página los utilice.
  • Rol en la aplicación: Este hook es el controlador de la lógica de un solo estudiante. Desacopla la complejidad de la gestión del perfil del componente de página, facilitando su prueba y mantenimiento, y promueve la reutilización de la lógica si se necesitara en otros lugares de la aplicación.
tsx
// src/hooks/use.profile.students.ts
import { useState, useEffect, useCallback } from "react"; // Hooks de React (añadido useCallback)
import {
  fetchStudent,
  updateStudent,
  deleteStudent,
} from "../utils/students.profile"; // Funciones para interactuar con la API de perfiles de estudiantes
import type { Student } from "../interface/student/student"; // Importación de tipo para la interfaz Student
import { toast } from "react-toastify"; // Librería para notificaciones toast
import { mapStudentToFormData } from "../utils/student.form.mapper"; // NUEVA IMPORTACIÓN: para mapear Student a FormData

/**
 * Hook personalizado para gestionar la lógica de un perfil de estudiante específico.
 * Se encarga de la obtención, edición, actualización y eliminación de los datos de un estudiante.
 * @param id El ID del estudiante a gestionar, puede ser `undefined` si no se ha proporcionado.
 * @param token El token de autenticación del usuario, puede ser `null` o `undefined`.
 * @returns Un objeto con los datos del estudiante, estados de UI y funciones de manejo.
 */
export const useStudentProfile = (
  id: string | undefined, // ID del estudiante obtenido de los parámetros de la URL
  token: string | null | undefined // Token de autenticación del usuario actual
) => {
  // Estados para almacenar los datos del estudiante y el control de la UI
  const [student, setStudent] = useState<Student | null>(null); // Datos originales del estudiante
  const [error, setError] = useState<string | null>(null); // Mensaje de error si algo falla
  const [isEditing, setIsEditing] = useState(false); // Bandera para controlar el modo de edición
  const [updatedData, setUpdatedData] = useState<Student | null>(null); // Datos del estudiante en el formulario de edición
  const [isDeleted, setIsDeleted] = useState(false); // Bandera para indicar si el estudiante ha sido eliminado
  const [isNotFound, setIsNotFound] = useState(false); // NUEVO ESTADO: Para indicar si el estudiante no fue encontrado (ej. 404)

  // Efecto para cargar los datos del estudiante cuando el 'id' o el 'token' cambian
  useEffect(() => {
    const fetchData = async () => {
      // Reiniciar estados de error y no encontrado al iniciar nueva carga
      setError(null);
      setIsNotFound(false);

      // Si el ID no está presente, marcamos como no encontrado y salimos.
      // Si el token no está presente, la `ProtectedRoute` debería manejar la redirección.
      if (!id) {
        setIsNotFound(true);
        return;
      }
      if (!token) {
        // En este punto, podríamos manejar un error específico aquí si el token es nulo
        // o confiar en que ProtectedRoute maneja la redirección.
        return;
      }

      try {
        // Llama a la función de utilidad para obtener los datos del estudiante
        const data = await fetchStudent(id, token);
        setStudent(data); // Guarda los datos originales
        setUpdatedData(data); // Inicializa los datos para el formulario de edición con los datos obtenidos
      } catch (err: any) {
        // Captura cualquier error durante la obtención de datos
        // Si el error es un mensaje que indica "no encontrado" (ej. de un 404), establecemos isNotFound
        if (
          err.message &&
          (err.message.includes("no se encontraron datos") ||
            err.message.includes("404") ||
            err.message.includes("not found"))
        ) {
          setIsNotFound(true);
          setError(null); // No es un error general, sino que el recurso no existe
        } else {
          setError(err.message || "Error desconocido al cargar el estudiante");
          setIsNotFound(false);
        }
      }
    };

    fetchData(); // Ejecuta la función de obtención de datos
  }, [id, token]); // Las dependencias aseguran que el efecto se re-ejecute si 'id' o 'token' cambian

  /**
   * Cambia el estado a modo de edición.
   */
  const handleEdit = useCallback(() => setIsEditing(true), []); // Memoizar para optimización

  /**
   * Manejador genérico para los cambios en los campos del formulario de edición.
   * Acepta eventos de cambio de inputs HTML o un objeto para actualizaciones programáticas.
   * Utiliza una función de actualización de estado para asegurar la fiabilidad con estados previos.
   * @param e El evento de cambio o un objeto con 'name' y 'value'.
   */
  const handleChange = useCallback(
    (
      e:
        | React.ChangeEvent<
            HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
          >
        | { name: keyof Student; value: any } // Permite actualizaciones programáticas de campos
    ) => {
      // Es crucial usar un "updater function" para setUpdatedData cuando la dependencia es el estado previo
      // para evitar problemas con closures y updatedData no siendo el más reciente.
      setUpdatedData((prevData) => {
        if (!prevData) return null; // Si no hay datos previos, no hacemos nada

        if ("target" in e) {
          // Si es un evento de cambio de un elemento HTML (input, select, textarea)
          const { name, value, type } = e.target;
          if (type === "file") {
            // Manejo especial para inputs de tipo 'file'
            const inputElement = e.target as HTMLInputElement;
            return {
              ...prevData,
              [name]: inputElement.files?.[0] || null, // Guarda el primer archivo seleccionado o null si se borra
            };
          } else {
            // Para otros tipos de inputs, actualiza el valor directamente
            return { ...prevData, [name]: value };
          }
        } else {
          // Si es una actualización programática (objeto con 'name' y 'value')
          return { ...prevData, [e.name]: e.value }; // Actualiza el campo específico
        }
      });
    },
    []
  ); // Dependencias vacías porque `setUpdatedData` se llama con un updater function.

  /**
   * Marca un campo de archivo específico para ser eliminado en el backend.
   * Establece el valor del campo a `null` en `updatedData`.
   * @param fieldName El nombre del campo del archivo a eliminar (ej. 'studentImage').
   */
  const handleDeleteFile = useCallback(
    (fieldName: keyof Student) => {
      // Simplemente usamos el handleChange para actualizar el campo a null
      handleChange({ name: fieldName, value: null });
    },
    [handleChange]
  ); // Dependencia: handleChange

  /**
   * Manejador para enviar el formulario de actualización del estudiante.
   * Utiliza la función `mapStudentToFormData` para preparar los datos.
   * @param e El evento de formulario.
   */
  const handleSubmitEdit = useCallback(
    async (e: React.FormEvent) => {
      e.preventDefault(); // Previene el comportamiento por defecto de envío del formulario
      if (!updatedData || !id || !token) {
        // Muestra un toast de error si falta información crítica
        toast.error("Falta información para actualizar el estudiante.", {
          position: "top-right",
        });
        return;
      }

      // Utiliza la nueva función utilitaria para mapear a FormData
      const formData = mapStudentToFormData(updatedData);

      try {
        // Llama a la función de utilidad para actualizar el estudiante con el FormData
        const data = await updateStudent(id, token, formData);
        setStudent(data); // Actualiza los datos originales del estudiante con la respuesta del servidor
        setUpdatedData(data); // Actualiza también los datos del formulario de edición para reflejar los cambios
        setIsEditing(false); // Sale del modo de edición
        toast.success("Estudiante actualizado con éxito", {
          position: "top-right",
        }); // Muestra una notificación de éxito
      } catch (error: any) {
        toast.error(error.message || "Error inesperado al actualizar", {
          position: "top-right",
        }); // Muestra una notificación de error
      }
    },
    [updatedData, id, token]
  ); // Dependencias para memoizar la función

  /**
   * Manejador para eliminar el estudiante del sistema.
   * Pide confirmación al usuario antes de proceder con la eliminación.
   */
  const handleDelete = useCallback(async () => {
    if (!id || !token) return;
    if (
      !window.confirm(
        "¿Estás seguro de eliminar este estudiante? Esta acción es irreversible."
      )
    )
      return;
    try {
      await deleteStudent(id, token);
      setIsDeleted(true);
      toast.success("Estudiante eliminado con éxito", {
        position: "top-right",
      });
    } catch (error: any) {
      toast.error(error.message || "Error al eliminar estudiante", {
        position: "top-right",
      });
    }
  }, [id, token]); // Dependencias para memoizar la función

  // La función 'createDeleteFileEvent' ha sido eliminada, ya que su lógica
  // ahora se maneja de forma más limpia a través de `handleDeleteFile` que usa `handleChange`.

  // Retorna los estados y funciones que el componente de página puede usar
  return {
    student,
    error,
    isEditing,
    updatedData,
    handleEdit,
    handleChange,
    handleSubmitEdit,
    handleDelete,
    isDeleted,
    isNotFound, // ¡NUEVO: Exportar el estado de "no encontrado"!
    handleDeleteFile,
  };
};

use.protected.route.ts

Este hook personalizado (useProtectedRoute) es fundamental para la seguridad de la aplicación, ya que implementa la lógica de protección de rutas del lado del cliente. Su propósito principal es redirigir a los usuarios no autenticados a la página de inicio de sesión cuando intentan acceder a rutas protegidas.

  • Descripción: useProtectedRoute monitorea el estado de autenticación del usuario (isAuthenticated, isLoading) desde el contexto de autenticación (useAuth). Si detecta que un usuario no está autenticado y la verificación de autenticación ha finalizado, lo redirige a la página de inicio de sesión.
  • Propósito: Asegurar que solo los usuarios autenticados puedan acceder a ciertas partes de la aplicación, mejorando la seguridad y la experiencia de usuario al manejar redirecciones automáticas y estados de carga.
  • Funcionalidad clave:
    • useAuth(): Accede al contexto de autenticación para obtener el estado isAuthenticated (si el usuario ha iniciado sesión) y isLoading (si la verificación de autenticación está en curso).
    • useNavigate() y useLocation(): Hooks de react-router-dom utilizados para la navegación programática (Maps) y para obtener la ruta actual (location). location.pathname se usa para recordar la ruta a la que el usuario intentaba acceder.
    • Efecto de Redirección (useEffect):
      • Este efecto se ejecuta cada vez que isAuthenticated, isLoading, Maps o location cambian.
      • La condición if (!isLoading && !isAuthenticated) es crucial:
        • !isLoading: Asegura que la redirección solo ocurra después de que el proceso de verificación de autenticación haya terminado. Esto evita redirecciones prematuras o "flashes" de contenido antes de que el estado real del usuario sea conocido.
        • !isAuthenticated: Comprueba si, una vez finalizada la carga, el usuario no está autenticado.
      • Si se cumplen ambas condiciones, Maps("/") redirige al usuario a la ruta raíz (que generalmente es la página de login).
      • { replace: true }: Es importante usar replace: true para que la URL de la página protegida no quede en el historial del navegador. De esta forma, el usuario no puede usar el botón "Atrás" del navegador para volver a la página protegida después de la redirección.
      • state: { from: location.pathname }: Almacena la URL original a la que el usuario intentaba acceder en el estado de la navegación. Esto es útil para que, una vez que el usuario inicie sesión con éxito, pueda ser redirigido automáticamente a la página que intentaba visitar inicialmente.
    • Retorno de isLoading: El hook retorna el estado isLoading. Esto permite que el componente ProtectedRoute.tsx (que utiliza este hook) pueda mostrar un indicador de carga (como un spinner) mientras se realiza la verificación de autenticación.
  • Rol en la aplicación: useProtectedRoute es el mecanismo activo de seguridad para las rutas del lado del cliente. Trabaja en conjunto con el componente ProtectedRoute.tsx (que lo utiliza) para formar un robusto sistema de guardia de rutas, esencial para aplicaciones que manejan datos sensibles o funcionalidades restringidas a usuarios logueados.
tsx
// src/hooks/use.protected.route.ts
import { useEffect } from "react"; // Hook de React para efectos secundarios
import { useAuth } from "../components/auth/auth.context"; // Hook para acceder al contexto de autenticación
import { useNavigate, useLocation } from "react-router-dom"; // Hooks para navegación de React Router

/**
 * Hook personalizado para implementar la lógica de protección de rutas.
 * Redirige a los usuarios no autenticados a la página de inicio de sesión.
 * @returns El estado de carga del proceso de autenticación.
 */
export const useProtectedRoute = () => {
  // Obtiene el estado de autenticación y carga del contexto de autenticación
  const { isAuthenticated, isLoading } = useAuth();
  // Hooks para la navegación programática y para obtener la ubicación actual
  const navigate = useNavigate();
  const location = useLocation();

  // Efecto que se ejecuta cuando cambian las dependencias clave
  useEffect(() => {
    // Si la carga de autenticación ha terminado Y el usuario NO está autenticado,
    // entonces lo redirigimos a la página de inicio de sesión.
    if (!isLoading && !isAuthenticated) {
      // Redirige a la ruta raíz (normalmente la página de login)
      // `replace: true` evita que la ruta actual quede en el historial de navegación.
      // `state: { from: location.pathname }` guarda la URL original para una posible redirección posterior al login.
      navigate("/", { replace: true, state: { from: location.pathname } });
    }
  }, [isAuthenticated, isLoading, navigate, location]); // Dependencias del efecto

  // El hook retorna el estado de carga, permitiendo que el componente que lo usa
  // (ej. ProtectedRoute.tsx) muestre un spinner mientras se verifica la autenticación.
  return isLoading;
};

use.school.class.ts

Este archivo define el hook useSchoolClass, utilizado principalmente en la página SchoolClass, encargado de traducir los parámetros de la URL (colegio y curso) a enums definidos en la aplicación y aplicar el filtrado correspondiente a la lista de estudiantes.

  • Descripción: useSchoolClass es un hook personalizado que extrae school y course desde la URL, los convierte a los enums School y Course, y expone una función que filtra la lista completa de estudiantes en base a esos valores.
  • Propósito: Centralizar la lógica de lectura de parámetros de ruta y filtrado por colegio y curso. Evita duplicación de lógica y mantiene la página SchoolClass limpia y enfocada en presentación.
  • Funcionalidad clave:
    • Parámetros de URL: Usa useParams<{ school: string; course: string }>() para extraer school y course.
    • Mapeo a enums: Contiene funciones auxiliares mapSchoolSlugToEnum y mapCourseSlugToEnum para convertir slugs (ej. quinta-normal) en valores de enum (ej. School.Quinta).
    • useMemo para normalización: Normaliza nombres para mostrar en la UI (normalizedSchool, normalizedCourse), priorizando el valor del enum, luego el slug de la URL, y finalmente un valor por defecto.
    • Filtrado con useCallback: La función getFilteredStudentsBySchoolAndCourse recibe una lista de estudiantes y devuelve los que coinciden con el colegio y curso definidos.
  • Rol en la aplicación: Actúa como intermediario entre la navegación basada en URLs y la lógica de negocio. Permite mantener la lógica de filtrado desacoplada del componente visual.
tsx
// src/hooks/use.school.class.ts

import { useMemo, useCallback } from "react";
import { useParams } from "react-router-dom";
import { School, Course } from "../interface/common/enums/enums";
import { Student } from "../interface/student/student";
import { UseSchoolClassReturn } from "../interface/hooks/school.class";

// Mapea slugs desde la URL al enum School
const mapSchoolSlugToEnum = (slug: string): School | undefined => {
  switch (slug) {
    case "quinta-normal":
      return School.Quinta;
    case "buin":
      return School.Buin;
    case "la-granja":
      return School.Granja;
    case "nunoa":
      return School.Nunoa;
    case "pudahuel":
      return School.Pudahuel;
    case "san-miguel":
      return School.Miguel;
    default:
      return undefined; // Slug desconocido
  }
};

// Mapea slugs desde la URL al enum Course
const mapCourseSlugToEnum = (slug: string): Course | undefined => {
  switch (slug) {
    case "1nb":
      return Course.NB1;
    case "2nb":
      return Course.NB2;
    case "3nb":
      return Course.NB3;
    case "1nm":
      return Course.NM1;
    case "2nm":
      return Course.NM2;
    default:
      return undefined; // Slug desconocido
  }
};

// Hook principal
export function useSchoolClass(): UseSchoolClassReturn {
  // Extrae los parámetros de la URL
  const { school, course } = useParams<{ school: string; course: string }>();

  // Obtiene el valor del enum School desde el slug de la URL
  const currentSchoolEnum = useMemo(() => {
    const mapped = school ? mapSchoolSlugToEnum(school) : undefined;
    return mapped;
  }, [school]);

  // Obtiene el valor del enum Course desde el slug de la URL
  const currentCourseEnum = useMemo(() => {
    const mapped = course ? mapCourseSlugToEnum(course) : undefined;
    return mapped;
  }, [course]);

  // Nombre del colegio para mostrar en UI (fallback si no hay match)
  const normalizedSchool = useMemo(() => {
    const displayValue = currentSchoolEnum || school || "Colegio Desconocido";
    return displayValue;
  }, [currentSchoolEnum, school]);

  // Nombre del curso para mostrar en UI (fallback si no hay match)
  const normalizedCourse = useMemo(() => {
    const displayValue = currentCourseEnum || course || "Curso Desconocido";
    return displayValue;
  }, [currentCourseEnum, course]);

  // Filtra estudiantes que coincidan con el colegio y curso actual
  const getFilteredStudentsBySchoolAndCourse = useCallback(
    (allStudents: Student[]): Student[] => {
      if (!currentSchoolEnum || !currentCourseEnum || !allStudents) {
        return [];
      }
      return allStudents.filter(
        (student) =>
          student.school === currentSchoolEnum &&
          student.course === currentCourseEnum
      );
    },
    [currentSchoolEnum, currentCourseEnum]
  );

  // Devuelve los valores necesarios para usar en la página SchoolClass
  return {
    normalizedSchool,
    normalizedCourse,
    getFilteredStudentsBySchoolAndCourse,
  };
}

use.students.list.ts

Este hook personalizado (useStudentsList) es responsable de obtener la lista completa de estudiantes del backend. Maneja los estados de carga, error y la disponibilidad de los datos, lo que lo convierte en un pilar fundamental para las páginas que necesitan una vista general de los estudiantes.

  • Descripción: useStudentsList gestiona el proceso de fetching de datos de estudiantes de la API. Monitorea la presencia de un token de autenticación y, una vez disponible, realiza la llamada a la API, actualizando los estados de carga y error según el resultado.
  • Propósito: Centralizar la lógica de obtención de la lista de estudiantes para toda la aplicación. Esto evita la duplicación de código en múltiples componentes y asegura una forma consistente de manejar el fetching de datos.
  • Funcionalidad clave:
    • useAuth(): Utiliza este hook para acceder al token de autenticación del usuario. El token es esencial para realizar la llamada autenticada a la API de estudiantes.
    • Estados Internos: Gestiona varios estados con useState:
      • students: Un array de objetos Student que almacena la lista de estudiantes obtenida de la API.
      • error: Una cadena de texto que almacena cualquier mensaje de error que ocurra durante la operación de fetching.
      • loading: Un booleano que indica si la operación de fetching de datos está actualmente en curso.
    • useEffect para Fetching de Datos:
      • Este hook se ejecuta cuando el token de autenticación cambia.
      • Define una función asíncrona fetchData que realiza la llamada a la API.
      • Validación de token: Antes de la llamada a la API, verifica si el token está presente. Si no lo está, establece un error específico y desactiva el estado de carga, retornando. Esto es importante para que el hook no intente llamar a la API sin credenciales válidas.
      • fetchStudentsList(token): Llama a la función utilitaria fetchStudentsList (importada de ../utils/fetch.students.ts) para realizar la petición real a la API.
      • Manejo de Errores y Datos:
        • Si la llamada a la API es exitosa, los datos recibidos (data) se guardan en el estado students. Se asume que data ya es un Student[] o un tipo compatible.
        • Si ocurre un error, se captura la excepción y se establece el estado error con el mensaje correspondiente.
        • El bloque finally asegura que setLoading(false) siempre se ejecute, independientemente del éxito o fracaso de la operación.
    • Retorno: El hook retorna un objeto que contiene los students obtenidos, el error (si lo hubo) y el estado de loading.
  • Rol en la aplicación: useStudentsList es un proveedor de datos principal para las vistas que necesitan trabajar con una lista de estudiantes (ej. dashboard.tsx, school.class.tsx). Simplifica la lógica de los componentes al abstraer los detalles de la comunicación con la API y la gestión de sus estados.
tsx
// src/hooks/use.students.list.ts

import { useEffect, useState } from "react"; // Hooks de React
import { useAuth } from "../components/auth/auth.context"; // Hook para acceder al contexto de autenticación
import { fetchStudentsList } from "../utils/fetch.students"; // Función utilitaria para obtener la lista de estudiantes
import { Student } from "../interface/student/student"; // Importa la interfaz Student para tipado

/**
 * Hook personalizado para obtener la lista completa de estudiantes.
 * Gestiona el estado de carga, errores y los datos de los estudiantes.
 * @returns Un objeto con la lista de estudiantes, el error (si lo hay) y el estado de carga.
 */
export const useStudentsList = () => {
  // Obtiene el token de autenticación del contexto
  const { token } = useAuth();
  // Estados para almacenar la lista de estudiantes, errores y el estado de carga
  const [students, setStudents] = useState<Student[]>([]); // Lista de estudiantes, tipada como array de Student
  const [error, setError] = useState<string | null>(null); // Mensaje de error, o null
  const [loading, setLoading] = useState(true); // Estado de carga (true al inicio)

  // Efecto que se ejecuta cuando el token cambia, para cargar los estudiantes
  useEffect(() => {
    const fetchData = async () => {
      // Si no hay token, establece un error y finaliza la carga, luego retorna.
      // Esto previene llamadas a la API sin autenticación.
      if (!token) {
        setError("Token no proporcionado");
        setLoading(false);
        return;
      }

      try {
        // Llama a la función utilitaria para obtener los estudiantes
        const data = await fetchStudentsList(token);
        // Establece la lista de estudiantes. Se asume que 'data' es compatible con Student[].
        setStudents(data);
      } catch (err: any) {
        // Captura cualquier error durante la obtención y lo guarda en el estado 'error'.
        setError(err.message || "Error desconocido al obtener estudiantes");
      } finally {
        // Siempre desactiva el estado de carga al finalizar la operación.
        setLoading(false);
      }
    };

    fetchData(); // Ejecuta la función de obtención de datos
  }, [token]); // El efecto se re-ejecuta si el token de autenticación cambia

  // Retorna los estados y los datos para que el componente que usa el hook pueda acceder a ellos
  return { students, error, loading };
};

use.add.students.ts

Este hook personalizado (useAddStudent) encapsula toda la lógica y el estado necesarios para el proceso de añadir un nuevo estudiante al sistema. Se encarga de gestionar los datos del formulario, los cambios en los inputs (incluyendo archivos), el envío a la API y la retroalimentación al usuario.

  • Descripción: useAddStudent proporciona la funcionalidad para inicializar un formulario de estudiante vacío, capturar los cambios del usuario, preparar los datos para el envío (incluyendo archivos), interactuar con la API para la creación del estudiante, y manejar los mensajes de éxito o error.
  • Propósito: Centralizar la lógica de adición de estudiantes en un solo lugar, desacoplando el formulario (AddStudentForm) de los detalles de la gestión de estado y las llamadas a la API. Esto facilita el mantenimiento, la prueba y la reutilización de la lógica de adición.
  • Funcionalidad clave:
    • Estado del Formulario (studentData): Un estado useState<Student> que almacena todos los datos del nuevo estudiante a ser agregado. Se inicializa con valores vacíos o undefined para cada campo.
    • Control de Reinicio del Formulario (formKey): formKey es un estado que se actualiza con Date.now() después de un envío exitoso. Cuando se utiliza como key en el componente del formulario (AddStudentForm), fuerza a React a "re-montar" el formulario, reseteando efectivamente todos sus campos a sus valores iniciales.
    • Mensajes de Feedback (successMessage, errorMessage): Estados useState<string | null> que almacenan mensajes para notificar al usuario sobre el éxito o fracaso de la operación.
    • handleChange(e: ChangeEvent<HTMLInputElement | HTMLSelectElement>):
      • Manejador de cambios genérico que actualiza el estado studentData basándose en la entrada del usuario.
      • Detecta el type del input ("file" para archivos) y maneja los datos de los archivos (inputElement.files?.[0]) por separado de otros tipos de inputs.
      • Gestiona la conversión de fechas (birthdate, contactDate) a un formato ISO (toISODate()) usando luxon.
      • Convierte la cadena "undefined" de los selectores (si un usuario selecciona una opción "vacía") a undefined real para los campos de studentData.
      • Utiliza una función de actualización de estado (setStudentData(prevData => ...)) para asegurar que siempre opera sobre el estado más reciente, lo que mejora la fiabilidad.
    • handleSubmit(e: FormEvent):
      • Es la función principal para enviar el formulario.
      • Validación de token: Verifica si el token de autenticación está presente. Si no, establece un mensaje de error y detiene el proceso.
      • Limpia mensajes de éxito/error previos.
      • Construcción de FormData (Reutilización): Ahora utiliza la función utilitaria mapStudentToFormData (importada de ../utils/student.form.mapper.ts) para construir un objeto FormData a partir de studentData. Esto centraliza la lógica de preparación de datos para multipart/form-data.
      • Llamada a la API (Delegación): Llama a la función de utilidad addStudent (importada de ../utils/fetch.students.ts) para realizar la petición POST. addStudent ahora se encarga de la lógica de la petición HTTP y el manejo primario de errores.
      • Manejo de Respuestas y Errores: Si la operación es exitosa, establece el successMessage y resetea el formulario. Si addStudent lanza un error, lo captura y establece un errorMessage con el mensaje proporcionado por addStudent (que ya contiene los detalles del backend).
    • Retorno: El hook retorna un objeto que contiene los mensajes de feedback, handleSubmit, studentData, handleChange y formKey.
  • Rol en la aplicación: useAddStudent es el controlador de la lógica del formulario de adición. Es el puente entre la interfaz de usuario de entrada de datos y el backend, asegurando que el proceso de creación de nuevos estudiantes sea robusto y fácil de manejar desde la página AddStudent.
tsx
// src/hooks/use.add.students.ts

import { useState, ChangeEvent, FormEvent, useCallback } from "react"; // Añadido useCallback
import type { Student } from "../interface/student/student"; // Importación de tipo
import { DateTime } from "luxon"; // Para manejo de fechas
import { toast } from "react-toastify"; // Para notificaciones
import { mapStudentToFormData } from "../utils/student.form.mapper"; // Importa la nueva utilidad
import { addStudent } from "../utils/fetch.students"; // Importa la función de utilidad para agregar estudiante

/**
 * Hook personalizado para gestionar la lógica de añadir un nuevo estudiante.
 * Controla el estado del formulario, maneja los cambios de input (incluyendo archivos),
 * y gestiona el envío de datos a la API.
 * @param token El token de autenticación del usuario. Puede ser `null` o `undefined`.
 * @returns Un objeto con estados (mensajes de éxito/error, datos del estudiante),
 * manejadores de eventos (handleChange, handleSubmit) y una clave para reiniciar el formulario.
 */
export function useAddStudent(token: string | null | undefined) {
  const [formKey, setFormKey] = useState(Date.now()); // Para forzar el reinicio del formulario
  const [studentData, setStudentData] = useState<Student>({
    // Inicialización de los datos del estudiante
    name: "",
    lastname: "",
    rut: "",
    sex: undefined,
    birthdate: undefined,
    nationality: "",
    address: "",
    phone: "",
    email: "",
    source: undefined,
    contact: "",
    contactDate: undefined,
    call1: "",
    call2: "",
    call3: "",
    comments1: "",
    comments2: "",
    comments3: "",
    positiveFeedback: "AÚN SIN RESPUESTAS",
    studentImage: undefined,
    birthCertificate: undefined,
    studyCertificate: undefined,
    linkDni: undefined,
    school: undefined,
    course: undefined,
    communicationPreference: undefined,
    createdAt: undefined,
  });

  const [successMessage, setSuccessMessage] = useState<string>("");
  const [errorMessage, setErrorMessage] = useState<string>("");

  /**
   * Manejador genérico para cambios en los campos del formulario.
   * Actualiza el estado `studentData` basándose en el input del usuario.
   * @param e El evento de cambio del input (HTMLInputElement | HTMLSelectElement).
   */
  const handleChange = useCallback(
    (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
      // Usamos una función de actualización de estado para asegurar el estado más reciente
      setStudentData((prevData) => {
        const { name, value, type } = e.target;

        if (type === "file") {
          const inputElement = e.target as HTMLInputElement;
          return { ...prevData, [name]: inputElement.files?.[0] || undefined }; // Guarda el archivo o undefined
        } else if ((name === "birthdate" || name === "contactDate") && value) {
          const formattedDate = DateTime.fromISO(value, {
            zone: "America/Santiago",
          }).isValid
            ? DateTime.fromISO(value, { zone: "America/Santiago" }).toISODate()
            : undefined; // Si no es válido, undefined
          return { ...prevData, [name]: formattedDate };
        } else if (value === "undefined") {
          return { ...prevData, [name]: undefined };
        } else {
          return { ...prevData, [name]: value };
        }
      });
    },
    []
  ); // Dependencias vacías, ya que se usa una función de actualización de estado

  /**
   * Manejador para el envío del formulario.
   * Envía los datos del estudiante a la API utilizando la función de utilidad `addStudent`.
   * @param e El evento de envío del formulario.
   */
  const handleSubmit = useCallback(
    async (e: FormEvent) => {
      e.preventDefault(); // Previene el comportamiento por defecto de envío del formulario

      if (!token) {
        setErrorMessage("No estás autenticado. Por favor, inicia sesión.");
        return;
      }

      setErrorMessage(""); // Limpia mensajes de error anteriores
      setSuccessMessage(""); // Limpia mensajes de éxito anteriores

      // Utiliza la utilidad mapStudentToFormData para preparar los datos
      const formData = mapStudentToFormData(studentData);

      try {
        // Llama a la función de utilidad `addStudent` que maneja la petición y errores primarios
        // NOTA: Asegúrate de que `addStudent` en `fetch.students.ts` acepte `FormData`.
        await addStudent(token, formData);

        setSuccessMessage("Estudiante agregado con éxito.");
        // Resetea los datos del formulario a sus valores iniciales
        setStudentData({
          name: "",
          lastname: "",
          rut: "",
          sex: undefined,
          birthdate: undefined,
          nationality: "",
          address: "",
          phone: "",
          email: "",
          source: undefined,
          contact: "",
          contactDate: undefined,
          call1: "",
          call2: "",
          call3: "",
          comments1: "",
          comments2: "",
          comments3: "",
          positiveFeedback: "AÚN SIN RESPUESTAS",
          studentImage: undefined,
          birthCertificate: undefined,
          studyCertificate: undefined,
          linkDni: undefined,
          school: undefined,
          course: undefined,
          communicationPreference: undefined,
          createdAt: undefined,
        });
        setFormKey(Date.now()); // Actualiza la clave para forzar el reinicio del formulario
      } catch (error: any) {
        // Captura el error lanzado por `addStudent` (que ya tiene un mensaje específico)
        console.error("Error al agregar estudiante:", error);
        setErrorMessage(
          error.message ||
            "Error de red al agregar el estudiante. Intente más tarde."
        );
      }
    },
    [token, studentData]
  ); // studentData como dependencia para que el formData se cree con el estado más reciente

  // Retorna los estados y manejadores para ser utilizados por el componente de página
  return {
    successMessage,
    errorMessage,
    handleSubmit,
    studentData,
    handleChange,
    formKey,
  };
}

use.decode.token.ts

Este hook personalizado (useDecodeToken) es esencial para trabajar con tokens JWT (JSON Web Tokens) en el cliente. Se encarga de decodificar un token, extraer su información (userId, email, etc.) y gestionar cualquier error que pueda surgir durante este proceso.

  • Descripción: useDecodeToken proporciona una función decodeToken que utiliza la librería jwt-decode para parsear un token JWT. Almacena la información decodificada (userId, email) en el estado interno y gestiona los errores de decodificación. El hook también decodifica un initialToken al montarse.
  • Propósito: Centralizar la lógica de decodificación y validación básica de tokens JWT en el lado del cliente. Esto permite que otros hooks o componentes puedan acceder a la información del usuario contenida en el token de forma segura y consistente, sin tener que reimplementar la lógica de decodificación.
  • Funcionalidad clave:
    • DecodedToken Interface: Define la estructura esperada de la carga útil (payload) del token decodificado. Es crucial ajustarla para que coincida con la estructura real de tus tokens JWT (ej., uid para ID de usuario, email, exp para expiración, etc.).
    • Estados Internos: Gestiona varios estados con useState:
      • userId: El ID del usuario extraído del token, o null.
      • email: El correo electrónico del usuario extraído del token, o null.
      • error: Cualquier mensaje de error que surja durante la decodificación.
    • decodeToken(token: string | null) Función:
      • Es la función principal del hook para decodificar un token.
      • Si se le pasa null o una cadena vacía, resetea los estados (userId, email, error) a null.
      • Utiliza jwtDecode<DecodedToken>(token) para intentar parsear el token.
      • Manejo de Errores: Si la decodificación falla (ej., el token es inválido o corrupto), captura el error, lo reporta a Sentry (Sentry.captureException), resetea los estados de userId y email, y establece un error descriptivo. Finalmente, retorna null.
      • Extracción de Datos: Si la decodificación es exitosa, extrae uid y email de la carga útil decodificada y los guarda en los estados userId y email respectivamente.
      • Retorno: Devuelve el objeto DecodedToken decodificado o null si hubo un error.
    • useEffect para initialToken: Este efecto se ejecuta al montar el hook o cuando initialToken cambia. Llama a decodeToken(initialToken) para procesar el token inicial que se le pasa al hook, asegurando que el estado del hook refleje la información del token desde el principio. La función decodeToken no se incluye en las dependencias de useEffect porque no depende de valores del closure del hook que cambien entre renders, lo que asegura la pureza y evita re-ejecuciones innecesarias.
    • Retorno del Hook: El hook retorna un objeto que incluye los estados userId, email, error, y la función decodeToken (para permitir la decodificación de tokens en cualquier momento).
  • Rol en la aplicación: useDecodeToken es un hook de utilidad para la seguridad y la experiencia de usuario. Permite a otros hooks o componentes obtener fácilmente información del usuario desde el token JWT, lo cual es fundamental para personalizar la UI, verificar roles, o realizar verificaciones de autorización ligeras en el cliente. Su integración con Sentry ayuda a monitorear problemas de tokens corruptos o inválidos.
tsx
// src/hooks/use.decode.token.ts
import { useState, useEffect } from "react"; // Hooks de React para estado y efectos
import { jwtDecode, JwtPayload } from "jwt-decode"; // Librería para decodificar JWTs, y tipo para la carga útil base
import * as Sentry from "@sentry/react"; // Librería para monitoreo de errores (Sentry)

/**
 * Hook personalizado para decodificar tokens JWT y extraer información del usuario.
 * Proporciona el ID, email y un estado de error, además de una función para decodificar tokens.
 * @param initialToken El token JWT inicial para decodificar al montar el hook.
 * @returns Un objeto con los datos del usuario decodificados, error y la función de decodificación.
 */
export const useDecodeToken = (
  initialToken: string | null // El token con el que se inicializa el hook (ej. token del localStorage)
): UseDecodeTokenResult => {
  // Estados para almacenar la información decodificada y errores
  const [userId, setUserId] = useState<string | null>(null);
  const [email, setEmail] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  /**
   * Función interna para decodificar un token JWT.
   * Actualiza los estados internos del hook con la información decodificada o el error.
   * @param token La cadena JWT a decodificar.
   * @returns El objeto `DecodedToken` si la decodificación es exitosa, o `null` en caso de error.
   */
  const decodeToken = (token: string | null): DecodedToken | null => {
    // Si no hay token, resetea todos los estados a null y retorna null.
    if (!token) {
      setUserId(null);
      setEmail(null);
      setError(null);
      return null;
    }

    try {
      // Intenta decodificar el token usando jwtDecode y la interfaz DecodedToken
      const decoded = jwtDecode<DecodedToken>(token);
      // Extrae y guarda el ID de usuario y el email. Usa null si la propiedad no existe en el token.
      setUserId(decoded.uid || null);
      setEmail(decoded.email || null);
      setError(null); // Limpia cualquier error previo si la decodificación es exitosa
      return decoded; // Retorna el token decodificado
    } catch (err) {
      // Captura cualquier error durante la decodificación (ej. token inválido, corrupto)
      const error = err as Error; // Asegura que el error sea tratado como un objeto Error
      Sentry.captureException(error); // Reporta la excepción a Sentry para monitoreo
      setUserId(null); // Resetea el ID de usuario
      setEmail(null); // Resetea el email
      setError(
        "Error al decodificar el token. El token podría ser inválido o corrupto."
      ); // Mensaje de error específico
      return null; // Retorna null para indicar fallo en la decodificación
    }
  };

  // Efecto que se ejecuta al montar el hook o cuando 'initialToken' cambia.
  // Decodifica el token inicial proporcionado.
  // NOTA: La función `decodeToken` no necesita ser incluida en las dependencias de este useEffect
  // porque es una función interna del hook que no depende de ningún estado o prop que cambie
  // entre renders (su definición es estable).
  useEffect(() => {
    decodeToken(initialToken);
  }, [initialToken]); // El efecto se re-ejecuta si el token inicial cambia.

  // Retorna los estados actuales y la función `decodeToken` para uso externo.
  return { userId, email, error, decodeToken };
};

use.document.handling.ts

Este hook personalizado (useDocumentHandling) centraliza la lógica para manejar la subida y eliminación de documentos (archivos) asociados a un estudiante en un formulario. Proporciona funciones de callback para integrarse con los inputs de archivo de la UI y con la lógica de eliminación de archivos del hook principal.

  • Descripción: useDocumentHandling ofrece manejadores para los eventos de cambio de inputs de tipo file y para confirmar la eliminación de archivos. Se integra con las funciones onChange y onDeleteFile proporcionadas por el componente o hook padre (como useStudentProfile).
  • Propósito: Abstraer la lógica repetitiva de manejo de archivos (selección y eliminación) y la confirmación de usuario, promoviendo la reutilización y manteniendo los componentes de formulario más limpios.
  • Funcionalidad clave:
    • UseDocumentHandlingHookProps y UseDocumentHandlingHookResult: Interfaces (importadas de ../interface/student/document.handling.ts) que definen las propiedades de entrada que el hook espera (onChange, onDeleteFile) y las funciones que retorna (handleFileChange, handleDeleteFile, getLabelForField).
    • handleFileChange(e: React.ChangeEvent<HTMLInputElement>) (useCallback):
      • Es la función a adjuntar al evento onChange de un input type="file".
      • Utiliza useCallback para memoizar la función, previniendo re-renderizados innecesarios en componentes hijos.
      • Extrae el name del input y el primer File seleccionado (files[0]).
      • Llama a la prop onChange del hook, pasando el name del campo y el File como value. Esto permite que el hook padre (useStudentProfile) actualice sus datos con el nuevo archivo.
    • handleDeleteFile(fieldName: keyof Student) (useCallback):
      • Es la función a adjuntar a los botones o íconos de "eliminar archivo".
      • Utiliza useCallback para memoizar la función.
      • Muestra un cuadro de diálogo de confirmación (window.confirm) al usuario, utilizando una etiqueta legible para el tipo de documento (obtenida de getLabelForField).
      • Si el usuario confirma, llama a la prop onDeleteFile, pasando el fieldName del documento a eliminar. Esta prop es la que finalmente le indicará al hook padre que el archivo debe ser marcado para eliminación (ej., estableciendo su valor a null).
    • getLabelForField(fieldName: keyof Student) (useCallback):
      • Una función auxiliar que mapea el fieldName (la clave de la propiedad del estudiante, como "studentImage") a una etiqueta legible y amigable para el usuario (ej., "Foto del Estudiante").
      • Utiliza useCallback para memoizar la función.
      • Se utiliza para construir el mensaje de confirmación de eliminación (window.confirm).
  • Rol en la aplicación: useDocumentHandling es un hook de utilidad de formulario. Abstrae la lógica de interacción con inputs de archivo y proporciona una capa de abstracción para confirmar acciones destructivas, mejorando la experiencia del usuario y la coherencia en la gestión de documentos en los formularios de estudiante.
tsx
// src/hooks/use.document.handling.ts

import { useCallback } from "react"; // Hook de React para memoizar funciones
import { Student } from "../interface/student/student"; // Interfaz para el tipo Student
import {
  UseDocumentHandlingHookProps, // Propiedades que el hook espera
  UseDocumentHandlingHookResult, // Resultado que el hook retorna
} from "../interface/student/document.handling"; // Rutas a las interfaces

/**
 * Hook personalizado para manejar la lógica de subida y eliminación de documentos (archivos).
 * Proporciona manejadores para eventos de cambio de archivo y confirmación de eliminación.
 * @param {UseDocumentHandlingHookProps} { onChange, onDeleteFile } Props que incluyen callbacks para la gestión de archivos.
 * @returns {UseDocumentHandlingHookResult} Un objeto con las funciones de manejo de archivos.
 */
export const useDocumentHandling = ({
  onChange, // Callback del componente padre para actualizar un campo del estudiante (incluyendo archivos)
  onDeleteFile, // Callback del componente padre para marcar un archivo como eliminado
}: UseDocumentHandlingHookProps): UseDocumentHandlingHookResult => {
  /**
   * Manejador para el evento `onChange` de inputs de tipo `file`.
   * Extrae el archivo seleccionado y llama a `onChange` del padre.
   * Memoizado con `useCallback`.
   * @param e El evento de cambio de un input HTML (tipo file).
   */
  const handleFileChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const { name, files } = e.target; // Extrae el nombre del input y la lista de archivos
      if (files && files.length > 0) {
        // Si hay archivos seleccionados, llama a onChange del padre con el primer archivo
        onChange({ name: name as keyof Student, value: files[0] });
      }
    },
    [onChange] // Dependencia: se re-crea si la función `onChange` del padre cambia
  );

  /**
   * Manejador para confirmar y ejecutar la eliminación de un archivo.
   * Muestra un `window.confirm` y llama a `onDeleteFile` del padre si se confirma.
   * Memoizado con `useCallback`.
   * @param fieldName El nombre del campo del estudiante asociado al archivo a eliminar.
   */
  const handleDeleteFile = useCallback(
    (fieldName: keyof Student) => {
      // Muestra un cuadro de diálogo de confirmación al usuario
      if (
        window.confirm(
          `¿Estás seguro de eliminar ${getLabelForField(
            fieldName
          ).toLowerCase()}?` // Usa getLabelForField para un mensaje amigable
        )
      ) {
        onDeleteFile(fieldName); // Si el usuario confirma, llama a onDeleteFile del padre
      }
    },
    [onDeleteFile] // Dependencia: se re-crea si la función `onDeleteFile` del padre cambia
  );

  /**
   * Función auxiliar para obtener una etiqueta legible para un campo de documento.
   * Memoizado con `useCallback`.
   * @param fieldName El nombre del campo del estudiante.
   * @returns La etiqueta legible del campo.
   */
  const getLabelForField = useCallback((fieldName: keyof Student): string => {
    switch (fieldName) {
      case "studentImage":
        return "Foto del Estudiante";
      case "birthCertificate":
        return "Certificado de nacimiento";
      case "studyCertificate":
        return "Certificado de estudio";
      case "linkDni":
        return "Cédula de identidad";
      default:
        return ""; // Retorna vacío si el campo no es reconocido
    }
  }, []); // Sin dependencias, ya que el mapeo es estático

  // Retorna las funciones de manejo de archivos para que el componente que usa el hook las utilice
  return { handleFileChange, handleDeleteFile, getLabelForField };
};