Skip to content

Archivos de la Carpeta src/components/

La carpeta src/components/ es fundamental para la gestión de la autenticación en la aplicación. Contiene componentes y hooks relacionados con el estado de la sesión del usuario, el almacenamiento del token y la protección de rutas.


auth.context.tsx

Este archivo define el Contexto de Autenticación (AuthContext) y el Proveedor de Autenticación (AuthProvider), que son el corazón del sistema de gestión de sesiones de usuario en tu aplicación React. Permite que cualquier componente anidado acceda al estado de autenticación (si el usuario está logueado, su ID, email, etc.) y a las funciones para iniciar o cerrar sesión.

  • Descripción: auth.context.tsx crea un contexto React para el estado de autenticación. Incluye el AuthProvider que gestiona el token (usando useAuthStorage), decodifica la información del usuario (usando useDecodeToken), y expone funciones (login, logout) y estados (isAuthenticated, isLoading, loginError) a través del contexto. Implementa un estado de carga inicial (isInitialLoading) para la validación del token al cargar la aplicación.
  • Propósito: Centralizar y hacer accesible globalmente el estado y las operaciones relacionadas con la autenticación del usuario. Esto evita la "prop drilling" (pasar props manualmente a muchos niveles) y asegura que todos los componentes puedan reaccionar a los cambios en el estado de autenticación.
  • Funcionalidad clave:
    • AuthContext (createContext): Crea el contexto de React que contendrá el estado y las funciones de autenticación. Se le proporciona un valor inicial predeterminado (AuthContextType) para asegurar el tipado.
    • AuthProvider: React.FC<AuthProviderProps>:
      • Es el componente proveedor que se envuelve alrededor de la parte de la aplicación que necesita acceso al contexto de autenticación (generalmente en main.tsx).
      • useAuthStorage(): Hook (de ./auth.storage.ts) para interactuar con el almacenamiento persistente (ej., localStorage) donde se guarda el token de autenticación. Proporciona token (el token almacenado), updateToken y clearToken.
      • useDecodeToken(storedToken): Hook (de ../../hooks/use.decode.token.ts) que decodifica el token almacenado para extraer userId y email. Este hook también gestiona internamente errores de decodificación.
      • Estados Internos (useState):
        • isInitialLoading: (Nuevo Estado) Booleano que indica si la aplicación está realizando la verificación inicial del token almacenado al cargar la página.
        • isAuthenticated: Un booleano que refleja si el usuario está actualmente autenticado.
        • loginError: Almacena un mensaje de error específico si el proceso de login falla.
        • isLoginProcessLoading: Indica si una operación de login() explícita (a través de la función login) está en curso.
      • useEffect para Validación Inicial del Token: Este efecto se ejecuta una vez al montar el AuthProvider (y cuando storedToken cambia). Es responsable de:
        • Establecer isInitialLoading a true al inicio.
        • Decodificar y validar el storedToken.
        • Establecer isAuthenticated a true o false y limpiar el token si es inválido.
        • Finalizar estableciendo isInitialLoading a false.
      • login(newToken: string) (useCallback):
        • Función asíncrona para iniciar sesión. Recibe el newToken (el token ya obtenido del backend, por ejemplo, por authenticateUser).
        • Establece isLoginProcessLoading a true y limpia loginError.
        • Procesamiento del Token: Llama a decodeToken(newToken) para validar y extraer información. Luego, updateToken(newToken) guarda el token. Si todo es exitoso, setIsAuthenticated(true).
        • Manejo de Errores: Captura cualquier error durante la decodificación o el almacenamiento. Si falla, limpia el token, establece isAuthenticated a false, guarda un loginError, y propaga el error.
        • El bloque finally asegura que isLoginProcessLoading se establezca en false.
      • logout() (useCallback):
        • Función para cerrar sesión.
        • Llama a clearToken() para eliminar el token del almacenamiento.
        • Establece setIsAuthenticated(false) y limpia cualquier loginError.
      • value (useMemo): Un objeto que contiene todos los estados (token, userId, email, isAuthenticated, loginError) y las funciones (login, logout) que se proporcionarán a los componentes consumidores. La propiedad isLoading se combina a partir de isInitialLoading || isLoginProcessLoading, lo que refleja el estado de carga general de la autenticación (ya sea la verificación inicial o un intento de login). useMemo memoiza este objeto para optimizar el rendimiento.
    • <AuthContext.Provider value={value}>: Envuelve a los children (el resto de la aplicación) y les proporciona el value que contiene el estado y las funciones del contexto.
    • useAuth = () => useContext(AuthContext): Una función useAuth exportada para facilitar el consumo del contexto en cualquier componente funcional React, simplemente llamando const auth = useAuth();.
  • Rol en la aplicación: auth.context.tsx es la fuente de verdad central para el estado de autenticación. Es fundamental para la seguridad (gestionando quién está logueado), la personalización de la UI (mostrando contenido diferente a usuarios autenticados), y la gestión global de la sesión, manejando de forma robusta los estados de carga inicial y de proceso.
tsx
// src/components/auth/auth.context.tsx
import { useContext, createContext, useState, useEffect, useMemo, useCallback } from "react"; // Hooks de React
import { useAuthStorage } => "./auth.storage"; // Hook para la gestión del almacenamiento del token
import { useDecodeToken } from "../../hooks/use.decode.token"; // Hook para decodificar tokens JWT
import AuthProviderProps from "../../interface/auth/auth.provider.props"; // Interfaz para las props del AuthProvider
import AuthContextType from "../../interface/auth/auth.context.type"; // Interfaz para el tipo del contexto

// Crea el contexto de autenticación con un valor predeterminado (para tipado y valor inicial)
const AuthContext = createContext<AuthContextType>({
  token: null,
  userId: null,
  email: null,
  isAuthenticated: false,
  login: async () => {},
  logout: () => {},
  isLoading: false, // Por defecto, se gestionará de forma combinada
  loginError: null,
});

/**
 * Componente proveedor de autenticación.
 * Envuelve a los componentes hijos y les proporciona el contexto de autenticación.
 */
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
  // Obtiene el token almacenado y funciones para actualizarlo/limpiarlo
  const { token: storedToken, updateToken, clearToken } = useAuthStorage();
  // Decodifica el token almacenado para obtener userId y email
  // useDecodeToken también gestiona errores de decodificación internamente
  const { userId, email, decodeToken } = useDecodeToken(storedToken);

  // NUEVO ESTADO: Indica si la verificación inicial del token está en curso.
  const [isInitialLoading, setIsInitialLoading] = useState(true);

  // Estado que refleja si el usuario está autenticado.
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  // Estado para errores específicos del proceso de login (más allá de la inicialización).
  const [loginError, setLoginError] = useState<string | null>(null);
  // Estado para el isLoading del proceso de login específico (cuando se llama a la función login).
  const [isLoginProcessLoading, setIsLoginProcessLoading] = useState(false);

  // Efecto para la validación inicial del token al cargar la aplicación.
  useEffect(() => {
    // Al inicio del efecto, asumimos que estamos cargando.
    setIsInitialLoading(true);
    // Limpiamos errores previos.
    setLoginError(null);

    if (storedToken) {
      // Si hay un token almacenado, intentamos decodificarlo.
      const decoded = decodeToken(storedToken);
      if (decoded) {
        // Si la decodificación es exitosa, el usuario está autenticado.
        setIsAuthenticated(true);
      } else {
        // Si la decodificación falla (token corrupto/inválido), lo limpiamos.
        clearToken();
        setIsAuthenticated(false);
        // Podrías añadir un toast aquí si quieres notificar al usuario que su sesión expiró o es inválida.
      }
    } else {
      // Si no hay token almacenado, el usuario no está autenticado.
      setIsAuthenticated(false);
    }
    // Una vez que la verificación inicial ha terminado, se desactiva la carga inicial.
    setIsInitialLoading(false);
  }, [storedToken, decodeToken, clearToken]); // Dependencias para re-ejecutar si el token almacenado cambia.

  /**
   * Función para iniciar sesión.
   * Procesa y almacena un nuevo token de autenticación.
   * @param newToken El token JWT recién obtenido del backend.
   */
  const login = useCallback(
    async (newToken: string) => {
      setIsLoginProcessLoading(true); // Activa el isLoading para el proceso de login.
      setLoginError(null);
      try {
        const decoded = decodeToken(newToken);
        if (!decoded) {
          clearToken();
          setIsAuthenticated(false);
          const errorMessage = "El token recibido es inválido o corrupto. Por favor, intente iniciar sesión nuevamente.";
          setLoginError(errorMessage);
          throw new Error(errorMessage);
        }
        await updateToken(newToken);
        setIsAuthenticated(true);
      } catch (error: any) {
        console.error("Error al iniciar sesión en el AuthProvider:", error);
        clearToken();
        setIsAuthenticated(false);
        setLoginError(error.message || "Error al procesar el token de sesión.");
        throw error;
      } finally {
        setIsLoginProcessLoading(false); // Desactiva el isLoading del proceso de login.
      }
    },
    [updateToken, clearToken, decodeToken, setIsAuthenticated]
  );

  /**
   * Función para cerrar sesión.
   * Elimina el token y resetea el estado de autenticación.
   */
  const logout = useCallback(() => {
    clearToken();
    setIsAuthenticated(false);
    setLoginError(null);
    // Nota: El isInitialLoading no se reinicia aquí porque es para la carga INICIAL.
    // Si la aplicación se recarga después del logout, se re-evaluará.
  }, [clearToken, setIsAuthenticated]);

  /**
   * Valor del contexto memoizado para optimizar re-renderizados.
   * 'isLoading' combina 'isInitialLoading' y 'isLoginProcessLoading'.
   */
  const value = useMemo(
    () => ({
      token: storedToken,
      userId,
      email,
      isAuthenticated,
      isLoading: isInitialLoading || isLoginProcessLoading, // true si cualquiera de los dos está cargando.
      login,
      logout,
      loginError,
    }),
    // Dependencias de useMemo: se re-calcula si alguno de estos valores cambia.
    [
      storedToken,
      userId,
      email,
      isAuthenticated,
      isInitialLoading,
      isLoginProcessLoading,
      login,
      logout,
      loginError,
    ]
  );

  // Proporciona el contexto a los componentes hijos.
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

/**
 * Hook personalizado para consumir el contexto de autenticación.
 * Permite a cualquier componente funcional acceder al estado y las funciones de autenticación.
 */
export const useAuth = () => useContext(AuthContext);

auth.storage.ts

Este hook personalizado (useAuthStorage) es responsable de la lectura, escritura y eliminación del token de autenticación de forma persistente en el navegador, utilizando las cookies.

  • Descripción: useAuthStorage inicializa el estado del token leyendo una cookie. Proporciona funciones para actualizar y limpiar el token, y un useEffect que sincroniza el estado del token con la cookie, asegurando que el token se guarde o elimine cuando su estado interno cambia.
  • Propósito: Abstraer la lógica de interacción con las cookies para el almacenamiento del token de autenticación. Esto permite que otros hooks y componentes de autenticación (como auth.context.tsx) no necesiten conocer los detalles de cómo se persiste el token, manteniendo la lógica limpia y reutilizable.
  • Funcionalidad clave:
    • Constantes de Configuración:
      • TOKEN_COOKIE_NAME: El nombre de la cookie donde se almacenará el token (ej., "token").
      • TOKEN_EXPIRATION_DAYS: La duración en días que la cookie será válida (actualmente 1 día).
    • Estado del token (useState):
      • Se inicializa con una función que intenta leer el token existente de la cookie TOKEN_COOKIE_NAME utilizando Cookies.get(). Si la cookie no existe, el token se inicializa como null.
    • clearToken() (useCallback):
      • Es una función memoizada (con useCallback) que establece el estado token a null. Al hacer esto, dispara el useEffect para eliminar la cookie.
    • updateToken(newToken: string) (useCallback):
      • Es una función memoizada que establece el estado token a un nuevo valor (newToken). Al hacer esto, dispara el useEffect para guardar el nuevo token en la cookie.
    • Sincronización con Cookies (useEffect):
      • Este efecto se ejecuta cada vez que el estado interno token cambia.
      • Si token tiene un valor: Cookies.set(TOKEN_COOKIE_NAME, token, { ... }) guarda el token en una cookie. Las opciones expires, secure y sameSite se configuran para la seguridad y persistencia de la cookie:
        • expires: TOKEN_EXPIRATION_DAYS: La cookie expirará después del número de días configurado.
        • secure: process.env.NODE_ENV === "production": La cookie solo se enviará sobre HTTPS en producción, protegiéndola.
        • sameSite: "strict": Proporciona protección contra ataques CSRF al asegurar que la cookie solo se envíe en peticiones del mismo sitio.
      • Si token es null: Cookies.remove(TOKEN_COOKIE_NAME) elimina la cookie del navegador, limpiando la sesión.
    • Retorno Memoizado (useMemo):
      • El hook retorna un objeto que contiene el token actual, y las funciones clearToken y updateToken.
      • useMemo se utiliza para memoizar este objeto, asegurando que no se recree en cada render, lo que optimiza el rendimiento y evita problemas de re-renderizado en componentes hijos que dependen de este hook.
  • Rol en la aplicación: useAuthStorage es el mecanismo de persistencia del token de autenticación en el cliente. Es fundamental para mantener la sesión del usuario a través de recargas de página y para la seguridad básica de las cookies, sirviendo como la capa de abstracción para el almacenamiento del token.
tsx
// src/components/auth/auth.storage.ts
import { useState, useEffect, useMemo, useCallback } from "react"; // Hooks de React
import Cookies from "js-cookie"; // Librería para la gestión de cookies del navegador

// Nombre de la cookie donde se almacenará el token de autenticación
const TOKEN_COOKIE_NAME = "token";
// Días de expiración de la cookie del token (actualmente 1 día)
const TOKEN_EXPIRATION_DAYS = 1;

/**
 * Hook personalizado para la gestión del almacenamiento persistente del token de autenticación.
 * Utiliza cookies para guardar, leer y eliminar el token.
 * @returns Un objeto con el token actual y funciones para limpiar/actualizar el token.
 */
export const useAuthStorage = () => {
  // Estado local para almacenar el token. Se inicializa leyendo la cookie al montar el hook.
  const [token, setToken] = useState<string | null>(() => {
    const cookieToken = Cookies.get(TOKEN_COOKIE_NAME); // Intenta obtener el token de la cookie
    return cookieToken || null; // Retorna el token de la cookie o null si no existe
  });

  /**
   * Función memoizada para limpiar el token (establecerlo a null).
   * Al cambiar el estado 'token', el useEffect se encargará de eliminar la cookie.
   */
  const clearToken = useCallback(() => setToken(null), []);

  /**
   * Función memoizada para actualizar el token con un nuevo valor.
   * Al cambiar el estado 'token', el useEffect se encargará de guardar la nueva cookie.
   * @param newToken El nuevo token de autenticación a guardar.
   */
  const updateToken = useCallback((newToken: string) => setToken(newToken), []);

  // Efecto secundario que se ejecuta cada vez que el estado 'token' cambia.
  // Se encarga de sincronizar el estado del token con la cookie del navegador.
  useEffect(() => {
    if (token) {
      // Si el token tiene un valor, lo guarda en una cookie con las opciones de seguridad.
      Cookies.set(TOKEN_COOKIE_NAME, token, {
        expires: TOKEN_EXPIRATION_DAYS, // Días de expiración de la cookie
        secure: process.env.NODE_ENV === "production", // La cookie solo se envía sobre HTTPS en producción
        sameSite: "strict", // Protección CSRF: la cookie solo se envía en peticiones del mismo sitio
      });
    } else {
      // Si el token es null, elimina la cookie.
      Cookies.remove(TOKEN_COOKIE_NAME);
    }
  }, [token]); // Dependencia: el efecto se re-ejecuta cuando el 'token' cambia.

  // Memoiza el objeto de retorno del hook para evitar re-renderizados innecesarios en componentes consumidores.
  return useMemo(
    () => ({
      token, // El token actual
      clearToken, // Función para limpiar el token
      updateToken, // Función para actualizar el token
    }),
    [token, clearToken, updateToken] // Dependencias de useMemo
  );
};

student.profile.details.tsx

Este archivo define el componente StudentDetails, que es la interfaz de usuario para visualizar el perfil completo de un estudiante. Muestra toda la información personal, de contacto, de llamadas y de documentación en un formato organizado.

  • Descripción: StudentDetails es un componente presentacional que recibe los datos de un estudiante (student) y funciones de callback (onEdit, onDelete) para acciones. Organiza la información en varias secciones visuales (información principal, datos personales, datos de contacto, datos de llamadas, documentos) utilizando el componente Grid y ProfileField para la presentación de cada campo.
  • Propósito: Proporcionar una vista detallada, clara y bien estructurada del perfil de un estudiante, permitiendo una rápida comprensión de sus datos. También sirve como punto de interacción para iniciar la edición o eliminación del perfil.
  • Funcionalidad clave:
    • StudentDetailsProps Interface: Define las propiedades que este componente espera: el objeto student completo, y funciones onEdit y onDelete para acciones de usuario.
    • getDocumentButton(label: string, link?: string | File | null):
      • Es una función auxiliar que renderiza un botón para ver documentos asociados al estudiante.
      • Si existe un link (una URL de tipo string), renderiza un enlace (<a>) con un <button> que permite ver el documento en una nueva pestaña.
      • Si no hay link (es undefined, null o un objeto File no persistido), renderiza un <button disabled> con un icono de archivo, indicando que no hay documento disponible. Utiliza la clase css.disabledButton.
    • Sección de Información Principal (mainInformation):
      • Utiliza Grid con grid-columns-2 y css.mainInformation.
      • Muestra un icono de usuario grande (faCircleUser) y el nombre completo del estudiante (student.name, student.lastname).
      • Presenta el email y teléfono del estudiante utilizando componentes ProfileField dentro de una lista (ul).
      • Contiene los botones "Editar" y "Eliminar", que llaman a las props onEdit y onDelete respectivamente. Estos botones están anidados en otro Grid con grid-columns-2.
    • Sección de Datos Personales (personalData):
      • Otra sección organizada con Grid (grid-columns-2 y css.personalData).
      • Muestra campos como RUT, fecha de nacimiento, género, dirección, nacionalidad, colegio y curso, cada uno presentado por un componente ProfileField. Las fechas se formatean a cadena legible (toLocaleDateString).
    • Sección de Datos de Contacto:
      • Anidada dentro de otro Grid (grid-columns-2 y css.personalData), contiene ProfileFields para la fuente, estado de feedback, contacto, fecha de contacto y preferencia de comunicación.
    • Sección de Datos de Llamadas (Datos llamadas):
      • Organizada con Grid (grid-columns-2 y css.personalData).
      • Muestra la información de las tres llamadas (call1, call2, call3) y sus respectivos comentarios (comments1, comments2, comments3) utilizando ProfileFields. Aquí se aplican las clases css.comments a los <ul> que contienen los comentarios.
    • Sección de Documentos:
      • Muestra los botones de documentos generados por getDocumentButton para studentImage, birthCertificate, studyCertificate y linkDni.
    • Sección de Fecha de Creación:
      • Muestra la fecha de creación del registro del estudiante.
    • ProfileField Componente: Utilizado extensivamente para renderizar cada campo de forma consistente, mostrando un icono, una etiqueta y el valor del dato.
  • Rol en la aplicación: StudentDetails es un componente de visualización clave que transforma los datos crudos de un estudiante en una interfaz de usuario organizada y fácil de consumir. Es el componente principal para presentar el perfil de un estudiante en un estado de "solo lectura", permitiendo al usuario iniciar acciones como la edición o eliminación.
tsx
// src/components/common/details-views/student.profile.details.tsx
import type { Student } from "../../../interface/student/student"; // Importación de tipo para la interfaz Student
import { Grid } from "../grid/grid"; // Componente de layout Grid
import css from "../../../assets/styles/layout/student.profile.module.scss"; // Estilos SCSS específicos del perfil
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // Componente para iconos de Font Awesome
import {
  faCircleUser, faIdCard, faBirthdayCake, faVenusMars, faMapMarkerAlt, faFlag,
  faSchool, faChalkboardTeacher, faBook, faCheckCircle, faUser, faCalendarAlt,
  faComment, faMobile, faEnvelope, faFile, // Importación de iconos específicos
} from "@fortawesome/free-solid-svg-icons"; // Paquete de iconos sólidos

