Appearance
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.tsxcrea un contexto React para el estado de autenticación. Incluye elAuthProviderque gestiona el token (usandouseAuthStorage), decodifica la información del usuario (usandouseDecodeToken), 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. Proporcionatoken(el token almacenado),updateTokenyclearToken.useDecodeToken(storedToken): Hook (de../../hooks/use.decode.token.ts) que decodifica el token almacenado para extraeruserIdyemail. 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 delogin()explícita (a través de la funciónlogin) está en curso.
useEffectpara Validación Inicial del Token: Este efecto se ejecuta una vez al montar elAuthProvider(y cuandostoredTokencambia). Es responsable de:- Establecer
isInitialLoadingatrueal inicio. - Decodificar y validar el
storedToken. - Establecer
isAuthenticatedatrueofalsey limpiar el token si es inválido. - Finalizar estableciendo
isInitialLoadingafalse.
- Establecer
login(newToken: string)(useCallback):- Función asíncrona para iniciar sesión. Recibe el
newToken(el token ya obtenido del backend, por ejemplo, porauthenticateUser). - Establece
isLoginProcessLoadingatruey limpialoginError. - 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
isAuthenticatedafalse, guarda unloginError, y propaga el error. - El bloque
finallyasegura queisLoginProcessLoadingse establezca enfalse.
- Función asíncrona para iniciar sesión. Recibe el
logout()(useCallback):- Función para cerrar sesión.
- Llama a
clearToken()para eliminar el token del almacenamiento. - Establece
setIsAuthenticated(false)y limpia cualquierloginError.
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 propiedadisLoadingse combina a partir deisInitialLoading || isLoginProcessLoading, lo que refleja el estado de carga general de la autenticación (ya sea la verificación inicial o un intento de login).useMemomemoiza este objeto para optimizar el rendimiento.
- 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
<AuthContext.Provider value={value}>: Envuelve a loschildren(el resto de la aplicación) y les proporciona elvalueque contiene el estado y las funciones del contexto.useAuth = () => useContext(AuthContext): Una funciónuseAuthexportada para facilitar el consumo del contexto en cualquier componente funcional React, simplemente llamandoconst auth = useAuth();.
- Rol en la aplicación:
auth.context.tsxes 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:
useAuthStorageinicializa el estado del token leyendo una cookie. Proporciona funciones para actualizar y limpiar el token, y unuseEffectque 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_NAMEutilizandoCookies.get(). Si la cookie no existe, el token se inicializa comonull.
- Se inicializa con una función que intenta leer el token existente de la cookie
clearToken()(useCallback):- Es una función memoizada (con
useCallback) que establece el estadotokenanull. Al hacer esto, dispara eluseEffectpara eliminar la cookie.
- Es una función memoizada (con
updateToken(newToken: string)(useCallback):- Es una función memoizada que establece el estado
tokena un nuevo valor (newToken). Al hacer esto, dispara eluseEffectpara guardar el nuevo token en la cookie.
- Es una función memoizada que establece el estado
- Sincronización con Cookies (
useEffect):- Este efecto se ejecuta cada vez que el estado interno
tokencambia. - Si
tokentiene un valor:Cookies.set(TOKEN_COOKIE_NAME, token, { ... })guarda el token en una cookie. Las opcionesexpires,secureysameSitese 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: p: La cookie solo se enviará sobre HTTPS en producción, protegiéndola.rocess.env.NODE_ENV === "production" sameSite: "strict": Proporciona protección contra ataques CSRF al asegurar que la cookie solo se envíe en peticiones del mismo sitio.
- Si
tokenesnull:Cookies.remove(TOKEN_COOKIE_NAME)elimina la cookie del navegador, limpiando la sesión.
- Este efecto se ejecuta cada vez que el estado interno
- Retorno Memoizado (
useMemo):- El hook retorna un objeto que contiene el
tokenactual, y las funcionesclearTokenyupdateToken. useMemose 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.
- El hook retorna un objeto que contiene el
- Constantes de Configuración:
- Rol en la aplicación:
useAuthStoragees 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:
StudentDetailses 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 componenteGridyProfileFieldpara 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:
StudentDetailsPropsInterface: Define las propiedades que este componente espera: el objetostudentcompleto, y funcionesonEdityonDeletepara 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 tipostring), renderiza un enlace (<a>) con un<button>que permite ver el documento en una nueva pestaña. - Si no hay
link(esundefined,nullo un objetoFileno persistido), renderiza un<button disabled>con un icono de archivo, indicando que no hay documento disponible. Utiliza la clasecss.disabledButton.
- Sección de Información Principal (
mainInformation):- Utiliza
Gridcongrid-columns-2ycss.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
ProfileFielddentro de una lista (ul). - Contiene los botones "Editar" y "Eliminar", que llaman a las props
onEdityonDeleterespectivamente. Estos botones están anidados en otroGridcongrid-columns-2.
- Utiliza
- Sección de Datos Personales (
personalData):- Otra sección organizada con
Grid(grid-columns-2ycss.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).
- Otra sección organizada con
- Sección de Datos de Contacto:
- Anidada dentro de otro
Grid(grid-columns-2ycss.personalData), contieneProfileFields para la fuente, estado de feedback, contacto, fecha de contacto y preferencia de comunicación.
- Anidada dentro de otro
- Sección de Datos de Llamadas (
Datos llamadas):- Organizada con
Grid(grid-columns-2ycss.personalData). - Muestra la información de las tres llamadas (
call1,call2,call3) y sus respectivos comentarios (comments1,comments2,comments3) utilizandoProfileFields. Aquí se aplican las clasescss.commentsa los<ul>que contienen los comentarios.
- Organizada con
- Sección de Documentos:
- Muestra los botones de documentos generados por
getDocumentButtonparastudentImage,birthCertificate,studyCertificateylinkDni.
- Muestra los botones de documentos generados por
- Sección de Fecha de Creación:
- Muestra la fecha de creación del registro del estudiante.
ProfileFieldComponente: Utilizado extensivamente para renderizar cada campo de forma consistente, mostrando un icono, una etiqueta y el valor del dato.
- Rol en la aplicación:
StudentDetailses 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;
drop.down.options.tsx
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:
DropdownOptionses un componente presentacional que recibe un array de objetosDropdownOption(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:
DropdownOptionInterface: Importada de../../../interface/common/dropdown-options/document.dropdown, define la estructura de cada opción (label: string; path: string;).useNavigate(): Hook dereact-router-domutilizado 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
optionsrecibido como prop utilizandooptions.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.
- Itera sobre el array
- Estilización: El componente acepta props opcionales
classNameystylepara aplicar estilos personalizados desde su contexto padre. Ya no se importa el archivo CSS directamente dentro del componente.
- Rol en la aplicación:
DropdownOptionses un componente presentacional y de navegación. Es utilizado por componentes de nivel superior (como laSidebar) 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:
FormInputes 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:
FormInputPropsInterface: 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
typees"select"y hayoptions, se renderiza un<select>. En caso contrario, se renderiza un<input>. - Control de Valor:
- En
select, el valor se fuerza aString(value ?? "")para asegurar que siempre haya un valor de tipo cadena. - En
input, si es tipofile, se deja comoundefinedpara mantenerlo como input no controlado; si no, se convierte a cadena conString(value ?? "").
- En
- Mapeo de Opciones: Si el tipo es
"select", se agregan las opciones dentro del<select>con su clave comovaluey texto. - Propagación directa de eventos y atributos: Props como
onChange,placeholder,autocompleteyacceptse pasan directamente al input correspondiente. - Accesibilidad (
htmlFor): La etiqueta<label>se enlaza con el input a través del atributohtmlFor, usandoidoname.
- Rol en la aplicación:
FormInputes 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:
AddStudentFormes un componente de React que renderiza un formulario extenso con campos para datos personales, de contacto, de llamadas y de documentación. Utiliza el componenteFormInputreutilizable para cada campo y elGridpara 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
useAddStudenty la páginaAddStudent). - Funcionalidad clave:
AddStudentFormPropsInterface: Define las propiedades que este componente espera recibir:studentData(el objetoStudentcon los datos actuales del formulario),handleChange(función para actualizar los datos al cambiar un input), yhandleSubmit(función para enviar el formulario).FormInputComponente: Utilizado extensamente para renderizar cada campo del formulario (nombres, apellidos, RUT, email, etc.).FormInputes 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 propvalueaFormInput. Se utiliza el patrónvalue={studentData.campo || ""}para asegurar que los camposnulloundefinedse traten como cadenas vacías, evitando advertencias de React para inputs controlados. onChange: La prophandleChangedelAddStudentFormse pasa directamente alonChangede cadaFormInput, 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 utilitariaformatDateForInputpara formatear la fecha a la cadenayyyy-MM-ddque los inputstype="date"esperan.
- Gestión de
FormSectionComponente (Reutilizado): Este es un componente genérico que encapsula la estructura común de las secciones del formulario (divcon títuloh2y unGrid). 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. GridComponente: 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ónhandleSubmitproporcionada por las props.
- Rol en la aplicación:
AddStudentFormes 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 hookuseAddStudenty la páginaAddStudent. 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:
FormSectiones 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
AddStudentFormoUpdateStudentForm. Promueve la consistencia visual y estructural de las secciones de formulario y reduce la repetición de código. - Funcionalidad clave:
FormSectionPropsInterface: 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 componenteGridinterno (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 componentesFormInputu otros elementos del formulario.sectionCssClass?: string: Una clase CSS opcional que se aplicará aldivcontenedor principal de la sección (ej.,css.datosPersonalesdestudent.profile.module.scss), permitiendo estilos de módulo específicos.
- Composición de UI: Combina un
<h2>para el título y un componenteGridpara organizar loschildren(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:
FormSectiones 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:
StudentFormrenderiza 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. UtilizaFormInputpara campos individuales,Gridpara el layout y el nuevo componenteFormSectionpara 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
useStudentProfiley la páginaStudentProfile). - Funcionalidad clave:
StudentFormPropsInterface: Define las propiedades que este componente espera recibir:student(el objetoStudentcon los datos actuales a editar),onChange(función para actualizar los datos al cambiar un input),onSubmit(función para enviar el formulario), yonDeleteFile(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 tipofiley la confirmación de eliminación de archivos. ProporcionahandleFileChange(para elonChangede inputs de archivo) yhandleDeleteFile(renombrada ahandleConfirmDeleteFilepara evitar conflictos) para la eliminación.renderDocumentInput(label: string, name: keyof Student)(useCallback):- Es una función auxiliar memoizada que renderiza un
FormInputde tipofile. - Si el estudiante ya tiene un archivo existente (
typeof value === "string"yvalueno 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
useCallbackpara memoizar la función y evitar re-creaciones innecesarias, mejorando el rendimiento.
- Es una función auxiliar memoizada que renderiza un
FormSectionComponente (Reutilizado): Este componente genérico (importado de../form-section/form.section.tsx) encapsula la estructura común de las secciones del formulario (divcon títuloh2y unGrid). 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. FormInputComponente: Utilizado extensivamente para renderizar cada campo del formulario.- Gestión de
value: Los valores de los campos se pasan como la propvalueaFormInput. Se usavalue={student.campo || ""}para asegurar que los camposnulloundefinedse traten como cadenas vacías. onChange: La proponChangedelStudentFormse pasa directamente alonChangede cadaFormInput.- Fechas (
type="date"): Para los campos de fecha, se utiliza la función utilitariaformatDateForInput(importada de../../../utils/date.formatters).
- Gestión de
GridComponente: 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ónonSubmitproporcionada por las props.
- Rol en la aplicación:
UpdateStudentFormes un componente presentacional especializado que proporciona la interfaz de usuario para la edición de perfiles de estudiantes. Funciona en conjunto con el hookuseStudentProfiley 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:
Grides 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:
GridPropsInterface: Importada de../../../interface/common/grid.props, define las propiedades que el componente espera, comochildren(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 aldivcontenedor).- Generación Dinámica de Clases:
const dynamicClass = columns ? \grid-columns-${columns}` : "";: Si se proporciona la propcolumns(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 ladynamicClass(si existe) y cualquierclassNameadicional que se le pase a la propclassName. Esto permite combinar estilos base con estilos dinámicos y específicos del componente.
- Propagación de Props:
...propsasegura que cualquier otra propiedad HTML estándar (ej.,id,style,aria-label) que se pase al componenteGridse propague directamente al elementodivsubyacente. - Contenedor de Hijos (
children): Renderizachildrendentro deldivde la cuadrícula, permitiendo que cualquier elemento HTML o componente React se coloque dentro del layout.
- Rol en la aplicación:
Grides 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:
FormInputes 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:
FormInputPropsInterface: 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 incluyenullen el tipovalue(el componente se encarga de convertirnulla"").- Renderizado Condicional de Inputs: El componente utiliza una sentencia condicional para renderizar un elemento HTML
<select>si eltypees"select"y se proporcionanoptions, o un elemento<input>para todos los demás tipos de inputs. - Manejo de
valueControlado:- Para los
selects yinputs (exceptofile),value={String(value ?? "")}asegura que el valor sea siempre una cadena de texto.value ?? ""convierteundefinedonulla una cadena vacía (""), yString()convierteDate,numberoFile(si un tipo inesperado llegara a pasar) a su representación en cadena, satisfaciendo al atributovaluedel HTML. - Para los
inputs detype="file", elvaluese establece explícitamente comoundefined, ya que estos inputs son por naturaleza "no controlados" en React por la propvalue.
- Para los
- Mapeo de Opciones para
select: Si eltypees"select", el componente mapea el arrayoptionsa elementos<option>, incluyendo una opción por defecto "Seleccione una opción". Se utiliza la opción misma comokey, lo cual es adecuado si las opciones son cadenas de texto únicas y estables. - Propagación Directa de
onChange: La proponChangese pasa directamente al atributoonChangede los elementos<input>o<select>, eliminando una funciónwrapperinnecesaria y simplificando el flujo de datos. - Accesibilidad (
htmlFor): La etiqueta (<label>) está correctamente asociada con su input (<input>o<select>) usando el atributohtmlFory elid(oname) del input, lo que mejora la accesibilidad. - Atributos HTML Estándar: Soporta atributos HTML estándar como
type,name,required, yaccept(para inputs de archivo).
- Rol en la aplicación:
FormInputes 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 (comoLoginForm,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:
ProfileFieldes 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:
ProfileFieldPropsInterface: 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,nulloundefined.
- 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
FontAwesomeIconpara mostrar el ícono (icon={icon}) proporcionado. Se le aplica la clasecss.profileIconpara su estilización. - Etiqueta y Valor: Muestra la
labelen negrita (<strong>) seguida de dos puntos y elvalue. - Manejo de Valores Vacíos: Si el
valueesnulloundefined, 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.profileIconal ícono y asume que los estilos del<li>contenedor (comodisplay: flex; flex-wrap: wrap;) provienen de un SCSS padre (ej.,student.profile.module.scss).
- Rol en la aplicación:
ProfileFieldes 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:
ToastMessagees una función utilitaria que, al ser llamada, invoca los métodos dereact-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 dereact-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:
ToastMessagePropsInterface: Define las propiedades que la función espera:type: 'success' | 'error' | 'warning' | 'info': El tipo de notificación, que corresponde a los métodos detoastdereact-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.
defaultOptionsConstante: Un objeto que define la configuración predeterminada para todas las notificaciones toast si no se proporcionan opciones específicas. Incluyeposition,autoClose,hideProgressBar,closeOnClick,pauseOnHover,draggable,progress, ytheme.ToastMessageFunción:- Toma las props
type,messageyoptions. - Combina las
defaultOptionscon cualquieroptionspersonalizada proporcionada (mergedOptions). - Utiliza una sentencia
switchpara llamar al métodotoastapropiado (toast.success,toast.error, etc.) basándose en latypede la notificación. Eldefaultmaneja tipos no reconocidos con untoastgené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 elToastContainer(definido enmain.tsx).
- Toma las props
- Rol en la aplicación:
ToastMessagees una utilidad de UI que actúa como una capa de abstracción sobrereact-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:
ProtectedLayoutes un componente de React que utilizaReact.memopara 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 laProtectedRoute(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.memomemoiza 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
isLoadingoisAuthenticated. Se asume que el componenteProtectedRoute(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.ProtectedLayoutse renderiza solo cuando el usuario ya está autenticado y listo. SidebarComponente: 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
mainSemántico: Utiliza la etiqueta semántica<main>para el área de contenido principal, aplicando la clasecss.mainContent. Esto mejora la estructura del documento y la accesibilidad. - Contenedor de Contenido (
mainBackground): Dentro de<main>, undivcon la clasecss.mainBackgroundenvuelve el contenido real de la ruta. Esta clase probablemente aplica estilos de fondo, padding y sombra. <Outlet />: Componente dereact-router-dom. Es un marcador de posición que renderiza el elementochildrende la ruta anidada actual que coincide con la URL. Por ejemplo, si la ruta es/dashboard/student/123,Outletrenderizará elStudentProfilecorrespondiente.- Estilización (
css.layoutContainer,css.mainContent,css.mainBackground): Aplica clases CSS desdeprotected.layout.module.scsspara definir el layout Flexbox principal, el área de contenido y el fondo de las secciones.
- Rol en la aplicación:
ProtectedLayoutes 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:
PublicLayoutes 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:
PublicLayoutPropsInterface: Importada de../../../interface/layouts/public.layouts.props, esta interfaz define las propiedades que el componente espera, principalmentechildren(el contenido de la página pública a renderizar dentro del layout).- Elemento
mainSemá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 undivcontenedor. Esta clase sería responsable de cualquier estilo general para el layout público (como centrado, fondos, etc.). - Contenedor de Hijos (
children): Simplemente renderiza loschildrenpasados como propiedades dentro del elemento<main>, actuando como un simple "passthrough" para el contenido de la página.
- Rol en la aplicación:
PublicLayoutes 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;
sidebar.tsx
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:
Sidebares 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 ensidebar.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, onullsi ninguno lo está.
useAuth(): Hook para acceder al contexto de autenticación, específicamente para la funciónlogoutque cierra la sesión del usuario.useNavigate(): Hook dereact-router-dompara la navegación programática.toggleMenu(): Función que alterna el estado deisMenuOpen, 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.
- Llama a la función
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 arrayoptions(generado porgenerateCourseOptions) que contiene los cursos de ese colegio con sus respectivas rutas. useMemooptimiza 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).
- Un array memoizado que define la estructura de los menús desplegables para los colegios. Cada ítem incluye una
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 hookuseSchoolClass.
- Ahora importada de
handleDropdownClick(label: string): Manejador de clic para los títulos de los colegios en la barra lateral. Alterna el estadoactiveDropdownpara abrir o cerrar el desplegable correspondiente.handleItemClick(path?: string): Manejador de clic para los elementos de navegación. Si se proporciona unapath, redirige al usuario a esa ruta y cierra el menú lateral.- Centralización de Constantes: Importa el objeto
Constantsde../../utils/constants.tspara acceder a valores comoConstants.DROPDOWN_ITEM_HEIGHT_PX, asegurando la consistencia de las dimensiones calculadas para el menú desplegable. - Componente
DropdownOptions: Utiliza el componenteDropdownOptions(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 objetostyle(que contiene la altura dinámica yoverflow: 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 porisMenuOpenycss.openpara la animación responsiva. - Contiene un logo, títulos de sección (
<h3>Colegios</h3>), y listas de navegación (<ul>). - Los
dropdownItemsse mapean para crear los menús desplegables de colegios/cursos. - Un botón "Salir" al final para cerrar la sesión.
- Un botón de menú "hamburguesa" (
- Estados Internos (
- Rol en la aplicación:
Sidebares 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:
CourseFilteres una función pura que toma un array de objetosStudenty un valor deCourse. Devuelve un nuevo array que contiene solo aquellos estudiantes cuya propiedadcoursecoincide 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:
CourseFilterPropsInterface: Importada de../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas:students(el array de estudiantes a filtrar) ycourse(el valor específico deCoursepor el cual filtrar).- Lógica de Filtrado: Utiliza el método estándar de JavaScript
Array.prototype.filter(). Comprueba si la propiedadcoursede cada objetostudentes estrictamente igual (===) al valorcourseproporcionado 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:
CourseFilteractú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:
SchoolFilteres una función pura que toma un array de objetosStudenty un valor deSchool. Devuelve un nuevo array que contiene solo aquellos estudiantes cuya propiedadschoolcoincide 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:
SchoolFilterPropsInterface: Importada de../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas:students(el array de estudiantes a filtrar) yschool(el valor específico deSchoolpor el cual filtrar).- Lógica de Filtrado: Utiliza el método estándar de JavaScript
Array.prototype.filter(). Comprueba si la propiedadschoolde cada objetostudentes estrictamente igual (===) al valorschoolproporcionado 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:
SchoolFilteractú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:
StudentFilterses 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:
StudentFiltersPropsInterface: Importada de../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas:students(la lista de estudiantes completa), y los criterios de filtro opcionalessourceFilter,schoolFilter, ycourseFilter.- Importación de Filtros Individuales: Importa las funciones
SourceFilter,SchoolFilter, yCourseFilterdesde otros archivos en la misma carpeta, lo que permite reutilizar la lógica de filtrado atómico. - Lógica de Filtrado Secuencial:
- Inicializa
filteredStudentscon la listastudentscompleta. - Para cada
sourceFilter,schoolFilterocourseFilterque esté presente (nonullniundefined), aplica la función de filtrado correspondiente, pasando la listafilteredStudentsactual como entrada y el criterio específico. filteredStudentsse actualiza en cada paso, encadenando los filtros.
- Inicializa
- 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:
StudentFilterses 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:
StatisticsFilterControlses un componente presentacional que renderiza tres selectores (<select>) para filtrar estudiantes porSource,SchoolyCourse. Mantiene los valores seleccionados sincronizados con su componente padre a través de un callbackonFilterChange. 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:
StatisticsFilterControlsPropsInterface: Define las propiedades esperadas por el componente:selectedSource,selectedSchool,selectedCourse(los valores de filtro actuales del padre) yonFilterChange(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
valuedel selector. Si el valor es una cadena vacía (''), lo convierten anull(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 tiposEnumdeSource,SchoolyCourse.
- Cada selector tiene su propio manejador de cambio (
- Población de Selectores con Constantes Centralizadas:
- Ahora, las opciones de cada selector (
Source,School,Course) se obtienen directamente de las constantes centralizadas enConstants.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...".
- Ahora, las opciones de cada selector (
- Sincronización de Valor (
value={selectedSource || ''}):- Los selectores están "controlados", lo que significa que su
valuese enlaza a las propsselectedSource,selectedSchoolyselectedCourse. Si la prop esnull, se usa una cadena vacía para el selector.
- Los selectores están "controlados", lo que significa que su
- Estilización en Línea: El contenedor principal tiene estilos en línea para
marginBottom,display: flexygap.
- Rol en la aplicación:
StatisticsFilterControlses 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:
StudentFilterses 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:
StudentFiltersPropsInterface: Importada de../../../interface/common/statistics, esta interfaz define las propiedades de entrada esperadas:students(la lista de estudiantes completa), y los criterios de filtro opcionalessourceFilter,schoolFilter, ycourseFilter.- Importación de Filtros Individuales: Importa las funciones
SourceFilter,SchoolFilter, yCourseFilterdesde 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
filteredStudentscon la listastudentscompleta. - Para cada criterio de filtro (
sourceFilter,schoolFilter,courseFilter) que esté presente (es decir, nonullniundefined), aplica la función de filtrado correspondiente, pasando la listafilteredStudentsactual como entrada y el criterio específico. - La variable
filteredStudentsse actualiza en cada paso, encadenando los filtros.
- Inicializa
- 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:
StudentFilterses 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:
BarChartrecibe un array de datos (data) y un título (title). Utiliza un elemento<canvas>y el hookuseEffectpara inicializar y gestionar la instancia del gráfico de barras deChart.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:
BarChartPropsInterface: Importada de../../../interface/common/statistics, esta interfaz define las propiedades que el componente espera:data(un array de objetos{ name: string; value: number }) ytitle(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 objetoChart.jscreada. 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 propiedadesdataotitlecambian, 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.currentya existe. Si es así, llama achartInstance.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
Chartpasando el contexto, el tipo de gráfico ("bar"), los datos (mapeandonamealabelsyvalueadata), y lasoptionsbásicas (comobeginAtZero: truepara el eje Y). - Función de Limpieza (
return () => { ... }): La función de retorno deuseEffectes esencial. Se ejecuta cuando el componente se desmonta o antes de que eluseEffectse re-ejecute (por un cambio en las dependencias). Asegura quechartInstance.current.destroy()se llame para limpiar correctamente el gráfico ychartInstance.currentse resetee anull.
- Este hook gestiona la creación y destrucción del gráfico de
- Renderizado de
<canvas>: El componente solo renderiza un elemento<canvas>básico, dejando toda la lógica de dibujo aChart.jsa través deluseEffect.
- Rol en la aplicación:
BarChartes 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:
Tablerecibe 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:
TablePropsInterface: 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) ycolumns(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 arraycolumnspara crear dinámicamente las celdas de encabezado (<th>). Cada<th>utiliza el nombre de la columna como sukeypara una reconciliación eficiente por parte de React. - Generación del Cuerpo (
<tbody>): Itera sobre el arraydatapara crear las filas de la tabla (<tr>).- Clave Única para Filas (
key={row.id || JSON.stringify(row)}): Para cada fila (<tr>), utiliza la propiedadiddel objetorowcomo clave única. Siidno está presente, se recurre aJSON.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
columnspara crear las celdas de datos (<td>). Accede al valor de la celda usandorow[column].
- Clave Única para Filas (
- 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 (comoglobal.scssostudent.table.module.scss).
- Rol en la aplicación:
Tablees 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:
CaptatorsTableSectiones un componente presentacional que recibe el objetofilteredStatistics(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éricoTablepara 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.memomemoiza el componente, lo que significa que React lo re-renderizará solo si susprops(filteredStatistics) cambian, evitando renderizados innecesarios.CaptatorsTableSectionPropsInterface: Define las propiedades que el componente espera, que es el objetofilteredStatistics(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. TableComponente: Utiliza el componente genéricoTable(importado de../layouts/table.tsx) para la representación tabular de los datos. Le pasa el arraycontactsByPersoncomodatay un array de cadenas["Captador", "Contacto"]comocolumns.- Estilización: El contenido está envuelto en un
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
CaptatorsTableSectiones 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áginastatistics.tsxsea 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:
CommunicationPreferenceSectiones un componente presentacional que recibe el objetofilteredStatistics. 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 componenteBarChart. - 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 susprops(filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.CommunicationPreferenceSectionPropsInterface: Define las propiedades que el componente espera, específicamente el objetofilteredStatistics(importado de../../../interface/common/statistics).- Preparación de Datos: La lógica interna del componente transforma una parte de
filteredStatisticsen el formato específico queBarChartespera: un array de objetos con propiedadesname(la etiqueta, ej., "WhatsApp") yvalue(el conteo numérico). BarChartComponente: Utiliza el componente genéricoBarChart(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
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
CommunicationPreferenceSectiones 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áginastatistics.tsxsea 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:
EnrollmentSourceSectiones un componente presentacional que recibe el objetofilteredStatistics. Extrae los conteos relevantes de las fuentes de inscripción (Redes Sociales, Captador, No registrada) y los formatea para ser visualizados utilizando el componenteBarChart. - 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 susprops(filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.EnrollmentSourceSectionPropsInterface: Define las propiedades que el componente espera, específicamente el objetofilteredStatistics(importado de../../../interface/common/statistics).- Preparación de Datos: La lógica interna del componente transforma una parte de
filteredStatisticsen el formato específico queBarChartespera: un array de objetos con propiedadesname(la etiqueta, ej., "Redes Sociales") yvalue(el conteo numérico). BarChartComponente: Utiliza el componente genéricoBarChart(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
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
EnrollmentSourceSectiones 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áginastatistics.tsxsea 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:
FeedbackBySchoolCourseSectiones un componente presentacional que recibe el objetofilteredStatistics. Itera sobre todos los posibles estados de feedback (obtenidos deConstants.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 susprops(filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.FeedbackBySchoolCourseSectionPropsInterface: Define las propiedades que el componente espera, específicamente el objetofilteredStatistics(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
allFeedbackspara 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.studentsBySchoolCourseFeedbackpara incluir solo los ítems que coinciden con el estado de feedback actual. - Mapea los ítems filtrados a un formato adecuado para el componente
Table.
- Cada sección muestra un subtítulo (
TableComponente: Utiliza el componente genéricoTable(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
StudentsByLevelSectionpara mantener la consistencia y evitar redundancia. - Estilización: El contenido está envuelto en un
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
FeedbackBySchoolCourseSectiones 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áginastatistics.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:
GenderDistributionSectiones un componente presentacional que recibe el objetofilteredStatistics. Extrae los conteos de estudiantes clasificados por género (hombres, mujeres, otros, no registrado) y los formatea para ser visualizados usando el componenteBarChart. - 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 susprops(filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.GenderDistributionSectionPropsInterface: Define las propiedades que el componente espera, específicamente el objetofilteredStatistics(importado de../../../interface/common/statistics).- Preparación de Datos: La lógica interna del componente transforma una parte de
filteredStatisticsen el formato específico queBarChartespera: un array de objetos con propiedadesname(la etiqueta, ej., "Hombres") yvalue(el conteo numérico). Accede directamente a los conteos degenderCountdentro defilteredStatistics. BarChartComponente: Utiliza el componente genéricoBarChart(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
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
GenderDistributionSectiones 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áginastatistics.tsxsea 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:
GeneralStudentStatsSectiones un componente presentacional que recibe el objetofilteredStatisticsy un array deallFeedbacks(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éricoTablepara 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 susprops(filteredStatistics,allFeedbacks) cambian, evitando cálculos y renderizados innecesarios.GeneralStudentStatsSectionPropsInterface: Define las propiedades que el componente espera: el objetofilteredStatistics(importado de../../../interface/common/statistics) yallFeedbacks(un array de cadenas con todos los posibles estados de feedback).- Preparación de Datos (
tableData):- Utiliza el array
allFeedbackspara mapear y crear los datos para la tabla. - Para cada
feedbackposible, busca elcountcorrespondiente enfilteredStatistics.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.
- Utiliza el array
TableComponente: Utiliza el componente genéricoTable(importado de../layouts/table.tsx) para la representación tabular de los datos. Le pasa el arraytableDatapreparado y un array de cadenas["Estado", "Estudiantes"]comocolumns.- Estilización: El contenido está envuelto en un
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
GeneralStudentStatsSectiones 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áginastatistics.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:
StudentsByLevelSectiones un componente presentacional que recibe el objetofilteredStatistics. Procesa los datos destudentsByCollegeCoursepara 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 susprops(filteredStatistics) cambian, evitando cálculos y renderizados innecesarios.StudentsByLevelSectionPropsInterface: Define las propiedades que el componente espera, específicamente el objetofilteredStatistics(importado de../../../interface/common/statistics).- Preparación de Datos por Colegio y Curso (
studentsBySchoolCourseData):- Utiliza
Object.entries()para iterar sobrefilteredStatistics.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 decourses(cada uno con suCursoyEstudiantes). count || 0asegura que los cursos con cero estudiantes también se muestren con un valor numérico.
- Utiliza
- Iteración por Colegio: El componente mapea sobre
studentsBySchoolCourseDatapara 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.
- Cada subsección tiene un título (
- 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
divcon la claseclassName="modulos". Esta clase (definida enstatistics.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:
StudentsByLevelSectiones 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:
calculateStatisticses 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
Enuma Nombres Legibles (schoolMap,courseMap):- Define objetos
schoolMapycourseMapque mapean los valores de losenumsSchoolyCourse(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.
- Define objetos
calculateStatisticsFunción:StatisticsFiltersPropsInterface: Define las propiedades de entrada:students(la lista completa de estudiantes), y los criterios de filtro opcionalessource,school,course.- Filtrado Inicial:
- Inicializa
filteredStudentscon 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 componenteStatisticsactualice los datos dinámicamente.
- Inicializa
- 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 (utilizandouniqueFeedbacks).- Conteo por Fuente:
socialMediaCount,captadorCount,noRegisteredSource. - Conteo por Preferencia de Comunicación:
whatsappCount,phoneCount,noRegisteredCommunication. - Conteo por Captador (
contactsByPerson): Ahora utilizaConstants.CAPTATOR_NAMESpara 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.
FilteredStatisticsInterface: El valor de retorno decalculateStatisticssigue 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,SchoolFilteryCourseFilterde la subcarpetafilters.
- Mapeos de
- Rol en la aplicación:
statistics.filters.tsxes 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:
LoadingSpinneres 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
divexterno que actúa como una superposición (.spinner-overlay). Estedivcubre 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 unz-indexalto 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
divque 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.
- Contenedor de Superposición: Renderiza un
- Rol en la aplicación:
LoadingSpinneres 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>
);