From 012a4e14ecb01a87ad45b6c7e3f727c224347087 Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 28 Sep 2026 16:36:57 -0500 Subject: [PATCH] Actualizar frontend/src/pages/Ventas.jsx --- frontend/src/pages/Ventas.jsx | 62 +++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/frontend/src/pages/Ventas.jsx b/frontend/src/pages/Ventas.jsx index c0f1487..88cb4a1 100644 --- a/frontend/src/pages/Ventas.jsx +++ b/frontend/src/pages/Ventas.jsx @@ -12,7 +12,10 @@ const SEDES = ["LIMA", "AREQUIPA", "PIURA", "TRUJILLO"]; const PROGRAMAS = ["SEMINARIOS", "OTROS", "TEAC", "TERC"]; const COLS = ["VENDEDOR","INSCRITOS","VENTA TOTAL","INSCRITOS P.C DEL MES","VENTA P.C DEL MES","INSCRITOS MES ANTERIOR","VENTA MES ANTERIOR","AVANCE P.C TOTAL","OPC."]; 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"]; +const HEADERS_DETALLE = ["ALUMNO","PROGRAMA","F. MATRÍCULA","F. CANCELACIÓN","TIPO","CUOTA PROM.","DESC. ESPECIAL","INV. NETA"]; +// Índice en la fila del backend para cada columna del detalle (arriba). +// Fila: [0]vend [1]alumno [2]f.mat [3]f.canc [4]tipo [5]cuota [6]desc [7]inv [8]programa +const IDX_DETALLE = [1, 8, 2, 3, 4, 5, 6, 7]; // ── Ordenamiento de tablas (detecta número/soles, fecha dd-mm-yyyy o texto) ── function _valorOrden(v) { @@ -356,13 +359,15 @@ export default function Ventas() { } {modal && ( - setModal(null)} /> + setModal(null)} /> )} ); } -function ModalDetalle({ vendedor, ano, mes, onClose }) { +function ModalDetalle({ vendedor, ano, mes, sede = "TODOS", programa = "TODOS", onClose }) { const [tipo, setTipo] = useState(TIPOS[0]); const [filas, setFilas] = useState([]); const [loading, setLoading] = useState(true); @@ -378,15 +383,35 @@ function ModalDetalle({ vendedor, ano, mes, onClose }) { useEffect(() => { let activo = true; setLoading(true); - api.ventasDetalle(vendedor, ano, mes, tipo, "TODOS", "TODOS", true) + api.ventasDetalle(vendedor, ano, mes, tipo, sede, programa, true) .then((res) => { if (activo) { setFilas(res.filas || []); setLoading(false); } }) .catch(() => { if (activo) { setFilas([]); setLoading(false); } }); return () => { activo = false; }; - }, [vendedor, ano, mes, tipo]); + }, [vendedor, ano, mes, tipo, sede, programa]); // 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]); + const filasOrd = useMemo(() => { + const _n = (v) => { const n = parseFloat(String(v ?? "").replace("S/","").replace(/,/g,"").trim()); return isNaN(n)?0:n; }; + const _m = (v) => "S/ " + Math.round(v).toLocaleString("es-PE"); + // Ignorar el TOTAL que venga del backend y recalcularlo siempre desde los alumnos + // (al filtrar por sede/programa el backend NO envía la fila TOTAL). + const alumnos = filas.filter((f) => String(f[0]).toUpperCase() !== "TOTAL GENERAL"); + const ordenadas = _ordenarFilas(alumnos, ordCol == null ? null : IDX_DETALLE[ordCol], ordDir); + if (alumnos.length === 0) return ordenadas; + // Fila TOTAL: INV.NETA[7] y DESC[6] suman; CUOTA PROM[5] promedio de los que tienen valor. + let sInv = 0, sDesc = 0, sCuota = 0, nCuota = 0; + alumnos.forEach((f) => { + sInv += _n(f[7]); sDesc += _n(f[6]); + const c = _n(f[5]); if (c > 0) { sCuota += c; nCuota += 1; } + }); + const total = []; + total[0] = "TOTAL GENERAL"; + total[5] = nCuota ? _m(sCuota / nCuota) : ""; + total[6] = sDesc ? _m(sDesc) : ""; + total[7] = _m(sInv); + return [...ordenadas, total]; + }, [filas, ordCol, ordDir]); return ( @@ -405,14 +430,23 @@ function ModalDetalle({ vendedor, ano, mes, onClose }) { ))} - {filasOrd.map((f, i) => ( - - {f.slice(0, HEADERS_DETALLE.length).map((v, j) => ( - {v} - ))} - - ))} + {filasOrd.map((f, i) => { + const esTotal = String(f[0]).toUpperCase() === "TOTAL GENERAL"; + return ( + + {IDX_DETALLE.map((idx, j) => { + const base = { textAlign:"center", whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis" }; + if (esTotal) { + const st = { ...base, background:"#eff6ff", fontWeight:700, color:"#1e40af", borderTop:"2px solid #bfdbfe" }; + return {j === 0 ? "TOTAL GENERAL" : f[idx]}; + } + return ( + {f[idx]} + ); + })} + + ); + })} }