feat: tablero pagos muestra solo validos y errores aparte con motivo

This commit is contained in:
Nurfog committed 2026-09-24 09:45:48 -03:00
1 parent c64e990dfa
commit ba71c4daa6
2 files changed
+77 -13

No files matched your search

+5 -1
View File
@@ -18,11 +18,15 @@ export async function GET(req: Request): Promise<Response> {
const year = url.searchParams.get("year") ?? String(now.getFullYear()); const year = url.searchParams.get("year") ?? String(now.getFullYear());
const m = Number(mes); const m = Number(mes);
const y = Number(year); const y = Number(year);
const l = Number(url.searchParams.get("limite") ?? "200");
if (!Number.isInteger(m) || m < 1 || m > 12 || !Number.isInteger(y) || y < 2000 || y > 2100) { if (!Number.isInteger(m) || m < 1 || m > 12 || !Number.isInteger(y) || y < 2000 || y > 2100) {
return NextResponse.json({ error: "mes (1-12) y year válidos requeridos" }, { status: 400 }); return NextResponse.json({ error: "mes (1-12) y year válidos requeridos" }, { status: 400 });
} }
if (!Number.isInteger(l) || l < 1 || l > 1000) {
return NextResponse.json({ error: "limite (1-1000) requerido" }, { status: 400 });
}
const res = await fetch( const res = await fetch(
`${SAM_API_URL}/api/Boletas/payments?mes=${m}&year=${y}`, `${SAM_API_URL}/api/Boletas/payments?mes=${m}&year=${y}&limite=${l}`,
{ cache: "no-store" } { cache: "no-store" }
); );
const text = await res.text(); const text = await res.text();
+72 -12
View File
@@ -67,6 +67,18 @@ interface Boleta {
monto: number; monto: number;
tipo?: string; tipo?: string;
estado?: string; estado?: string;
esValido?: boolean;
errorMotivo?: string | null;
}
// Fecha real = distinta de nula/vacía y del MinValue de .NET
// (0001-01-01: la función PG no informó fecha de caja).
function fechaReal(f: string | null | undefined): boolean {
return !!f && !f.startsWith("0001-01-01");
}
function fechaCorta(f: string | null | undefined): string {
return fechaReal(f) ? String(f).slice(0, 10) : "—";
} }
function HelpdeskUsage() { function HelpdeskUsage() {
@@ -229,7 +241,7 @@ function PagosUsage() {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const res = await fetch(`/api/portal/pagos-validados?mes=${m}&year=${y}`, { const res = await fetch(`/api/portal/pagos-validados?mes=${m}&year=${y}&limite=200`, {
cache: "no-store", cache: "no-store",
}); });
const data = await res.json().catch(() => null); const data = await res.json().catch(() => null);
@@ -254,7 +266,13 @@ function PagosUsage() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const total = (rows ?? []).reduce((acc, r) => acc + (Number(r.monto) || 0), 0); const valid = (rows ?? []).filter(
(r) => r.esValido !== false && fechaReal(r.fecha)
);
const errors = (rows ?? []).filter(
(r) => r.esValido === false || !fechaReal(r.fecha)
);
const total = valid.reduce((acc, r) => acc + (Number(r.monto) || 0), 0);
return ( return (
<> <>
@@ -300,7 +318,9 @@ function PagosUsage() {
</button> </button>
{rows && ( {rows && (
<span className="ms-auto small text-muted align-self-center"> <span className="ms-auto small text-muted align-self-center">
{rows.length} pagos · Total ${total.toLocaleString("es-CL")} {valid.length} válidos
{errors.length > 0 && ` · ${errors.length} observados`} · Total
${total.toLocaleString("es-CL")}
</span> </span>
)} )}
</form> </form>
@@ -318,11 +338,11 @@ function PagosUsage() {
</div> </div>
)} )}
{!error && !loading && rows && rows.length === 0 && ( {!error && !loading && rows && valid.length === 0 && (
<p className="text-muted mb-0">Sin pagos validados para {mes}/{year}.</p> <p className="text-muted mb-0">Sin pagos válidos para {mes}/{year}.</p>
)} )}
{!error && !loading && rows && rows.length > 0 && ( {!error && !loading && rows && valid.length > 0 && (
<div className="table-responsive"> <div className="table-responsive">
<table className="table table-sm table-hover mb-0"> <table className="table table-sm table-hover mb-0">
<thead> <thead>
@@ -335,7 +355,7 @@ function PagosUsage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows.slice(0, 100).map((r, i) => ( {valid.slice(0, 100).map((r, i) => (
<tr key={`${r.folio}-${i}`}> <tr key={`${r.folio}-${i}`}>
<td>{r.folio}</td> <td>{r.folio}</td>
<td className="small"> <td className="small">
@@ -344,20 +364,60 @@ function PagosUsage() {
<span className="text-muted d-block">{r.alumnoRut}</span> <span className="text-muted d-block">{r.alumnoRut}</span>
) : null} ) : null}
</td> </td>
<td className="small"> <td className="small">{fechaCorta(r.fecha)}</td>
{r.fecha ? String(r.fecha).slice(0, 10) : "—"}
</td>
<td className="text-end">${Number(r.monto || 0).toLocaleString("es-CL")}</td> <td className="text-end">${Number(r.monto || 0).toLocaleString("es-CL")}</td>
<td className="small">{r.estado || r.tipo || "—"}</td> <td className="small">{r.estado || r.tipo || "—"}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
{rows.length > 100 && ( {valid.length > 100 && (
<small className="text-muted">Mostrando 100 de {rows.length}.</small> <small className="text-muted">Mostrando 100 de {valid.length}.</small>
)} )}
</div> </div>
)} )}
{!error && !loading && rows && errors.length > 0 && (
<details className="mt-3">
<summary className="small text-warning fw-semibold" style={{ cursor: "pointer" }}>
<i className="bx bx-error me-1" />
{errors.length} registro{errors.length !== 1 ? "s" : ""} observado{
errors.length !== 1 ? "s" : ""
} (no son ventas válidas)
</summary>
<div className="table-responsive mt-2">
<table className="table table-sm table-hover mb-0">
<thead>
<tr>
<th>Folio</th>
<th>Alumno</th>
<th>Fecha</th>
<th className="text-end">Monto</th>
<th>Motivo</th>
</tr>
</thead>
<tbody>
{errors.slice(0, 100).map((r, i) => (
<tr key={`err-${r.folio}-${i}`}>
<td>{r.folio || "—"}</td>
<td className="small">
{r.alumnoNombre ?? "—"}
{r.alumnoRut ? (
<span className="text-muted d-block">{r.alumnoRut}</span>
) : null}
</td>
<td className="small">{fechaCorta(r.fecha)}</td>
<td className="text-end">${Number(r.monto || 0).toLocaleString("es-CL")}</td>
<td className="small text-warning">{r.errorMotivo || "Sin fecha válida"}</td>
</tr>
))}
</tbody>
</table>
{errors.length > 100 && (
<small className="text-muted">Mostrando 100 de {errors.length}.</small>
)}
</div>
</details>
)}
<small className="text-muted d-block mt-2"> <small className="text-muted d-block mt-2">
Fuente: <code>GET /api/Boletas/payments?mes=&amp;year=</code> (SAM.API). Fuente: <code>GET /api/Boletas/payments?mes=&amp;year=</code> (SAM.API).
</small> </small>