Appearance
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:
useLoginproporciona una funciónloginque 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 comoupdateTokenpara guardar el token yclearTokenpara eliminarlo si es inválido.useDecodeToken(): Utiliza este hook (importado de./use.decode.token.ts) para obtener la funcióndecodeToken. 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
asyncy está envuelta enuseCallbackpara optimizar su rendimiento. - Establece
isLoadingatruey limpia cualquierloginErroranterior. - Decodificación y Validación del Token Mejorada: Llama a
decodeToken(newToken). Si la decodificación falla (decodedesnulloundefined), 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 deError). El error se propaga (throw error) para que el componente que invocóloginpueda manejarlo también. - Finalización: El bloque
finallyasegura quesetIsLoading(false)siempre se ejecute, independientemente del éxito o fracaso de la operación.
- Es la función principal expuesta por el hook. Es una función
- Retorno: El hook retorna un objeto que contiene
isLoading,loginErrory la funciónlogin.
- Estado Interno: Gestiona los estados con
- Rol en la aplicación:
useLogines 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 conuseAuthStorageyuseDecodeTokenlo 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:
useLogoutproporciona una funciónlogoutque 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ónclearToken. 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 enuseCallbackpara 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 aclearToken(), 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:
useLogoutes 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:
useStudentProfilemaneja 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 objetoStudentactual (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): UtilizauseEffectpara llamar a la funciónfetchStudent(importada de../utils/students.profile.ts) cuando eliddel estudiante o eltokende 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 queidytokenestén presentes antes de intentar la llamada a la API. También maneja explícitamente el estadoisNotFoundsi el estudiante no se encuentra. - Funciones Optimizadas con
useCallback: La mayoría de las funciones manejadoras (handleEdit,handleChange,handleDeleteFile,handleSubmitEdit,handleDelete) están envueltas enuseCallback. 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 estadoisEditingatrue.handleChange: Un manejador de cambios versátil que actualiza el estadoupdatedData. Es capaz de manejar eventos de cambio estándar de inputs HTML (incluyendotype="file") y actualizaciones programáticas (mediante un objeto{ name: keyof Student; value: any }). Utiliza actualizaciones funcionales deuseStatepara asegurar que siempre opera sobre el estado más reciente.handleDeleteFile: Permite "marcar" un campo de archivo (comostudentImage) comonullenupdatedData. Internamente, reutilizahandleChangepara 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 objetoFormDataa partir deupdatedData. Esto desacopla la lógica de preparación deFormDatadel hook. - Llama a la función
updateStudent(que ahora aceptaFormData) con los datos preparados. - Actualiza los estados
studentyupdatedDatacon la respuesta exitosa del servidor y desactiva el modo de edición. - Muestra notificaciones toast de éxito o error.
- Utiliza la nueva función utilitaria
handleDelete: Gestiona la eliminación de un estudiante. Solicita confirmación al usuario antes de llamar a la funcióndeleteStudent. Si la eliminación es exitosa, estableceisDeletedatrue(lo que dispara una redirección en la página) y muestra un toast de éxito.
createDeleteFileEvent(Eliminada): La función auxiliarcreateDeleteFileEventha sido eliminada, ya que su lógica ahora se maneja de forma más limpia y directa mediante el uso dehandleChangepara actualizar campos anull.- 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.
- Estados Internos: Gestiona varios estados con
- 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:
useProtectedRoutemonitorea 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 estadoisAuthenticated(si el usuario ha iniciado sesión) yisLoading(si la verificación de autenticación está en curso).useNavigate()yuseLocation(): Hooks dereact-router-domutilizados para la navegación programática (Maps) y para obtener la ruta actual (location).location.pathnamese 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,Mapsolocationcambian. - 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 usarreplace: truepara 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.
- Este efecto se ejecuta cada vez que
- Retorno de
isLoading: El hook retorna el estadoisLoading. Esto permite que el componenteProtectedRoute.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:
useProtectedRoutees el mecanismo activo de seguridad para las rutas del lado del cliente. Trabaja en conjunto con el componenteProtectedRoute.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:
useSchoolClasses un hook personalizado que extraeschoolycoursedesde la URL, los convierte a los enumsSchoolyCourse, 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
SchoolClasslimpia y enfocada en presentación. - Funcionalidad clave:
- Parámetros de URL: Usa
useParams<{ school: string; course: string }>()para extraerschoolycourse. - Mapeo a enums: Contiene funciones auxiliares
mapSchoolSlugToEnumymapCourseSlugToEnumpara convertir slugs (ej.quinta-normal) en valores de enum (ej.School.Quinta). useMemopara 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óngetFilteredStudentsBySchoolAndCourserecibe una lista de estudiantes y devuelve los que coinciden con el colegio y curso definidos.
- Parámetros de URL: Usa
- 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:
useStudentsListgestiona 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 altokende 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 objetosStudentque 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.
useEffectpara Fetching de Datos:- Este hook se ejecuta cuando el
tokende autenticación cambia. - Define una función asíncrona
fetchDataque realiza la llamada a la API. - Validación de
token: Antes de la llamada a la API, verifica si eltokenestá 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 utilitariafetchStudentsList(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 estadostudents. Se asume quedataya es unStudent[]o un tipo compatible. - Si ocurre un error, se captura la excepción y se establece el estado
errorcon el mensaje correspondiente. - El bloque
finallyasegura quesetLoading(false)siempre se ejecute, independientemente del éxito o fracaso de la operación.
- Si la llamada a la API es exitosa, los datos recibidos (
- Este hook se ejecuta cuando el
- Retorno: El hook retorna un objeto que contiene los
studentsobtenidos, elerror(si lo hubo) y el estado deloading.
- Rol en la aplicación:
useStudentsListes 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:
useAddStudentproporciona 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 estadouseState<Student>que almacena todos los datos del nuevo estudiante a ser agregado. Se inicializa con valores vacíos oundefinedpara cada campo. - Control de Reinicio del Formulario (
formKey):formKeyes un estado que se actualiza conDate.now()después de un envío exitoso. Cuando se utiliza comokeyen 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): EstadosuseState<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
studentDatabasándose en la entrada del usuario. - Detecta el
typedel 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()) usandoluxon. - Convierte la cadena
"undefined"de los selectores (si un usuario selecciona una opción "vacía") aundefinedreal para los campos destudentData. - 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.
- Manejador de cambios genérico que actualiza el estado
handleSubmit(e: FormEvent):- Es la función principal para enviar el formulario.
- Validación de
token: Verifica si eltokende 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 utilitariamapStudentToFormData(importada de../utils/student.form.mapper.ts) para construir un objetoFormDataa partir destudentData. Esto centraliza la lógica de preparación de datos paramultipart/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ónPOST.addStudentahora 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
successMessagey resetea el formulario. SiaddStudentlanza un error, lo captura y establece unerrorMessagecon el mensaje proporcionado poraddStudent(que ya contiene los detalles del backend).
- Retorno: El hook retorna un objeto que contiene los mensajes de feedback,
handleSubmit,studentData,handleChangeyformKey.
- Estado del Formulario (
- Rol en la aplicación:
useAddStudentes 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áginaAddStudent.
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:
useDecodeTokenproporciona una funcióndecodeTokenque utiliza la libreríajwt-decodepara 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 uninitialTokenal 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:
DecodedTokenInterface: 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.,uidpara ID de usuario,email,exppara expiración, etc.).- Estados Internos: Gestiona varios estados con
useState:userId: El ID del usuario extraído del token, onull.email: El correo electrónico del usuario extraído del token, onull.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
nullo una cadena vacía, resetea los estados (userId,email,error) anull. - 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 deuserIdyemail, y establece unerrordescriptivo. Finalmente, retornanull. - Extracción de Datos: Si la decodificación es exitosa, extrae
uidyemailde la carga útil decodificada y los guarda en los estadosuserIdyemailrespectivamente. - Retorno: Devuelve el objeto
DecodedTokendecodificado onullsi hubo un error.
useEffectparainitialToken: Este efecto se ejecuta al montar el hook o cuandoinitialTokencambia. Llama adecodeToken(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óndecodeTokenno se incluye en las dependencias deuseEffectporque 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óndecodeToken(para permitir la decodificación de tokens en cualquier momento).
- Rol en la aplicación:
useDecodeTokenes 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:
useDocumentHandlingofrece manejadores para los eventos de cambio de inputs de tipofiley para confirmar la eliminación de archivos. Se integra con las funcionesonChangeyonDeleteFileproporcionadas por el componente o hook padre (comouseStudentProfile). - 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:
UseDocumentHandlingHookPropsyUseDocumentHandlingHookResult: 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
onChangede un inputtype="file". - Utiliza
useCallbackpara memoizar la función, previniendo re-renderizados innecesarios en componentes hijos. - Extrae el
namedel input y el primerFileseleccionado (files[0]). - Llama a la prop
onChangedel hook, pasando elnamedel campo y elFilecomovalue. Esto permite que el hook padre (useStudentProfile) actualice sus datos con el nuevo archivo.
- Es la función a adjuntar al evento
handleDeleteFile(fieldName: keyof Student)(useCallback):- Es la función a adjuntar a los botones o íconos de "eliminar archivo".
- Utiliza
useCallbackpara 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 degetLabelForField). - Si el usuario confirma, llama a la prop
onDeleteFile, pasando elfieldNamedel 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 anull).
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
useCallbackpara memoizar la función. - Se utiliza para construir el mensaje de confirmación de eliminación (
window.confirm).
- Una función auxiliar que mapea el
- Rol en la aplicación:
useDocumentHandlinges 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 };
};