feat(dashboard): agregar scorecards de notas de crédito emitidas y recibidas

- Extiende KpiData con ncsEmitidas, ncsEmitidasPorRegimen, ncsRecibidas y ncsRecibidasPorRegimen.
- En getKpis se reutilizan calcularNcsEmitidasPorRegimen y calcularNcsRecibidasPorRegimen en paralelo.
- En el dashboard se agregan dos KpiCard y su desglose por régimen.
This commit is contained in:
Horux Dev
2026-06-13 20:46:57 +00:00
parent 66d68c652c
commit b52ff875be
3 changed files with 90 additions and 6 deletions

View File

@@ -1107,10 +1107,21 @@ export async function getKpis(
const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId);
const esEmisor = ctx.esEmisor;
const esReceptor = ctx.esReceptor;
const ingresosData = await calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId);
const egresosData = await calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId);
const adquisicionData = await calcularAdquisicionesMercancias(pool, tenantId, fechaInicio, fechaFin, conciliacion, contribuyenteId);
const ivaData = await calcularIvaBalancePorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId);
const [
ingresosData,
egresosData,
adquisicionData,
ivaData,
ncsEmitidasData,
ncsRecibidasData,
] = await Promise.all([
calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId),
calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId),
calcularAdquisicionesMercancias(pool, tenantId, fechaInicio, fechaFin, conciliacion, contribuyenteId),
calcularIvaBalancePorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId),
calcularNcsEmitidasPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId),
calcularNcsRecibidasPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId),
]);
// IVA a favor año actual: desde enero del año en curso
const ivaAFavorAcumulado = await calcularIvaAFavorAcumulado(pool, tenantId, fechaFin, undefined, conciliacion, contribuyenteId);
@@ -1163,6 +1174,10 @@ export async function getKpis(
cfdisEmitidosPorRegimen: emitidosPorRegimen,
cfdisRecibidos: recibidosPorRegimen.reduce((s: number, r: any) => s + r.total, 0),
cfdisRecibidosPorRegimen: recibidosPorRegimen,
ncsEmitidas: ncsEmitidasData.total,
ncsEmitidasPorRegimen: ncsEmitidasData.porRegimen,
ncsRecibidas: ncsRecibidasData.total,
ncsRecibidasPorRegimen: ncsRecibidasData.porRegimen,
};
}

View File

@@ -19,6 +19,8 @@ import {
AlertTriangle,
ShoppingCart,
CheckSquare,
FileMinus,
FilePlus,
} from 'lucide-react';
import { cn } from '@horux/shared-ui';
import { FiscalDisclaimer } from '@/components/fiscal-disclaimer';
@@ -118,6 +120,15 @@ export default function DashboardPage() {
? kpis?.ivaBalancePorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
: kpis?.ivaBalance || 0;
// Notas de crédito
const ncsEmitidasDisplay = regimenSeleccionado
? kpis?.ncsEmitidasPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
: kpis?.ncsEmitidas || 0;
const ncsRecibidasDisplay = regimenSeleccionado
? kpis?.ncsRecibidasPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
: kpis?.ncsRecibidas || 0;
const ivaAnterior = regimenSeleccionado
? kpisAnterior?.ivaBalancePorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
: kpisAnterior?.ivaBalance || 0;
@@ -203,7 +214,7 @@ export default function DashboardPage() {
</div>
{/* KPIs */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<KpiCard
title={regimenSeleccionado ? `Ingresos del Mes (${regimenSeleccionado})` : 'Ingresos del Mes'}
value={ingresosDisplay}
@@ -248,11 +259,25 @@ export default function DashboardPage() {
}
href={drillUrl('Balance IVA - CFDIs', {})}
/>
<KpiCard
title={regimenSeleccionado ? `NCs Emitidas (${regimenSeleccionado})` : 'NCs Emitidas'}
value={ncsEmitidasDisplay}
icon={<FileMinus className="h-4 w-4" />}
trend="neutral"
trendValue="Notas de crédito emitidas"
/>
<KpiCard
title={regimenSeleccionado ? `NCs Recibidas (${regimenSeleccionado})` : 'NCs Recibidas'}
value={ncsRecibidasDisplay}
icon={<FilePlus className="h-4 w-4" />}
trend="neutral"
trendValue="Notas de crédito recibidas"
/>
</div>
{/* Desglose por régimen */}
{!regimenSeleccionado && kpis && (
(kpis.ingresosPorRegimen.length > 1 || kpis.egresosPorRegimen.length > 1 || kpis.ivaBalancePorRegimen.length > 1) && (
(kpis.ingresosPorRegimen.length > 1 || kpis.egresosPorRegimen.length > 1 || kpis.ivaBalancePorRegimen.length > 1 || kpis.ncsEmitidasPorRegimen.length > 1 || kpis.ncsRecibidasPorRegimen.length > 1) && (
<div className="grid gap-4 md:grid-cols-2 3xl:grid-cols-3">
{kpis.ingresosPorRegimen.length > 1 && (
<Card>
@@ -316,6 +341,46 @@ export default function DashboardPage() {
</CardContent>
</Card>
)}
{kpis.ncsEmitidasPorRegimen.length > 1 && (
<Card>
<CardHeader>
<CardTitle className="text-base font-medium">NCs Emitidas por Regimen</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{kpis.ncsEmitidasPorRegimen.map((r) => (
<div key={r.regimenClave} className="flex items-center justify-between py-2 border-b last:border-0">
<div className="flex items-center gap-3">
<span className="text-xs font-mono font-bold bg-muted px-2 py-1 rounded">{r.regimenClave}</span>
<span className="text-sm">{r.regimenDescripcion}</span>
</div>
<span className="text-sm font-semibold">{formatCurrency(r.monto)}</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
{kpis.ncsRecibidasPorRegimen.length > 1 && (
<Card>
<CardHeader>
<CardTitle className="text-base font-medium">NCs Recibidas por Regimen</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{kpis.ncsRecibidasPorRegimen.map((r) => (
<div key={r.regimenClave} className="flex items-center justify-between py-2 border-b last:border-0">
<div className="flex items-center gap-3">
<span className="text-xs font-mono font-bold bg-muted px-2 py-1 rounded">{r.regimenClave}</span>
<span className="text-sm">{r.regimenDescripcion}</span>
</div>
<span className="text-sm font-semibold">{formatCurrency(r.monto)}</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
))}