import ProfileField from "../profile-field/profile.field"; // Componente para mostrar campos de perfil

/**
 * Propiedades esperadas por el componente StudentDetails.
 */
interface StudentDetailsProps {
  student: Student; // Objeto Student con los datos del perfil a mostrar.
  onEdit: () => void; // Función callback para iniciar el modo de edición.
  onDelete: () => void; // Función callback para iniciar el proceso de eliminación.
}

/**
 * Componente que muestra los detalles del perfil de un estudiante en modo de visualización.
 * @param props.student El objeto Student con la información a mostrar.
 * @param props.onEdit Callback para el botón de edición.
 * @param props.onDelete Callback para el botón de eliminación.
 */
const StudentDetails = ({ student, onEdit, onDelete }: StudentDetailsProps) => {
  /**
   * Función auxiliar para renderizar un botón de documento.
   * Si hay un enlace válido, es un botón clicable que lleva al documento.
   * Si no, es un botón deshabilitado con un icono de archivo.
   * @param label La etiqueta del botón (ej. "Certificado de nacimiento").
   * @param link El enlace al documento, puede ser string (URL), File (nuevo archivo) o null.
   * @returns Un elemento <a> con un botón o un <button> deshabilitado.
   */
  const getDocumentButton = (label: string, link?: string | File | null) => {
    // Determina si el link es una cadena de texto (URL existente)
    const href = typeof link === "string" ? link : undefined;

    if (href) {
      // Si hay un link válido, renderiza un enlace con un botón para ver el documento
      return (
        <a href={href} target="_blank" rel="noopener noreferrer">
          <button>{label}</button>
        </a>
      );
    } else {
      // Si no hay link, renderiza un botón deshabilitado con un icono para indicar que no hay documento
      return (
        <button disabled className={css.disabledButton}>
          <FontAwesomeIcon icon={faFile} /> {label}
        </button>
      );
    }
  };

  return (
    <>
      {/* Sección principal del perfil con nombre, email, teléfono y botones de acción */}
      <Grid className={`grid-columns-2 ${css.mainInformation}`}>
        <div>
          {/* Icono grande de usuario */}
          <FontAwesomeIcon icon={faCircleUser} className={css.iconUser} />
        </div>

        <div>
          {/* Nombre completo del estudiante */}
          <h1>{`${student.name || "NO REGISTRADO"} ${
            student.lastname || "NO REGISTRADO"
          }`}</h1>

          {/* Lista de campos de perfil (Email, Teléfono) */}
          <ul className={css.profile}>
            <ProfileField
              icon={faEnvelope}
              label="Email"
              value={student.email}
            />
            <ProfileField
              icon={faMobile}
              label="Teléfono"
              value={student.phone}
            />
          </ul>
          {/* Contenedor de botones de acción */}
          <Grid className="grid-columns-2">
            <button onClick={onEdit}>Editar</button>
            <button onClick={onDelete}>Eliminar</button>
          </Grid>
        </div>
      </Grid>

      {/* Sección de Datos Personales */}
      <Grid className={`grid-columns-2 ${css.personalData}`}>
        <div>
          <h2>Datos Personales</h2>
          <ul>
            <ProfileField icon={faIdCard} label="Rut" value={student.rut} />
            <ProfileField
              icon={faBirthdayCake}
              label="Fecha de Nacimiento"
              value={
                student.birthdate
                  ? new Date(student.birthdate).toLocaleDateString()
                  : "NO REGISTRADA"
              }
            />
            <ProfileField
              icon={faVenusMars}
              label="Género"
              value={student.sex}
            />
            <ProfileField
              icon={faMapMarkerAlt}
              label="Dirección"
              value={student.address}
            />
            <ProfileField
              icon={faFlag}
              label="Nacionalidad"
              value={student.nationality}
            />
            <ProfileField
              icon={faSchool}
              label="Colegio"
              value={student.school}
            />
            <ProfileField
              icon={faChalkboardTeacher}
              label="Curso"
              value={student.course}
            />
          </ul>
        </div>

        {/* Sección de Datos de Contacto (dentro de personalData grid) */}
        <div>
          <h2>Datos de Contacto</h2>
          <ul>
            <ProfileField icon={faBook} label="Fuente" value={student.source} />
            <ProfileField
              icon={faCheckCircle}
              label="Estado"
              value={student.positiveFeedback}
            />
            <ProfileField
              icon={faUser}
              label="¿Quién realizó el contacto?"
              value={student.contact}
            />
            <ProfileField
              icon={faCalendarAlt}
              label="Fecha de contacto"
              value={
                student.contactDate
                  ? new Date(student.contactDate).toLocaleDateString()
                  : "NO REGISTRADA"
              }
            />
            <ProfileField
              icon={faComment}
              label="Preferencia de Comunicación"
              value={student.communicationPreference}
            />
          </ul>
        </div>
      </Grid>

      {/* Sección de Datos de Llamadas */}
      <Grid className={`grid-columns-2 ${css.personalData}`}>
        <div>
          <h2>Datos llamadas</h2>
        </div>
        {/* Este div vacío puede ser un placeholder o un div para layout, revisar su propósito */}
        <div></div> 

        {/* Sección para Llamada 01 y su comentario */}
        <div>
          <h3>Llamada 01</h3>
          <ul>
            <ProfileField
              icon={faMobile}
              label="¿Contestó la llamada?"
              value={student.call1}
            />
          </ul>
          {/* ul con clase css.comments para el comentario. Aquí es donde el texto se desbordaba. */}
          <ul className={css.comments}>
            <ProfileField
              icon={faComment}
              label="Comentario 01"
              value={student.comments1}
            />
          </ul>
        </div>

        {/* Sección para Llamada 02 y su comentario */}
        <div>
          <h3>Llamada 02</h3>
          <ul>
            <ProfileField
              icon={faMobile}
              label="¿Contestó la llamada?"
              value={student.call2}
            />
          </ul>
          <ul className={css.comments}>
            <ProfileField
              icon={faComment}
              label="Comentario 01"
              value={student.comments2}
            />
          </ul>
        </div>

        {/* Sección para Llamada 03 y su comentario */}
        <div>
          <h3>Llamada 03</h3>
          <ul>
            <ProfileField
              icon={faMobile}
              label="¿Contestó la llamada?"
              value={student.call3}
            />
          </ul>
          <ul className={css.comments}>
            <ProfileField
              icon={faComment}
              label="Comentario 01"
              value={student.comments3}
            />
          </ul>
        </div>
      </Grid>

      {/* Sección de Documentos */}
      <div className={`grid-columns-3 ${css.personalData}`}>
        <div>
          <h2>Documentos</h2>
          <Grid className="grid-columns-2">
            {getDocumentButton("Foto del Estudiante", student.studentImage)}
            {getDocumentButton(
              "Certificado de nacimiento",
              student.birthCertificate
            )}
            {getDocumentButton(
              "Certificado de estudio",
              student.studyCertificate
            )}
            {getDocumentButton("Cédula de identidad", student.linkDni)}
          </Grid>
        </div>
      </div>
      {/* Sección de Fecha de Creación */}
      <div>
        <div>
          <ul>
            <ProfileField
              icon={faCalendarAlt}
              label="Fecha de Creación"
              value={
                student.createdAt
                  ? new Date(student.createdAt).toLocaleDateString()
                  : "NO REGISTRADA"
              }
            />
          </ul>
        </div>
      </div>
    </>
  );
};

export default StudentDetails;

Este archivo define el componente DropdownOptions, responsable de renderizar un conjunto de opciones de menú, generalmente como un submenú desplegable, permitiendo la navegación al seleccionar una opción.

  • Descripción: DropdownOptions es un componente presentacional que recibe un array de objetos DropdownOption (cada uno con una etiqueta y una ruta). Mapea estas opciones a elementos de lista clicables (<li>) que, al ser seleccionados, redirigen al usuario a la ruta especificada.
  • Propósito: Proporcionar un componente reutilizable para mostrar listas de opciones navegables, como los cursos dentro de un colegio en la barra lateral de navegación. Centraliza la lógica de renderizado de opciones y la navegación.
  • Funcionalidad clave:
    • DropdownOption Interface: Importada de ../../../interface/common/dropdown-options/document.dropdown, define la estructura de cada opción (label: string; path: string;).
    • useNavigate(): Hook de react-router-dom utilizado para la navegación programática. Cuando un usuario hace clic en una opción, navigate(option.path) lo lleva a la URL correspondiente.
    • Mapeo de Opciones:
      • Itera sobre el array options recibido como prop utilizando options.map().
      • Renderiza un <li> con:
        • key={option.path}: clave única y estable.
        • onClick={() => navigate(option.path)}: activa la navegación.
        • {option.label}: muestra el texto de la opción.
    • Estilización: El componente acepta props opcionales className y style para aplicar estilos personalizados desde su contexto padre. Ya no se importa el archivo CSS directamente dentro del componente.
  • Rol en la aplicación: DropdownOptions es un componente presentacional y de navegación. Es utilizado por componentes de nivel superior (como la Sidebar) para construir menús dinámicos e interactivos, garantizando que las opciones se muestren de forma coherente y que la interacción del usuario resulte en la navegación esperada.
tsx
// src/components/common/dropdown-options/drop.down.options.tsx

import React from "react";
import { useNavigate } from "react-router-dom";
import { DropdownOptionsProps } from "../../../interface/common/dropdown-options/document.dropdown";

/**
 * Componente que renderiza un conjunto de opciones de menú como lista.
 * @param options - Array de opciones a mostrar (label y path).
 * @param className - Clase CSS opcional para personalizar el menú.
 * @param style - Estilo en línea opcional.
 */
const DropdownOptions: React.FC<DropdownOptionsProps> = ({
  options,
  className,
  style,
}) => {
  const navigate = useNavigate();

  return (
    <ul className={className} style={style}>
      {options.map((option) => (
        <li key={option.path} onClick={() => navigate(option.path)}>
          {option.label}
        </li>
      ))}
    </ul>
  );
};

export default DropdownOptions;

form.input.tsx

Este archivo define el componente FormInput, un componente de UI reutilizable y versátil para representar diferentes tipos de campos de entrada de formulario (texto, email, contraseña, fecha, archivo, select).

  • Descripción: FormInput es un componente presentacional que abstrae la complejidad de renderizar diversos tipos de inputs HTML. Se encarga de mostrar la etiqueta, el input o selector correspondiente, y de gestionar el flujo de datos controlados.
  • Propósito: Proporcionar un bloque de construcción consistente y de fácil uso para la creación de formularios en toda la aplicación. Esto reduce la repetición de código y asegura una apariencia y un comportamiento uniformes para los campos de entrada.
  • Funcionalidad clave:
    • FormInputProps Interface: Importada de ../../../interface/common/input-field/input.field, esta interfaz define todas las propiedades que el componente espera recibir (id, label, name, type, value, onChange, required, options, accept, placeholder, autocomplete).
    • Renderizado Condicional de Inputs: Si type es "select" y hay options, se renderiza un <select>. En caso contrario, se renderiza un <input>.
    • Control de Valor:
      • En select, el valor se fuerza a String(value ?? "") para asegurar que siempre haya un valor de tipo cadena.
      • En input, si es tipo file, se deja como undefined para mantenerlo como input no controlado; si no, se convierte a cadena con String(value ?? "").
    • Mapeo de Opciones: Si el tipo es "select", se agregan las opciones dentro del <select> con su clave como value y texto.
    • Propagación directa de eventos y atributos: Props como onChange, placeholder, autocomplete y accept se pasan directamente al input correspondiente.
    • Accesibilidad (htmlFor): La etiqueta <label> se enlaza con el input a través del atributo htmlFor, usando id o name.
  • Rol en la aplicación: FormInput es un componente base para formularios. Promueve una interfaz uniforme, reduce errores y facilita la mantenibilidad de formularios complejos en toda la aplicación.
tsx
// src/components/common/forms/form.input.tsx

import React from "react";
import type FormInputProps from "../../../interface/common/input-field/input.field";

