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:
@@ -1107,10 +1107,21 @@ export async function getKpis(
|
|||||||
const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId);
|
const ctx = await resolveContribuyenteContext(pool, tenantId, contribuyenteId);
|
||||||
const esEmisor = ctx.esEmisor;
|
const esEmisor = ctx.esEmisor;
|
||||||
const esReceptor = ctx.esReceptor;
|
const esReceptor = ctx.esReceptor;
|
||||||
const ingresosData = await calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId);
|
const [
|
||||||
const egresosData = await calcularEgresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId);
|
ingresosData,
|
||||||
const adquisicionData = await calcularAdquisicionesMercancias(pool, tenantId, fechaInicio, fechaFin, conciliacion, contribuyenteId);
|
egresosData,
|
||||||
const ivaData = await calcularIvaBalancePorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId);
|
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
|
// IVA a favor año actual: desde enero del año en curso
|
||||||
const ivaAFavorAcumulado = await calcularIvaAFavorAcumulado(pool, tenantId, fechaFin, undefined, conciliacion, contribuyenteId);
|
const ivaAFavorAcumulado = await calcularIvaAFavorAcumulado(pool, tenantId, fechaFin, undefined, conciliacion, contribuyenteId);
|
||||||
@@ -1163,6 +1174,10 @@ export async function getKpis(
|
|||||||
cfdisEmitidosPorRegimen: emitidosPorRegimen,
|
cfdisEmitidosPorRegimen: emitidosPorRegimen,
|
||||||
cfdisRecibidos: recibidosPorRegimen.reduce((s: number, r: any) => s + r.total, 0),
|
cfdisRecibidos: recibidosPorRegimen.reduce((s: number, r: any) => s + r.total, 0),
|
||||||
cfdisRecibidosPorRegimen: recibidosPorRegimen,
|
cfdisRecibidosPorRegimen: recibidosPorRegimen,
|
||||||
|
ncsEmitidas: ncsEmitidasData.total,
|
||||||
|
ncsEmitidasPorRegimen: ncsEmitidasData.porRegimen,
|
||||||
|
ncsRecibidas: ncsRecibidasData.total,
|
||||||
|
ncsRecibidasPorRegimen: ncsRecibidasData.porRegimen,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
CheckSquare,
|
CheckSquare,
|
||||||
|
FileMinus,
|
||||||
|
FilePlus,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@horux/shared-ui';
|
import { cn } from '@horux/shared-ui';
|
||||||
import { FiscalDisclaimer } from '@/components/fiscal-disclaimer';
|
import { FiscalDisclaimer } from '@/components/fiscal-disclaimer';
|
||||||
@@ -118,6 +120,15 @@ export default function DashboardPage() {
|
|||||||
? kpis?.ivaBalancePorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
|
? kpis?.ivaBalancePorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
|
||||||
: kpis?.ivaBalance || 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
|
const ivaAnterior = regimenSeleccionado
|
||||||
? kpisAnterior?.ivaBalancePorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
|
? kpisAnterior?.ivaBalancePorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0
|
||||||
: kpisAnterior?.ivaBalance || 0;
|
: kpisAnterior?.ivaBalance || 0;
|
||||||
@@ -203,7 +214,7 @@ export default function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* KPIs */}
|
{/* 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
|
<KpiCard
|
||||||
title={regimenSeleccionado ? `Ingresos del Mes (${regimenSeleccionado})` : 'Ingresos del Mes'}
|
title={regimenSeleccionado ? `Ingresos del Mes (${regimenSeleccionado})` : 'Ingresos del Mes'}
|
||||||
value={ingresosDisplay}
|
value={ingresosDisplay}
|
||||||
@@ -248,11 +259,25 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
href={drillUrl('Balance IVA - CFDIs', {})}
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Desglose por régimen */}
|
{/* Desglose por régimen */}
|
||||||
{!regimenSeleccionado && kpis && (
|
{!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">
|
<div className="grid gap-4 md:grid-cols-2 3xl:grid-cols-3">
|
||||||
{kpis.ingresosPorRegimen.length > 1 && (
|
{kpis.ingresosPorRegimen.length > 1 && (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -316,6 +341,46 @@ export default function DashboardPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export interface KpiData {
|
|||||||
cfdisEmitidosPorRegimen: { regimen: string; total: number }[];
|
cfdisEmitidosPorRegimen: { regimen: string; total: number }[];
|
||||||
cfdisRecibidos: number;
|
cfdisRecibidos: number;
|
||||||
cfdisRecibidosPorRegimen: { regimen: string; total: number }[];
|
cfdisRecibidosPorRegimen: { regimen: string; total: number }[];
|
||||||
|
ncsEmitidas: number;
|
||||||
|
ncsEmitidasPorRegimen: IngresoRegimen[];
|
||||||
|
ncsRecibidas: number;
|
||||||
|
ncsRecibidasPorRegimen: IngresoRegimen[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IngresosEgresosData {
|
export interface IngresosEgresosData {
|
||||||
|
|||||||
Reference in New Issue
Block a user