Actualizar frontend/src/pages/Cobranza.jsx
This commit is contained in:
@@ -176,7 +176,8 @@ export default function Cobranza() {
|
|||||||
const [agrupacion, setAgrupacion] = useState("SEDE");
|
const [agrupacion, setAgrupacion] = useState("SEDE");
|
||||||
const [sectoristasSel, setSectoristasSel] = useState([]); // multi; vacío = todos
|
const [sectoristasSel, setSectoristasSel] = useState([]); // multi; vacío = todos
|
||||||
const [sede, setSede] = useState("TODOS");
|
const [sede, setSede] = useState("TODOS");
|
||||||
const [frec, setFrec] = useState("TODOS");
|
const [frecsSel, setFrecsSel] = useState([]); // multi; vacío = todas
|
||||||
|
const frecFiltro = frecsSel.length ? frecsSel.join(",") : "TODOS";
|
||||||
// Filtros extra para la vista "Por Sede" (usan JSON REPLACE_META). Multi; vacío = todos.
|
// Filtros extra para la vista "Por Sede" (usan JSON REPLACE_META). Multi; vacío = todos.
|
||||||
const [sedesSel, setSedesSel] = useState([]);
|
const [sedesSel, setSedesSel] = useState([]);
|
||||||
const [progsSel, setProgsSel] = useState([]);
|
const [progsSel, setProgsSel] = useState([]);
|
||||||
@@ -210,10 +211,10 @@ export default function Cobranza() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let activo = true;
|
let activo = true;
|
||||||
setVistaActual(`cobranza:${ano}|${mes}|${sectorista}|${agrupacion}|${sedeFiltro}|${progFiltro}|${vencHasta}`);
|
setVistaActual(`cobranza:${ano}|${mes}|${sectorista}|${agrupacion}|${sedeFiltro}|${progFiltro}|${vencHasta}|${frecFiltro}`);
|
||||||
const cargar = () => {
|
const cargar = () => {
|
||||||
setLoading(true); setError(null);
|
setLoading(true); setError(null);
|
||||||
api.cobranza(ano, mes, sectorista, agrupacion, sedeFiltro, progFiltro, vencHasta)
|
api.cobranza(ano, mes, sectorista, agrupacion, sedeFiltro, progFiltro, vencHasta, frecFiltro)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!activo) return;
|
if (!activo) return;
|
||||||
setFilas(res.filas || []);
|
setFilas(res.filas || []);
|
||||||
@@ -225,7 +226,7 @@ export default function Cobranza() {
|
|||||||
cargar();
|
cargar();
|
||||||
const off = onRefrescar(cargar);
|
const off = onRefrescar(cargar);
|
||||||
return () => { activo = false; off(); };
|
return () => { activo = false; off(); };
|
||||||
}, [ano, mes, agrupacion, sectorista, sedeFiltro, progFiltro, vencHasta]);
|
}, [ano, mes, agrupacion, sectorista, sedeFiltro, progFiltro, vencHasta, frecFiltro]);
|
||||||
|
|
||||||
// Cargar TODOS los alumnos una sola vez (para exportar y buscar) — 1 consulta cacheada
|
// Cargar TODOS los alumnos una sola vez (para exportar y buscar) — 1 consulta cacheada
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -251,7 +252,7 @@ export default function Cobranza() {
|
|||||||
|
|
||||||
// Al cambiar filtros/periodo/agrupación, limpiar el cache de programas por asesor
|
// Al cambiar filtros/periodo/agrupación, limpiar el cache de programas por asesor
|
||||||
useEffect(() => { setProgramasAsesor({}); setExpandidas(new Set()); },
|
useEffect(() => { setProgramasAsesor({}); setExpandidas(new Set()); },
|
||||||
[ano, mes, agrupacion, sectorista, sedeFiltro, progFiltro, vencHasta]);
|
[ano, mes, agrupacion, sectorista, sedeFiltro, progFiltro, vencHasta, frecFiltro]);
|
||||||
|
|
||||||
// Cargar matrículas POR RETIRAR (para el "Saldo por Retirar" de las tarjetas)
|
// Cargar matrículas POR RETIRAR (para el "Saldo por Retirar" de las tarjetas)
|
||||||
const [porRetirar, setPorRetirar] = useState(new Set());
|
const [porRetirar, setPorRetirar] = useState(new Set());
|
||||||
@@ -266,7 +267,7 @@ export default function Cobranza() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (agrupacion !== "SEDE") { setFilasPrograma([]); setExpandidas(new Set()); return; }
|
if (agrupacion !== "SEDE") { setFilasPrograma([]); setExpandidas(new Set()); return; }
|
||||||
let activo = true;
|
let activo = true;
|
||||||
api.cobranza(ano, mes, sectorista, "PROGRAMA", sedeFiltro, progFiltro, vencHasta)
|
api.cobranza(ano, mes, sectorista, "PROGRAMA", sedeFiltro, progFiltro, vencHasta, frecFiltro)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!activo) return;
|
if (!activo) return;
|
||||||
const fp = (res.filas || []).filter((f)=>String(f[0]).toUpperCase()!=="TOTAL GENERAL");
|
const fp = (res.filas || []).filter((f)=>String(f[0]).toUpperCase()!=="TOTAL GENERAL");
|
||||||
@@ -281,7 +282,7 @@ export default function Cobranza() {
|
|||||||
})
|
})
|
||||||
.catch(() => { if (activo) setFilasPrograma([]); });
|
.catch(() => { if (activo) setFilasPrograma([]); });
|
||||||
return () => { activo = false; };
|
return () => { activo = false; };
|
||||||
}, [agrupacion, ano, mes, sectorista, sedeFiltro, progFiltro, vencHasta]);
|
}, [agrupacion, ano, mes, sectorista, sedeFiltro, progFiltro, vencHasta, frecFiltro]);
|
||||||
|
|
||||||
// Cargar clasificación de sede (sede.json) para los programas visibles
|
// Cargar clasificación de sede (sede.json) para los programas visibles
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -297,7 +298,8 @@ export default function Cobranza() {
|
|||||||
return () => { activo = false; };
|
return () => { activo = false; };
|
||||||
}, [filas, esPrograma]);
|
}, [filas, esPrograma]);
|
||||||
|
|
||||||
// Filtro sede/frecuencia (solo PROGRAMA) en cliente — usa sede.json
|
// Filtro sede (solo PROGRAMA) en cliente — usa sede.json.
|
||||||
|
// La frecuencia ya la filtra el backend (por matrícula) en todas las agrupaciones.
|
||||||
const datosCalc = useMemo(() => {
|
const datosCalc = useMemo(() => {
|
||||||
let arr = filas.filter((f) => String(f[0]).toUpperCase() !== "TOTAL GENERAL");
|
let arr = filas.filter((f) => String(f[0]).toUpperCase() !== "TOTAL GENERAL");
|
||||||
if (esPrograma) {
|
if (esPrograma) {
|
||||||
@@ -307,14 +309,13 @@ export default function Cobranza() {
|
|||||||
return s === sede.toUpperCase();
|
return s === sede.toUpperCase();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (frec !== "TODOS") arr = arr.filter((f) => String(f[1]).toUpperCase() === frec);
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vista POR SEDE / POR ASESOR: el backend ya devuelve las filas filtradas por
|
// Vista POR SEDE / POR ASESOR: el backend ya devuelve las filas filtradas por
|
||||||
// Sede y Programa (JSON REPLACE_META), así que se muestran tal cual.
|
// Sede y Programa (JSON REPLACE_META), así que se muestran tal cual.
|
||||||
return arr;
|
return arr;
|
||||||
}, [filas, esPrograma, sede, frec, sedeMap]);
|
}, [filas, esPrograma, sede, sedeMap]);
|
||||||
|
|
||||||
// Mientras carga, mostrar la ÚLTIMA vista renderizada (congelada) hasta que llegue
|
// Mientras carga, mostrar la ÚLTIMA vista renderizada (congelada) hasta que llegue
|
||||||
// la respuesta nueva. Así al filtrar solo se opaca lo anterior, sin recalcular a 0.
|
// la respuesta nueva. Así al filtrar solo se opaca lo anterior, sin recalcular a 0.
|
||||||
@@ -394,7 +395,7 @@ export default function Cobranza() {
|
|||||||
});
|
});
|
||||||
// En ASESOR: al abrir, cargar los programas de ese asesor (vista PROGRAMA filtrada por él)
|
// En ASESOR: al abrir, cargar los programas de ese asesor (vista PROGRAMA filtrada por él)
|
||||||
if (esAsesor && abriendo && !programasAsesor[key]) {
|
if (esAsesor && abriendo && !programasAsesor[key]) {
|
||||||
api.cobranza(ano, mes, s, "PROGRAMA", sedeFiltro, progFiltro, vencHasta)
|
api.cobranza(ano, mes, s, "PROGRAMA", sedeFiltro, progFiltro, vencHasta, frecFiltro)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const fp = (res.filas || []).filter((f)=>String(f[0]).toUpperCase()!=="TOTAL GENERAL");
|
const fp = (res.filas || []).filter((f)=>String(f[0]).toUpperCase()!=="TOTAL GENERAL");
|
||||||
setProgramasAsesor((prev) => ({ ...prev, [key]: fp }));
|
setProgramasAsesor((prev) => ({ ...prev, [key]: fp }));
|
||||||
@@ -423,8 +424,9 @@ export default function Cobranza() {
|
|||||||
let arr = alumnos;
|
let arr = alumnos;
|
||||||
if (esPrograma) {
|
if (esPrograma) {
|
||||||
if (sede !== "TODOS") arr = arr.filter((a) => String(a.sede).toUpperCase() === sede.toUpperCase());
|
if (sede !== "TODOS") arr = arr.filter((a) => String(a.sede).toUpperCase() === sede.toUpperCase());
|
||||||
if (frec !== "TODOS") arr = arr.filter((a) => String(a.frecuencia).toUpperCase() === frec);
|
|
||||||
}
|
}
|
||||||
|
// Frecuencia multi (aplica en todas las agrupaciones, igual que el backend)
|
||||||
|
if (frecsSel.length) arr = arr.filter((a) => frecsSel.includes(String(a.frecuencia).toUpperCase()));
|
||||||
// Filtros Sede / Programa (REPLACE_META) — multi; aplican en Por Sede y Por Asesor
|
// Filtros Sede / Programa (REPLACE_META) — multi; aplican en Por Sede y Por Asesor
|
||||||
if (sedesSel.length) {
|
if (sedesSel.length) {
|
||||||
arr = arr.filter((a) => {
|
arr = arr.filter((a) => {
|
||||||
@@ -448,7 +450,7 @@ export default function Cobranza() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}, [alumnos, esPrograma, sede, frec, sedeFiltro, progFiltro, progClasif, vencHasta]);
|
}, [alumnos, esPrograma, sede, frecsSel, sedeFiltro, progFiltro, progClasif, vencHasta]);
|
||||||
|
|
||||||
function grupoDe(a) {
|
function grupoDe(a) {
|
||||||
// El programa detallado del alumno, en TODOS los filtros (Sede, Asesor, Programa).
|
// El programa detallado del alumno, en TODOS los filtros (Sede, Asesor, Programa).
|
||||||
@@ -550,10 +552,10 @@ export default function Cobranza() {
|
|||||||
<Filters>
|
<Filters>
|
||||||
<SelectBonito label="Año" value={ano} options={ANOS} onChange={(v)=>setAno(+v)} minWidth={110} />
|
<SelectBonito label="Año" value={ano} options={ANOS} onChange={(v)=>setAno(+v)} minWidth={110} />
|
||||||
<SelectBonito label="Mes" value={mes} options={MESES.map((m,i)=>({value:i+1,label:m}))} onChange={(v)=>setMes(+v)} minWidth={150} />
|
<SelectBonito label="Mes" value={mes} options={MESES.map((m,i)=>({value:i+1,label:m}))} onChange={(v)=>setMes(+v)} minWidth={150} />
|
||||||
<SelectBonito label="Agrupación" value={agrupacion} options={AGRUPACIONES} onChange={(v)=>{setAgrupacion(v);setSede("TODOS");setFrec("TODOS");setSedesSel([]);setProgsSel([]);}} minWidth={150} />
|
<SelectBonito label="Agrupación" value={agrupacion} options={AGRUPACIONES} onChange={(v)=>{setAgrupacion(v);setSede("TODOS");setSedesSel([]);setProgsSel([]);}} minWidth={150} />
|
||||||
<ListaMulti label="Sectorista" opciones={(sectoristas||[]).filter((s)=>s!=="TODOS")} seleccion={sectoristasSel} onChange={setSectoristasSel} plural="sectoristas" minWidth={250} />
|
<ListaMulti label="Sectorista" opciones={(sectoristas||[]).filter((s)=>s!=="TODOS")} seleccion={sectoristasSel} onChange={setSectoristasSel} plural="sectoristas" minWidth={250} />
|
||||||
{esPrograma && <SelectBonito label="Sede" value={sede} options={SEDES} onChange={setSede} minWidth={140} />}
|
{esPrograma && <SelectBonito label="Sede" value={sede} options={SEDES} onChange={setSede} minWidth={140} />}
|
||||||
{esPrograma && <SelectBonito label="Frecuencia" value={frec} options={FRECS} onChange={setFrec} minWidth={140} />}
|
<ListaMulti label="Frecuencia" opciones={FRECS.filter((f)=>f!=="TODOS")} seleccion={frecsSel} onChange={(v)=>{setFrecsSel(v);setExpandidas(new Set());}} plural="frecuencias" minWidth={140} />
|
||||||
{(agrupacion==="SEDE"||agrupacion==="ASESOR") && <ListaMulti label="Sede" opciones={SEDES_MULTI} seleccion={sedesSel} onChange={(v)=>{setSedesSel(v);setExpandidas(new Set());}} plural="sedes" minWidth={140} />}
|
{(agrupacion==="SEDE"||agrupacion==="ASESOR") && <ListaMulti label="Sede" opciones={SEDES_MULTI} seleccion={sedesSel} onChange={(v)=>{setSedesSel(v);setExpandidas(new Set());}} plural="sedes" minWidth={140} />}
|
||||||
{(agrupacion==="SEDE"||agrupacion==="ASESOR") && <ListaMulti label="Programa" opciones={PROGRAMAS_MULTI} seleccion={progsSel} onChange={(v)=>{setProgsSel(v);setExpandidas(new Set());}} plural="programas" minWidth={150} />}
|
{(agrupacion==="SEDE"||agrupacion==="ASESOR") && <ListaMulti label="Programa" opciones={PROGRAMAS_MULTI} seleccion={progsSel} onChange={(v)=>{setProgsSel(v);setExpandidas(new Set());}} plural="programas" minWidth={150} />}
|
||||||
<div style={{display:"flex",gap:8,alignItems:"flex-end",marginLeft:"auto"}}>
|
<div style={{display:"flex",gap:8,alignItems:"flex-end",marginLeft:"auto"}}>
|
||||||
@@ -721,7 +723,7 @@ export default function Cobranza() {
|
|||||||
{modal && (
|
{modal && (
|
||||||
<ModalEstadoCuenta grupo={modal.grupo} ano={ano} mes={mes}
|
<ModalEstadoCuenta grupo={modal.grupo} ano={ano} mes={mes}
|
||||||
sectorista={modal.sectorista || sectorista} agrupacion={modal.agrupacion || agrupacion}
|
sectorista={modal.sectorista || sectorista} agrupacion={modal.agrupacion || agrupacion}
|
||||||
progFiltro={progFiltro} sedeFiltro={sedeFiltro} vencHasta={vencHasta}
|
progFiltro={progFiltro} sedeFiltro={sedeFiltro} vencHasta={vencHasta} frecFiltro={frecFiltro}
|
||||||
onClose={() => setModal(null)} />
|
onClose={() => setModal(null)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -840,6 +842,7 @@ function Tarjetas({ kpis, retiro, loading }) {
|
|||||||
<Row k="Cobrado" v={`S/ ${cob.toLocaleString("es-PE",{maximumFractionDigits:0})}`} />
|
<Row k="Cobrado" v={`S/ ${cob.toLocaleString("es-PE",{maximumFractionDigits:0})}`} />
|
||||||
<Row k="Saldo" v={`S/ ${saldo.toLocaleString("es-PE",{maximumFractionDigits:0})}`} bold color={saldo>0?"#dc2626":"#10b981"} />
|
<Row k="Saldo" v={`S/ ${saldo.toLocaleString("es-PE",{maximumFractionDigits:0})}`} bold color={saldo>0?"#dc2626":"#10b981"} />
|
||||||
<Row k="Saldo por Retirar" v={`S/ ${(saldoRetiro||0).toLocaleString("es-PE",{maximumFractionDigits:0})}`} color="#dc2626" />
|
<Row k="Saldo por Retirar" v={`S/ ${(saldoRetiro||0).toLocaleString("es-PE",{maximumFractionDigits:0})}`} color="#dc2626" />
|
||||||
|
<Row k="Retiros" v={`S/ ${(saldo-(saldoRetiro||0)).toLocaleString("es-PE",{maximumFractionDigits:0})}`} bold color="#7c3aed" />
|
||||||
{cta>0 && (
|
{cta>0 && (
|
||||||
<div style={{marginTop:10}}>
|
<div style={{marginTop:10}}>
|
||||||
<div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}>
|
<div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}>
|
||||||
@@ -890,6 +893,7 @@ function TarjetasResumen({ r, retiro }) {
|
|||||||
<Row k="Cobrado" v={`S/ ${cob.toLocaleString("es-PE",{maximumFractionDigits:0})}`} />
|
<Row k="Cobrado" v={`S/ ${cob.toLocaleString("es-PE",{maximumFractionDigits:0})}`} />
|
||||||
<Row k="Saldo" v={`S/ ${saldo.toLocaleString("es-PE",{maximumFractionDigits:0})}`} bold color={saldo>0?"#dc2626":"#10b981"} />
|
<Row k="Saldo" v={`S/ ${saldo.toLocaleString("es-PE",{maximumFractionDigits:0})}`} bold color={saldo>0?"#dc2626":"#10b981"} />
|
||||||
<Row k="Saldo por Retirar" v={`S/ ${(saldoRetiro||0).toLocaleString("es-PE",{maximumFractionDigits:0})}`} color="#dc2626" />
|
<Row k="Saldo por Retirar" v={`S/ ${(saldoRetiro||0).toLocaleString("es-PE",{maximumFractionDigits:0})}`} color="#dc2626" />
|
||||||
|
<Row k="Retiros" v={`S/ ${(saldo-(saldoRetiro||0)).toLocaleString("es-PE",{maximumFractionDigits:0})}`} bold color="#7c3aed" />
|
||||||
{cta>0 && (
|
{cta>0 && (
|
||||||
<div style={{marginTop:8}}>
|
<div style={{marginTop:8}}>
|
||||||
<div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}>
|
<div style={{display:"flex",justifyContent:"space-between",marginBottom:4}}>
|
||||||
@@ -1093,7 +1097,7 @@ function ModalBuscador({ alumnos, agrupacion, sectorista, ano, mes, onClose }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, progFiltro = "TODOS", sedeFiltro = "TODOS", vencHasta = "TODOS", onClose }) {
|
function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, progFiltro = "TODOS", sedeFiltro = "TODOS", vencHasta = "TODOS", frecFiltro = "TODOS", onClose }) {
|
||||||
const [filas, setFilas] = useState([]);
|
const [filas, setFilas] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [porRetirar, setPorRetirar] = useState(new Set()); // matrículas POR RETIRAR
|
const [porRetirar, setPorRetirar] = useState(new Set()); // matrículas POR RETIRAR
|
||||||
@@ -1118,11 +1122,11 @@ function ModalEstadoCuenta({ grupo, ano, mes, sectorista, agrupacion, progFiltro
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let activo = true;
|
let activo = true;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
api.cobranzaDetalle(grupo, ano, mes, sectorista, agrupacion, progFiltro, sedeFiltro, vencHasta)
|
api.cobranzaDetalle(grupo, ano, mes, sectorista, agrupacion, progFiltro, sedeFiltro, vencHasta, frecFiltro)
|
||||||
.then((res) => { if (activo) { setFilas(res.filas || []); setLoading(false); } })
|
.then((res) => { if (activo) { setFilas(res.filas || []); setLoading(false); } })
|
||||||
.catch(() => { if (activo) { setFilas([]); setLoading(false); } });
|
.catch(() => { if (activo) { setFilas([]); setLoading(false); } });
|
||||||
return () => { activo = false; };
|
return () => { activo = false; };
|
||||||
}, [grupo, ano, mes, sectorista, agrupacion, progFiltro, sedeFiltro, vencHasta]);
|
}, [grupo, ano, mes, sectorista, agrupacion, progFiltro, sedeFiltro, vencHasta, frecFiltro]);
|
||||||
|
|
||||||
// Cargar (y recargar) las matrículas marcadas POR RETIRAR (de este mes) para tachar sus filas
|
// Cargar (y recargar) las matrículas marcadas POR RETIRAR (de este mes) para tachar sus filas
|
||||||
const cargarPorRetirar = () => {
|
const cargarPorRetirar = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user