export const FormInput: React.FC<FormInputProps> = ({
  id,
  label,
  name,
  type,
  value,
  onChange,
  placeholder,
  required,
  options,
  accept,
  autocomplete,
}) => (
  <div>
    {/* Etiqueta para accesibilidad */}
    <label htmlFor={id || name}>{label}</label>

    {/* Si es un selector */}
    {type === "select" && options ? (
      <select
        id={id || name}
        name={name}
        value={String(value ?? "")}
        onChange={onChange}
        required={required}
      >
        <option value="">Seleccione una opción</option>
        {options.map((option) => (
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    ) : (
      <input
        id={id || name}
        type={type}
        name={name}
        value={type !== "file" ? String(value ?? "") : undefined}
        onChange={onChange}
        placeholder={placeholder}
        required={required}
        accept={accept}
        autoComplete={autocomplete}
      />
    )}
  </div>
);

export default FormInput;

add.student.form.tsx

Este archivo define el componente AddStudentForm, que es el formulario presentacional utilizado para ingresar la información de un nuevo estudiante en el sistema.

  • Descripción: AddStudentForm es un componente de React que renderiza un formulario extenso con campos para datos personales, de contacto, de llamadas y de documentación. Utiliza el componente FormInput reutilizable para cada campo y el Grid para la organización del layout. Recibe los datos, el manejador de cambios y el manejador de envío como propiedades.
  • Propósito: Proporcionar una interfaz de usuario completa y estructurada para la entrada de datos de nuevos estudiantes, desacoplando la presentación del formulario de la lógica de gestión de estado y envío a la API (que reside en el hook useAddStudent y la página AddStudent).
  • Funcionalidad clave:
    • AddStudentFormProps Interface: Define las propiedades que este componente espera recibir: studentData (el objeto Student con los datos actuales del formulario), handleChange (función para actualizar los datos al cambiar un input), y handleSubmit (función para enviar el formulario).
    • FormInput Componente: Utilizado extensamente para renderizar cada campo del formulario (nombres, apellidos, RUT, email, etc.). FormInput es un componente genérico que puede mostrar diferentes tipos de inputs (text, email, date, select, file).
      • Gestión de value: Los valores de los campos (studentData.name, studentData.rut, etc.) se pasan como la prop value a FormInput. Se utiliza el patrón value={studentData.campo || ""} para asegurar que los campos null o undefined se traten como cadenas vacías, evitando advertencias de React para inputs controlados.
      • onChange: La prop handleChange del AddStudentForm se pasa directamente al onChange de cada FormInput, permitiendo que el hook padre (useAddStudent) gestione los cambios de estado de forma centralizada.
      • Fechas (type="date"): Para los campos de fecha (birthdate, contactDate), se utiliza la función utilitaria formatDateForInput para formatear la fecha a la cadena yyyy-MM-dd que los inputs type="date" esperan.
    • FormSection Componente (Reutilizado): Este es un componente genérico que encapsula la estructura común de las secciones del formulario (div con título h2 y un Grid). Esto hace que el JSX del formulario principal sea mucho más conciso y legible.
    • Centralización de Opciones para Selectores: Todos los arrays de opciones para los selects (Género, Fuente, Escuela, Curso, Estado, etc.) ahora se importan desde ../../../../utils/constants. Esto elimina la duplicación de datos directamente en el JSX, facilitando el mantenimiento y asegurando la consistencia de las opciones en toda la aplicación.
    • Grid Componente: Utilizado para organizar los campos del formulario en columnas responsivas, aplicando clases CSS como "grid-columns-3" o "grid-columns-2" para estructurar visualmente las secciones.
    • Secciones del Formulario: El formulario está dividido en secciones lógicas mediante el uso del componente FormSection, mejorando la legibilidad y la organización visual.
    • Botón de Envío (<button type="submit">): Un botón al final del formulario que, al ser clicado, activa la función handleSubmit proporcionada por las props.
  • Rol en la aplicación: AddStudentForm es un componente presentacional especializado. Es la interfaz de usuario para el ingreso de datos de nuevos estudiantes, manteniendo la lógica de formulario y la interacción con la API completamente abstraída en el hook useAddStudent y la página AddStudent. Su diseño modular y el uso de componentes genéricos lo hacen robusto y fácil de mantener.
tsx
// src/components/common/forms/add-student/add.student.form.tsx
import React from "react";
import FormInput from "../form.input";
import css from "../../../../assets/styles/layout/student.profile.module.scss";
import { formatDateForInput } from "../../../../utils/date.formatters";
import { AddStudentFormProps } from "../../../../interface/common/forms/add.student.form.props";
import { FormSection } from "../form-section/form.section";
import Constants from "../../../../utils/constants";

export const AddStudentForm: React.FC<AddStudentFormProps> = ({
  studentData,
  handleChange,
  handleSubmit,
}) => {
  return (
    <form onSubmit={handleSubmit}>
      {/* Sección: Datos personales */}
      <FormSection
        title="Datos personales"
        gridClassName="grid-columns-3"
        sectionCssClass={css.datosPersonales}
      >
        <FormInput label="Nombres" name="name" type="text" value={studentData.name} onChange={handleChange} required />
        <FormInput label="Apellidos" name="lastname" type="text" value={studentData.lastname} onChange={handleChange} required />
        <FormInput label="RUT" name="rut" type="text" value={studentData.rut || ""} onChange={handleChange} />
        <FormInput label="Email" name="email" type="email" value={studentData.email || ""} onChange={handleChange} />
        <FormInput label="Teléfono" name="phone" type="text" value={studentData.phone || ""} onChange={handleChange} />
        <FormInput label="Fecha de Nacimiento" name="birthdate" type="date" value={formatDateForInput(studentData.birthdate)} onChange={handleChange} />
        <FormInput label="Género" name="sex" type="select" value={studentData.sex || ""} onChange={handleChange} options={Constants.GENDER_OPTIONS} />
        <FormInput label="Dirección" name="address" type="text" value={studentData.address || ""} onChange={handleChange} />
        <FormInput label="Nacionalidad" name="nationality" type="text" value={studentData.nationality || ""} onChange={handleChange} />
        <FormInput label="Fuente" name="source" type="select" value={studentData.source || ""} onChange={handleChange} options={Constants.SOURCE_OPTIONS} />
        <FormInput label="Escuela" name="school" type="select" value={studentData.school || ""} onChange={handleChange} options={Constants.SCHOOL_OPTIONS} />
        <FormInput label="Curso" name="course" type="select" value={studentData.course || ""} onChange={handleChange} options={Constants.COURSE_OPTIONS} />
        <FormInput label="Período de postulación" name="applicationPeriod" type="select" value={studentData.applicationPeriod || ""} onChange={handleChange} options={Constants.applicationPeriod} />
      </FormSection>

      {/* Sección: Contacto */}
      <FormSection
        title="Contacto"
        gridClassName="grid-columns-2"
        sectionCssClass={css.datosComunicacion}
      >
        <FormInput label="Estado" name="positiveFeedback" type="select" value={studentData.positiveFeedback || ""} onChange={handleChange} options={Constants.allFeedbacks} />
        <FormInput label="¿Quién realizó el contacto?" name="contact" type="select" value={studentData.contact || ""} onChange={handleChange} options={Constants.CONTACT_PERSON_OPTIONS} />
        <FormInput label="Fecha de contacto" name="contactDate" type="date" value={formatDateForInput(studentData.contactDate)} onChange={handleChange} />
        <FormInput label="Preferencia de Comunicación" name="communicationPreference" type="select" value={studentData.communicationPreference || ""} onChange={handleChange} options={Constants.COMMUNICATION_PREFERENCE_OPTIONS} />
        <FormInput label="Llamada 1 - Completada" name="call1" type="select" value={studentData.call1 || ""} onChange={handleChange} options={Constants.CALL_STATUS_OPTIONS} />
        <FormInput label="Comentario Llamada 1" name="comments1" type="text" value={studentData.comments1 || ""} onChange={handleChange} />
        <FormInput label="Llamada 2 - Completada" name="call2" type="select" value={studentData.call2 || ""} onChange={handleChange} options={Constants.CALL_STATUS_OPTIONS} />
        <FormInput label="Comentario Llamada 2" name="comments2" type="text" value={studentData.comments2 || ""} onChange={handleChange} />
        <FormInput label="Llamada 3 - Completada" name="call3" type="select" value={studentData.call3 || ""} onChange={handleChange} options={Constants.CALL_STATUS_OPTIONS} />
        <FormInput label="Comentario Llamada 3" name="comments3" type="text" value={studentData.comments3 || ""} onChange={handleChange} />
      </FormSection>

      {/* Sección: Documentación */}
      <FormSection
        title="Documentación"
        gridClassName="grid-columns-2"
        sectionCssClass={css.datosDocumentacion}
      >
        <FormInput label="Foto del Estudiante" name="studentImage" type="file" accept="image/*" onChange={handleChange} />
        <FormInput label="Certificado de nacimiento" name="birthCertificate" type="file" accept="image/*" onChange={handleChange} />
        <FormInput label="Certificado de estudio" name="studyCertificate" type="file" accept="image/*" onChange={handleChange} />
        <FormInput label="Cédula de identidad" name="linkDni" type="file" accept="image/*" onChange={handleChange} />
      </FormSection>

      <button type="submit">Agregar Estudiante</button>
    </form>
  );
};

form.section.tsx

Este archivo define el componente FormSection, una abstracción reutilizable para estructurar las diferentes secciones dentro de un formulario. Encapsula un patrón común de diseño de formulario, como un título de sección y un layout de cuadrícula para sus campos internos.

  • Descripción: FormSection es un componente presentacional que recibe un título, una clase para su layout de cuadrícula, y el contenido (los campos del formulario) como hijos. Aplica una clase CSS opcional para estilos específicos de la sección.
  • Propósito: Simplificar el JSX de formularios grandes y complejos, como AddStudentForm o UpdateStudentForm. Promueve la consistencia visual y estructural de las secciones de formulario y reduce la repetición de código.
  • Funcionalidad clave:
    • FormSectionProps Interface: Define las propiedades que el componente espera:
      • title: string: El texto que aparecerá como título de la sección (<h2>).
      • gridClassName?: string: Una clase CSS para el componente Grid interno (ej., "grid-columns-3") que define el layout de los campos dentro de esta sección.
      • children: ReactNode: El contenido real de la sección, que serán los componentes FormInput u otros elementos del formulario.
      • sectionCssClass?: string: Una clase CSS opcional que se aplicará al div contenedor principal de la sección (ej., css.datosPersonales de student.profile.module.scss), permitiendo estilos de módulo específicos.
    • Composición de UI: Combina un <h2> para el título y un componente Grid para organizar los children (campos del formulario).
    • Reutilización de Estilos: Permite aplicar estilos de módulo específicos a la sección contenedora a través de sectionCssClass, conectándose a las definiciones de layout en tus archivos SCSS.
  • Rol en la aplicación: FormSection es un componente común de UI de formularios que actúa como un "bloque de construcción" para organizar formularios. Mejora la legibilidad y la modularidad de formularios complejos al encapsular la estructura repetitiva de sus secciones.
tsx
// src/components/common/forms/form-section/form.section.tsx
import React from 'react';
import { Grid } from '../../grid/grid'; // Ajusta la ruta a tu componente Grid
import type { ReactNode } from 'react'; // Importación de tipo para los hijos de React

// Propiedades esperadas por el componente FormSection
interface FormSectionProps {
  title: string;          // Título de la sección (ej. "Datos personales")
  gridClassName?: string; // Clase para el layout de la cuadrícula (ej. "grid-columns-3")
  children: ReactNode;    // Contenido de la sección (los FormInput, etc.)
  sectionCssClass?: string; // Clase CSS específica para el div de la sección (ej. css.datosPersonales)
}

/**
 * Componente genérico para crear secciones de formulario.
 * Encapsula el título, el layout de cuadrícula y el contenido de la sección.
 * @param props.title Título de la sección.
 * @param props.gridClassName Clase CSS para el componente Grid interno.
 * @param props.children Los elementos de React a renderizar dentro de la sección.
 * @param props.sectionCssClass Clase CSS opcional para el div principal de la sección.
 */
export const FormSection: React.FC<FormSectionProps> = ({
  title,
  gridClassName,
  children,
  sectionCssClass,
}) => {
  return (
    // El div principal de la sección, aplicando la clase CSS específica si se proporciona
    <div className={sectionCssClass}>
      <h2>{title}</h2> {/* Título de la sección */}
      {/* El componente Grid organiza el contenido interno del formulario */}
      <Grid className={gridClassName}>
        {children} {/* Aquí se renderizan los campos del formulario */}
      </Grid>
    </div>
  );
};

export default FormSection;

update.student.form.tsx

Este archivo define el componente StudentForm (exportado como UpdateStudentForm), que es el formulario presentacional utilizado para editar la información de un estudiante existente en el sistema.

  • Descripción: StudentForm renderiza un formulario extenso similar al de adición, pero adaptado para la edición. Permite modificar datos personales, de contacto, de llamadas y de documentación. Incluye la funcionalidad para ver documentos existentes y marcarlos para eliminación. Utiliza FormInput para campos individuales, Grid para el layout y el nuevo componente FormSection para organizar las secciones. Recibe los datos del estudiante a editar, junto con los manejadores de cambios, envío y eliminación de archivos.
  • Propósito: Proporcionar una interfaz de usuario completa y estructurada para la edición de perfiles de estudiantes, desacoplando la presentación del formulario de la lógica de gestión de estado y envío a la API (que reside en el hook useStudentProfile y la página StudentProfile).
  • Funcionalidad clave:
    • StudentFormProps Interface: Define las propiedades que este componente espera recibir: student (el objeto Student con los datos actuales a editar), onChange (función para actualizar los datos al cambiar un input), onSubmit (función para enviar el formulario), y onDeleteFile (función para marcar un archivo específico para eliminación).
    • useDocumentHandling() Hook: Utiliza este hook (importado de ../../../../hooks/use.document.handling.ts) para centralizar la lógica de manejo de inputs de tipo file y la confirmación de eliminación de archivos. Proporciona handleFileChange (para el onChange de inputs de archivo) y handleDeleteFile (renombrada a handleConfirmDeleteFile para evitar conflictos) para la eliminación.
    • renderDocumentInput(label: string, name: keyof Student) (useCallback):
      • Es una función auxiliar memoizada que renderiza un FormInput de tipo file.
      • Si el estudiante ya tiene un archivo existente (typeof value === "string" y value no es nulo/vacío), muestra un enlace para "Ver actual" y un botón "Eliminar" con un icono de basura.
      • El botón "Eliminar" llama a handleConfirmDeleteFile, activando el diálogo de confirmación.
      • Utiliza useCallback para memoizar la función y evitar re-creaciones innecesarias, mejorando el rendimiento.
    • FormSection Componente (Reutilizado): Este componente genérico (importado de ../form-section/form.section.tsx) encapsula la estructura común de las secciones del formulario (div con título h2 y un Grid). Mejora significativamente la legibilidad y la modularidad del JSX del formulario.
    • Centralización de Opciones para Selectores: Todos los arrays de opciones para los selects (Género, Fuente, Escuela, Curso, Estado, etc.) ahora se importan desde ../../../utils/constants.ts. Esto elimina la duplicación de datos directamente en el JSX, facilitando el mantenimiento y asegurando la consistencia de las opciones en toda la aplicación.
    • FormInput Componente: Utilizado extensivamente para renderizar cada campo del formulario.
      • Gestión de value: Los valores de los campos se pasan como la prop value a FormInput. Se usa value={student.campo || ""} para asegurar que los campos null o undefined se traten como cadenas vacías.
      • onChange: La prop onChange del StudentForm se pasa directamente al onChange de cada FormInput.
      • Fechas (type="date"): Para los campos de fecha, se utiliza la función utilitaria formatDateForInput (importada de ../../../utils/date.formatters).
    • Grid Componente: Utilizado para organizar los campos del formulario en columnas responsivas.
    • Botón de Envío (<button type="submit">): Un botón al final del formulario que, al ser clicado, activa la función onSubmit proporcionada por las props.
  • Rol en la aplicación: UpdateStudentForm es un componente presentacional especializado que proporciona la interfaz de usuario para la edición de perfiles de estudiantes. Funciona en conjunto con el hook useStudentProfile y delega la lógica de estado y API, manteniendo el formulario limpio y enfocado en la interacción del usuario.
tsx
// src/components/common/forms/update-student/update.student.form.tsx

import { useCallback } from "react"; // Hook de React para memoizar funciones
import type { Student } from "../../../../interface/student/student"; // Importación de tipo para la interfaz Student
import FormInput from "../form.input"; // Importa el componente de input genérico
import { Grid } from "../../grid/grid"; // Importa el componente de layout de cuadrícula
import css from "../../../../assets/styles/layout/student.profile.module.scss"; // Estilos CSS para el layout del perfil
import { formatDateForInput } from "../../../../utils/date.formatters"; // Importa la función utilitaria para formatear fechas
import { useDocumentHandling } from "../../../../hooks/use.document.handling"; // Hook para manejo de documentos de archivos
import type { StudentFormProps } from "../../../../interface/student/update.student.form.props"; // Importación de tipo para las propiedades del formulario
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // Componente para iconos de Font Awesome
import { faTrash } from "@fortawesome/free-solid-svg-icons"; // Icono de basura
import { FormSection } from "../form-section/form.section"; // Importa el componente FormSection

// Importaciones de constantes para las opciones de los selectores, centralizadas en utils/constants.ts
import {
  GENDER_OPTIONS,
  SOURCE_OPTIONS,
  SCHOOL_OPTIONS,
  COURSE_OPTIONS,
  allFeedbacks,
  CONTACT_PERSON_OPTIONS,
  COMMUNICATION_PREFERENCE_OPTIONS,
  CALL_STATUS_OPTIONS,
} from "../../../../utils/constants";

/**
 * Componente presentacional para el formulario de actualización de un estudiante existente.
 * Recibe los datos del estudiante, manejadores de cambio, envío y eliminación de archivos desde un hook padre.
 * @param props.student El objeto Student con los datos a editar.
 * @param props.onChange Función manejadora de cambios en los inputs.
 * @param props.onSubmit Función manejadora del envío del formulario.
 * @param props.onDeleteFile Función manejadora para marcar un archivo como eliminado.
 */
const StudentForm: React.FC<StudentFormProps> = ({
  student, // Datos del estudiante a mostrar/editar
  onChange, // Manejador de cambios para los inputs del formulario
  onSubmit, // Manejador para el envío del formulario
  onDeleteFile, // Manejador para la eliminación de archivos específicos
}) => {
  // Hook useDocumentHandling: Proporciona funciones para manejar inputs de tipo file
  // y para la confirmación de eliminación de archivos.
  // 'handleDeleteFile' del hook es renombrada a 'handleConfirmDeleteFile' para evitar conflictos.
  const { handleFileChange, handleDeleteFile: handleConfirmDeleteFile } =
    useDocumentHandling({ onChange, onDeleteFile });

  /**
   * Función auxiliar memoizada para renderizar un FormInput específico para la carga de documentos.
   * Muestra un enlace para ver el archivo existente y un botón para eliminarlo si ya existe.
   * @param label Etiqueta visible del input (ej. "Foto del Estudiante").
   * @param name Nombre del campo en el objeto Student (ej. 'studentImage').
   * @returns Un elemento JSX que representa el input de documento con sus acciones.
   */
  const renderDocumentInput = useCallback(
    (label: string, name: keyof Student) => {
      const value = student[name]; // Obtiene el valor actual del campo de documento del estudiante
      const hasExistingFile = value && typeof value === "string"; // Determina si hay un archivo existente (URL)

      return (
        <div className={css.documentInputContainer}>
          <FormInput
            label={label}
            name={name}
            type="file" // Tipo de input para archivos
            accept="image/*" // Solo acepta archivos de imagen
            // Se usa el handleFileChange del hook useDocumentHandling para procesar la selección de archivos
            onChange={(e) =>
              handleFileChange(e as React.ChangeEvent<HTMLInputElement>)
            }
          />
          {hasExistingFile && ( // Si existe un archivo previo (URL), muestra las acciones para verlo/eliminarlo
            <div className={css.documentActions}>
              <a
                href={value as string} // Usa el valor como href (se asegura que es string)
                target="_blank" // Abre el enlace en una nueva pestaña
                rel="noopener noreferrer" // Mejora la seguridad al abrir enlaces externos
                className={css.viewDocument}
              >
                Ver actual
              </a>
              <button
                type="button" // Importante: previene que este botón envíe el formulario
                // Llama a la función de confirmación de eliminación del hook useDocumentHandling
                onClick={() => handleConfirmDeleteFile(name)}
                className={css.deleteDocument}
              >
                <FontAwesomeIcon icon={faTrash} /> Eliminar
              </button>
            </div>
          )}
        </div>
      );
    },
    // Dependencias de useCallback: se re-crea si alguna de estas funciones o el 'student' cambia.
    // 'css' también se incluye si se usa en el closure y no es una constante global.
    [handleFileChange, handleConfirmDeleteFile, student, css]
  );

  return (
    <form onSubmit={onSubmit}>
      {/* Sección de Datos Personales usando el componente FormSection */}
      <FormSection
        title="Datos personales"
        gridClassName="grid-columns-3"
        sectionCssClass={css.datosPersonales}
      >
        <FormInput
          label="Nombres"
          name="name"
          type="text"
          value={student.name || ""} // Asegura que el valor sea una cadena para el input controlado
          onChange={onChange}
          required
        />
        <FormInput
          label="Apellido"
          name="lastname"
          type="text"
          value={student.lastname || ""}
          onChange={onChange}
          required
        />
        <FormInput
          label="RUT"
          name="rut"
          type="text"
          value={student.rut || ""}
          onChange={onChange}
        />
        <FormInput
          label="Email"
          name="email"
          type="email"
          value={student.email || ""}
          onChange={onChange}
        />
        <FormInput
          label="Teléfono"
          name="phone"
          type="text"
          value={student.phone || ""}
          onChange={onChange}
        />
        <FormInput
          label="Fecha de Nacimiento"
          name="birthdate"
          type="date"
          // Uso directo de la función utilitaria formatDateForInput para formatear la fecha
          value={formatDateForInput(student.birthdate)}
          onChange={onChange}
        />
        <FormInput
          label="Género"
          name="sex"
          type="select"
          value={student.sex || ""}
          onChange={onChange}
          options={GENDER_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Dirección"
          name="address"
          type="text"
          value={student.address || ""}
          onChange={onChange}
        />
        <FormInput
          label="Nacionalidad"
          name="nationality"
          type="text"
          value={student.nationality || ""}
          onChange={onChange}
        />
        <FormInput
          label="Fuente"
          name="source"
          type="select"
          value={student.source || ""}
          onChange={onChange}
          options={SOURCE_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Escuela"
          name="school"
          type="select"
          value={student.school || ""}
          onChange={onChange}
          options={SCHOOL_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Curso"
          name="course"
          type="select"
          value={student.course || ""}
          onChange={onChange}
          options={COURSE_OPTIONS} // Utiliza la constante importada
        />
      </FormSection>

      {/* Sección de Contacto usando el componente FormSection */}
      <FormSection
        title="Contacto"
        gridClassName="grid-columns-2"
        sectionCssClass={css.datosComunicacion}
      >
        <FormInput
          label="Estado"
          name="positiveFeedback"
          type="select"
          value={student.positiveFeedback || ""}
          onChange={onChange}
          options={allFeedbacks} // Utiliza la constante importada
        />
        <FormInput
          label="¿Quién realizó el contacto?"
          name="contact"
          type="select"
          value={student.contact || ""}
          onChange={onChange}
          options={CONTACT_PERSON_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Fecha de contacto"
          name="contactDate"
          type="date"
          value={formatDateForInput(student.contactDate)}
          onChange={onChange}
        />
        <FormInput
          label="Preferencia de Comunicación"
          name="communicationPreference"
          type="select"
          value={student.communicationPreference || ""}
          onChange={onChange}
          options={COMMUNICATION_PREFERENCE_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Llamada 1 - Completada"
          name="call1"
          type="select"
          value={student.call1 || ""}
          onChange={onChange}
          options={CALL_STATUS_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Comentario Llamada 1"
          name="comments1"
          type="text"
          value={student.comments1 || ""}
          onChange={onChange}
        />
        <FormInput
          label="Llamada 2 - Completada"
          name="call2"
          type="select"
          value={student.call2 || ""}
          onChange={onChange}
          options={CALL_STATUS_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Comentario Llamada 2"
          name="comments2"
          type="text"
          value={student.comments2 || ""}
          onChange={onChange}
        />
        <FormInput
          label="Llamada 3 - Completada"
          name="call3"
          type="select"
          value={student.call3 || ""}
          onChange={onChange}
          options={CALL_STATUS_OPTIONS} // Utiliza la constante importada
        />
        <FormInput
          label="Comentario Llamada 3"
          name="comments3"
          type="text"
          value={student.comments3 || ""}
          onChange={onChange}
        />
      </FormSection>

      {/* Sección de Documentación usando el componente FormSection */}
      <FormSection
        title="Documentación"
        gridClassName="grid-columns-2"
        sectionCssClass={css.datosDocumentacion}
      >
        {renderDocumentInput("Foto del Estudiante", "studentImage")}
        {renderDocumentInput("Certificado de nacimiento", "birthCertificate")}
        {renderDocumentInput("Certificado de estudio", "studyCertificate")}
        {renderDocumentInput("Cédula de identidad", "linkDni")}
      </FormSection>

      <button type="submit">Actualizar estudiante</button> {/* Botón de envío */}
    </form>
  );
};

export default StudentForm;

grid.tsx

Este archivo define el componente Grid, un componente React versátil que facilita la creación de layouts de cuadrícula utilizando CSS Grid. Permite definir dinámicamente el número de columnas o aplicar clases CSS de cuadrícula predefinidas.

  • Descripción: Grid es un componente presentacional que actúa como un contenedor para organizar sus elementos hijos en un layout de cuadrícula. Recibe propiedades para controlar el número de columnas y puede fusionar clases CSS adicionales para personalizar su apariencia.
  • Propósito: Proporcionar una forma simple y estandarizada de crear layouts de cuadrícula responsivos en toda la aplicación. Esto reduce la repetición de código CSS y JSX para estructuras de cuadrícula comunes.
  • Funcionalidad clave:
    • GridProps Interface: Importada de ../../../interface/common/grid.props, define las propiedades que el componente espera, como children (el contenido a renderizar dentro de la cuadrícula), columns (un número opcional para columnas dinámicas), className (clases CSS adicionales), y ...props (para pasar atributos HTML adicionales al div contenedor).
    • Generación Dinámica de Clases:
      • const dynamicClass = columns ? \grid-columns-${columns}` : "";: Si se proporciona la prop columns(ej.,columns={2}), genera una clase CSS dinámica como "grid-columns-2". Esta clase se espera que se combine con las reglas de CSS que definen el comportamiento de cuadrícula para 2, 3, 4, etc., columnas (definidas en _grid.scss`).
    • Composición de Clases CSS:
      • className={`grid ${dynamicClass} ${className || ""}`}: El componente siempre aplica la clase base "grid". A esta, se le añade la dynamicClass (si existe) y cualquier className adicional que se le pase a la prop className. Esto permite combinar estilos base con estilos dinámicos y específicos del componente.
    • Propagación de Props: ...props asegura que cualquier otra propiedad HTML estándar (ej., id, style, aria-label) que se pase al componente Grid se propague directamente al elemento div subyacente.
    • Contenedor de Hijos (children): Renderiza children dentro del div de la cuadrícula, permitiendo que cualquier elemento HTML o componente React se coloque dentro del layout.
  • Rol en la aplicación: Grid es un componente fundamental de layout. Proporciona una herramienta flexible y declarativa para estructurar el contenido de las páginas de forma responsiva, utilizando las capacidades de CSS Grid definidas en los archivos de estilo (_grid.scss).
tsx
// src/components/common/grid/grid.tsx
import React from "react"; // Importa React
import GridProps from "../../../interface/common/grid.props"; // Importa la interfaz para las propiedades del componente Grid

/**
 * Componente reutilizable para crear layouts de cuadrícula (CSS Grid).
 * Permite organizar elementos hijos en una cuadrícula flexible y responsiva.
 * @param props.children Los elementos a renderizar dentro de la cuadrícula.
 * @param props.columns Opcional. Un número para generar dinámicamente clases como "grid-columns-2".
 * @param props.className Opcional. Clases CSS adicionales a aplicar al contenedor de la cuadrícula.
 * @param {...any} props Atributos HTML estándar que se pasan directamente al div contenedor.
 */
export const Grid: React.FC<GridProps> = ({
  children, // Contenido a renderizar dentro de la cuadrícula
  columns, // Número de columnas para clases dinámicas (ej. 2 -> "grid-columns-2")
  className, // Clases CSS adicionales para el componente Grid
  ...props // Cualquier otra prop HTML estándar (id, style, etc.)
}) => {
  // Genera una clase CSS dinámica si se proporciona el número de columnas (ej. "grid-columns-3")
  const dynamicClass = columns ? `grid-columns-${columns}` : "";

  return (
    // Renderiza un div con las clases CSS base ("grid"), la clase dinámica (si aplica),
    // y cualquier clase adicional proporcionada, fusionándolas.
    <div className={`grid ${dynamicClass} ${className || ""}`} {...props}>
      {children} {/* Renderiza los hijos dentro de la cuadrícula */}
    </div>
  );
};

// Exportación por defecto para mantener la coherencia con otros componentes.
export default Grid;

input.field.tsx

Este archivo define el componente FormInput (aunque en el código se exporta como InputField), un componente React altamente reutilizable para representar diferentes tipos de campos de entrada en formularios (texto, email, contraseña, fecha, archivo, select). Encapsula la etiqueta (<label>) y el elemento de entrada (<input> o <select>), proporcionando una interfaz limpia y consistente para su uso.

  • Descripción: FormInput es un componente presentacional que abstrae la complejidad de renderizar diversos tipos de inputs HTML. Se encarga de mostrar la etiqueta, el input o selector correspondiente, y de gestionar el flujo de datos controlados.
  • Propósito: Proporcionar un bloque de construcción consistente y de fácil uso para la creación de formularios en toda la aplicación. Esto reduce la repetición de código HTML y CSS para inputs básicos y asegura una apariencia y un comportamiento uniformes en toda la aplicación.
  • Funcionalidad clave:
    • FormInputProps Interface: Importada de ../../../interface/common/input.field, esta interfaz define todas las propiedades que el componente espera recibir. Es una interfaz consolidada y más precisa, ya que no incluye null en el tipo value (el componente se encarga de convertir null a "").
    • Renderizado Condicional de Inputs: El componente utiliza una sentencia condicional para renderizar un elemento HTML <select> si el type es "select" y se proporcionan options, o un elemento <input> para todos los demás tipos de inputs.
    • Manejo de value Controlado:
      • Para los selects y inputs (excepto file), value={String(value ?? "")} asegura que el valor sea siempre una cadena de texto. value ?? "" convierte undefined o null a una cadena vacía (""), y String() convierte Date, number o File (si un tipo inesperado llegara a pasar) a su representación en cadena, satisfaciendo al atributo value del HTML.
      • Para los inputs de type="file", el value se establece explícitamente como undefined, ya que estos inputs son por naturaleza "no controlados" en React por la prop value.
    • Mapeo de Opciones para select: Si el type es "select", el componente mapea el array options a elementos <option>, incluyendo una opción por defecto "Seleccione una opción". Se utiliza la opción misma como key, lo cual es adecuado si las opciones son cadenas de texto únicas y estables.
    • Propagación Directa de onChange: La prop onChange se pasa directamente al atributo onChange de los elementos <input> o <select>, eliminando una función wrapper innecesaria y simplificando el flujo de datos.
    • Accesibilidad (htmlFor): La etiqueta (<label>) está correctamente asociada con su input (<input> o <select>) usando el atributo htmlFor y el id (o name) del input, lo que mejora la accesibilidad.
    • Atributos HTML Estándar: Soporta atributos HTML estándar como type, name, required, y accept (para inputs de archivo).
  • Rol en la aplicación: FormInput es un componente común de UI. Es una abstracción de bajo nivel que se utiliza en la construcción de formularios más complejos (como LoginForm, AddStudentForm, UpdateStudentForm) para garantizar la consistencia en la apariencia y la funcionalidad de todos los campos de entrada de texto.
tsx
// src/components/common/forms/form.input.tsx
import React from "react"; // Importa React
import type FormInputProps from "../../../interface/common/input.field"; // Importa la interfaz de propiedades corregida y consolidada

export const FormInput: React.FC<FormInputProps> = ({
  id,
  label,
  name,
  type,
  value, // Este 'value' ahora es string | number | readonly string[] | File | Date | undefined según FormInputProps
  onChange, // Función manejadora del evento de cambio
  className, // Clase CSS para el div contenedor
  placeholder, // Texto de marcador de posición
  required, // Indica si el campo es obligatorio
  options, // Array de opciones para selects
  accept, // Atributo accept para inputs de tipo file
}) => (
  // Contenedor principal del campo de entrada, al que se le aplica la clase CSS.
  <div className={className}>
    {/* Etiqueta asociada al input/select para accesibilidad. Usa 'id' o 'name' como fallback. */}
    <label htmlFor={id || name}>{label}</label>

    {/* Renderizado condicional: si es tipo 'select' y tiene opciones, renderiza un <select> */}
    {type === "select" && options ? (
      <select
        id={id || name} // ID para asociar con la etiqueta
        name={name} // Atributo 'name' para el formulario
        // Convierte el valor a string, manejando undefined/null a "" antes.
        // Esto satisface el requisito de React para inputs controlados.
        value={String(value ?? "")}
        onChange={onChange} // onChange pasado directamente al select
        required={required} // Si el campo es obligatorio
      >
        {/* Opción por defecto para selectores, útil para "seleccione una opción" */}
        <option value="">Seleccione una opción</option>
        {options.map((option) => (
          // Renderiza cada opción del array, usando la opción como clave única
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    ) : (
      // Si no es un 'select', renderiza un <input>
      <input
        id={id || name} // ID para asociar con la etiqueta
        type={type} // Tipo de input (text, email, date, file, etc.)
        name={name} // Atributo 'name' para el formulario
        // Para inputs que no son 'file', convierte a string.
        // Para inputs de tipo 'file', el 'value' debe ser `undefined` para que no sea controlado.
        value={
          type !== "file"
            ? String(value ?? "") // Convierte a string para inputs controlados (ej. text, email, date)
            : undefined // Para inputs de tipo 'file', el valor no debe ser controlado
        }
        onChange={onChange} // onChange pasado directamente al input
        placeholder={placeholder} // Texto de marcador de posición
        required={required} // Si el campo es obligatorio
        accept={accept} // Atributo 'accept' para tipos 'file'
      />
    )}
  </div>
);

// Exportación por defecto del componente InputField.
export default FormInput;

profile.field.tsx

Este archivo define el componente ProfileField, un componente React altamente reutilizable diseñado para mostrar un único campo de información de perfil. Combina un icono, una etiqueta descriptiva y el valor del campo, todo dentro de una estructura de lista.

  • Descripción: ProfileField es un componente presentacional que toma un ícono de Font Awesome, una etiqueta de texto y un valor, y los renderiza juntos. Está optimizado para la legibilidad y la consistencia visual en las páginas de perfil.
  • Propósito: Estandarizar la forma en que se presentan los datos individuales en un perfil (ej., el RUT, la fecha de nacimiento, el email). Esto asegura una apariencia uniforme y facilita el mantenimiento de las páginas de detalle, como student.profile.details.tsx.
  • Funcionalidad clave:
    • ProfileFieldProps Interface: Define las propiedades que el componente espera:
      • icon: IconDefinition: La definición del ícono de Font Awesome a mostrar.
      • label: string: El texto de la etiqueta que describe el campo (ej., "Email").
      • value: string | number | null | undefined: El valor real del campo a mostrar. Puede ser una cadena, un número, null o undefined.
    • Renderizado de Elementos de Lista (<li>): El componente se renderiza como un elemento de lista <li>, lo que lo hace ideal para ser usado dentro de listas (<ul>) para estructurar los datos.
    • Integración de Íconos: Utiliza el componente FontAwesomeIcon para mostrar el ícono (icon={icon}) proporcionado. Se le aplica la clase css.profileIcon para su estilización.
    • Etiqueta y Valor: Muestra la label en negrita (<strong>) seguida de dos puntos y el value.
    • Manejo de Valores Vacíos: Si el value es null o undefined, se renderiza la cadena "NO REGISTRADO" en su lugar. Esto asegura que no queden espacios en blanco confusos en la UI para campos sin datos.
    • Estilización: Aplica css.profileIcon al ícono y asume que los estilos del <li> contenedor (como display: flex; flex-wrap: wrap;) provienen de un SCSS padre (ej., student.profile.module.scss).
  • Rol en la aplicación: ProfileField es un componente común de UI de nivel atómico. Es un bloque de construcción fundamental para las vistas de detalle, simplificando la presentación de información y garantizando la coherencia visual de los campos individuales.
tsx
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import css from "../../../assets/styles/layout/student.profile.module.scss";
import { ProfileFieldProps } from "../../../interface/common/profile-field/profile.field";

const ProfileField: React.FC<ProfileFieldProps> = ({ icon, label, value }) => (
  <li>
    <FontAwesomeIcon icon={icon} className={css.profileIcon} />{" "}
    <strong>{label}:</strong> {value || "NO REGISTRADO"}
  </li>
);

export default ProfileField;

toast.message.ts

Este archivo define la función ToastMessage, que centraliza la lógica para mostrar diferentes tipos de notificaciones toast en la aplicación utilizando la librería react-toastify.

  • Descripción: ToastMessage es una función utilitaria que, al ser llamada, invoca los métodos de react-toastify (toast.success, toast.error, etc.) para mostrar un mensaje. Recibe el tipo de mensaje (success, error, etc.), el texto del mensaje y opciones de configuración adicionales. Su propósito es actuar como un wrapper para la API de react-toastify.
  • Propósito: Estandarizar la forma en que se muestran las notificaciones toast en toda la aplicación. Al centralizar esta funcionalidad, asegura la consistencia en la apariencia y el comportamiento de las notificaciones, y facilita la configuración global de las mismas.
  • Funcionalidad clave:
    • ToastMessageProps Interface: Define las propiedades que la función espera:
      • type: 'success' | 'error' | 'warning' | 'info': El tipo de notificación, que corresponde a los métodos de toast de react-toastify.
      • message: string: El texto principal del mensaje a mostrar.
      • options?: ToastOptions: Un objeto opcional para sobrescribir las opciones de configuración predeterminadas de la notificación.
    • defaultOptions Constante: Un objeto que define la configuración predeterminada para todas las notificaciones toast si no se proporcionan opciones específicas. Incluye position, autoClose, hideProgressBar, closeOnClick, pauseOnHover, draggable, progress, y theme.
    • ToastMessage Función:
      • Toma las props type, message y options.
      • Combina las defaultOptions con cualquier options personalizada proporcionada (mergedOptions).
      • Utiliza una sentencia switch para llamar al método toast apropiado (toast.success, toast.error, etc.) basándose en la type de la notificación. El default maneja tipos no reconocidos con un toast genérico.
      • No renderiza UI: Esta función no retorna ningún elemento de React (HTML/JSX), ya que su único propósito es disparar la notificación a través de la API de react-toastify, la cual es gestionada por el ToastContainer (definido en main.tsx).
  • Rol en la aplicación: ToastMessage es una utilidad de UI que actúa como una capa de abstracción sobre react-toastify. Simplifica cómo los desarrolladores muestran notificaciones en cualquier parte de la aplicación, interactuando con la infraestructura de notificaciones global.
tsx
// src/components/common/toast-message/toast.message.ts
import { toast, ToastOptions } from 'react-toastify'; // Importa la API de toast y sus opciones de tipo

/**
 * Define las propiedades esperadas por la función ToastMessage.
 */
interface ToastMessageProps {
    type: 'success' | 'error' | 'warning' | 'info'; // Tipo de notificación (determina el estilo y el método de toast)
    message: string;                               // El mensaje de texto a mostrar en la notificación
    options?: ToastOptions;                        // Opciones de configuración adicionales para la notificación (opcional)
}

/**
 * Opciones de configuración predeterminadas para todas las notificaciones toast.
 * Estas pueden ser sobrescritas por las 'options' pasadas a ToastMessage.
 */
const defaultOptions: ToastOptions = {
    position: "top-right",   // Posición en la pantalla (ej. esquina superior derecha)
    autoClose: 3000,         // Tiempo en milisegundos para que la notificación se cierre automáticamente (3 segundos)
    hideProgressBar: false,  // Si se muestra la barra de progreso de cierre
    closeOnClick: true,      // Cierra la notificación al hacer clic en ella
    pauseOnHover: true,      // Pausa el temporizador de cierre automático al pasar el ratón
    draggable: true,         // Permite arrastrar la notificación
    progress: undefined,     // Progreso de la barra (por defecto undefined, react-toastify lo gestiona)
    theme: "light",          // Tema visual de la notificación (claro u oscuro)
};

/**
 * Función para mostrar notificaciones toast en la aplicación.
 * Este componente no renderiza nada en el DOM directamente, sino que dispara una notificación
 * a través de la API de `react-toastify`, la cual es gestionada por `ToastContainer` en `main.tsx`.
 * @param props.type El tipo de notificación (success, error, warning, info).
 * @param props.message El mensaje a mostrar.
 * @param props.options Opciones para sobrescribir las predeterminadas.
 */
export const ToastMessage = ({ type, message, options }: ToastMessageProps) => {
    // Combina las opciones predeterminadas con las opciones pasadas por props.
    const mergedOptions = { ...defaultOptions, ...options };

    // Utiliza un switch para llamar al método de toast correspondiente al tipo.
    switch (type) {
        case 'success':
            toast.success(message, mergedOptions);
            break;
        case 'error':
            toast.error(message, mergedOptions);
            break;
        case 'warning':
            toast.warn(message, mergedOptions);
            break;
        case 'info':
            toast.info(message, mergedOptions);
            break;
        default:
            toast(message, mergedOptions); // Para tipos no reconocidos o genéricos
            break;
    }
};

protected.layout.tsx

Este archivo define el componente ProtectedLayout, que establece la estructura visual principal para todas las rutas de la aplicación a las que solo pueden acceder los usuarios autenticados. Típicamente, este layout incluye elementos de navegación persistentes, como una barra lateral, y un área designada para el contenido de la ruta actual.

  • Descripción: ProtectedLayout es un componente de React que utiliza React.memo para optimización de rendimiento. Renderiza una barra lateral (Sidebar) y un área de contenido principal. Su diseño simplificado se basa en la premisa de que la ProtectedRoute (su componente padre en la jerarquía de rutas) ya ha manejado la autenticación y los estados de carga.
  • Propósito: Proporcionar una estructura de interfaz de usuario consistente (barra lateral, área de contenido principal) para todas las páginas protegidas de la aplicación. Centraliza los elementos de layout comunes para evitar la duplicación de código en cada página individual.
  • Funcionalidad clave:
    • React.memo(() => { ... }): Envuelve el componente para optimizar su rendimiento. React.memo memoiza el componente, lo que significa que React lo re-renderizará solo si sus props cambian. Dado que un layout a menudo no recibe props que cambian frecuentemente, esto puede evitar re-renders innecesarios.
    • Delegación de Autenticación y Carga: A diferencia de versiones anteriores, este layout ya no contiene comprobaciones explícitas de isLoading o isAuthenticated. Se asume que el componente ProtectedRoute (que es su padre directo en el enrutamiento) ya ha verificado el estado de autenticación y ha gestionado las redirecciones o la visualización de un spinner de carga. ProtectedLayout se renderiza solo cuando el usuario ya está autenticado y listo.
    • Sidebar Componente: Renderiza el componente de la barra lateral de navegación (importado de ../../sidebar/sidebar.tsx), que proporciona la navegación principal para la aplicación.
    • Elemento main Semántico: Utiliza la etiqueta semántica <main> para el área de contenido principal, aplicando la clase css.mainContent. Esto mejora la estructura del documento y la accesibilidad.
    • Contenedor de Contenido (mainBackground): Dentro de <main>, un div con la clase css.mainBackground envuelve el contenido real de la ruta. Esta clase probablemente aplica estilos de fondo, padding y sombra.
    • <Outlet />: Componente de react-router-dom. Es un marcador de posición que renderiza el elemento children de la ruta anidada actual que coincide con la URL. Por ejemplo, si la ruta es /dashboard/student/123, Outlet renderizará el StudentProfile correspondiente.
    • Estilización (css.layoutContainer, css.mainContent, css.mainBackground): Aplica clases CSS desde protected.layout.module.scss para definir el layout Flexbox principal, el área de contenido y el fondo de las secciones.
  • Rol en la aplicación: ProtectedLayout es un componente estructural clave. Es el "esqueleto" que proporciona una experiencia de usuario unificada para todas las páginas protegidas, integrando la navegación persistente y el área de contenido dinámico de manera coherente y eficiente, basándose en la pre-verificación de autenticación de sus componentes padres.
tsx
// src/components/layouts/protected-layout/protected.layout.tsx
import React from "react"; // Importa React
import { Outlet } from "react-router-dom"; // Componente para renderizar rutas anidadas
import Sidebar from "../../sidebar/sidebar"; // Importa el componente Sidebar
import { useAuth } from "../../auth/auth.context"; // Importa el hook para acceder al contexto de autenticación (para posible uso futuro, no para renderizado condicional directo aquí)
import css from "../../../assets/styles/layout/protected.layout.module.scss"; // Estilos SCSS para el layout protegido

/**
 * Componente de layout para rutas protegidas.
 * Define la estructura visual principal (barra lateral y contenido) para usuarios autenticados.
 * Utiliza React.memo para optimización de rendimiento.
 *
 * Este layout asume que la `ProtectedRoute` padre ya ha gestionado la autenticación
 * y los estados de carga, por lo que no realiza comprobaciones redundantes aquí.
 */
const ProtectedLayout: React.FC = React.memo(() => {
  // Las comprobaciones de isLoading e isAuthenticated ya se gestionan en ProtectedRoute.
  // Este layout solo se renderiza si el usuario está autenticado y la carga ha finalizado.
  // No es necesario usar useAuth().isAuthenticated o useAuth().isLoading aquí directamente
  // para el renderizado condicional, lo que simplifica el componente.
  // La importación de useAuth se mantiene por si en el futuro se necesitara acceder a otros
  // aspectos del contexto de autenticación no relacionados con el renderizado condicional principal.

  return (
    // Contenedor principal del layout, aplicando estilos CSS.
    <div className={css.layoutContainer}>
      {/* Componente de la barra lateral de navegación. */}
      <Sidebar />
      {/* Contenedor principal del contenido de la página. */}
      <main className={css.mainContent}>
        {/* Un div para el fondo y la sombra del contenido principal. */}
        <div className={css.mainBackground}>
          {/* Outlet renderiza el componente de la ruta anidada actual. */}
          <Outlet />
        </div>
      </main>
    </div>
  );
});

export default ProtectedLayout;

public.layout.tsx

Este archivo define el componente PublicLayout, que establece la estructura visual para todas las rutas de la aplicación a las que no se requiere autenticación para acceder.

  • Descripción: PublicLayout es un componente presentacional simple que sirve como un envoltorio para el contenido de las páginas públicas. Recibe y renderiza sus elementos hijos (children) dentro de un contenedor principal (<main>).
  • Propósito: Proporcionar una estructura de interfaz de usuario consistente y mínima para las páginas accesibles públicamente (como la página de inicio de sesión), asegurando que se presenten sin elementos de navegación protegidos (como una barra lateral).
  • Funcionalidad clave:
    • PublicLayoutProps Interface: Importada de ../../../interface/layouts/public.layouts.props, esta interfaz define las propiedades que el componente espera, principalmente children (el contenido de la página pública a renderizar dentro del layout).
    • Elemento main Semántico: Utiliza la etiqueta semántica <main> para el área de contenido principal. Esto mejora la estructura del documento y la accesibilidad.
    • Clase CSS (public-layout): Aplica una clase CSS global (className="public-layout") a un div contenedor. Esta clase sería responsable de cualquier estilo general para el layout público (como centrado, fondos, etc.).
    • Contenedor de Hijos (children): Simplemente renderiza los children pasados como propiedades dentro del elemento <main>, actuando como un simple "passthrough" para el contenido de la página.
  • Rol en la aplicación: PublicLayout es un componente estructural que define el esqueleto básico para las páginas públicas. Asegura que estas páginas tengan un diseño coherente y estén visualmente separadas de las áreas protegidas de la aplicación.
tsx
// src/components/layouts/public-layout/public.layout.tsx
import PublicLayoutProps from "../../../interface/layouts/public.layouts.props"; // Importa la interfaz de propiedades para el layout público

/**
 * Componente de layout para rutas públicas (no protegidas).
 * Proporciona una estructura mínima para el contenido de páginas accesibles sin autenticación.
 * @param props.children Los elementos de React a renderizar dentro del layout.
 */
const PublicLayout: React.FC<PublicLayoutProps> = ({ children }) => {
  return (
    // Contenedor principal del layout público.
    // La clase 'public-layout' se espera que sea definida globalmente en CSS/SCSS.
    <div className="public-layout">
      {/* Elemento semántico <main> para el contenido principal de la página. */}
      <main>{children}</main>
    </div>
  );
};

export default PublicLayout;

Este archivo define el componente Sidebar, que es la barra lateral de navegación principal de la aplicación. Proporciona enlaces a las diferentes secciones del dashboard y menús desplegables para filtrar estudiantes por colegio y curso.

  • Descripción: Sidebar es un componente de React que gestiona su estado de apertura/cierre (para móvil) y el estado de los menús desplegables. Muestra enlaces estáticos y genera dinámicamente opciones de navegación basadas en datos predefinidos. Utiliza Font Awesome para iconos y estilos específicos definidos en sidebar.module.scss.
  • Propósito: Facilitar la navegación del usuario a través de las diferentes vistas de la aplicación. En pantallas pequeñas, se convierte en un menú "hamburguesa" oculto/visible, mejorando la usabilidad móvil.
  • Funcionalidad clave:
    • Estados Internos (useState):
      • isMenuOpen: Booleano que controla la visibilidad de la barra lateral en dispositivos móviles (si el menú está abierto o cerrado).
      • activeDropdown: Una cadena de texto que almacena la etiqueta del menú desplegable de colegio que está actualmente abierto, o null si ninguno lo está.
    • useAuth(): Hook para acceder al contexto de autenticación, específicamente para la función logout que cierra la sesión del usuario.
    • useNavigate(): Hook de react-router-dom para la navegación programática.
    • toggleMenu(): Función que alterna el estado de isMenuOpen, controlando la visibilidad del menú lateral en pantallas móviles.
    • handleLogout():
      • Llama a la función logout() del contexto de autenticación para limpiar la sesión del usuario.
      • Redirige al usuario a la página de inicio (/) después de cerrar sesión.
      • Cierra el menú lateral (setIsMenuOpen(false)) al completar el logout.
    • dropdownItems (useMemo):
      • Un array memoizado que define la estructura de los menús desplegables para los colegios. Cada ítem incluye una label (nombre del colegio) y un array options (generado por generateCourseOptions) que contiene los cursos de ese colegio con sus respectivas rutas.
      • useMemo optimiza el rendimiento al asegurar que este array solo se recalcule si sus dependencias cambian (en este caso, ninguna, ya que los datos son estáticos).
    • generateCourseOptions(school: string) Función:
      • Ahora importada de ../../utils/navigation.options.ts. Es una función auxiliar que genera las opciones de curso para un colegio dado, construyendo rutas con slugs (/dashboard/colegio-slug/curso-slug) que luego son interpretadas por el hook useSchoolClass.
    • handleDropdownClick(label: string): Manejador de clic para los títulos de los colegios en la barra lateral. Alterna el estado activeDropdown para abrir o cerrar el desplegable correspondiente.
    • handleItemClick(path?: string): Manejador de clic para los elementos de navegación. Si se proporciona una path, redirige al usuario a esa ruta y cierra el menú lateral.
    • Centralización de Constantes: Importa el objeto Constants de ../../utils/constants.ts para acceder a valores como Constants.DROPDOWN_ITEM_HEIGHT_PX, asegurando la consistencia de las dimensiones calculadas para el menú desplegable.
    • Componente DropdownOptions: Utiliza el componente DropdownOptions (importado de ../common/dropdown-options/drop.down.options.tsx) para renderizar los submenús de cursos. Le pasa la clase CSS (css.dropdownMenu) y el objeto style (que contiene la altura dinámica y overflow: hidden) como props, lo que desacopla la estilización y el control del despliegue del componente principal.
    • Estructura del JSX:
      • Un botón de menú "hamburguesa" (menuButton) visible solo en móviles.
      • El contenedor principal de la barra lateral (sidebar), cuya visibilidad es controlada por isMenuOpen y css.open para la animación responsiva.
      • Contiene un logo, títulos de sección (<h3>Colegios</h3>), y listas de navegación (<ul>).
      • Los dropdownItems se mapean para crear los menús desplegables de colegios/cursos.
      • Un botón "Salir" al final para cerrar la sesión.
  • Rol en la aplicación: Sidebar es un componente crucial de navegación y UI. Es la principal interfaz para que los usuarios accedan a diferentes secciones y apliquen filtros globales (a través de los menús desplegables de colegios/cursos), mejorando la usabilidad y la estructura general de la aplicación.
tsx
// src/components/sidebar/sidebar.tsx

import React, { useState, useMemo } from "react";
import { useAuth } from "../auth/auth.context";
import { useNavigate } from "react-router-dom";
import css from "../../assets/styles/components/sidebar.module.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faBars,
  faTimes,
  faHouse,
  faChartPie,
  faGraduationCap,
  faSchool,
} from "@fortawesome/free-solid-svg-icons";
import logo from "../../assets/images/logo-escuelas-blanco.png";

// Importa la función de utilidad para generar opciones de cursos desde utils
import { generateCourseOptions } from "../../utils/navigation.options";
// Importa el objeto Constants que contiene constantes globales como la altura del dropdown
import Constants from "../../utils/constants";

// Importa el componente DropdownOptions desde su ubicación común.
import DropdownOptions from "../common/dropdown-options/drop.down.options";

const Sidebar: React.FC = () => {
  const { logout } = useAuth(); // Obtiene la función de logout del contexto de autenticación
  const navigate = useNavigate(); // Hook para navegar programáticamente
  const [isMenuOpen, setIsMenuOpen] = useState(false); // Estado para controlar la visibilidad del menú lateral en móviles
  const [activeDropdown, setActiveDropdown] = useState<string | null>(null); // Estado para controlar qué menú desplegable de colegio está activo

  /**
   * Alterna el estado de apertura/cierre del menú lateral (para la vista móvil).
   */
  const toggleMenu = () => setIsMenuOpen((prev) => !prev);

  /**
   * Manejador del cierre de sesión.
   * Cierra la sesión del usuario, redirige a la página de inicio y cierra el menú lateral.
   */
  const handleLogout = () => {
    logout(); // Llama a la función de logout del contexto
    navigate("/"); // Redirige a la página de inicio de sesión
    setIsMenuOpen(false); // Asegura que el menú lateral se cierre al hacer logout
  };

  /**
   * Opciones de los menús desplegables de colegios.
   * Se memoizan para optimizar el rendimiento, ya que los datos son estáticos.
   * Cada colegio tiene opciones de curso generadas por 'generateCourseOptions'.
   */
  const dropdownItems = useMemo(
    () => [
      { label: "Quinta normal", options: generateCourseOptions("quinta-normal") },
      { label: "Buín", options: generateCourseOptions("buin") },
      { label: "La Granja", options: generateCourseOptions("la-granja") },
      { label: "Ñuñoa", options: generateCourseOptions("nunoa") },
      { label: "Pudahuel", options: generateCourseOptions("pudahuel") },
      { label: "San Miguel", options: generateCourseOptions("san-miguel") },
    ],
    [] // Array de dependencias vacío, indica que este cálculo solo se hace una vez.
  );

  /**
   * Manejador de clic para los títulos de los colegios en el menú lateral.
   * Alterna el estado 'activeDropdown' para abrir o cerrar el submenú correspondiente.
   * @param label La etiqueta del colegio clicado.
   */
  const handleDropdownClick = (label: string) => {
    // Si el desplegable clicado ya está activo, lo cierra; de lo contrario, lo abre.
    setActiveDropdown(activeDropdown === label ? null : label);
  };

  /**
   * Manejador de clic para los ítems de navegación estáticos y dinámicos (enlaces).
   * Navega a la ruta especificada y cierra el menú lateral.
   * @param path La ruta a la que navegar (opcional).
   */
  const handleItemClick = (path?: string) => {
    if (path) {
      navigate(path); // Navega a la ruta si se proporciona una
    }
    setIsMenuOpen(false); // Asegura que el menú lateral se cierre después de hacer clic en un ítem.
  };

  return (
    <>
      {/* Botón de menú 'hamburguesa' para móviles. Su visibilidad es controlada por CSS. */}
      <button className={css.menuButton} onClick={toggleMenu}>
        {/* Cambia el icono entre 'faBars' (menú cerrado) y 'faTimes' (menú abierto). */}
        <FontAwesomeIcon icon={isMenuOpen ? faTimes : faBars} />
      </button>

      {/* Contenedor principal de la barra lateral.
          La clase 'css.open' (controlada por 'isMenuOpen') gestiona la animación de deslizamiento en móviles. */}
      <div className={`${css.sidebar} ${isMenuOpen ? css.open : ""}`}>
        <img src={logo} alt="logo" className={css.logo} /> {/* Logo de la aplicación */}

        <h3>Principal</h3> {/* Título de la sección de navegación principal */}
        <ul>
          {/* Ítems de navegación principales, con iconos y manejadores de clic. */}
          <li onClick={() => handleItemClick("/dashboard")}>
            <FontAwesomeIcon icon={faHouse} className={css.icons} /> Dashboard
          </li>
          <li onClick={() => handleItemClick("/dashboard/add-student")}>
            <FontAwesomeIcon icon={faGraduationCap} className={css.icons} />
            Agregar estudiante
          </li>
          <li onClick={() => handleItemClick("/dashboard/estadisticas")}>
            <FontAwesomeIcon icon={faChartPie} className={css.icons} />
            Estadísticas
          </li>
        </ul>

        <h3>Colegios</h3> {/* Título de la sección de colegios */}
        <ul>
          {/* Mapea los ítems de colegios para crear los menús desplegables dinámicos. */}
          {dropdownItems.map((item) => (
            <li
              key={item.label} // Utiliza la etiqueta del colegio como clave única.
              className={`${css.dropdownContainer} ${
                activeDropdown === item.label ? css.open : "" // Aplica clase 'open' si el colegio está activo.
              }`}
              onClick={() => handleDropdownClick(item.label)} // Manejador de clic para abrir/cerrar desplegable.
            >
              {/* Contenedor flexible para alinear el icono y el texto del colegio. */}
              <div className={css.dropdownToggle}>
                <FontAwesomeIcon icon={faSchool} className={css.icons} />
                <span>{item.label}</span>
              </div>
              {/* Componente DropdownOptions para renderizar los cursos del colegio. */}
              {/* Pasa la clase CSS para el <ul> y el objeto 'style' para controlar la altura dinámica del desplegable. */}
              <DropdownOptions
                options={item.options} // Opciones de curso para este colegio.
                className={css.dropdownMenu} // Clase CSS para el <ul> del desplegable.
                style={{ // Estilos en línea para la altura dinámica y ocultamiento del overflow.
                  height:
                    activeDropdown === item.label
                      ? `${item.options.length * Constants.DROPDOWN_ITEM_HEIGHT_PX}px` // Altura calculada usando la constante importada.
                      : "0", // Altura 0 para ocultar el menú cuando está colapsado.
                  overflow: "hidden", // Oculta el contenido fuera del límite de altura.
                }}
              />
            </li>
          ))}
        </ul>

        {/* Botón de cierre de sesión */}
        <button onClick={handleLogout}>Salir</button>
      </div>
    </>
  );
};

export default Sidebar;

course.filter.ts

Este archivo define la función CourseFilter, una utilidad para filtrar una lista de estudiantes basándose en un curso específico.

  • Descripción: CourseFilter es una función pura que toma un array de objetos Student y un valor de Course. Devuelve un nuevo array que contiene solo aquellos estudiantes cuya propiedad course coincide con el curso proporcionado.
  • Propósito: Proporcionar una unidad de lógica de filtrado por curso que sea reutilizable y testeable. Esta función se utiliza típicamente por un orquestador de filtros de nivel superior (como StudentFilters) para aplicar criterios de filtrado específicos.
  • Funcionalidad clave:
    • CourseFilterProps Interface: Importada de ../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas: students (el array de estudiantes a filtrar) y course (el valor específico de Course por el cual filtrar).
    • Lógica de Filtrado: Utiliza el método estándar de JavaScript Array.prototype.filter(). Comprueba si la propiedad course de cada objeto student es estrictamente igual (===) al valor course proporcionado en las props.
    • Valor de Retorno: La función devuelve un nuevo array Student[], conteniendo solo los estudiantes que satisfacen la condición de filtrado.
  • Rol en la aplicación: CourseFilter actúa como una utilidad de filtrado específica dentro del sistema de filtrado de estadísticas más amplio. Es un bloque de construcción fundamental para segmentar los datos de los estudiantes por curso académico.
typescript
// src/components/statistics/filters/course.filter.ts
import { Student } from "../../../interface/student/student"; // Importa la interfaz Student
import { CourseFilterProps } from "../../../interface/common/statistics"; // Importa la interfaz CourseFilterProps

/**
 * Filtra una lista de estudiantes por un curso específico.
 * @param {CourseFilterProps} props - Las propiedades para el filtrado.
 * @param {Student[]} props.students - El array de estudiantes a filtrar.
 * @param {Course} props.course - El valor específico del curso por el cual filtrar.
 * @returns {Student[]} Un nuevo array que contiene solo los estudiantes que coinciden con el curso especificado.
 */
export const CourseFilter = ({
  students, // El array de estudiantes a ser filtrado
  course,   // El curso específico por el cual filtrar
}: CourseFilterProps): Student[] => {
  // Devuelve un nuevo array de estudiantes donde el curso del estudiante coincide con el curso proporcionado.
  return students.filter((student) => student.course === course);
};

school.filter.ts

Este archivo define la función SchoolFilter, una utilidad para filtrar una lista de estudiantes basándose en un colegio específico.

  • Descripción: SchoolFilter es una función pura que toma un array de objetos Student y un valor de School. Devuelve un nuevo array que contiene solo aquellos estudiantes cuya propiedad school coincide con el colegio proporcionado.
  • Propósito: Proporcionar una unidad de lógica de filtrado por colegio que sea reutilizable y testeable. Esta función se utiliza típicamente por un orquestador de filtros de nivel superior (como StudentFilters) para aplicar criterios de filtrado específicos.
  • Funcionalidad clave:
    • SchoolFilterProps Interface: Importada de ../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas: students (el array de estudiantes a filtrar) y school (el valor específico de School por el cual filtrar).
    • Lógica de Filtrado: Utiliza el método estándar de JavaScript Array.prototype.filter(). Comprueba si la propiedad school de cada objeto student es estrictamente igual (===) al valor school proporcionado en las props.
    • Valor de Retorno: La función devuelve un nuevo array Student[], conteniendo solo los estudiantes que satisfacen la condición de filtrado.
  • Rol en la aplicación: SchoolFilter actúa como una utilidad de filtrado específica dentro del sistema de filtrado de estadísticas más amplio. Es un bloque de construcción fundamental para segmentar los datos de los estudiantes por institución educativa.
typescript
// src/components/statistics/filters/school.filter.ts
import { Student } from "../../../interface/student/student"; // Importa la interfaz Student
import { SchoolFilterProps } from "../../../interface/common/statistics"; // Importa la interfaz SchoolFilterProps

/**
 * Filtra una lista de estudiantes por un colegio específico.
 * @param {SchoolFilterProps} props - Las propiedades para el filtrado.
 * @param {Student[]} props.students - El array de estudiantes a filtrar.
 * @param {School} props.school - El valor específico del colegio por el cual filtrar.
 * @returns {Student[]} Un nuevo array que contiene solo los estudiantes que coinciden con el colegio especificado.
 */
export const SchoolFilter = ({
  students, // El array de estudiantes a ser filtrado
  school,   // El colegio específico por el cual filtrar
}: SchoolFilterProps): Student[] => {
  // Devuelve un nuevo array de estudiantes donde el colegio del estudiante coincide con el colegio proporcionado.
  return students.filter((student) => student.school === school);
};

student.filter.ts

Este archivo define la función StudentFilters, que actúa como un orquestador de filtros para la lista de estudiantes. Su propósito es aplicar una secuencia de filtros individuales (por fuente, colegio y curso) a una lista de estudiantes, devolviendo un conjunto de datos más refinado.

  • Descripción: StudentFilters es una función pura que recibe una lista de estudiantes y un conjunto de criterios de filtro opcionales (sourceFilter, schoolFilter, courseFilter). Utiliza funciones de filtrado específicas (SourceFilter, SchoolFilter, CourseFilter) para aplicar cada criterio en orden, devolviendo la lista de estudiantes que cumplen con todas las condiciones.
  • Propósito: Centralizar y gestionar la aplicación de múltiples filtros a una lista de estudiantes. Esto asegura que el proceso de filtrado sea consistente, modular y fácil de extender con nuevos criterios en el futuro.
  • Funcionalidad clave:
    • StudentFiltersProps Interface: Importada de ../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas: students (la lista de estudiantes completa), y los criterios de filtro opcionales sourceFilter, schoolFilter, y courseFilter.
    • Importación de Filtros Individuales: Importa las funciones SourceFilter, SchoolFilter, y CourseFilter desde otros archivos en la misma carpeta, lo que permite reutilizar la lógica de filtrado atómico.
    • Lógica de Filtrado Secuencial:
      • Inicializa filteredStudents con la lista students completa.
      • Para cada sourceFilter, schoolFilter o courseFilter que esté presente (no null ni undefined), aplica la función de filtrado correspondiente, pasando la lista filteredStudents actual como entrada y el criterio específico.
      • filteredStudents se actualiza en cada paso, encadenando los filtros.
    • Valor de Retorno: La función devuelve el array Student[] final, que contiene solo los estudiantes que cumplen con todos los criterios de filtro aplicados.
  • Rol en la aplicación: StudentFilters es una utilidad de procesamiento de datos. Es fundamental en la página de estadísticas (y potencialmente en otras vistas de lista de estudiantes) para refinar los datos crudos antes de que se realicen cálculos o se muestren en la interfaz de usuario, permitiendo así análisis específicos y dinámicos.
typescript
// src/components/statistics/filters/student.filter.ts
import { StudentFiltersProps } from "../../../interface/common/statistics"; // Importa la interfaz para las propiedades del filtro orquestador

import { Student } from "../../../interface/student/student"; // Importa la interfaz Student para tipado
import { SourceFilter } from "./source.filter"; // Importa la función para filtrar por fuente
import { SchoolFilter } from "./school.filter"; // Importa la función para filtrar por colegio
import { CourseFilter } from "./course.filter"; // Importa la función para filtrar por curso

/**
 * Orquesta el filtrado de una lista de estudiantes aplicando múltiples criterios.
 * @param {StudentFiltersProps} props - Las propiedades que incluyen la lista de estudiantes y los criterios de filtro.
 * @param {Student[]} props.students - El array de estudiantes a filtrar.
 * @param {Source | null} props.sourceFilter - Criterio opcional para filtrar por fuente.
 * @param {School | null} props.schoolFilter - Criterio opcional para filtrar por colegio.
 * @param {Course | null} props.courseFilter - Criterio opcional para filtrar por curso.
 * @returns {Student[]} Un nuevo array de estudiantes filtrados por todos los criterios aplicados.
 */
export const StudentFilters = ({
  students,       // Lista completa de estudiantes
  sourceFilter,   // Criterio para filtrar por fuente (opcional)
  schoolFilter,   // Criterio para filtrar por colegio (opcional)
  courseFilter,   // Criterio para filtrar por curso (opcional)
}: StudentFiltersProps): Student[] => {
  let filteredStudents = students; // Inicializa la lista filtrada con todos los estudiantes

  // Aplica el filtro por fuente si sourceFilter está presente
  if (sourceFilter) {
    filteredStudents = SourceFilter({
      students: filteredStudents, // Pasa la lista ya filtrada (o la original)
      source: sourceFilter,      // Pasa el criterio de fuente
    });
  }

  // Aplica el filtro por colegio si schoolFilter está presente
  if (schoolFilter) {
    filteredStudents = SchoolFilter({
      students: filteredStudents, // Pasa la lista filtrada hasta ahora
      school: schoolFilter,      // Pasa el criterio de colegio
    });
  }

  // Aplica el filtro por curso si courseFilter está presente
  if (courseFilter) {
    filteredStudents = CourseFilter({
      students: filteredStudents, // Pasa la lista filtrada hasta ahora
      course: courseFilter,      // Pasa el criterio de curso
    });
  }

  return filteredStudents; // Devuelve la lista final de estudiantes filtrados
};

statistics.filter.controls.tsx

Este archivo define el componente StatisticsFilterControls, que proporciona la interfaz de usuario para que los usuarios seleccionen criterios de filtrado (por fuente, colegio y curso) en la página de estadísticas.

  • Descripción: StatisticsFilterControls es un componente presentacional que renderiza tres selectores (<select>) para filtrar estudiantes por Source, School y Course. Mantiene los valores seleccionados sincronizados con su componente padre a través de un callback onFilterChange. Los estilos se aplican directamente en línea en el JSX.
  • Propósito: Ofrecer una interfaz de usuario interactiva para la aplicación de filtros en la página de estadísticas, permitiendo al usuario refinar dinámicamente los datos visualizados.
  • Funcionalidad clave:
    • StatisticsFilterControlsProps Interface: Define las propiedades esperadas por el componente: selectedSource, selectedSchool, selectedCourse (los valores de filtro actuales del padre) y onFilterChange (la función callback para notificar al padre sobre los cambios en los filtros).
    • Manejo de Cambios (handleSourceChange, handleSchoolChange, handleCourseChange):
      • Cada selector tiene su propio manejador de cambio (onChange).
      • Estos manejadores extraen el value del selector. Si el valor es una cadena vacía (''), lo convierten a null (para indicar "sin filtro").
      • Luego, llaman a la prop onFilterChange, pasando un objeto con el filtro actualizado ({ source: newValue }, { school: newValue }, etc.). Esto permite al componente padre (statistics.tsx) actualizar su estado.
      • Utilizan aserciones de tipo (as Source | '') para asegurar la compatibilidad con los tipos Enum de Source, School y Course.
    • Población de Selectores con Constantes Centralizadas:
      • Ahora, las opciones de cada selector (Source, School, Course) se obtienen directamente de las constantes centralizadas en Constants.SOURCE_OPTIONS, Constants.SCHOOL_OPTIONS, Constants.COURSE_OPTIONS (importadas de ../../../utils/constants.ts).
      • Esto asegura que las opciones sean consistentes en toda la aplicación y facilita su mantenimiento.
      • Se filtra la opción "" para evitar duplicar la opción por defecto "Todas las...".
    • Sincronización de Valor (value={selectedSource || ''}):
      • Los selectores están "controlados", lo que significa que su value se enlaza a las props selectedSource, selectedSchool y selectedCourse. Si la prop es null, se usa una cadena vacía para el selector.
    • Estilización en Línea: El contenedor principal tiene estilos en línea para marginBottom, display: flex y gap.
  • Rol en la aplicación: StatisticsFilterControls es un componente presentacional y de interacción. Es la interfaz clave que permite a los usuarios manipular los datos en la página de estadísticas, enviando los criterios de filtrado seleccionados al componente padre para que se apliquen a los cálculos y visualizaciones.
tsx
// src/components/statistics/filters/statistics.filter.controls.tsx
import React from 'react';
import { Source, School, Course } from '../../../interface/student/student'; // Importa los enums para las opciones de filtro

// ¡IMPORTANTE! Importa el objeto Constants que contiene todas las opciones de los selectores.
import Constants from '../../../utils/constants'; 

// NO SE IMPORTAN ESTILOS SCSS EN ESTE CASO, YA QUE LOS ESTILOS SON EN LÍNEA.

/**
 * Define las propiedades esperadas por el componente StatisticsFilterControls.
 */
interface StatisticsFilterControlsProps {
  selectedSource: Source | null; // El valor de fuente actualmente seleccionado (o null si no hay filtro)
  selectedSchool: School | null; // El valor de colegio actualmente seleccionado (o null si no hay filtro)
  selectedCourse: Course | null; // El valor de curso actualmente seleccionado (o null si no hay filtro)
  onFilterChange: (filters: { // Función callback para notificar cambios de filtro al componente padre
    source?: Source | null;
    school?: School | null;
    course?: Course | null;
  }) => void;
}

/**
 * Componente que proporciona controles de interfaz de usuario para filtrar estadísticas.
 * Renderiza selectores para filtrar por Fuente, Colegio y Curso.
 * @param props.selectedSource Valor de fuente actualmente seleccionado.
 * @param props.selectedSchool Valor de colegio actualmente seleccionado.
 * @param props.selectedCourse Valor de curso actualmente seleccionado.
 * @param props.onFilterChange Función callback para notificar cambios de filtro.
 */
export const StatisticsFilterControls: React.FC<StatisticsFilterControlsProps> = ({
  selectedSource,
  selectedSchool,
  selectedCourse,
  onFilterChange,
}) => {
  /**
   * Manejador de cambio para el selector de Fuente.
   * Notifica al padre sobre la nueva fuente seleccionada.
   * @param e Evento de cambio del elemento select.
   */
  const handleSourceChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const value = e.target.value as Source | ''; // Obtiene el valor, puede ser del Enum o cadena vacía
    // Llama al callback onFilterChange del padre, pasando null si el valor es vacío.
    onFilterChange({ source: value === '' ? null : value });
  };

  /**
   * Manejador de cambio para el selector de Colegio.
   * Notifica al padre sobre el nuevo colegio seleccionado.
   * @param e Evento de cambio del elemento select.
   */
  const handleSchoolChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const value = e.target.value as School | ''; // Obtiene el valor, puede ser del Enum o cadena vacía
    onFilterChange({ school: value === '' ? null : value });
  };

  /**
   * Manejador de cambio para el selector de Curso.
   * Notifica al padre sobre el nuevo curso seleccionado.
   * @param e Evento de cambio del elemento select.
   */
  const handleCourseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const value = e.target.value as Course | ''; // Obtiene el valor, puede ser del Enum o cadena vacía
    onFilterChange({ course: value === '' ? null : value });
  };

  return (
    // Contenedor principal de los selectores de filtro.
    // Estilos en línea para layout básico (margen inferior, flexbox, gap).
    <div style={{ marginBottom: '20px', display: 'flex', gap: '15px' }}>
      {/* Selector de Fuente */}
      <select value={selectedSource || ''} onChange={handleSourceChange}>
        <option value="">Todas las Fuentes</option> {/* Opción por defecto "sin filtro" */}
        {/* MODIFICADO: Usa las opciones de Constants.SOURCE_OPTIONS y filtra la opción vacía para no duplicar */}
        {Constants.SOURCE_OPTIONS.filter(option => option !== "").map((src) => ( 
          <option key={src} value={src}>
            {src}
          </option>
        ))}
      </select>

      {/* Selector de Colegio */}
      <select value={selectedSchool || ''} onChange={handleSchoolChange}>
        <option value="">Todos los Colegios</option> {/* Opción por defecto "sin filtro" */}
        {/* MODIFICADO: Usa las opciones de Constants.SCHOOL_OPTIONS y filtra la opción vacía */}
        {Constants.SCHOOL_OPTIONS.filter(option => option !== "").map((sch) => ( 
          <option key={sch} value={sch}>
            {sch}
          </option>
        ))}
      </select>

      {/* Selector de Curso */}
      <select value={selectedCourse || ''} onChange={handleCourseChange}>
        <option value="">Todos los Cursos</option> {/* Opción por defecto "sin filtro" */}
        {/* MODIFICADO: Usa las opciones de Constants.COURSE_OPTIONS y filtra la opción vacía */}
        {Constants.COURSE_OPTIONS.filter(option => option !== "").map((crs) => ( 
          <option key={crs} value={crs}>
            {crs}
          </option>
        ))}
      </select>
    </div>
  );
  };

student.filters.ts

Este archivo define la función StudentFilters, que actúa como un orquestador de filtros para la lista de estudiantes. Su propósito es aplicar una secuencia de filtros individuales (por fuente, colegio y curso) a una lista de estudiantes, devolviendo un conjunto de datos más refinado.

  • Descripción: StudentFilters es una función pura que recibe una lista de estudiantes y un conjunto de criterios de filtro opcionales (sourceFilter, schoolFilter, courseFilter). Utiliza funciones de filtrado específicas (SourceFilter, SchoolFilter, CourseFilter) para aplicar cada criterio en orden, devolviendo la lista de estudiantes que cumplen con todas las condiciones.
  • Propósito: Centralizar y gestionar la aplicación de múltiples filtros a una lista de estudiantes. Esto asegura que el proceso de filtrado sea consistente, modular y fácil de extender con nuevos criterios en el futuro.
  • Funcionalidad clave:
    • StudentFiltersProps Interface: Importada de ../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas: students (la lista de estudiantes completa), y los criterios de filtro opcionales sourceFilter, schoolFilter, y courseFilter.
    • Importación de Filtros Individuales: Importa las funciones SourceFilter, SchoolFilter, y CourseFilter desde otros archivos en la misma carpeta (./source.filter, ./school.filter, ./course.filter), lo que permite reutilizar la lógica de filtrado atómico.
    • Lógica de Filtrado Secuencial:
      • Inicializa filteredStudents con la lista students completa.
      • Para cada criterio de filtro (sourceFilter, schoolFilter, courseFilter) que esté presente (es decir, no null ni undefined), aplica la función de filtrado correspondiente, pasando la lista filteredStudents actual como entrada y el criterio específico.
      • La variable filteredStudents se actualiza en cada paso, encadenando los filtros.
    • Valor de Retorno: La función devuelve el array Student[] final, que contiene solo los estudiantes que cumplen con todos los criterios de filtro aplicados.
  • Rol en la aplicación: StudentFilters es una utilidad de procesamiento de datos. Es fundamental en la página de estadísticas (y potencialmente en otras vistas de lista de estudiantes) para refinar los datos crudos antes de que se realicen cálculos o se muestren en la interfaz de usuario, permitiendo así análisis específicos y dinámicos.
typescript
// src/components/statistics/filters/student.filter.ts
import { StudentFiltersProps } from "../../../interface/common/statistics"; // Importa la interfaz para las propiedades del filtro orquestador

import { Student } from "../../../interface/student/student"; // Importa la interfaz Student para tipado
import { SourceFilter } from "./source.filter"; // Importa la función para filtrar por fuente
import { SchoolFilter } from "./school.filter"; // Importa la función para filtrar por colegio
import { CourseFilter } from "./course.filter"; // Importa la función para filtrar por curso

/**
 * Orquesta el filtrado de una lista de estudiantes aplicando múltiples criterios.
 * @param {StudentFiltersProps} props - Las propiedades que incluyen la lista de estudiantes y los criterios de filtro.
 * @param {Student[]} props.students - El array de estudiantes a filtrar.
 * @param {Source | null} props.sourceFilter - Criterio opcional para filtrar por fuente.
 * @param {School | null} props.schoolFilter - Criterio opcional para filtrar por colegio.
 * @param {Course | null} props.courseFilter - Criterio opcional para filtrar por curso.
 * @returns {Student[]} Un nuevo array de estudiantes filtrados por todos los criterios aplicados.
 */
export const StudentFilters = ({
  students,       // Lista completa de estudiantes
  sourceFilter,   // Criterio para filtrar por fuente (opcional)
  schoolFilter,   // Criterio para filtrar por colegio (opcional)
  courseFilter,   // Criterio para filtrar por curso (opcional)
}: StudentFiltersProps): Student[] => {
  let filteredStudents = students; // Inicializa la lista filtrada con todos los estudiantes

  // Aplica el filtro por fuente si sourceFilter está presente
  if (sourceFilter) {
    filteredStudents = SourceFilter({
      students: filteredStudents, // Pasa la lista ya filtrada (o la original)
      source: sourceFilter,      // Pasa el criterio de fuente
    });
  }

  // Aplica el filtro por colegio si schoolFilter está presente
  if (schoolFilter) {
    filteredStudents = SchoolFilter({
      students: filteredStudents, // Pasa la lista filtrada hasta ahora
      school: schoolFilter,      // Pasa el criterio de colegio
    });
  }

  // Aplica el filtro por curso si courseFilter está presente
  if (courseFilter) {
    filteredStudents = CourseFilter({
      students: filteredStudents, // Pasa la lista filtrada hasta ahora
      course: courseFilter,      // Pasa el criterio de curso
    });
  }

  return filteredStudents; // Devuelve la lista final de estudiantes filtrados
};

bar.chart.tsx

Este archivo define el componente BarChart, un componente React que renderiza un gráfico de barras utilizando la librería Chart.js. Es un componente presentacional que visualiza datos estadísticos en un formato de barras.

  • Descripción: BarChart recibe un array de datos (data) y un título (title). Utiliza un elemento <canvas> y el hook useEffect para inicializar y gestionar la instancia del gráfico de barras de Chart.js. Se asegura de que el gráfico se actualice cuando los datos cambian y se destruya correctamente al desmontar el componente.
  • Propósito: Proporcionar una forma reutilizable y encapsulada de visualizar datos cuantitativos como gráficos de barras. Esto permite que la página de estadísticas muestre diferentes tipos de datos de forma consistente y sin duplicar la lógica de Chart.js.
  • Funcionalidad clave:
    • BarChartProps Interface: Importada de ../../../interface/common/statistics, esta interfaz define las propiedades que el componente espera: data (un array de objetos { name: string; value: number }) y title (el título del gráfico que se usará como etiqueta del dataset).
    • Referencias (useRef):
      • chartRef: Una referencia (ref) de React adjunta al elemento <canvas>. Permite acceder al contexto de dibujo 2D del canvas.
      • chartInstance: Una referencia que almacena la instancia del objeto Chart.js creada. Esto es crucial para poder destruir el gráfico existente antes de crear uno nuevo (al actualizarse) y al desmontar el componente.
    • Ciclo de Vida del Gráfico (useEffect):
      • Este hook gestiona la creación y destrucción del gráfico de Chart.js. Se ejecuta cuando las propiedades data o title cambian, o al montar/desmontar el componente.
      • Obtención del Contexto: Intenta obtener el contexto "2d" del elemento <canvas>. Si no lo obtiene, sale.
      • Destrucción de Instancia Anterior: Antes de crear un nuevo gráfico, comprueba si chartInstance.current ya existe. Si es así, llama a chartInstance.current.destroy() para limpiar el gráfico anterior y evitar fugas de memoria o superposiciones.
      • Creación del Gráfico: Crea una nueva instancia de Chart pasando el contexto, el tipo de gráfico ("bar"), los datos (mapeando name a labels y value a data), y las options básicas (como beginAtZero: true para el eje Y).
      • Función de Limpieza (return () => { ... }): La función de retorno de useEffect es esencial. Se ejecuta cuando el componente se desmonta o antes de que el useEffect se re-ejecute (por un cambio en las dependencias). Asegura que chartInstance.current.destroy() se llame para limpiar correctamente el gráfico y chartInstance.current se resetee a null.
    • Renderizado de <canvas>: El componente solo renderiza un elemento <canvas> básico, dejando toda la lógica de dibujo a Chart.js a través del useEffect.
  • Rol en la aplicación: BarChart es un componente presentacional de visualización de datos. Su función es transformar datos crudos en un gráfico de barras interactivo y claro, lo cual es fundamental para las páginas de estadísticas que buscan resumir y presentar información cuantitativa de manera efectiva.
tsx
// src/components/statistics/layouts/bar.chart.tsx
import React, { useEffect, useRef } from "react"; // Importa React, useEffect para efectos secundarios, useRef para referencias a elementos DOM
import Chart from "chart.js/auto"; // Importa la librería Chart.js (incluye todos los controladores necesarios)
import { BarChartProps } from "../../../interface/common/statistics"; // Importa la interfaz de propiedades para BarChart

/**
 * Componente funcional que renderiza un gráfico de barras utilizando Chart.js.
 * Visualiza un conjunto de datos como un gráfico de barras.
 * @param props.data Un array de objetos con propiedades 'name' (etiqueta) y 'value' (valor).
 * @param props.title El título del gráfico, usado como etiqueta para el dataset.
 */
export const BarChart: React.FC<BarChartProps> = ({ data, title }) => {
  // Crea una referencia al elemento <canvas> donde se dibujará el gráfico.
  const chartRef = useRef<HTMLCanvasElement>(null);
  // Crea una referencia para almacenar la instancia del gráfico de Chart.js.
  // Esto es crucial para poder destruir el gráfico cuando los datos cambian o el componente se desmonta.
  const chartInstance = useRef<Chart | null>(null);

  // Efecto que se ejecuta al montar el componente, y cada vez que 'data' o 'title' cambian.
  useEffect(() => {
    // Obtiene el contexto de renderizado 2D del elemento canvas.
    const ctx = chartRef.current?.getContext("2d");
    // Si no se puede obtener el contexto (ej. el canvas no existe), no hace nada.
    if (!ctx) return;

    // Antes de crear un nuevo gráfico, destruye la instancia anterior si existe.
    // Esto es vital para evitar fugas de memoria y que los gráficos se superpongan o actúen de forma extraña.
    if (chartInstance.current) {
      chartInstance.current.destroy();
    }

    // Crea una nueva instancia del gráfico de Chart.js.
    chartInstance.current = new Chart(ctx, {
      type: "bar", // Define el tipo de gráfico como 'bar' (barras)
      data: {
        // 'labels' son las categorías en el eje X (mapeadas desde 'name' de los datos)
        labels: data.map((item) => item.name),
        datasets: [
          {
            label: title, // La etiqueta para el conjunto de datos (ej. "Distribución por Género")
            data: data.map((item) => item.value), // Los valores numéricos para las barras
            backgroundColor: "rgba(54, 162, 235, 0.2)", // Color de fondo de las barras (azul semitransparente)
            borderColor: "rgba(54, 162, 235, 1)", // Color del borde de las barras (azul opaco)
            borderWidth: 1, // Ancho del borde de las barras
          },
        ],
      },
      options: {
        // Opciones de configuración del gráfico
        scales: {
          y: {
            beginAtZero: true, // El eje Y siempre comenzará desde cero
          },
        },
      },
    });

    // Función de limpieza del efecto. Se ejecuta cuando el componente se desmonta
    // o antes de que el efecto se re-ejecute debido a un cambio en las dependencias.
    return () => {
      if (chartInstance.current) {
        chartInstance.current.destroy(); // Destruye el gráfico para liberar recursos
        chartInstance.current = null; // Limpia la referencia a la instancia
      }
    };
  }, [data, title]); // Dependencias del efecto: el gráfico se re-dibuja si 'data' o 'title' cambian

  // Renderiza el elemento <canvas> al que Chart.js se adjuntará.
  // El 'id' es opcional ya que Chart.js usa la referencia.
  return <canvas ref={chartRef} id="myChart" />;
};

table.tsx

Este archivo define el componente Table, un componente React versátil que renderiza una tabla HTML genérica a partir de un conjunto de datos y una lista de columnas. Es un componente presentacional diseñado para mostrar datos tabulares de manera flexible.

  • Descripción: Table recibe un array de datos (data), donde cada elemento es un objeto (Record<string, any>), y un array de cadenas (columns) que representan los nombres de las columnas a mostrar. El componente genera dinámicamente el encabezado (<thead>) y el cuerpo (<tbody>) de la tabla.
  • Propósito: Proporcionar una forma reutilizable y encapsulada de mostrar datos en formato tabular. Esto permite que la página de estadísticas y otras vistas presenten información estructurada de manera consistente y sin duplicar la lógica de renderizado de tablas.
  • Funcionalidad clave:
    • TableProps Interface: Importada de ../../../interface/common/statistics, esta interfaz define las propiedades que el componente espera: data (un array de objetos, donde cada objeto representa una fila) y columns (un array de cadenas, donde cada cadena es el nombre de una columna y la clave para acceder al valor en cada fila).
    • Generación del Encabezado (<thead>): Itera sobre el array columns para crear dinámicamente las celdas de encabezado (<th>). Cada <th> utiliza el nombre de la columna como su key para una reconciliación eficiente por parte de React.
    • Generación del Cuerpo (<tbody>): Itera sobre el array data para crear las filas de la tabla (<tr>).
      • Clave Única para Filas (key={row.id || JSON.stringify(row)}): Para cada fila (<tr>), utiliza la propiedad id del objeto row como clave única. Si id no está presente, se recurre a JSON.stringify(row) como un fallback para asegurar que React tenga una clave única y estable para la reconciliación de la lista. Esto mejora el rendimiento y la fiabilidad de las listas en React.
      • Dentro de cada fila, itera nuevamente sobre el array columns para crear las celdas de datos (<td>). Accede al valor de la celda usando row[column].
    • Estilización: El componente renderiza la estructura HTML básica de una tabla (<table>, <thead>, <tbody>, <tr>, <th>, <td>). Se espera que los estilos para la tabla (bordes, alineación, etc.) provengan de hojas de estilo globales o específicas del layout (como global.scss o student.table.module.scss).
  • Rol en la aplicación: Table es un componente presentacional de visualización de datos. Su función es tomar datos estructurados y presentarlos en un formato tabular claro, lo cual es fundamental para las páginas de estadísticas que necesitan mostrar resúmenes detallados o listas de elementos.
tsx
// src/components/statistics/layouts/table.tsx
import React from "react"; // Importa React
import { TableProps } from "../../../interface/common/statistics"; // Importa la interfaz de propiedades para Table

/**
 * Componente funcional que renderiza una tabla HTML genérica.
 * Muestra datos en formato tabular con encabezados de columna y filas de datos.
 * @param props.data Un array de objetos, donde cada objeto representa una fila de la tabla.
 * @param props.columns Un array de cadenas, donde cada cadena es el nombre de una columna.
 */
export const Table: React.FC<TableProps> = ({ data, columns }) => {
  return (
    <div>
      <table>
        {/* Encabezado de la tabla */}
        <thead>
          <tr>
            {/* Mapea los nombres de las columnas para crear los encabezados de la tabla */}
            {columns.map((column) => (
              <th key={column}>{column}</th> // Usa el nombre de la columna como key
            ))}
          </tr>
        </thead>
        {/* Cuerpo de la tabla */}
        <tbody>
          {/* Mapea cada fila de datos para crear una fila de tabla */}
          {data.map((row) => (
            // Usa la propiedad 'id' de la fila como clave única.
            // Si 'id' no está presente, JSON.stringify(row) se usa como fallback para asegurar una clave única.
            <tr key={row.id || JSON.stringify(row)}>
              {/* Mapea las columnas para crear las celdas de datos en cada fila */}
              {columns.map((column) => (
                <td key={column}>{row[column]}</td> // Usa el nombre de la columna como key y accede al valor de la fila
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

captators.table.section.tsx

Este archivo define el componente CaptatorsTableSection, una sección de la página de estadísticas dedicada a mostrar la distribución de contactos por cada captador.

  • Descripción: CaptatorsTableSection es un componente presentacional que recibe el objeto filteredStatistics (que contiene los datos ya procesados) y extrae la información de los contactos por persona. Renderiza un título <h2> y utiliza el componente genérico Table para visualizar estos datos en formato tabular.
  • Propósito: Proporcionar una visualización clara y segmentada de la eficiencia de los captadores, mostrando cuántos contactos ha realizado cada uno. Esto contribuye al análisis del rendimiento del equipo.
  • Funcionalidad clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. React.memo memoiza el componente, lo que significa que React lo re-renderizará solo si sus props (filteredStatistics) cambian, evitando renderizados innecesarios.
    • CaptatorsTableSectionProps Interface: Define las propiedades que el componente espera, que es el objeto filteredStatistics (importado de ../../../interface/common/statistics).
    • Extracción de Datos: Accede directamente a filteredStatistics.contactsByPerson, que ya contiene los datos pre-agrupados ({ Captador: string; Contacto: number }[]) listos para ser pasados a la tabla.
    • Table Componente: Utiliza el componente genérico Table (importado de ../layouts/table.tsx) para la representación tabular de los datos. Le pasa el array contactsByPerson como data y un array de cadenas ["Captador", "Contacto"] como columns.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: CaptatorsTableSection es un componente de sección de estadísticas. Su rol es presentar una visualización específica de datos sin gestionar la lógica de filtrado o cálculo, haciendo que la página statistics.tsx sea más modular y fácil de leer.
tsx
// src/components/statistics/sections/captators.table.section.tsx
import React from 'react'; // Importa React
import { Table } from '../layouts/table'; // Importa el componente genérico de tabla
import type { FilteredStatistics } from '../../../interface/common/statistics'; // Importa la interfaz FilteredStatistics

/**
 * Propiedades esperadas por el componente CaptatorsTableSection.
 */
interface CaptatorsTableSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas
}

/**
 * Componente de sección que muestra una tabla de captadores y sus contactos.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 */
export const CaptatorsTableSection: React.FC<CaptatorsTableSectionProps> = React.memo(({
  filteredStatistics, // Datos de estadísticas filtrados
}) => {
  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Captadores</h2> {/* Título de la sección */}
      <Table
        data={filteredStatistics.contactsByPerson} // Datos de los contactos por persona
        columns={["Captador", "Contacto"]} // Nombres de las columnas para la tabla
      />
    </div>
  );
});

communication.preference.section.tsx

Este archivo define el componente CommunicationPreferenceSection, una sección de la página de estadísticas dedicada a visualizar las preferencias de comunicación de los estudiantes a través de un gráfico de barras.

  • Descripción: CommunicationPreferenceSection es un componente presentacional que recibe el objeto filteredStatistics. Extrae los datos relevantes sobre el conteo de preferencias de comunicación (WhatsApp, Teléfono, No registrada) y los formatea para ser visualizados utilizando el componente BarChart.
  • Propósito: Proporcionar una visualización clara y concisa de cómo los estudiantes prefieren ser contactados o cómo se ha registrado su método de comunicación. Esto es útil para entender los canales de comunicación más efectivos.
  • Funcionalidad Clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. Esto garantiza que el componente solo se re-renderice si sus props (filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.
    • CommunicationPreferenceSectionProps Interface: Define las propiedades que el componente espera, específicamente el objeto filteredStatistics (importado de ../../../interface/common/statistics).
    • Preparación de Datos: La lógica interna del componente transforma una parte de filteredStatistics en el formato específico que BarChart espera: un array de objetos con propiedades name (la etiqueta, ej., "WhatsApp") y value (el conteo numérico).
    • BarChart Componente: Utiliza el componente genérico BarChart (importado de ../layouts/bar.chart.tsx) para la representación visual de los datos. Le pasa los datos preparados y un título descriptivo.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: CommunicationPreferenceSection es un componente de sección de estadísticas. Su rol es presentar una visualización específica de datos sin gestionar la lógica de filtrado o cálculo, haciendo que la página statistics.tsx sea más modular y fácil de leer.
tsx
// src/components/statistics/sections/communication.preference.section.tsx
import React from 'react'; // Importa React
import { BarChart } from '../layouts/bar.chart'; // Importa el componente genérico de gráfico de barras
import type { FilteredStatistics } from '../../../interface/common/statistics'; // Importa la interfaz FilteredStatistics

/**
 * Propiedades esperadas por el componente CommunicationPreferenceSection.
 */
interface CommunicationPreferenceSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas
}

/**
 * Componente de sección que muestra un gráfico de barras de la preferencia de comunicación de los estudiantes.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 */
export const CommunicationPreferenceSection: React.FC<CommunicationPreferenceSectionProps> = React.memo(({
  filteredStatistics, // Datos de estadísticas filtrados
}) => {
  // Prepara los datos en el formato que BarChart espera (array de { name, value })
  const data = [
    { name: "WhatsApp", value: filteredStatistics.whatsappCount },
    { name: "Teléfono", value: filteredStatistics.phoneCount },
    {
      name: "No registrada",
      value: filteredStatistics.noRegisteredCommunication,
    },
  ];

  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Preferencia de comunicación</h2> {/* Título de la sección */}
      <BarChart data={data} title="Preferencia de comunicación" /> {/* Renderiza el gráfico de barras */}
    </div>
  );
});

enrollment.source.section.tsx

Este archivo define el componente EnrollmentSourceSection, una sección de la página de estadísticas dedicada a visualizar las fuentes de donde provienen las inscripciones de los estudiantes a través de un gráfico de barras.

  • Descripción: EnrollmentSourceSection es un componente presentacional que recibe el objeto filteredStatistics. Extrae los conteos relevantes de las fuentes de inscripción (Redes Sociales, Captador, No registrada) y los formatea para ser visualizados utilizando el componente BarChart.
  • Propósito: Proporcionar una visualización clara y concisa de los canales por los cuales los estudiantes se inscriben. Esto es útil para evaluar la efectividad de las diferentes estrategias de captación.
  • Funcionalidad clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. Esto asegura que el componente solo se re-renderice si sus props (filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.
    • EnrollmentSourceSectionProps Interface: Define las propiedades que el componente espera, específicamente el objeto filteredStatistics (importado de ../../../interface/common/statistics).
    • Preparación de Datos: La lógica interna del componente transforma una parte de filteredStatistics en el formato específico que BarChart espera: un array de objetos con propiedades name (la etiqueta, ej., "Redes Sociales") y value (el conteo numérico).
    • BarChart Componente: Utiliza el componente genérico BarChart (importado de ../layouts/bar.chart.tsx) para la representación visual de los datos. Le pasa los datos preparados y un título descriptivo.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: EnrollmentSourceSection es un componente de sección de estadísticas. Su rol es presentar una visualización específica de datos sin gestionar la lógica de filtrado o cálculo, haciendo que la página statistics.tsx sea más modular y fácil de leer.
tsx
// src/components/statistics/sections/enrollment.source.section.tsx
import React from 'react'; // Importa React
import { BarChart } from '../layouts/bar.chart'; // Importa el componente genérico de gráfico de barras
import type { FilteredStatistics } from '../../../interface/common/statistics'; // Importa la interfaz FilteredStatistics

/**
 * Propiedades esperadas por el componente EnrollmentSourceSection.
 */
interface EnrollmentSourceSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas
}

/**
 * Componente de sección que muestra un gráfico de barras de las fuentes de inscripción de los estudiantes.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 */
export const EnrollmentSourceSection: React.FC<EnrollmentSourceSectionProps> = React.memo(({
  filteredStatistics, // Datos de estadísticas filtrados
}) => {
  // Prepara los datos en el formato que BarChart espera (array de { name, value })
  const data = [
    {
      name: "Redes Sociales",
      value: filteredStatistics.socialMediaCount,
    },
    { name: "Captador", value: filteredStatistics.captadorCount },
    {
      name: "No registrada",
      value: filteredStatistics.noRegisteredSource,
    },
  ];

  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Fuente de inscripción</h2> {/* Título de la sección */}
      <BarChart data={data} title="Fuentes de Inscripción" /> {/* Renderiza el gráfico de barras */}
    </div>
  );
});

feedback.by.school.course.section.tsx

Este archivo define el componente FeedbackBySchoolCourseSection, una sección de la página de estadísticas dedicada a visualizar el estado de los estudiantes (su "feedback positivo") desglosado por escuela y curso.

  • Descripción: FeedbackBySchoolCourseSection es un componente presentacional que recibe el objeto filteredStatistics. Itera sobre todos los posibles estados de feedback (obtenidos de Constants.allFeedbacks) y, para cada uno, renderiza una subsección con un título (<h3>) y una tabla (Table) que muestra la cantidad de estudiantes por escuela y curso para ese estado de feedback específico. La tabla para "Estudiantes sin colegio/curso" se ha movido a otra sección para evitar duplicación.
  • Propósito: Proporcionar una visualización detallada del progreso de los estudiantes a través de las etapas de inscripción, permitiendo un análisis granular por escuela y curso según su estado de feedback.
  • Funcionalidad Clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. Esto asegura que el componente solo se re-renderice si sus props (filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.
    • FeedbackBySchoolCourseSectionProps Interface: Define las propiedades que el componente espera, específicamente el objeto filteredStatistics (importado de ../../../interface/common/statistics).
    • Importación de Constantes: Accede a Constants.allFeedbacks (importado de ../../../utils/constants) para obtener la lista exhaustiva de todos los estados de feedback posibles. Esto asegura que todas las categorías se presenten, incluso si no tienen datos, y centraliza la definición de estas opciones.
    • Iteración por Estado de Feedback: El componente mapea sobre allFeedbacks para crear una sección por cada estado.
      • Cada sección muestra un subtítulo (<h3>) con el nombre del estado de feedback.
      • Filtra los datos de filteredStatistics.studentsBySchoolCourseFeedback para incluir solo los ítems que coinciden con el estado de feedback actual.
      • Mapea los ítems filtrados a un formato adecuado para el componente Table.
    • Table Componente: Utiliza el componente genérico Table (importado de ../layouts/table.tsx) para la representación tabular de los datos. Para cada estado de feedback, muestra una tabla con columnas "Escuela", "Curso" y "Estudiantes".
    • Gestión de Duplicación: A diferencia de versiones anteriores, esta sección ya no incluye la tabla para "Estudiantes sin colegio/curso", delegando esa visualización a StudentsByLevelSection para mantener la consistencia y evitar redundancia.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: FeedbackBySchoolCourseSection es un componente de sección de estadísticas crucial para el análisis de seguimiento de estudiantes. Su rol es presentar una visualización específica y detallada del progreso de los estudiantes a través de diferentes etapas, contribuyendo a la modularidad y legibilidad de la página statistics.tsx.
tsx
// src/components/statistics/sections/feedback.by.school.course.section.tsx
import React from "react"; // Importa React
import { Table } from "../layouts/table"; // Importa el componente genérico de tabla
import type { FilteredStatistics } from "../../../interface/common/statistics"; // Importa la interfaz FilteredStatistics
import Constants from "../../../utils/constants"; // Importa el objeto Constants para acceder a constantes como allFeedbacks

/**
 * Propiedades esperadas por el componente FeedbackBySchoolCourseSection.
 */
interface FeedbackBySchoolCourseSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas.
}

/**
 * Componente de sección que muestra el estado de los estudiantes desglosado por escuela y curso.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 */
export const FeedbackBySchoolCourseSection: React.FC<
  FeedbackBySchoolCourseSectionProps
> = React.memo(({ filteredStatistics }) => {
  // Obtiene la lista completa de feedbacks desde el objeto Constants.
  const allFeedbacks = Constants.allFeedbacks;

  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Estado de los estudiantes por escuela y curso</h2> {/* Título principal de la sección */}
      {/* Mapea sobre todos los feedbacks para crear una subsección por cada uno. */}
      {allFeedbacks.map((feedback) => (
        <div key={feedback}>
          <h3>{feedback}</h3> {/* Título de la subsección (nombre del feedback) */}
          <Table
            // Filtra los datos para el feedback actual y los mapea al formato de tabla.
            data={filteredStatistics.studentsBySchoolCourseFeedback
              .filter((item) => item.feedback === feedback)
              .map((item) => ({
                Escuela: item.Escuela,
                Curso: item.Curso,
                Estudiantes: item.Estudiantes,
              }))}
            columns={["Escuela", "Curso", "Estudiantes"]} // Columnas de la tabla
          />
        </div>
      ))}
      {/* La tabla "Estudiantes sin colegio/curso" se ha eliminado de aquí para evitar duplicación.
          Ahora se muestra en StudentsByLevelSection.tsx. */}
    </div>
  );
});

gender.distribution.section.tsx

Este archivo define el componente GenderDistributionSection, una sección de la página de estadísticas dedicada a visualizar la distribución de estudiantes por género a través de un gráfico de barras.

  • Descripción: GenderDistributionSection es un componente presentacional que recibe el objeto filteredStatistics. Extrae los conteos de estudiantes clasificados por género (hombres, mujeres, otros, no registrado) y los formatea para ser visualizados usando el componente BarChart.
  • Propósito: Ofrecer una visualización clara y concisa de la composición de género de la población estudiantil. Esto es útil para análisis demográficos o para asegurar la equidad en la representación.
  • Funcionalidad Clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. Esto garantiza que el componente solo se re-renderice si sus props (filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.
    • GenderDistributionSectionProps Interface: Define las propiedades que el componente espera, específicamente el objeto filteredStatistics (importado de ../../../interface/common/statistics).
    • Preparación de Datos: La lógica interna del componente transforma una parte de filteredStatistics en el formato específico que BarChart espera: un array de objetos con propiedades name (la etiqueta, ej., "Hombres") y value (el conteo numérico). Accede directamente a los conteos de genderCount dentro de filteredStatistics.
    • BarChart Componente: Utiliza el componente genérico BarChart (importado de ../layouts/bar.chart.tsx) para la representación visual de los datos. Le pasa los datos preparados y un título descriptivo.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: GenderDistributionSection es un componente de sección de estadísticas. Su rol es presentar una visualización específica de datos sin gestionar la lógica de filtrado o cálculo, haciendo que la página statistics.tsx sea más modular y fácil de leer.
tsx
// src/components/statistics/sections/gender.distribution.section.tsx
import React from 'react'; // Importa React
import { BarChart } from '../layouts/bar.chart'; // Importa el componente genérico de gráfico de barras
import type { FilteredStatistics } from '../../../interface/common/statistics'; // Importa la interfaz FilteredStatistics

/**
 * Propiedades esperadas por el componente GenderDistributionSection.
 */
interface GenderDistributionSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas
}

/**
 * Componente de sección que muestra un gráfico de barras de la distribución por género de los estudiantes.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 */
export const GenderDistributionSection: React.FC<GenderDistributionSectionProps> = React.memo(({
  filteredStatistics, // Datos de estadísticas filtrados
}) => {
  // Prepara los datos en el formato que BarChart espera (array de { name, value })
  const data = [
    { name: "Hombres", value: filteredStatistics.genderCount.hombres },
    { name: "Mujeres", value: filteredStatistics.genderCount.mujeres },
    { name: "Otros", value: filteredStatistics.genderCount.otros },
    {
      name: "No registrado",
      value: filteredStatistics.genderCount.noContesta,
    },
  ];

  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Distribución por Género</h2> {/* Título de la sección */}
      <BarChart data={data} title="Distribución por Género" /> {/* Renderiza el gráfico de barras */}
    </div>
  );
});

general.student.stats.section.tsx

Este archivo define el componente GeneralStudentStatsSection, una sección de la página de estadísticas dedicada a mostrar una tabla que resume el estado general de todos los estudiantes.

  • Descripción: GeneralStudentStatsSection es un componente presentacional que recibe el objeto filteredStatistics y un array de allFeedbacks (todos los estados de feedback posibles). Prepara los datos para una tabla, asegurando que todos los estados de feedback se presenten, incluso si no tienen estudiantes asociados. Renderiza un título <h2> y utiliza el componente genérico Table para visualizar estos datos.
  • Propósito: Ofrecer una visión de alto nivel y un resumen numérico del estado actual de todos los estudiantes, clasificados por sus estados de feedback, lo cual es fundamental para una visión rápida de la población estudiantil.
  • Funcionalidad clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. Esto asegura que el componente solo se re-renderice si sus props (filteredStatistics, allFeedbacks) cambian, evitando cálculos y renderizados innecesarios.
    • GeneralStudentStatsSectionProps Interface: Define las propiedades que el componente espera: el objeto filteredStatistics (importado de ../../../interface/common/statistics) y allFeedbacks (un array de cadenas con todos los posibles estados de feedback).
    • Preparación de Datos (tableData):
      • Utiliza el array allFeedbacks para mapear y crear los datos para la tabla.
      • Para cada feedback posible, busca el count correspondiente en filteredStatistics.studentsByGeneralState.
      • Si un estado de feedback no tiene estudiantes, se asigna 0 (count || 0), asegurando que todos los estados se muestren en la tabla con un valor numérico, lo cual es crucial para la consistencia visual y analítica.
    • Table Componente: Utiliza el componente genérico Table (importado de ../layouts/table.tsx) para la representación tabular de los datos. Le pasa el array tableData preparado y un array de cadenas ["Estado", "Estudiantes"] como columns.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: GeneralStudentStatsSection es un componente de sección de estadísticas. Su rol es presentar una visualización de datos específica y normalizada, contribuyendo a la modularidad y legibilidad de la página statistics.tsx.
tsx
// src/components/statistics/sections/general.student.stats.section.tsx
import React from 'react'; // Importa React
import { Table } from '../layouts/table'; // Importa el componente genérico de tabla
import type { FilteredStatistics } from '../../../interface/common/statistics'; // Importa la interfaz FilteredStatistics

/**
 * Propiedades esperadas por el componente GeneralStudentStatsSection.
 */
interface GeneralStudentStatsSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas
  allFeedbacks: string[]; // Array de todos los posibles estados de feedback (para normalizar la tabla)
}

/**
 * Componente de sección que muestra una tabla del estado general de los estudiantes.
 * Asegura que todos los estados de feedback se presenten, incluso si no tienen estudiantes.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 * @param props.allFeedbacks Array de todos los feedbacks posibles para la tabla.
 */
export const GeneralStudentStatsSection: React.FC<GeneralStudentStatsSectionProps> = React.memo(({
  filteredStatistics, // Datos de estadísticas filtrados
  allFeedbacks,       // Lista completa de feedbacks para asegurar todas las filas
}) => {
  // Prepara los datos para la tabla, asegurando que todos los estados de feedback estén presentes
  const tableData = allFeedbacks.map((feedback) => {
    // Busca la cantidad de estudiantes para el feedback actual.
    // Si no se encuentra, 'cantidad' será undefined, por eso se usa '|| 0'.
    const count = filteredStatistics.studentsByGeneralState.find(
      (item) => item.estado === feedback
    )?.cantidad;

    return {
      Estado: feedback,        // La etiqueta del estado de feedback
      Estudiantes: count || 0, // La cantidad de estudiantes, o 0 si no hay
    };
  });

  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Estado general de los estudiantes</h2> {/* Título de la sección */}
      <Table data={tableData} columns={["Estado", "Estudiantes"]} /> {/* Renderiza la tabla */}
    </div>
  );
});

students.by.level.section.tsx

Este archivo define el componente StudentsByLevelSection, una sección de la página de estadísticas que visualiza la distribución de estudiantes por nivel (curso) dentro de cada colegio. También incluye un conteo de estudiantes sin colegio o curso asignado, siendo la ubicación principal para esta estadística.

  • Descripción: StudentsByLevelSection es un componente presentacional que recibe el objeto filteredStatistics. Procesa los datos de studentsByCollegeCourse para generar una serie de tablas, una por cada colegio, mostrando el número de estudiantes en cada curso. Además, presenta una tabla final para estudiantes que no tienen asignado un colegio o curso.
  • Propósito: Ofrecer una visión detallada de la distribución académica de los estudiantes, permitiendo analizar la composición de cada nivel educativo por institución, y destacar aquellos que aún no tienen una asignación clara.
  • Funcionalidad Clave:
    • React.memo(): Envuelve el componente para optimizar el rendimiento. Esto asegura que solo se re-renderice si sus props (filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.
    • StudentsByLevelSectionProps Interface: Define las propiedades que el componente espera, específicamente el objeto filteredStatistics (importado de ../../../interface/common/statistics).
    • Preparación de Datos por Colegio y Curso (studentsBySchoolCourseData):
      • Utiliza Object.entries() para iterar sobre filteredStatistics.studentsByCollegeCourse, que es un objeto anidado ({ [school: string]: { [course: string]: number } }).
      • Mapea estos datos para crear una estructura más amigable para el renderizado, donde cada colegio (schoolData) tiene un nombre y un array de courses (cada uno con su Curso y Estudiantes).
      • count || 0 asegura que los cursos con cero estudiantes también se muestren con un valor numérico.
    • Iteración por Colegio: El componente mapea sobre studentsBySchoolCourseData para renderizar una subsección por cada colegio.
      • Cada subsección tiene un título (<h3>) con el nombre del colegio.
      • Utiliza el componente Table (importado de ../layouts/table.tsx) para mostrar los cursos y el conteo de estudiantes para ese colegio.
    • Tabla de Estudiantes sin Colegio/Curso (Ubicación Principal): Al final de todas las tablas por nivel, se renderiza una tabla adicional para filteredStatistics.studentsWithoutSchoolAndCourse. Esta es ahora la ubicación principal y única para esta estadística en la página de estadísticas, evitando duplicación con otras secciones.
    • Estilización: El contenido está envuelto en un div con la clase className="modulos". Esta clase (definida en statistics.module.scss) le da un estilo de módulo común (padding, sombra, márgenes, bordes redondeados) a esta sección de estadísticas.
  • Rol en la aplicación: StudentsByLevelSection es un componente de sección de estadísticas clave para la gestión académica. Su rol es presentar una visualización estructurada y detallada de la asignación de estudiantes a colegios y cursos, así como una visión de los estudiantes pendientes de asignación.
tsx
// src/components/statistics/sections/students.by.level.section.tsx
import React from 'react'; // Importa React
import { Table } from '../layouts/table'; // Importa el componente genérico de tabla
import type { FilteredStatistics } from '../../../interface/common/statistics'; // Importa la interfaz FilteredStatistics

/**
 * Propiedades esperadas por el componente StudentsByLevelSection.
 */
interface StudentsByLevelSectionProps {
  filteredStatistics: FilteredStatistics; // Objeto que contiene todas las estadísticas filtradas
}

/**
 * Componente de sección que muestra una tabla de estudiantes desglosados por nivel (curso) y colegio.
 * También incluye un conteo de estudiantes sin asignación.
 * Utiliza React.memo para optimización de rendimiento.
 * @param props.filteredStatistics El objeto con las estadísticas ya filtradas.
 */
export const StudentsByLevelSection: React.FC<StudentsByLevelSectionProps> = React.memo(({
  filteredStatistics, // Datos de estadísticas filtrados
}) => {
  // Procesa los datos para agrupar estudiantes por colegio y curso, preparándolos para las tablas.
  const studentsBySchoolCourseData = Object.entries(
    filteredStatistics.studentsByCollegeCourse
  ).map(([school, courses]) => {
    // Mapea los cursos dentro de cada colegio a un formato de tabla
    const courseData = Object.entries(courses).map(([course, count]) => ({
      Curso: course,
      Estudiantes: count || 0, // Asegura que los conteos sean numéricos, 0 si no hay estudiantes
    }));

    return {
      school,   // Nombre del colegio
      courses: courseData, // Datos de cursos para ese colegio
    };
  });

  return (
    // Contenedor de la sección de estadísticas, aplicando una clase de módulo CSS.
    // La clase "modulos" proporciona estilos comunes como padding, sombra y márgenes.
    <div className="modulos">
      <h2>Estudiantes por nivel</h2> {/* Título principal de la sección */}
      {/* Mapea y renderiza una tabla por cada colegio. */}
      {studentsBySchoolCourseData.map((schoolData) => (
        <div key={schoolData.school}>
          <h3>{schoolData.school}</h3> {/* Título para cada colegio */}
          <Table
            data={schoolData.courses} // Datos de cursos para la tabla actual
            columns={["Curso", "Estudiantes"]} // Columnas de la tabla
          />
        </div>
      ))}
      {/* Tabla para mostrar estudiantes sin colegio o curso asignado.
          Esta es la ubicación principal y única para esta estadística. */}
      <Table
        data={[
          {
            "Estudiantes sin colegio/curso": // Etiqueta de la fila
              filteredStatistics.studentsWithoutSchoolAndCourse, // Conteo de estudiantes
          },
        ]}
        columns={["Estudiantes sin colegio/curso"]} // Columnas de la tabla
      />
    </div>
  );
});

statistics.filters.tsx

Este archivo define la función calculateStatistics, que es el "cerebro" de la página de estadísticas. Se encarga de procesar una lista de estudiantes, aplicar filtros y calcular diversas métricas estadísticas que luego se visualizan en gráficos y tablas.

  • Descripción: calculateStatistics es una función pura que toma una lista de estudiantes y criterios de filtro opcionales (fuente, colegio, curso). Aplica estos filtros secuencialmente y luego calcula una amplia gama de estadísticas, incluyendo conteos por fuente, comunicación, género, estado general, distribución por colegio/curso y captador. Ahora, utiliza una constante centralizada para los nombres de los captadores, mejorando la consistencia.
  • Propósito: Centralizar y encapsular toda la lógica de cálculo de estadísticas. Esto asegura que los datos mostrados en la página de estadísticas sean consistentes, actualizados según los filtros, y que la lógica de negocio esté separada de los componentes de la UI.
  • Funcionalidad clave:
    • Mapeos de Enum a Nombres Legibles (schoolMap, courseMap):
      • Define objetos schoolMap y courseMap que mapean los valores de los enums School y Course (ej., School.Quinta) a sus nombres completos y legibles (ej., "QUINTA NORMAL"). Estos mapeos son esenciales para la presentación de los datos en la UI.
    • calculateStatistics Función:
      • StatisticsFiltersProps Interface: Define las propiedades de entrada: students (la lista completa de estudiantes), y los criterios de filtro opcionales source, school, course.
      • Filtrado Inicial:
        • Inicializa filteredStudents con la lista completa.
        • Aplica filtros individuales (SourceFilter, SchoolFilter, CourseFilter, importados de ./filters/) solo si los criterios correspondientes (source, school, course) están presentes. Esto permite que el componente Statistics actualice los datos dinámicamente.
      • Cálculo de Estadísticas Clave:
        • studentsWithoutSchoolAndCourse: Calcula el número de estudiantes que no tienen asignado un colegio o un curso.
        • studentsByGeneralState: Agrupa y cuenta estudiantes por cada estado de feedback (utilizando uniqueFeedbacks).
        • Conteo por Fuente: socialMediaCount, captadorCount, noRegisteredSource.
        • Conteo por Preferencia de Comunicación: whatsappCount, phoneCount, noRegisteredCommunication.
        • Conteo por Captador (contactsByPerson): Ahora utiliza Constants.CAPTATOR_NAMES para iterar sobre una lista predefinida y centralizada de captadores (incluyendo "No Ingresa Captador"), contando los estudiantes asignados a cada uno.
        • Conteo por Colegio (studentsBySchool): Cuenta los estudiantes por cada colegio.
        • Conteo por Colegio y Curso (studentsByCollegeCourse): Estructura los datos en un objeto anidado {[colegio]: {[curso]: cantidad}}.
        • Conteo Detallado por Feedback, Colegio y Curso (studentsBySchoolCourseFeedback): Genera una lista plana de objetos que desglosan la cantidad de estudiantes por escuela, curso y estado de feedback.
        • Conteo por Género (genderCount): Cuenta estudiantes por categorías de género.
      • FilteredStatistics Interface: El valor de retorno de calculateStatistics sigue esta interfaz, asegurando que todos los conteos y agrupaciones esperadas estén presentes y tipados correctamente.
    • Importación de Filtros Individuales: Reutiliza las funciones SourceFilter, SchoolFilter y CourseFilter de la subcarpeta filters.
  • Rol en la aplicación: statistics.filters.tsx es una utilidad de procesamiento de datos fundamental. Es el motor detrás de todas las visualizaciones en la página de estadísticas, transformando los datos crudos de los estudiantes en insights estructurados y filtrables que la interfaz de usuario puede presentar.
typescript
// src/components/statistics/statistics.filters.tsx
import { School, Course } from "../../interface/student/student"; // Importa los enums School y Course
import { SourceFilter } from "./filters/source.filter"; // Importa la función de filtro por fuente
import { SchoolFilter } from "./filters/school.filter"; // Importa la función de filtro por colegio
import { CourseFilter } from "./filters/course.filter"; // Importa la función de filtro por curso
import {
  StatisticsFiltersProps, // Interfaz para las props de entrada de calculateStatistics
  FilteredStatistics,     // Interfaz para la estructura de datos de salida
} from "../../interface/common/statistics"; // Importa las interfaces de tipado

// ¡CAMBIO CLAVE! Importa el objeto Constants para acceder a CAPTATOR_NAMES
import Constants from "../../utils/constants";

// Mapeos de valores de Enum a nombres legibles para la UI
const schoolMap: Record<School, string> = {
  [School.Quinta]: "QUINTA NORMAL",
  [School.Buin]: "BUÍN",
  [School.Granja]: "LA GRANJA",
  [School.Nunoa]: "ÑUÑOA",
  [School.Pudahuel]: "PUDAHUEL", // ¡CORREGIR TYPO! (PUDAHUEL)
  [School.Miguel]: "SAN MIGUEL",
};

const courseMap: Record<Course, string> = {
  [Course.NB1]: "1° NIVEL BÁSICO",
  [Course.NB2]: "2° NIVEL BÁSICO",
  [Course.NB3]: "3° NIVEL BÁSICO",
  [Course.NM1]: "1° NIVEL MEDIO",
  [Course.NM2]: "2° NIVEL MEDIO",
};

/**
 * Calcula diversas estadísticas a partir de una lista de estudiantes, aplicando filtros opcionales.
 * @param {StatisticsFiltersProps} props - El objeto con los estudiantes y los criterios de filtro.
 * @returns {FilteredStatistics} Un objeto que contiene todas las estadísticas calculadas y filtradas.
 */
export const calculateStatistics = ({
  students, // Lista completa de estudiantes
  source,   // Criterio de filtro por fuente (opcional)
  school,   // Criterio de filtro por colegio (opcional)
  course,   // Criterio de filtro por curso (opcional)
}: StatisticsFiltersProps): FilteredStatistics => {
  let filteredStudents = students; // Copia inicial de los estudiantes

  // Primero, obtenemos los estados únicos de feedback de TODOS los estudiantes originales
  // Esto asegura que la lista de feedbacks sea exhaustiva antes de aplicar filtros específicos.
  const uniqueFeedbacks = Array.from(
    new Set(students.map((s) => s.positiveFeedback || "AÚN SIN RESPUESTAS"))
  );

  // Luego, aplicamos los filtros a la lista de estudiantes.
  // La lista 'filteredStudents' se actualiza secuencialmente con cada filtro aplicado.
  if (source) {
    filteredStudents = SourceFilter({ students: filteredStudents, source });
  }
  if (school) {
    filteredStudents = SchoolFilter({ students: filteredStudents, school });
  }
  if (course) {
    filteredStudents = CourseFilter({ students: filteredStudents, course });
  }

  // --- Cálculos de Estadísticas ---

  // 1. Conteo de estudiantes sin asignación de colegio/curso.
  const studentsWithoutSchoolAndCourse = filteredStudents.filter(
    (s) => !s.school || !s.course
  ).length;

  // 2. Conteo de estudiantes por estado general de feedback.
  const studentsByGeneralState: { estado: string; cantidad: number }[] = [];
  uniqueFeedbacks.forEach((feedback) => {
    studentsByGeneralState.push({
      estado: feedback,
      cantidad: filteredStudents.filter(
        (s) => (s.positiveFeedback || "AÚN SIN RESPUESTAS") === feedback
      ).length,
    });
  });

  // 3. Conteo de estudiantes por fuente de inscripción.
  const socialMediaCount = filteredStudents.filter(
    (s) => s.source === "REDES SOCIALES"
  ).length;
  const captadorCount = filteredStudents.filter(
    (s) => s.source === "CAPTADOR"
  ).length;
  const noRegisteredSource = filteredStudents.filter((s) => !s.source).length;

  // 4. Conteo de estudiantes por preferencia de comunicación.
  const whatsappCount = filteredStudents.filter(
    (s) => s.communicationPreference === "WHATSAPP"
  ).length;
  const phoneCount = filteredStudents.filter(
    (s) => s.communicationPreference === "TELÉFONO"
  ).length;
  const noRegisteredCommunication = filteredStudents.filter(
    (s) => !s.communicationPreference
  ).length;

  // 5. Conteo de contactos por captador. ¡Ahora usa Constants.CAPTATOR_NAMES!
  const contactsByPerson = Constants.CAPTATOR_NAMES.map((person) => {
    let studentCount = 0;
    if (person === "No Ingresa Captador") {
      studentCount = filteredStudents.filter((s) => !s.contact).length;
    } else {
      studentCount = filteredStudents.filter(
        (s) => s.contact === person.toUpperCase()
      ).length;
    }
    return {
      Captador: person,
      Contacto: studentCount,
    };
  });

  // 6. Conteo de estudiantes por colegio.
  const studentsBySchool = Object.values(School).map((schoolValue) => ({
    school: schoolMap[schoolValue], // Usa el mapeo a nombre legible
    students: filteredStudents.filter((s) => s.school === schoolValue).length,
  }));

  // 7. Conteo de estudiantes por colegio y curso (anidado).
  const studentsByCollegeCourse: {
    [school: string]: { [course: string]: number };
  } = {};
  Object.values(School).forEach((schoolValue) => {
    const schoolName = schoolMap[schoolValue];
    studentsByCollegeCourse[schoolName] = {};
    Object.values(Course).forEach((courseValue) => {
      const courseName = courseMap[courseValue];
      studentsByCollegeCourse[schoolName][courseName] = filteredStudents.filter(
        (s) => s.school === schoolValue && s.course === courseValue
      ).length;
    });
  });

  // 8. Conteo de estudiantes por feedback, escuela y curso (detallado).
  const studentsBySchoolCourseFeedback: {
    Escuela: string;
    Curso: string;
    feedback: string;
    Estudiantes: number;
  }[] = [];
  Object.values(School).forEach((schoolValue) => {
    Object.values(Course).forEach((courseValue) => {
      uniqueFeedbacks.forEach((feedback) => {
        studentsBySchoolCourseFeedback.push({
          Escuela: schoolMap[schoolValue],
          Curso: courseMap[courseValue],
          feedback,
          Estudiantes: filteredStudents.filter(
            (s) =>
              s.school === schoolValue &&
              s.course === courseValue &&
              (s.positiveFeedback || "AÚN SIN RESPUESTAS") === feedback
          ).length,
        });
      });
    });
  });

  // 9. Conteo de estudiantes por género.
  const genderCount = {
    hombres: filteredStudents.filter((s) => s.sex === "MASCULINO").length,
    mujeres: filteredStudents.filter((s) => s.sex === "FEMENINO").length,
    otros: filteredStudents.filter((s) => s.sex === "OTROS").length,
    noContesta: filteredStudents.filter((s) => !s.sex).length,
  };

  // Retorna todas las estadísticas calculadas en un solo objeto.
  return {
    socialMediaCount,
    captadorCount,
    noRegisteredSource,
    whatsappCount,
    phoneCount,
    noRegisteredCommunication,
    contactsByPerson,
    studentsBySchool,
    studentsByCollegeCourse,
    studentsBySchoolCourseFeedback,
    genderCount,
    studentsByGeneralState,
    studentsWithoutSchoolAndCourse,
  };
};

loading.spinner.tsx

Este archivo define el componente LoadingSpinner, un indicador visual que se muestra al usuario para señalar que la aplicación está procesando o cargando información.

  • Descripción: LoadingSpinner es un componente presentacional simple que renderiza un icono de carga animado (spinner) centrado en la pantalla. Sus estilos se definen en _loading-spinner.scss, un archivo SCSS dedicado que lo posiciona, le da estilos visuales y anima su rotación.
  • Propósito: Mejorar la experiencia del usuario proporcionando retroalimentación visual durante periodos de espera (ej., llamadas a la API, carga de datos). Esto evita que la aplicación parezca "congelada" y mantiene al usuario informado de que hay una actividad en curso.
  • Funcionalidad clave:
    • Contenedor de Superposición: Renderiza un div externo que actúa como una superposición (.spinner-overlay). Este div cubre toda la pantalla (position: fixed; top: 0; ...), centra su contenido (display: flex; ...), y aplica un fondo semitransparente (background-color: rgba(vars.$white, 0.75);) para enfocar la atención en el spinner. Tiene un z-index alto para asegurar que se muestre por encima de la mayoría de los otros elementos de la UI.
    • Spinner Visual: Dentro del contenedor, hay un div que se estiliza como el spinner circular animado (.spinner). Sus propiedades CSS le dan forma circular, un borde de color primario con una parte transparente, y una animación de rotación continua.
  • Rol en la aplicación: LoadingSpinner es un componente común de UI que mejora la usabilidad y la experiencia de usuario. Se utiliza en cualquier lugar donde sea necesario indicar una actividad de carga, como durante el proceso de autenticación (ProtectedRoute) o la obtención de datos en páginas.
tsx
// src/components/ui/loading.spinner.tsx
import React from "react";
// No necesitamos importar un CSS Module aquí, ya que las clases son globales
// y se espera que se apliquen directamente a través de global.scss.

/**
 * Componente presentacional que renderiza un spinner de carga.
 * Se utiliza para indicar que la aplicación está procesando o cargando datos.
 * El spinner se superpone y centra en la pantalla.
 */
export const LoadingSpinner = () => (
  // Aplica las clases SCSS definidas en _loading-spinner.scss
  <div className="spinner-overlay">
    <div className="spinner"></div>
  </div>
);