Actualizar frontend/src/pages/Cobranza.jsx
This commit is contained in:
@@ -541,8 +541,6 @@ export default function Cobranza() {
|
|||||||
|
|
||||||
// ── HOJA 2: "Grafico" — imagen del gráfico + tabla de los 5 grupos ──
|
// ── HOJA 2: "Grafico" — imagen del gráfico + tabla de los 5 grupos ──
|
||||||
const ws2 = wb.addWorksheet("Grafico");
|
const ws2 = wb.addWorksheet("Grafico");
|
||||||
const filasG = esPrograma ? datosCalc : filasPrograma;
|
|
||||||
const IXg = { venc:3, ctaAnt:4, cobAnt:5, ctaCur:7, cobCur:8, saldo:13, saldoRet:14 };
|
|
||||||
const finM = new Date(Number(ano), Number(mes), 0).getDate();
|
const finM = new Date(Number(ano), Number(mes), 0).getDate();
|
||||||
const GRP = [
|
const GRP = [
|
||||||
{ label:"Vencido (mes anterior)", vencido:true },
|
{ label:"Vencido (mes anterior)", vencido:true },
|
||||||
@@ -551,22 +549,17 @@ export default function Cobranza() {
|
|||||||
{ label:"21 – 25", lo:21, hi:25 },
|
{ label:"21 – 25", lo:21, hi:25 },
|
||||||
{ label:`26 – ${finM}`, lo:26, hi:finM },
|
{ label:`26 – ${finM}`, lo:26, hi:finM },
|
||||||
];
|
];
|
||||||
const toM = (v)=>{ const n=parseFloat(String(v??"").replace(/S\/|,|\s/g,"")); return isNaN(n)?0:n; };
|
|
||||||
const diaV = (s)=>{ const p=String(s||"").split("/").map(Number); return (p.length===3&&p[0])?p[0]:null; };
|
const diaV = (s)=>{ const p=String(s||"").split("/").map(Number); return (p.length===3&&p[0])?p[0]:null; };
|
||||||
const G = GRP.map(()=>({cta:0,cob:0,ret:0,dif:0,n:0}));
|
const G = GRP.map(()=>({cta:0,cob:0,ret:0,dif:0,n:0}));
|
||||||
(filasG||[]).forEach((f)=>{
|
// Mismo cálculo POR ALUMNO que el gráfico (saldo ant→vencido, saldo cur→su día;
|
||||||
if (!Array.isArray(f) || String(f[0]).toUpperCase().includes("TOTAL")) return;
|
// POR RETIRAR→retiros, normal→diferencia).
|
||||||
const ctaAnt=toM(f[IXg.ctaAnt]), cobAnt=toM(f[IXg.cobAnt]);
|
(alumnosFiltrados||[]).forEach((a)=>{
|
||||||
const ctaCur=toM(f[IXg.ctaCur]), cobCur=toM(f[IXg.cobCur]);
|
const ctaAnt=Number(a.cta_ant)||0, cobAnt=Number(a.cob_ant)||0;
|
||||||
const saldo=toM(f[IXg.saldo]), saldoRet=toM(f[IXg.saldoRet]);
|
const ctaCur=Number(a.cta_cur)||0, cobCur=Number(a.cob_cur)||0;
|
||||||
const retT=saldo-saldoRet, difT=saldoRet;
|
const saldoAnt=ctaAnt-cobAnt, saldoCur=ctaCur-cobCur;
|
||||||
if (ctaAnt!==0||cobAnt!==0){ G[0].cta+=ctaAnt; G[0].cob+=cobAnt; G[0].n+=1; }
|
const esRet = porRetirar.has(String(a.matricula));
|
||||||
let gi=-1;
|
if (ctaAnt!==0||cobAnt!==0){ G[0].cta+=ctaAnt; G[0].cob+=cobAnt; G[0].n+=1; if(esRet) G[0].ret+=saldoAnt; else G[0].dif+=saldoAnt; }
|
||||||
if (ctaCur!==0||cobCur!==0){ const d=diaV(f[IXg.venc]); gi=d!=null?GRP.findIndex((g)=>!g.vencido&&d>=g.lo&&d<=g.hi):-1; if(gi<0)gi=GRP.length-1; G[gi].cta+=ctaCur; G[gi].cob+=cobCur; G[gi].n+=1; }
|
if (ctaCur!==0||cobCur!==0){ const d=diaV(a.fch_venc); let gi=d!=null?GRP.findIndex((g)=>!g.vencido&&d>=g.lo&&d<=g.hi):-1; if(gi<0)gi=GRP.length-1; G[gi].cta+=ctaCur; G[gi].cob+=cobCur; G[gi].n+=1; if(esRet) G[gi].ret+=saldoCur; else G[gi].dif+=saldoCur; }
|
||||||
const totCta=(ctaAnt+ctaCur)||1, propC=ctaCur/totCta;
|
|
||||||
const retC=retT*propC, difC=difT*propC, retA=retT-retC, difA=difT-difC;
|
|
||||||
if (ctaCur!==0||cobCur!==0){ G[gi].ret+=retC; G[gi].dif+=difC; G[0].ret+=retA; G[0].dif+=difA; }
|
|
||||||
else { G[0].ret+=retT; G[0].dif+=difT; }
|
|
||||||
});
|
});
|
||||||
// Insertar imagen (si se capturó), dejando filas arriba para ella
|
// Insertar imagen (si se capturó), dejando filas arriba para ella
|
||||||
let filaTabla = 1;
|
let filaTabla = 1;
|
||||||
@@ -794,7 +787,7 @@ export default function Cobranza() {
|
|||||||
Usa alumnosFiltrados (respeta Sectorista/Frecuencia/Sede/Programa). No afecta
|
Usa alumnosFiltrados (respeta Sectorista/Frecuencia/Sede/Programa). No afecta
|
||||||
ninguna tabla. Barras: CTA x COB EN CURSO, COBRADO EN CURSO, RETIROS, DIFERENCIA. */}
|
ninguna tabla. Barras: CTA x COB EN CURSO, COBRADO EN CURSO, RETIROS, DIFERENCIA. */}
|
||||||
{!loading && !error && (
|
{!loading && !error && (
|
||||||
<GraficoVencimientos filas={esPrograma ? datosCalc : filasPrograma} ano={ano} mes={mes} />
|
<GraficoVencimientos alumnos={alumnosFiltrados} porRetirar={porRetirar} ano={ano} mes={mes} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{modal && (
|
{modal && (
|
||||||
@@ -825,11 +818,7 @@ export default function Cobranza() {
|
|||||||
// DIFERENCIA (= (cta_cur-cob_cur) del grupo − RETIROS del grupo).
|
// DIFERENCIA (= (cta_cur-cob_cur) del grupo − RETIROS del grupo).
|
||||||
// SVG puro (sin librerías). No afecta ninguna tabla; respeta los filtros (alumnos
|
// SVG puro (sin librerías). No afecta ninguna tabla; respeta los filtros (alumnos
|
||||||
// ya viene filtrado por Sectorista/Frecuencia/Sede/Programa).
|
// ya viene filtrado por Sectorista/Frecuencia/Sede/Programa).
|
||||||
function GraficoVencimientos({ filas, ano, mes }) {
|
function GraficoVencimientos({ alumnos, porRetirar, ano, mes }) {
|
||||||
// Filas Por Programa, layout fijo:
|
|
||||||
// [0]=prog [3]=VENC [4]=CTA MES ANT [5]=COB MES ANT [7]=CTA EN CURSO [8]=COB EN CURSO
|
|
||||||
// [13]=SALDO [14]=SALDO POR RETIRAR. RETIROS=saldo-saldoRet; DIFERENCIA=(cta_tot-cob_tot)-retiros.
|
|
||||||
const IX = { venc: 3, ctaAnt: 4, cobAnt: 5, ctaCur: 7, cobCur: 8, saldo: 13, saldoRet: 14 };
|
|
||||||
// Paleta sobria y minimalista (tonos apagados, nada saturado)
|
// Paleta sobria y minimalista (tonos apagados, nada saturado)
|
||||||
const SERIES = [
|
const SERIES = [
|
||||||
{ k: "cta", label: "CTA x COB en curso", color: "#64748b" }, // gris azulado
|
{ k: "cta", label: "CTA x COB en curso", color: "#64748b" }, // gris azulado
|
||||||
@@ -862,69 +851,39 @@ function GraficoVencimientos({ filas, ano, mes }) {
|
|||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
const g = GRUPOS.map(() => ({ cta: 0, cob: 0, ret: 0, dif: 0, n: 0 }));
|
const g = GRUPOS.map(() => ({ cta: 0, cob: 0, ret: 0, dif: 0, n: 0 }));
|
||||||
// Índices de grupos en curso (1..4). El 0 es VENCIDO.
|
// Cálculo POR ALUMNO (preciso): cada alumno aporta su saldo vencido (mes anterior)
|
||||||
const idxCurso = GRUPOS.map((grp, i) => i).filter((i) => !GRUPOS[i].vencido);
|
// al grupo VENCIDO y su saldo en curso al grupo de su día de vencimiento.
|
||||||
(filas || []).forEach((f) => {
|
// Si el alumno está POR RETIRAR → ese saldo va a RETIROS; si no → a DIFERENCIA.
|
||||||
if (!Array.isArray(f) || String(f[0]).toUpperCase().includes("TOTAL")) return;
|
(alumnos || []).forEach((a) => {
|
||||||
const ctaAnt = toMonto(f[IX.ctaAnt]); // CTA x COB MES ANTERIOR
|
const ctaAnt = Number(a.cta_ant) || 0, cobAnt = Number(a.cob_ant) || 0;
|
||||||
const cobAnt = toMonto(f[IX.cobAnt]); // COB MES ANTERIOR
|
const ctaCur = Number(a.cta_cur) || 0, cobCur = Number(a.cob_cur) || 0;
|
||||||
const ctaCur = toMonto(f[IX.ctaCur]); // CTA x COB EN CURSO
|
const saldoAnt = ctaAnt - cobAnt; // lo que debe del mes anterior (vencido)
|
||||||
const cobCur = toMonto(f[IX.cobCur]); // COB EN CURSO
|
const saldoCur = ctaCur - cobCur; // lo que debe del mes en curso
|
||||||
// RETIROS y DIFERENCIA TOTALES del programa, EXACTO como la tabla:
|
const esRetiro = porRetirar.has(String(a.matricula));
|
||||||
// RETIROS = SALDO − SALDO POR RETIRAR
|
|
||||||
// DIFERENCIA = SALDO POR RETIRAR
|
|
||||||
const saldo = toMonto(f[IX.saldo]);
|
|
||||||
const saldoRet = toMonto(f[IX.saldoRet]);
|
|
||||||
const retirosTot = saldo - saldoRet; // 3ª barra: RETIROS (24,297)
|
|
||||||
const difTot = saldoRet; // 4ª barra: DIFERENCIA (15,786)
|
|
||||||
|
|
||||||
// ── Grupo VENCIDO (mes anterior) ──
|
// ── Grupo VENCIDO (mes anterior) ──
|
||||||
if (ctaAnt !== 0 || cobAnt !== 0) {
|
if (ctaAnt !== 0 || cobAnt !== 0) {
|
||||||
g[0].cta += ctaAnt;
|
g[0].cta += ctaAnt;
|
||||||
g[0].cob += cobAnt;
|
g[0].cob += cobAnt;
|
||||||
g[0].n += 1;
|
g[0].n += 1;
|
||||||
|
if (esRetiro) g[0].ret += saldoAnt; // POR RETIRAR → retiros
|
||||||
|
else g[0].dif += saldoAnt; // normal → diferencia
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Grupos en curso: por día de vencimiento ──
|
// ── Grupos en curso: por el día de la fecha de vencimiento del alumno ──
|
||||||
if (ctaCur !== 0 || cobCur !== 0) {
|
if (ctaCur !== 0 || cobCur !== 0) {
|
||||||
const d = diaVenc(f[IX.venc]);
|
const d = diaVenc(a.fch_venc);
|
||||||
let gi = d != null ? GRUPOS.findIndex((grp) => !grp.vencido && d >= grp.lo && d <= grp.hi) : -1;
|
let gi = d != null ? GRUPOS.findIndex((grp) => !grp.vencido && d >= grp.lo && d <= grp.hi) : -1;
|
||||||
if (gi < 0) gi = GRUPOS.length - 1; // sin día → último grupo en curso
|
if (gi < 0) gi = GRUPOS.length - 1; // sin día válido → último grupo en curso
|
||||||
g[gi].cta += ctaCur;
|
g[gi].cta += ctaCur;
|
||||||
g[gi].cob += cobCur;
|
g[gi].cob += cobCur;
|
||||||
g[gi].n += 1;
|
g[gi].n += 1;
|
||||||
}
|
if (esRetiro) g[gi].ret += saldoCur; // POR RETIRAR → retiros
|
||||||
|
else g[gi].dif += saldoCur; // normal → diferencia
|
||||||
// ── RETIROS y DIFERENCIA totales: se reparten en curso (por su cta_cur) y el
|
|
||||||
// resto va a VENCIDO, para que la suma de los 5 grupos calce con el total. ──
|
|
||||||
// Proporción: si hay en-curso, la parte proporcional al en-curso va al grupo de
|
|
||||||
// su día; el remanente (vencido) va al grupo VENCIDO.
|
|
||||||
const totCta = (ctaAnt + ctaCur) || 1;
|
|
||||||
const propCur = ctaCur / totCta; // fracción del en-curso
|
|
||||||
const retCur = retirosTot * propCur;
|
|
||||||
const difCur = difTot * propCur;
|
|
||||||
const retAnt = retirosTot - retCur; // remanente → VENCIDO
|
|
||||||
const difAnt = difTot - difCur;
|
|
||||||
// sumar al grupo en curso correspondiente
|
|
||||||
if (ctaCur !== 0 || cobCur !== 0) {
|
|
||||||
const d = diaVenc(f[IX.venc]);
|
|
||||||
let gi = d != null ? GRUPOS.findIndex((grp) => !grp.vencido && d >= grp.lo && d <= grp.hi) : -1;
|
|
||||||
if (gi < 0) gi = GRUPOS.length - 1;
|
|
||||||
g[gi].ret += retCur;
|
|
||||||
g[gi].dif += difCur;
|
|
||||||
} else {
|
|
||||||
// sin en-curso: todo retiros/dif van a VENCIDO
|
|
||||||
g[0].ret += retirosTot;
|
|
||||||
g[0].dif += difTot;
|
|
||||||
}
|
|
||||||
// remanente al VENCIDO (cuando sí hubo en-curso)
|
|
||||||
if (ctaCur !== 0 || cobCur !== 0) {
|
|
||||||
g[0].ret += retAnt;
|
|
||||||
g[0].dif += difAnt;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return g;
|
return g;
|
||||||
}, [filas, ano, mes]);
|
}, [alumnos, porRetirar, ano, mes]);
|
||||||
|
|
||||||
const maxVal = Math.max(1, ...data.flatMap((d) => SERIES.map((s) => Math.abs(d[s.k]))));
|
const maxVal = Math.max(1, ...data.flatMap((d) => SERIES.map((s) => Math.abs(d[s.k]))));
|
||||||
// Techo ajustado: un poco por encima del máximo (≈8% de aire), redondeado a un
|
// Techo ajustado: un poco por encima del máximo (≈8% de aire), redondeado a un
|
||||||
|
|||||||
Reference in New Issue
Block a user