reporte por mes y año

This commit is contained in:
2026-03-26 18:43:54 -06:00
parent 4ab004c5ea
commit bb90e40906
2 changed files with 181 additions and 105 deletions
+163 -105
View File
@@ -10,6 +10,7 @@ import { saveAs } from "file-saver";
interface Periodo {
id_periodo: number;
semestre: string;
fecha_inicio_servicio: string;
}
interface ApiResponse {
@@ -21,6 +22,8 @@ interface ApiResponse {
total: string;
}
type Tipo = "periodo" | "mes" | "anio";
type TipoConteo = "WINDOWS" | "MACINTOSH" | "LINUX" | "PROFESORES";
interface TablaRow {
@@ -37,13 +40,20 @@ interface TablaRow {
export default function Inscripciones() {
const [periodo, setPeriodo] = useState<Periodo[]>([]);
const [periodoInicio, setPeriodoInicio] = useState<number | null>(null);
const [periodoFin, setPeriodoFin] = useState<number | null>(null);
const [tipo, setTipo] = useState<Tipo>("periodo");
const [periodoInicio, setPeriodoInicio] = useState<any>(null);
const [periodoFin, setPeriodoFin] = useState<any>(null);
const [anio, setAnio] = useState<number | null>(null);
const [tablaData, setTablaData] = useState<TablaRow[]>([]);
const [rawData, setRawData] = useState<ApiResponse[]>([]);
const [rawData, setRawData] = useState<ApiResponse[]>([]);
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
// Obtener periodos
useEffect(() => {
const getPeriodo = async () => {
const response = await axios.get(`${envConfig.apiUrl}/periodo`, { headers });
@@ -52,77 +62,101 @@ export default function Inscripciones() {
getPeriodo();
}, []);
const handleBuscar = async (e: React.FormEvent) => {
e.preventDefault();
// Obtener años desde fechas
const aniosDisponibles = Array.from(
new Set(
periodo.map((p) =>
new Date(p.fecha_inicio_servicio).getFullYear()
)
)
).sort();
if (!periodoInicio || !periodoFin) {
alert("Selecciona ambos periodos");
return;
}
// ============================
// 🔍 BUSCAR
// ============================
const handleBuscar = async (e: React.FormEvent) => {
e.preventDefault();
if (periodoInicio > periodoFin) {
alert("El periodo inicio no puede ser mayor al final");
return;
}
let url = "";
const esMismoPeriodo = periodoInicio === periodoFin;
try {
const url = esMismoPeriodo
? `${envConfig.apiUrl}/alumno-inscrito/inscritos/${periodoInicio}` // 👈 uno solo
: `${envConfig.apiUrl}/alumno-inscrito/inscritos/${periodoInicio}/${periodoFin}`; // 👈 rango
const response = await axios.get(url, { headers });
const data: ApiResponse[] = response.data;
setRawData(data);
const agrupado: Record<string, TablaRow> = {};
data.forEach((item) => {
const key = `${item.carrera}-${item.semestre}`;
if (!agrupado[key]) {
agrupado[key] = {
carrera: item.carrera,
semestre: item.semestre,
WINDOWS: 0,
MACINTOSH: 0,
LINUX: 0,
PROFESORES: 0,
TOTAL: 0,
femenino: 0,
masculino: 0,
};
if (tipo === "periodo") {
if (!periodoInicio || !periodoFin) {
alert("Selecciona ambos periodos");
return;
}
const fila = agrupado[key];
const tipoProfesor = item.profesor as TipoConteo;
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/periodo/${periodoInicio}/${periodoFin}`;
}
const total = Number(item.total);
const fem = Number(item.femenino);
const masc = Number(item.masculino);
if (tipo === "anio") {
if (!periodoInicio || !periodoFin) {
alert("Selecciona ambos años");
return;
}
fila[tipoProfesor] += total;
fila.TOTAL += total;
fila.femenino += fem;
fila.masculino += masc;
});
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/anio/${periodoInicio}/${periodoFin}`;
}
setTablaData(Object.values(agrupado));
} catch (error) {
console.error("Error al obtener inscritos", error);
if (tipo === "mes") {
if (!anio) {
alert("Selecciona un año");
return;
}
};
// Export Excel
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/mes/${anio}`;
}
try {
console.log("URL:", url);
const response = await axios.get(url, { headers });
const data: ApiResponse[] = response.data;
setRawData(data);
const agrupado: Record<string, TablaRow> = {};
data.forEach((item) => {
const key = `${item.carrera}-${item.semestre}`;
if (!agrupado[key]) {
agrupado[key] = {
carrera: item.carrera,
semestre: item.semestre,
WINDOWS: 0,
MACINTOSH: 0,
LINUX: 0,
PROFESORES: 0,
TOTAL: 0,
femenino: 0,
masculino: 0,
};
}
const fila = agrupado[key];
const tipoProfesor = item.profesor as TipoConteo;
const total = Number(item.total);
const fem = Number(item.femenino);
const masc = Number(item.masculino);
fila[tipoProfesor] += total;
fila.TOTAL += total;
fila.femenino += fem;
fila.masculino += masc;
});
setTablaData(Object.values(agrupado));
} catch (error) {
console.error("Error al obtener inscritos", error);
}
};
const exportarExcel = () => {
if (!rawData.length) return;
const periodosUnicos = Array.from(
new Set(rawData.map((t) => t.semestre))
).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
);
const carrerasUnicas = Array.from(
new Set(rawData.map((t) => t.carrera))
@@ -132,8 +166,6 @@ export default function Inscripciones() {
const fila: any = { Carrera: carrera };
let totalFila = 0;
let totalFemenino = 0;
let totalMasculino = 0;
periodosUnicos.forEach((periodo) => {
const items = rawData.filter(
@@ -147,36 +179,16 @@ export default function Inscripciones() {
fila[periodo] = totalPeriodo;
totalFila += totalPeriodo;
totalFemenino += items.reduce(
(acc, item) => acc + Number(item.femenino),
0
);
totalMasculino += items.reduce(
(acc, item) => acc + Number(item.masculino),
0
);
});
fila["Femenino"] = totalFemenino;
fila["Masculino"] = totalMasculino;
fila["Total"] = totalFila;
return fila;
});
const worksheet = XLSX.utils.json_to_sheet(dataExcel);
worksheet["!cols"] = [
{ wch: 25 },
...periodosUnicos.map(() => ({ wch: 15 })),
{ wch: 15 },
{ wch: 15 },
{ wch: 15 },
];
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Inscritos");
const excelBuffer = XLSX.write(workbook, {
@@ -196,41 +208,87 @@ export default function Inscripciones() {
<h2 className="title">INSCRITOS</h2>
<form className="containerForm" onSubmit={handleBuscar}>
<label>Seleccione rango de periodos</label>
<label>Filtro</label>
{/* Selector tipo */}
<select value={tipo} onChange={(e) => setTipo(e.target.value as Tipo)}>
<option value="periodo">Periodo</option>
<option value="anio">Año</option>
<option value="mes">Mes</option>
</select>
<div className="groupInput">
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))} defaultValue="">
<option value="" disabled>Inicio</option>
{periodo.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
{/* PERIODO */}
{tipo === "periodo" && (
<>
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))}>
<option value="">Inicio</option>
{periodo.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
<select onChange={(e) => setPeriodoFin(Number(e.target.value))} defaultValue="">
<option value="" disabled>Fin</option>
{periodo.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
<select onChange={(e) => setPeriodoFin(Number(e.target.value))}>
<option value="">Fin</option>
{periodo.map((p) => (
<option key={p.id_periodo} value={p.id_periodo}>
{p.semestre}
</option>
))}
</select>
</>
)}
{/* AÑO */}
{tipo === "anio" && (
<>
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))}>
<option value="">Año inicio</option>
{aniosDisponibles.map((a) => (
<option key={a} value={a}>
{a}
</option>
))}
</select>
<select onChange={(e) => setPeriodoFin(Number(e.target.value))}>
<option value="">Año fin</option>
{aniosDisponibles.map((a) => (
<option key={a} value={a}>
{a}
</option>
))}
</select>
</>
)}
{/* MES */}
{tipo === "mes" && (
<>
<select onChange={(e) => setAnio(Number(e.target.value))}>
<option value="">Año</option>
{aniosDisponibles.map((a) => (
<option key={a} value={a}>
{a}
</option>
))}
</select>
</>
)}
<button className="button buttonSearch" type="submit">
Buscar
</button>
<button
type="button"
className="button buttonSearch"
onClick={exportarExcel}
>
<button type="button" className="button buttonSearch" onClick={exportarExcel}>
Exportar Excel
</button>
</div>
</form>
{/* TABLA */}
<div className="containerTable">
<table>
<thead>
@@ -250,7 +308,7 @@ export default function Inscripciones() {
<tbody>
{tablaData.map((row, index) => (
<tr key={index}>
<td className="textLeft">{row.carrera}</td>
<td>{row.carrera}</td>
<td>{row.semestre}</td>
<td>{row.femenino}</td>
<td>{row.masculino}</td>
+18
View File
@@ -696,6 +696,7 @@
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -735,6 +736,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -755,6 +757,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -775,6 +778,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -795,6 +799,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -815,6 +820,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -835,6 +841,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -855,6 +862,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -875,6 +883,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -895,6 +904,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -915,6 +925,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -935,6 +946,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -955,6 +967,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -975,6 +988,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2635,6 +2649,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
@@ -2677,6 +2692,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -3190,6 +3206,7 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
@@ -3290,6 +3307,7 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {