added new restriction and fixed button dowload

This commit is contained in:
2025-12-10 13:13:47 -06:00
parent d8e37b5475
commit 5e13d3424a
14 changed files with 427 additions and 230 deletions
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#169154" d="M29,6H15.744C14.781,6,14,6.781,14,7.744v7.259h15V6z"/><path fill="#18482a" d="M14,33.054v7.202C14,41.219,14.781,42,15.743,42H29v-8.946H14z"/><path fill="#0c8045" d="M14 15.003H29V24.005000000000003H14z"/><path fill="#17472a" d="M14 24.005H29V33.055H14z"/><g><path fill="#29c27f" d="M42.256,6H29v9.003h15V7.744C44,6.781,43.219,6,42.256,6z"/><path fill="#27663f" d="M29,33.054V42h13.257C43.219,42,44,41.219,44,40.257v-7.202H29z"/><path fill="#19ac65" d="M29 15.003H44V24.005000000000003H29z"/><path fill="#129652" d="M29 24.005H44V33.055H29z"/></g><path fill="#0c7238" d="M22.319,34H5.681C4.753,34,4,33.247,4,32.319V15.681C4,14.753,4.753,14,5.681,14h16.638 C23.247,14,24,14.753,24,15.681v16.638C24,33.247,23.247,34,22.319,34z"/><path fill="#fff" d="M9.807 19L12.193 19 14.129 22.754 16.175 19 18.404 19 15.333 24 18.474 29 16.123 29 14.013 25.07 11.912 29 9.526 29 12.719 23.982z"/></svg>

After

Width:  |  Height:  |  Size: 998 B

+4
View File
@@ -125,6 +125,10 @@
width: 100%;
}
.barNavigation li{
width: 100%;
}
.barNavigation ul {
position: absolute;
background-color: #f4f5f7;
+3 -2
View File
@@ -59,6 +59,7 @@
border-radius: 0 0 4px 4px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
overflow: hidden;
min-height: 243px;
}
table {
@@ -71,7 +72,7 @@ thead th {
color: #fff;
text-align: left;
padding: 14px;
font-size: 15px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
user-select: none;
@@ -85,7 +86,7 @@ thead th {
tbody td {
padding: 12px 14px;
border-bottom: 1px solid #f0f0f0;
font-size: 15px;
font-size: 12px;
}
tr:nth-child(even) {
+35 -26
View File
@@ -373,6 +373,18 @@ export default function Page() {
return;
}
const inventario = formData.inventario;
if (/^0/.test(inventario)) {
toast.error("El inventario no puede iniciar con 0");
return;
}
if (!/^[0-9E]+$/.test(inventario)) {
toast.error("El inventario solo puede contener números y la letra E");
return;
}
try {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
@@ -538,33 +550,30 @@ export default function Page() {
)}
</select>
</div>
<div className="formGroup">
<label>Sistema operativo</label>
<select
disabled={!formData.id_tipo_equipo}
value={formData.id_sistema_operativo}
onChange={(e) =>
handleInputChange(
"id_sistema_operativo",
e.target.value
)
}
>
<option value="">
{formData.id_tipo_equipo
? "Selecciona sistema operativo"
: "Selecciona primero el tipo de equipo"}
<div className="formGroup">
<label>Sistema operativo</label>
<select
disabled={!formData.id_tipo_equipo}
value={formData.id_sistema_operativo}
onChange={(e) =>
handleInputChange("id_sistema_operativo", e.target.value)
}
>
<option value="">
{formData.id_tipo_equipo
? "Selecciona sistema operativo"
: "Selecciona primero el tipo de equipo"}
</option>
{SO_POR_EQUIPO[formData.id_tipo_equipo]?.map((so) => (
<option
key={so.id_sistema_operativo}
value={so.id_sistema_operativo}
>
{so.sistema_operativo}
</option>
{SO_POR_EQUIPO[formData.id_tipo_equipo]?.map((so) => (
<option
key={so.id_sistema_operativo}
value={so.id_sistema_operativo}
>
{so.sistema_operativo}
</option>
))}
</select>
</div>
))}
</select>
</div>
</>
)}
+3 -8
View File
@@ -19,7 +19,6 @@ export default function BarcodeScanner({ onScan }: BarcodeScannerProps) {
let zxingReader: BrowserMultiFormatReader | null = null;
let detector: any = null;
/** 🚀 Intentar usar BarcodeDetector API (moderno) */
const startBarcodeDetector = async () => {
const supported = "BarcodeDetector" in window;
if (!supported) {
@@ -100,13 +99,10 @@ export default function BarcodeScanner({ onScan }: BarcodeScannerProps) {
return;
}
// 🔍 Intentar encontrar la cámara trasera
const backCamera =
devices.find((d) =>
d.label.toLowerCase().includes("back")
) || devices.find((d) =>
d.label.toLowerCase().includes("rear")
) || devices[devices.length - 1]; // fallback
devices.find((d) => d.label.toLowerCase().includes("back")) ||
devices.find((d) => d.label.toLowerCase().includes("rear")) ||
devices[devices.length - 1];
const selectedDeviceId = backCamera.deviceId;
@@ -136,7 +132,6 @@ export default function BarcodeScanner({ onScan }: BarcodeScannerProps) {
startBarcodeDetector();
return () => {
if (animationFrame) cancelAnimationFrame(animationFrame);
if (stream) stream.getTracks().forEach((t) => t.stop());
+20 -5
View File
@@ -2,15 +2,20 @@
import styles from "./style.module.css";
import Cookies from "js-cookie";
import { useState } from "react";
export default function DownloadReporteXLSX() {
const [loading, setLoading] = useState(false);
const handleDownload = async () => {
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
setLoading(true); // 🔹 Activa loader
const token = Cookies.get("token");
const headers = { Authorization: `Bearer ${token}` };
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/equipos/reporteXLSX`,{headers}
`${process.env.NEXT_PUBLIC_API_URL}/equipos/reporteXLSX`,
{ headers }
);
if (!response.ok) {
@@ -31,12 +36,22 @@ export default function DownloadReporteXLSX() {
} catch (error) {
console.error(error);
alert("Hubo un problema al descargar el archivo.");
} finally {
setLoading(false); // 🔹 Reactiva botón y oculta loader
}
};
return (
<button className={styles.downloadBtn} onClick={handleDownload}>
Descargar XLSX
<button
className={styles.downloadBtn}
onClick={handleDownload}
disabled={loading} // 🔹 Botón deshabilitado
>
{loading ? (
<div className={styles.loader}></div> // 🔹 Icono de cargando
) : (
<img src="/excel.svg" alt="Excel" className={styles.iconExcel} width={30} height={30}/>
)}
</button>
);
}
+30 -11
View File
@@ -1,24 +1,43 @@
.downloadBtn {
display: flex;
justify-content: center;
align-items: center;
background: #217346;
color: white;
border: none;
padding: 10px 16px;
gap: 8px;
background: #ffffff;
border: 1px solid #ddd;
border-bottom: none;
padding: 5px 5px;
cursor: pointer;
border-radius: 10px 10px 0 0;
font-size: 14px;
font-weight: 600;
transition: background 0.2s ease;
min-width: 50px;
}
.downloadBtn:hover {
background: #1e5838;
background: #95d2b0;
}
/* Icono con css puro */
.icon {
width: 18px;
height: 18px;
position: relative;
}
.downloadBtn:disabled {
background: #a5a5a5;
cursor: not-allowed;
}
.loader {
border: 3px solid #ffffff55;
border-top: 3px solid white;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
+43 -15
View File
@@ -43,7 +43,7 @@ const PLATFORM_OS_LIST: Record<PlatformKey, string[]> = {
],
"apple-desktop": [
"MAC OS(15 - Sequoia)",
"MAC OS (SEQUOIA)",
"Mac OS (13 - Ventura, 14 - Sonoma)",
"Mac OS X (Mojave, Catalina, 11 - Big Sur, 12 - Monterrey)",
"Mac OS X (Snow Leopard, Lion, Mountain Lion, Mavericks)",
@@ -75,31 +75,49 @@ const PLATFORM_OS_LIST: Record<PlatformKey, string[]> = {
],
};
// reutilizable para normalizar comparaciones
function cleanName(str: string) {
return (
str
.normalize?.("NFKD")
.replace?.(/\u00A0/g, " ")
.replace(/\s+/g, " ")
.trim?.() ?? str
);
}
function transformPlatform(
raw: RawOsEntry[],
platform: PlatformKey
): OsEntry[] {
const fixedList = PLATFORM_OS_LIST[platform];
const clean = (str: string) =>
str
.normalize?.("NFKD")
.replace?.(/\u00A0/g, " ")
.trim?.() ?? str;
// mapa limpio de los datos que llegan
const mapRaw: Record<string, number> = {};
raw.forEach((item) => {
const name = clean(item.sistema_operativo);
mapRaw[name] = Number(item.total);
const name = cleanName(item.sistema_operativo);
mapRaw[name] = (mapRaw[name] || 0) + Number(item.total || 0);
});
// 1) primero los fijos, con su conteo (0 si no vienen)
const rows: OsEntry[] = fixedList.map((os) => ({
os,
count: mapRaw[clean(os)] ?? 0,
count: mapRaw[cleanName(os)] ?? 0,
}));
const total = rows.reduce((a, r) => a + r.count, 0);
// 2) luego agregamos cualquier SO extra que vino en los datos y no esté en la lista fija
const fixedCleanSet = new Set(fixedList.map((s) => cleanName(s)));
const extras = Object.keys(mapRaw)
.filter((rawName) => !fixedCleanSet.has(cleanName(rawName)))
// opcional: mantener orden natural o por nombre
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
.map((name) => ({ os: name, count: mapRaw[name] }));
// append extras after fixed ones
rows.push(...extras);
// total (incluye extras)
const total = rows.reduce((a, r) => a + r.count, 0);
rows.push({ os: "Total", count: total, isTotal: true });
return rows;
@@ -159,6 +177,15 @@ export default function Pregunta2() {
? data[activeTab]
: PLATFORM_OS_LIST[activeTab].map((os) => ({ os, count: 0 }));
const isOsInvalid = (os: string, tab: PlatformKey) => {
if (os === "Total") return false;
const fixedList = PLATFORM_OS_LIST[tab];
// comparamos con cleaned strings
return !fixedList.some(
(s) => cleanName(s).toLowerCase() === cleanName(os).toLowerCase()
);
};
return (
<div className={styles.container_P1}>
<div className={styles["contenedor-censo_p1"]}>
@@ -175,7 +202,6 @@ export default function Pregunta2() {
</div>
<div className={styles.scanView_P1}>
<div className={styles["main-content_P1"]}>
{/* Tabs */}
<div className={styles.tabs_P1}>
{Object.entries(PLATFORM_LABELS).map(([key, label]) => (
<button
@@ -198,9 +224,11 @@ export default function Pregunta2() {
{rowsToRender.map((item, index) => (
<div
key={index}
className={`${styles["data-row_P1"]} ${
item.isTotal ? styles["total-row_P1"] : ""
}`}
className={`
${styles["data-row_P1"]}
${item.isTotal ? styles["total-row_P1"] : ""}
${isOsInvalid(item.os, activeTab) ? styles.invalidRow : ""}
`}
>
<div className={styles["os-name_P1"]}>{item.os}</div>
<div
+263 -151
View File
@@ -5,7 +5,9 @@ import axios from "axios";
import Cookies from "js-cookie";
import styles from "./pregunta3.module.scss";
import ToggleButton from "../Toggle/ToggleButton";
import { PROCESADORES_POR_EQUIPO } from "@/data/procesadores";
/* ---------- types ---------- */
type ProcessorEntry = {
tipo: string;
alumnos: string;
@@ -26,6 +28,7 @@ type PlatformKey =
type PlatformData = Record<PlatformKey, ProcessorEntry[]>;
/* ---------- labels ---------- */
const PLATFORM_LABELS: Record<PlatformKey, string> = {
"pc-desktop": "Computadoras de escritorio Plataforma PC",
"apple-desktop": "Computadoras de escritorio Plataforma Apple",
@@ -34,7 +37,7 @@ const PLATFORM_LABELS: Record<PlatformKey, string> = {
servers: "Alto rendimiento Servidores",
};
// Mapeo de "uso" del JSON a campos en ProcessorEntry
/* ---------- mapeo uso -> campo ---------- */
const USO_TO_FIELD: Record<
string,
keyof Omit<ProcessorEntry, "tipo" | "total" | "isTotal">
@@ -46,80 +49,163 @@ const USO_TO_FIELD: Record<
ADMINISTRATIVO: "administrativos",
};
// Transforma un array plano del tipo [{procesador, uso, total}] en ProcessorEntry[]
function transformProcessorData(
rawArray: { procesador: string; uso: string; total: string }[]
): ProcessorEntry[] {
const map = new Map<string, ProcessorEntry>();
/* ---------- mapa platformKey -> PROCESADORES_POR_EQUIPO keys ---------- */
/* Este mapa indica qué conjuntos de procesadores corresponden a cada PlatformKey.
Ajusta si quieres incluir/excluir grupos. */
const PLATFORM_PROCESSOR_GROUPS: Record<PlatformKey, number[]> = {
"pc-desktop": [1, 3], // escritorio PC + escritorio Linux (ambos comparten procesadores)
"apple-desktop": [2],
"pc-laptop": [4, 5],
"apple-laptop": [6],
servers: [10],
};
for (const item of rawArray) {
const { procesador, uso, total } = item;
if (!map.has(procesador)) {
map.set(procesador, {
tipo: procesador,
alumnos: "0",
profesores: "0",
tecnicos: "0",
investigadores: "0",
administrativos: "0",
total: "0",
});
/* ---------- helpers ---------- */
function cleanName(str: string) {
return (
String(str)
.normalize?.("NFKD")
.replace?.(/\u00A0/g, " ")
.replace?.(/\s+/g, " ")
.trim?.() ?? String(str)
);
}
/* Devuelve la lista "oficial" de procesadores (strings) para la plataforma */
function getOfficialProcessors(platform: PlatformKey): string[] {
const groups = PLATFORM_PROCESSOR_GROUPS[platform] || [];
const names: string[] = [];
for (const g of groups) {
const arr = PROCESADORES_POR_EQUIPO[g] || [];
for (const p of arr) {
names.push(p.procesador);
}
}
// dedupe preserving order
const seen = new Set<string>();
return names.filter((n) => {
const c = cleanName(n).toLowerCase();
if (seen.has(c)) return false;
seen.add(c);
return true;
});
}
const entry = map.get(procesador)!;
const field = USO_TO_FIELD[uso];
/* ---------- transformación de datos ---------- */
function transformProcessorDataWithOfficial(
rawArray: { procesador: string; uso: string; total: string }[],
platform: PlatformKey
): ProcessorEntry[] {
// 1) crear mapa cleanedName -> aggregated numbers por uso
const mapRaw: Record<string, { [k in keyof typeof USO_TO_FIELD]?: number }> =
{};
for (const r of rawArray) {
const name = cleanName(r.procesador);
const usoKey = String(r.uso).toUpperCase();
const field = USO_TO_FIELD[usoKey];
if (!mapRaw[name]) {
mapRaw[name] = {};
}
const prev = parseInt(r.total || "0") || 0;
if (field) {
entry[field] = total;
mapRaw[name][field] = (mapRaw[name][field] || 0) + prev;
}
}
// Convertimos a array y calculamos el total por fila
const rows = Array.from(map.values()).map((entry) => {
const totalNum =
parseInt(entry.alumnos || "0") +
parseInt(entry.profesores || "0") +
parseInt(entry.tecnicos || "0") +
parseInt(entry.investigadores || "0") +
parseInt(entry.administrativos || "0");
// 2) obtener lista oficial y construir filas fijas primero
const official = getOfficialProcessors(platform);
const fixedRows: ProcessorEntry[] = official.map((proc) => {
const c = cleanName(proc);
const sums = mapRaw[c] || {};
const alumnos = (sums["alumnos"] || 0).toString();
const profesores = (sums["profesores"] || 0).toString();
const tecnicos = (sums["tecnicos"] || 0).toString();
const investigadores = (sums["investigadores"] || 0).toString();
const administrativos = (sums["administrativos"] || 0).toString();
const total =
(parseInt(alumnos) || 0) +
(parseInt(profesores) || 0) +
(parseInt(tecnicos) || 0) +
(parseInt(investigadores) || 0) +
(parseInt(administrativos) || 0);
return {
...entry,
total: totalNum.toString(),
tipo: proc,
alumnos,
profesores,
tecnicos,
investigadores,
administrativos,
total: total.toString(),
};
});
// Calculamos los totales generales
// 3) extras: procesadores del backend que no están en official
const officialSet = new Set(official.map((p) => cleanName(p).toLowerCase()));
const extras = Object.keys(mapRaw)
.filter((k) => !officialSet.has(k.toLowerCase()))
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
.map((cleaned) => {
const sums = mapRaw[cleaned] || {};
const alumnos = (sums["alumnos"] || 0).toString();
const profesores = (sums["profesores"] || 0).toString();
const tecnicos = (sums["tecnicos"] || 0).toString();
const investigadores = (sums["investigadores"] || 0).toString();
const administrativos = (sums["administrativos"] || 0).toString();
const total =
(parseInt(alumnos) || 0) +
(parseInt(profesores) || 0) +
(parseInt(tecnicos) || 0) +
(parseInt(investigadores) || 0) +
(parseInt(administrativos) || 0);
return {
tipo: cleaned, // mostrar cleaned name (luego puedes mostrar raw si tienes map)
alumnos,
profesores,
tecnicos,
investigadores,
administrativos,
total: total.toString(),
} as ProcessorEntry;
});
// 4) concatenar fixedRows + extras, luego grand total
const allRows = [...fixedRows, ...extras];
const grandTotal: ProcessorEntry = {
tipo: "Total",
alumnos: rows
.reduce((sum, r) => sum + parseInt(r.alumnos || "0"), 0)
alumnos: allRows
.reduce((sum, r) => sum + (parseInt(r.alumnos || "0") || 0), 0)
.toString(),
profesores: rows
.reduce((sum, r) => sum + parseInt(r.profesores || "0"), 0)
profesores: allRows
.reduce((sum, r) => sum + (parseInt(r.profesores || "0") || 0), 0)
.toString(),
tecnicos: rows
.reduce((sum, r) => sum + parseInt(r.tecnicos || "0"), 0)
tecnicos: allRows
.reduce((sum, r) => sum + (parseInt(r.tecnicos || "0") || 0), 0)
.toString(),
investigadores: rows
.reduce((sum, r) => sum + parseInt(r.investigadores || "0"), 0)
investigadores: allRows
.reduce((sum, r) => sum + (parseInt(r.investigadores || "0") || 0), 0)
.toString(),
administrativos: rows
.reduce((sum, r) => sum + parseInt(r.administrativos || "0"), 0)
administrativos: allRows
.reduce((sum, r) => sum + (parseInt(r.administrativos || "0") || 0), 0)
.toString(),
total: rows
.reduce((sum, r) => sum + parseInt(r.total || "0"), 0)
total: allRows
.reduce((sum, r) => sum + (parseInt(r.total || "0") || 0), 0)
.toString(),
isTotal: true,
};
return [...rows, grandTotal];
return [...allRows, grandTotal];
}
/* ---------- componente ---------- */
export default function Pregunta3_2() {
const [activeTab, setActiveTab] = useState<PlatformKey>("pc-desktop");
const [data, setData] = useState<PlatformData | null>(null);
const [loading, setLoading] = useState(true);
const cargarDatos = async (baja: boolean) => {
setLoading(true);
const token = Cookies.get("token");
if (!token) {
console.error("Token no encontrado");
@@ -128,8 +214,8 @@ export default function Pregunta3_2() {
}
const body = baja ? ["BAJA"] : ["EN DESUSO", "EN USO"];
const headers = { Authorization: `Bearer ${token}` };
axios
.post(
`${process.env.NEXT_PUBLIC_API_URL}/equipos/reporte/tipoEquipos_procesador`,
@@ -137,67 +223,64 @@ export default function Pregunta3_2() {
{ headers }
)
.then((res) => {
const json = res.data; // array de 5 arreglos
const json = res.data; // array con 5 arrays
const formatted: PlatformData = {
"pc-desktop": transformProcessorData(json[0] || []),
"apple-desktop": transformProcessorData(json[1] || []),
"pc-laptop": transformProcessorData(json[2] || []),
"apple-laptop": transformProcessorData(json[3] || []),
servers: transformProcessorData(json[4] || []),
"pc-desktop": transformProcessorDataWithOfficial(
json[0] || [],
"pc-desktop"
),
"apple-desktop": transformProcessorDataWithOfficial(
json[1] || [],
"apple-desktop"
),
"pc-laptop": transformProcessorDataWithOfficial(
json[2] || [],
"pc-laptop"
),
"apple-laptop": transformProcessorDataWithOfficial(
json[3] || [],
"apple-laptop"
),
servers: transformProcessorDataWithOfficial(json[4] || [], "servers"),
};
setData(formatted);
})
.catch((err) => {
console.error("Error cargando datos de procesadores", err);
// en error, dejamos listas vacías pero con rows oficiales (0)
const empty: PlatformData = {
"pc-desktop": transformProcessorDataWithOfficial([], "pc-desktop"),
"apple-desktop": transformProcessorDataWithOfficial(
[],
"apple-desktop"
),
"pc-laptop": transformProcessorDataWithOfficial([], "pc-laptop"),
"apple-laptop": transformProcessorDataWithOfficial(
[],
"apple-laptop"
),
servers: transformProcessorDataWithOfficial([], "servers"),
};
setData(empty);
})
.finally(() => {
setLoading(false);
});
.finally(() => setLoading(false));
};
useEffect(() => {
cargarDatos(false);
}, []);
if (loading)
return (
<div className={styles.container_P3}>
<div className={styles["contenedor-censo_P3"]}>
Censo de equipos de cómputo - Plataforma y tipo procesador
</div>
<div className={styles["pregunta-cuadro_P3"]}>
Cantidad de población beneficiada por plataforma y tipo de procesador.
<ToggleButton
onChange={(estado) => {
cargarDatos(estado);
}}
/>
</div>
<div className={styles.scanView_P3}>Cargando datos...</div>
</div>
);
const currentData = data?.[activeTab] ?? [];
if (!data)
return (
<div className={styles.container_P3}>
<div className={styles["contenedor-censo_P3"]}>
Censo de equipos de cómputo - Plataforma y tipo procesador
</div>
<div className={styles["pregunta-cuadro_P3"]}>
Cantidad de población beneficiada por plataforma y tipo de procesador.
<ToggleButton
onChange={(estado) => {
cargarDatos(estado);
}}
/>
</div>
<div className={styles.scanView_P3}>Error al cargar los datos.</div>
</div>
const isInvalidProcessor = (tipo: string, platform: PlatformKey) => {
if (tipo === "Total") return false;
const official = getOfficialProcessors(platform).map((p) =>
cleanName(p).toLowerCase()
);
const currentData = data[activeTab];
return !official.includes(cleanName(tipo).toLowerCase());
};
return (
<div className={styles.container_P3}>
@@ -213,9 +296,9 @@ export default function Pregunta3_2() {
}}
/>
</div>
<div className={styles.scanView_P3}>
<div className={styles.mainContent_P3}>
{/* Pestañas verticales */}
<div className={styles.tabs_P3}>
{Object.entries(PLATFORM_LABELS).map(([key, label]) => (
<button
@@ -231,97 +314,126 @@ export default function Pregunta3_2() {
))}
</div>
{/* Contenido */}
<div className={styles.content_P3}>
{currentData.length > 0 ? (
<>
<div className={styles.tableWrapper_P3}>
<table className={styles.table_P3}>
<thead>
<tr>
<th rowSpan={2} className={styles.headerProcesador}>
{activeTab.includes("apple")
? "Plataforma Apple"
: "Plataforma PC"}{" "}
<br />
Tipo de procesador
</th>
<th colSpan={5} className={styles.headerPoblacion}>
Población Beneficiada
</th>
<th rowSpan={2} className={styles.headerTotal}>
Total
</th>
</tr>
<tr>
<th className={styles.subHeader}>Alumnos</th>
<th className={styles.subHeader}>Profesores</th>
<th className={styles.subHeader}>
Técnicos Académicos
</th>
<th className={styles.subHeader}>Investigadores</th>
<th className={styles.subHeader}>Administrativos</th>
</tr>
</thead>
<tbody>
{currentData.map((item, index) => (
<tr
key={index}
className={
item.isTotal
? styles.totalRow_P3
: styles.dataRow_P3
}
>
<td className={styles.processor_P3}>{item.tipo}</td>
<td>
<div className={styles.tableWrapper_P3}>
<table className={styles.table_P3}>
<thead>
<tr>
<th rowSpan={2} className={styles.headerProcesador}>
{activeTab.includes("apple")
? "Plataforma Apple"
: "Plataforma PC"}{" "}
<br />
Tipo de procesador
</th>
<th colSpan={5} className={styles.headerPoblacion}>
Población Beneficiada
</th>
<th rowSpan={2} className={styles.headerTotal}>
Total
</th>
</tr>
<tr>
<th className={styles.subHeader}>Alumnos</th>
<th className={styles.subHeader}>Profesores</th>
<th className={styles.subHeader}>Técnicos Académicos</th>
<th className={styles.subHeader}>Investigadores</th>
<th className={styles.subHeader}>Administrativos</th>
</tr>
</thead>
<tbody>
{currentData.map((item, idx) => {
const invalid = isInvalidProcessor(item.tipo, activeTab);
const rowClass = item.isTotal
? styles.totalRow_P3
: invalid
? styles.invalidRow_P3
: styles.dataRow_P3;
return (
<tr key={idx} className={rowClass}>
<td className={styles.processor_P3}>{item.tipo}</td>
<td>
{loading ? (
<div className="skeleton" />
) : (
<input
type="text"
value={item.alumnos}
readOnly
className={styles.inputBox_P3}
/>
</td>
<td>
)}
</td>
<td>
{loading ? (
<div className="skeleton" />
) : (
<input
type="text"
value={item.profesores}
readOnly
className={styles.inputBox_P3}
/>
</td>
<td>
)}
</td>
<td>
{loading ? (
<div className="skeleton" />
) : (
<input
type="text"
value={item.tecnicos}
readOnly
className={styles.inputBox_P3}
/>
</td>
<td>
)}
</td>
<td>
{loading ? (
<div className="skeleton" />
) : (
<input
type="text"
value={item.investigadores}
readOnly
className={styles.inputBox_P3}
/>
</td>
<td>
)}
</td>
<td>
{loading ? (
<div className="skeleton" />
) : (
<input
type="text"
value={item.administrativos}
readOnly
className={styles.inputBox_P3}
/>
</td>
<td className={styles.totalCell_P3}>{item.total}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
) : (
)}
</td>
<td className={styles.totalCell_P3}>
{loading ? (
<div className="skeleton" />
) : (
item.total
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{currentData.length === 0 && (
<div className={styles.emptyState_P3}>
No hay datos disponibles para esta plataforma.
</div>
@@ -6,7 +6,7 @@
background-color: #f4f4f4;
}
.scanView_P1 {
padding: 20px;
padding: 1rem;
width: 100%;
}
.contenedor-censo_p1 {
@@ -35,7 +35,7 @@
}
.scanView_P3 {
padding: 20px;
padding: 1rem;
width: 100%;
background-color: white;
border-radius: 10px;
@@ -44,7 +44,6 @@
.scanView_P1 {
background-color: #fff;
border-radius: 10px;
padding: 20px;
width: 100%;
display: flex;
justify-content: center;
@@ -150,7 +149,11 @@
}
}
}
.invalidRow {
background-color: rgba(223, 53, 53, 0.08) !important;
color: #b30000;
font-weight: 600;
}
@media (max-width: 700px) {
.scanView_P1 .tabs_P1 {
width: 160px;
@@ -25,7 +25,7 @@
}
.scanView_P3 {
padding: 20px;
padding: 1rem;
width: 100%;
background-color: white;
border-radius: 10px;
@@ -70,7 +70,6 @@
.content_P3 {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
}
@@ -94,7 +93,7 @@
th {
background: #f1f5f9;
font-weight: 700;
padding: 12px 14px;
padding: 10px;
color: #003f80;
border-bottom: 2px solid #e2e8f0;
text-align: center;
@@ -102,7 +101,7 @@
}
td {
padding: 10px 12px;
padding: 5px 10px;
border-bottom: 1px solid #e5e7eb;
text-align: center;
vertical-align: middle;
@@ -128,7 +127,6 @@
padding: 6px;
border-radius: 6px;
border: 1px solid #cbd5e1;
background: #f8fafc;
font-size: 14px;
transition: 0.2s ease;
text-align: center;
@@ -185,6 +183,11 @@
}
}
.invalidRow_P3 {
background-color: rgba(255, 0, 0, 0.06) !important;
color: #8b0000;
}
@media (max-width: 768px) {
.scanView_P3 .mainContent_P3 {
flex-direction: column;
+1 -1
View File
@@ -14,7 +14,7 @@
.tabla-contenedor {
background: white;
padding: 20px;
padding: 1rem;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
overflow-x: auto;
+4 -1
View File
@@ -52,6 +52,8 @@ export const PROCESADORES_POR_EQUIPO: Record<
{ id_procesador: 19, procesador: "Celeron Serie J, G" },
],
/*"ESCRITORIO MAC OS"*/ 2: [
{ id_procesador: 32, procesador: "Familia M4" },
{ id_procesador: 31, procesador: "Familia M3" },
{ id_procesador: 20, procesador: "Familia M2" },
{ id_procesador: 21, procesador: "Familia M1" },
{ id_procesador: 2, procesador: "i7 o equivalentes (9a - 12a generación)" },
@@ -61,7 +63,6 @@ export const PROCESADORES_POR_EQUIPO: Record<
{ id_procesador: 8, procesador: "i5 o equivalentes (1a - 4a generación)" },
{ id_procesador: 12, procesador: "i3 o equivalentes (1a - 4a generación)" },
{ id_procesador: 22, procesador: "Core2 Mac, Quad Core o anteriores" },
{ id_procesador: 22, procesador: "MAC OS(15 - Sequoia)" },
],
/*"ESCRITORIO LINUX"*/ 3: [
{
@@ -205,6 +206,8 @@ export const PROCESADORES_POR_EQUIPO: Record<
{ id_procesador: 19, procesador: "Celeron Serie J, G" },
],
/*"PORTÁTILES MAC OS"*/ 6: [
{ id_procesador: 32, procesador: "Familia M4" },
{ id_procesador: 31, procesador: "Familia M3" },
{ id_procesador: 20, procesador: "Familia M2" },
{ id_procesador: 21, procesador: "Familia M1" },
{ id_procesador: 2, procesador: "i7 o equivalentes (9a - 12a generación)" },
+5 -1
View File
@@ -12,6 +12,10 @@ export const SO_POR_EQUIPO: Record<
{ id_sistema_operativo: 5, sistema_operativo: "Linux" },
],
/*"ESCRITORIO MAC OS"*/ 2: [
{
id_sistema_operativo: 13,
sistema_operativo: "MAC OS (SEQUOIA)",
},
{
id_sistema_operativo: 6,
sistema_operativo: "Mac OS (13 - Ventura, 14 - Sonoma)",
@@ -91,7 +95,7 @@ export const SO_POR_EQUIPO: Record<
"Linux (CentOS, Fedora, Ubuntu, Red Hat Enterprise, entre otros)",
},
{
id_sistema_operativo: 13,
id_sistema_operativo: 11,
sistema_operativo: "Unix (AIX, MAC OS Server, Solaris, entre otros)",
},
{ id_sistema_operativo: 14, sistema_operativo: "Windows Server 2022/2023" },