From 5b96e4cb59408b0eedbd9fdb4e1c9cf8a480a794 Mon Sep 17 00:00:00 2001 From: Administrator Date: Wed, 26 Aug 2026 11:01:29 -0500 Subject: [PATCH] Actualizar frontend/src/pages/Cobranza.jsx --- frontend/src/pages/Cobranza.jsx | 142 +++++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 31 deletions(-) diff --git a/frontend/src/pages/Cobranza.jsx b/frontend/src/pages/Cobranza.jsx index e28412f..1e00672 100644 --- a/frontend/src/pages/Cobranza.jsx +++ b/frontend/src/pages/Cobranza.jsx @@ -459,12 +459,14 @@ function ModalBuscador({ alumnos, agrupacion, sectorista, onClose }) { const [texto, setTexto] = useState(""); const [query, setQuery] = useState(""); const [porRetirar, setPorRetirar] = useState(new Set()); + const [infoAlumno, setInfoAlumno] = useState(null); - useEffect(() => { + const cargarPorRetirar = () => { api.alumnosPorRetirar() .then((r)=>setPorRetirar(new Set((r.matriculas||[]).map(String)))) .catch(()=>{}); - }, []); + }; + useEffect(() => { cargarPorRetirar(); }, []); const resultados = useMemo(() => { const q = query.trim().toLowerCase(); @@ -476,13 +478,37 @@ function ModalBuscador({ alumnos, agrupacion, sectorista, onClose }) { ); }, [alumnos, query]); - const COLS = ["GRUPO","MATRÍCULA","ALUMNO","N° CUOTA","F. VENC.", - "CTA ANT.","COB ANT.","% ANT","CTA MES","COB MES","% MES","CTA TOT","COB TOT","% TOT","SALDO"]; + const COLS = ["PROGRAMA","ALUMNO","N° CUOTA","F. VENC.", + "CTA ANT.","COB ANT.","% ANT","CTA MES","COB MES","% MES","CTA TOT","COB TOT","% TOT","SALDO","INFO"]; + + const anchosBusc = COLS.map((h,i) => { + if (i===0) return 250; + if (i===1) return 250; + if (h==="N° CUOTA" || h==="F. VENC.") return 100; + if (h==="INFO") return 70; + if (h.startsWith("%")) return 80; + return 110; + }); + const cols = useColumnasAjustables(anchosBusc); + + const FIJAS = 4; + const stickyCol = (j, { esHeader = false, bg = "#fff" } = {}) => { + if (j >= FIJAS) return {}; + let left = 0; + for (let k = 0; k < j; k++) left += (cols.anchos[k] || 0); + const ultima = j === FIJAS - 1; + const lineaColor = esHeader ? "#475569" : "#e2e8f0"; + return { + position: "sticky", left, zIndex: esHeader ? 20 : 10, backgroundColor: bg, + borderRight: "none", + boxShadow: `inset -1px 0 0 0 ${lineaColor}` + (ultima ? ", 3px 0 5px -2px rgba(0,0,0,0.18)" : ""), + }; + }; const pctStr = (cob, cta) => cta > 0 ? `${Math.round(cob/cta*100)}%` : ""; return ( - +
{resultados.length} alumno(s) encontrado(s)
-
- - {COLS.map((c)=>)} +
+
{c}
+ + {COLS.map((c,i)=>{ + const fija = i < FIJAS; + const stH = fija ? stickyCol(i,{esHeader:true,bg:"#334155"}) + : { position:"sticky", top:0, zIndex:12, backgroundColor:"#334155" }; + return ; + })} {resultados.map((a,i)=>{ const deuda = a.saldo > 0.01; const retira = porRetirar.has(String(a.matricula)); - const grp = agrupacion==="SEDE" ? a.sede : (agrupacion==="ASESOR" ? sectorista : a.programa); + const bgFila = (i % 2 === 1) ? "#f8fafc" : "#fff"; const celdas = [ - {v:grp, cls:"col-name", al:"left"}, - {v:a.matricula}, - {v:a.alumno, cls:"col-name", al:"left"}, + {v:a.programa, cls:"col-name", al:"left", texto:true}, + {v:a.alumno, cls:"col-name", al:"left", texto:true}, {v:a.num_cuota}, {v:a.fch_venc}, - {v:fmtMoneda("S/ "+a.cta_ant), al:"right", cart:"cart-ant"}, - {v:fmtMoneda("S/ "+a.cob_ant), al:"right", cart:"cart-ant"}, + {v:fmtMoneda("S/ "+a.cta_ant), cart:"cart-ant"}, + {v:fmtMoneda("S/ "+a.cob_ant), cart:"cart-ant"}, {pct:pctStr(a.cob_ant,a.cta_ant), cart:"cart-ant"}, - {v:fmtMoneda("S/ "+a.cta_cur), al:"right", cart:"cart-mes"}, - {v:fmtMoneda("S/ "+a.cob_cur), al:"right", cart:"cart-mes"}, + {v:fmtMoneda("S/ "+a.cta_cur), cart:"cart-mes"}, + {v:fmtMoneda("S/ "+a.cob_cur), cart:"cart-mes"}, {pct:pctStr(a.cob_cur,a.cta_cur), cart:"cart-mes"}, - {v:fmtMoneda("S/ "+a.cta_tot), al:"right", cart:"cart-tot"}, - {v:fmtMoneda("S/ "+a.cob_tot), al:"right", cart:"cart-tot"}, + {v:fmtMoneda("S/ "+a.cta_tot), cart:"cart-tot"}, + {v:fmtMoneda("S/ "+a.cob_tot), cart:"cart-tot"}, {pct:pctStr(a.cob_tot,a.cta_tot), cart:"cart-tot"}, - {v:fmtMoneda("S/ "+a.saldo), al:"right", saldo:true}, + {v:fmtMoneda("S/ "+a.saldo), saldo:true}, ]; return ( {celdas.map((c,j)=>( ))} + ); })} @@ -554,6 +599,10 @@ function ModalBuscador({ alumnos, agrupacion, sectorista, onClose }) { )} + {infoAlumno && ( + setInfoAlumno(null)} + onGuardado={cargarPorRetirar} /> + )} ); } @@ -588,6 +637,21 @@ function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, onClose }) const anchosDet = COLS.map((h, i) => i === 0 ? 240 : (i === 1 ? 220 : (h==="INFO" ? 70 : h.startsWith("%") ? 80 : 110))); const cols = useColumnasAjustables(anchosDet); + // Congelar las 4 primeras columnas (PROGRAMA, ALUMNO, N° CUOTA, F. VENC.). + const FIJAS = 4; + const stickyCol = (j, { esHeader = false, bg = "#fff" } = {}) => { + if (j >= FIJAS) return {}; + let left = 0; + for (let k = 0; k < j; k++) left += (cols.anchos[k] || 0); + const ultima = j === FIJAS - 1; + const lineaColor = esHeader ? "#475569" : "#e2e8f0"; + return { + position: "sticky", left, zIndex: esHeader ? 20 : 10, backgroundColor: bg, + borderRight: "none", + boxShadow: `inset -1px 0 0 0 ${lineaColor}` + (ultima ? ", 3px 0 5px -2px rgba(0,0,0,0.18)" : ""), + }; + }; + // Resumen para las 3 tarjetas (siempre visibles) — suma del detalle de alumnos const resumen = useMemo(() => { let cAnt=0,obAnt=0,cMes=0,obMes=0,cTot=0,obTot=0; @@ -666,28 +730,32 @@ function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, onClose }) Ver solo por retirar -
+
{c}
+ color:c.saldo&&deuda?"#dc2626":undefined, + whiteSpace:"nowrap", + overflow:c.texto?"hidden":undefined, + textOverflow:c.texto?"ellipsis":undefined, + ...stickyCol(j,{bg:bgFila})}}> {c.pct!==undefined ? (c.pct ? : "—") : c.v} + +
- {COLS.map((c,i)=>)} + {COLS.map((c,i)=>{ + const fija = i < FIJAS; + const stH = fija + ? stickyCol(i,{esHeader:true,bg:"#334155"}) + : { position:"sticky", top:0, zIndex:12, backgroundColor:"#334155" }; + return ; + })} {filas.filter((f)=>String(f[0]).toUpperCase()!=="TOTAL") .filter((f)=> !soloRetirar || porRetirar.has(String(f[0]))) .map((f, i) => { const saldo = toMonto(f[13]); const deuda = saldo > 0.01; - // Regla "-": si %ANT vacío → CTA/COB ANT a "—" (igual MES y TOT) const antVacio = !String(f[6]||"").trim() || ["-","—"].includes(String(f[6]).trim()); const mesVacio = !String(f[9]||"").trim() || ["-","—"].includes(String(f[9]).trim()); const totVacio = !String(f[12]||"").trim() || ["-","—"].includes(String(f[12]).trim()); - const programa = f[14] || "-"; // programa del alumno (extra del backend) - const retira = porRetirar.has(String(f[0])); // alumno POR RETIRAR + const programa = f[14] || "-"; + const retira = porRetirar.has(String(f[0])); return ( {f.slice(0, 14).map((v, j) => { - // Columna 0: mostrar PROGRAMA en vez de la matrícula let val = (j===0) ? programa : v; if ((j===4||j===5||j===6) && antVacio) val = "—"; if ((j===7||j===8||j===9) && mesVacio) val = "—"; @@ -699,6 +767,7 @@ function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, onClose }) else if ([10,11,12].includes(j)) cart = "cart-tot"; const clss = (((j===0||j===1)?"col-name":"") + (cart?` ${cart}`:"")).trim(); const esTexto = (j===0||j===1); + const bgFila = (i % 2 === 1) ? "#f8fafc" : "#fff"; return ( ); @@ -724,8 +794,9 @@ function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, onClose }) ); })} + + {(() => { - // TOTAL GENERAL del modal — solo alumnos visibles (respeta sede y filtro POR RETIRAR) const alumnos = filas.filter((f)=>String(f[0]).toUpperCase()!=="TOTAL") .filter((f)=> !soloRetirar || porRetirar.has(String(f[0]))); if (alumnos.length===0) return null; @@ -746,18 +817,27 @@ function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, onClose }) {Array.from({length:14}).map((_,j)=>{ const isPct=[6,9,12].includes(j); const val=celda(j); + const fija = j < FIJAS; + let left = 0; for (let k=0;k + style={{textAlign:j===0?"left":"center",whiteSpace:"nowrap",...st}}> {isPct?:val} ); })} - {/* columna INFO en la fila TOTAL */} + ); })()} - +
{c}
{c}
+ fontWeight:j===13?700:undefined, + ...stickyCol(j,{bg:bgFila})}}> {isPct ? (val==="—"?:) : fmtMoneda(val)}
}