From fd4e765908f24ba897a3864461e20a62b3c4e33e Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 22 Sep 2026 10:18:25 -0500 Subject: [PATCH] Actualizar frontend/src/pages/Ventas.jsx --- frontend/src/pages/Ventas.jsx | 84 +++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Ventas.jsx b/frontend/src/pages/Ventas.jsx index ce82c48..886240f 100644 --- a/frontend/src/pages/Ventas.jsx +++ b/frontend/src/pages/Ventas.jsx @@ -14,6 +14,47 @@ const COLS = ["VENDEDOR","INSCRITOS","VENTA TOTAL","INSCRITOS P.C DEL MES","VENT const TIPOS = ["Venta Inscritos", "Venta P.C", "Venta Pendientes"]; const HEADERS_DETALLE = ["VENDEDOR","ALUMNO","F. MATRÍCULA","F. CANCELACIÓN","TIPO","CUOTA PROM.","DESC. ESPECIAL","INV. NETA"]; +// ── Ordenamiento de tablas (detecta número/soles, fecha dd-mm-yyyy o texto) ── +function _valorOrden(v) { + const s = String(v ?? "").trim(); + if (s === "" || s === "—" || s === "-") return { t: "vacio", n: 0, s: "" }; + // Fecha dd-mm-yyyy o dd/mm/yyyy + const mf = s.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{4})$/); + if (mf) { + const [, d, m, y] = mf; + return { t: "fecha", n: new Date(+y, +m - 1, +d).getTime(), s }; + } + // Número / soles / porcentaje (quita S/, comas, %, espacios) + const limpio = s.replace(/S\/|%|,/g, "").replace(/\s/g, "").trim(); + if (limpio !== "" && !isNaN(Number(limpio))) { + return { t: "num", n: Number(limpio), s }; + } + return { t: "texto", n: 0, s: s.toLowerCase() }; +} + +// Ordena filas (arrays) por la columna idx. dir: "asc" | "desc". Vacíos al final. +function _ordenarFilas(filas, idx, dir) { + if (idx == null || !dir) return filas; + const mult = dir === "asc" ? 1 : -1; + return [...filas].sort((a, b) => { + const va = _valorOrden(a[idx]); + const vb = _valorOrden(b[idx]); + if (va.t === "vacio" && vb.t === "vacio") return 0; + if (va.t === "vacio") return 1; // vacíos siempre al final + if (vb.t === "vacio") return -1; + if (va.t === "texto" || vb.t === "texto") { + return mult * va.s.localeCompare(vb.s, "es"); + } + return mult * (va.n - vb.n); + }); +} + +// Flecha del encabezado según el estado de orden de esa columna. +function _flecha(activo, dir) { + if (!activo) return ⇅; + return {dir === "asc" ? "▲" : "▼"}; +} + export default function Ventas() { const hoy = new Date(); const [ano, setAno] = useState(hoy.getFullYear()); @@ -24,8 +65,18 @@ export default function Ventas() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [modal, setModal] = useState(null); // { vendedor } + const [ordCol, setOrdCol] = useState(null); // índice de columna ordenada + const [ordDir, setOrdDir] = useState(null); // "asc" | "desc" const cols = useColumnasAjustables([200, 100, 130, 120, 130, 120, 130, 140, 100]); + // Click en encabezado: alterna asc -> desc -> sin orden. + const clickOrden = (i) => { + if (i === COLS.length - 1) return; // columna OPC. no se ordena + if (ordCol !== i) { setOrdCol(i); setOrdDir("asc"); } + else if (ordDir === "asc") setOrdDir("desc"); + else { setOrdCol(null); setOrdDir(null); } + }; + useEffect(() => { let activo = true; setVistaActual(`ventas:${ano}|${mes}|${sede}|${programa}|False`); @@ -41,7 +92,10 @@ export default function Ventas() { }, [ano, mes, sede, programa]); const total = useMemo(() => filas.find((f) => f[0] === "TOTAL GENERAL"), [filas]); - const datos = useMemo(() => filas.filter((f) => f[0] !== "TOTAL GENERAL"), [filas]); + const datos = useMemo(() => { + const base = filas.filter((f) => f[0] !== "TOTAL GENERAL"); + return _ordenarFilas(base, ordCol, ordDir); + }, [filas, ordCol, ordDir]); const KPIS = total ? [ ["👤","Vendedores", datos.length, "activos este mes"], @@ -191,7 +245,15 @@ export default function Ventas() {
- {COLS.map((c, i) => )} + {COLS.map((c, i) => { + const ordenable = i !== COLS.length - 1; + return ( + + ); + })} {datos.map((f, i) => ( @@ -231,6 +293,14 @@ function ModalDetalle({ vendedor, ano, mes, onClose }) { const [tipo, setTipo] = useState(TIPOS[0]); const [filas, setFilas] = useState([]); const [loading, setLoading] = useState(true); + const [ordCol, setOrdCol] = useState(null); + const [ordDir, setOrdDir] = useState(null); + + const clickOrden = (i) => { + if (ordCol !== i) { setOrdCol(i); setOrdDir("asc"); } + else if (ordDir === "asc") setOrdDir("desc"); + else { setOrdCol(null); setOrdDir(null); } + }; useEffect(() => { let activo = true; @@ -243,6 +313,7 @@ function ModalDetalle({ vendedor, ano, mes, onClose }) { // Columnas redimensionables (igual que las tablas principales) const colsDet = useColumnasAjustables([200, 260, 120, 130, 120, 120, 130, 120]); + const filasOrd = useMemo(() => _ordenarFilas(filas, ordCol, ordDir), [filas, ordCol, ordDir]); return ( @@ -254,9 +325,14 @@ function ModalDetalle({ vendedor, ano, mes, onClose }) {
{c}
clickOrden(i)}> + {c}{ordenable && _flecha(ordCol === i, ordDir)} +
- {HEADERS_DETALLE.map((h, i) => )} + {HEADERS_DETALLE.map((h, i) => ( + + ))} - {filas.map((f, i) => ( + {filasOrd.map((f, i) => ( {f.slice(0, HEADERS_DETALLE.length).map((v, j) => (
{h}
clickOrden(i)}> + {h}{_flecha(ordCol === i, ordDir)} +