'use client'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Header } from '@/components/layouts/header'; import { KpiCard } from '@horux/shared-ui'; import { BarChart } from '@/components/charts/bar-chart'; import { Card, CardContent, CardHeader, CardTitle } from '@horux/shared-ui'; import { PeriodSelector, RegimenSelector } from '@horux/shared-ui'; import { useKpis, useIngresosEgresos, useAlertas, useRegimenesDelPeriodo } from '@/lib/hooks/use-dashboard'; import { useAuthStore } from '@/stores/auth-store'; import { useTenantViewStore } from '@/stores/tenant-view-store'; import { isGlobalAdminRfc } from '@horux/shared'; import { TrendingUp, TrendingDown, Wallet, Receipt, AlertTriangle, ShoppingCart, CheckSquare, FileMinus, FilePlus, } from 'lucide-react'; import { cn } from '@horux/shared-ui'; import { FiscalDisclaimer } from '@/components/fiscal-disclaimer'; function getMonthRange(year: number, month: number) { const start = `${year}-${String(month).padStart(2, '0')}-01`; const lastDay = new Date(year, month, 0).getDate(); const end = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`; return { start, end }; } function shiftDatesOneYear(fechaInicio: string, fechaFin: string, delta: number) { const s = new Date(fechaInicio + 'T00:00:00'); const e = new Date(fechaFin + 'T00:00:00'); s.setFullYear(s.getFullYear() + delta); e.setFullYear(e.getFullYear() + delta); // Ajustar último día del mes si cambió const lastDay = new Date(e.getFullYear(), e.getMonth() + 1, 0).getDate(); if (e.getDate() > lastDay) e.setDate(lastDay); return { fechaInicio: s.toISOString().split('T')[0], fechaFin: e.toISOString().split('T')[0], }; } export default function DashboardPage() { const router = useRouter(); const { user } = useAuthStore(); const { viewingTenantId } = useTenantViewStore(); // Admin global no opera sobre datos de despacho propios — su home natural // es `/clientes`. EXCEPCIÓN: si está impersonando un tenant (vía botón "Ver" // en /clientes), sí entra al dashboard para validar lo que ve el cliente. const isGlobalAdmin = isGlobalAdminRfc(user?.tenantRfc, user?.role, user?.platformRoles); useEffect(() => { if (isGlobalAdmin && !viewingTenantId) router.replace('/clientes'); }, [isGlobalAdmin, viewingTenantId, router]); const now = new Date(); const defaultRange = getMonthRange(now.getFullYear(), now.getMonth() + 1); const [fechaInicio, setFechaInicio] = useState(defaultRange.start); const [fechaFin, setFechaFin] = useState(defaultRange.end); const [regimenSeleccionado, setRegimenSeleccionado] = useState(null); const [conciliacion, setConciliacion] = useState(false); // Periodo anterior (mismo rango, un año atrás) const anterior = shiftDatesOneYear(fechaInicio, fechaFin, -1); // Año del inicio para el chart anual const añoChart = new Date(fechaInicio + 'T00:00:00').getFullYear(); const mesResumen = new Date(fechaInicio + 'T00:00:00').getMonth() + 1; const { data: kpis } = useKpis(fechaInicio, fechaFin, conciliacion); const { data: kpisAnterior } = useKpis(anterior.fechaInicio, anterior.fechaFin, conciliacion); const { data: chartData } = useIngresosEgresos(añoChart, conciliacion); const { data: alertas, isLoading: alertasLoading } = useAlertas(5); const { data: regimenesPeriodo, isLoading: regimenesLoading } = useRegimenesDelPeriodo(fechaInicio, fechaFin, conciliacion); const handlePeriodChange = (inicio: string, fin: string) => { setFechaInicio(inicio); setFechaFin(fin); }; // Filtrar ingresos por régimen seleccionado const ingresosDisplay = regimenSeleccionado ? kpis?.ingresosPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0 : kpis?.ingresos || 0; const ingresosAnterior = regimenSeleccionado ? kpisAnterior?.ingresosPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0 : kpisAnterior?.ingresos || 0; const ingresosVariacion = ingresosAnterior > 0 ? Math.round(((ingresosDisplay - ingresosAnterior) / ingresosAnterior) * 10000) / 100 : null; // Filtrar egresos por régimen seleccionado const egresosDisplay = regimenSeleccionado ? kpis?.egresosPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0 : kpis?.egresos || 0; const egresosAnterior = regimenSeleccionado ? kpisAnterior?.egresosPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0 : kpisAnterior?.egresos || 0; const egresosVariacion = egresosAnterior > 0 ? Math.round(((egresosDisplay - egresosAnterior) / egresosAnterior) * 10000) / 100 : null; // Adquisición de mercancías const adquisicionDisplay = regimenSeleccionado ? kpis?.adquisicionMercanciasPorRegimen?.find(r => r.regimenClave === regimenSeleccionado)?.monto || 0 : kpis?.adquisicionMercancias || 0; // Filtrar IVA por régimen seleccionado const ivaDisplay = regimenSeleccionado ? 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; const ivaVariacion = ivaAnterior !== 0 ? Math.round(((ivaDisplay - ivaAnterior) / Math.abs(ivaAnterior)) * 10000) / 100 : null; const utilidadDisplay = ingresosDisplay - egresosDisplay; const margenDisplay = ingresosDisplay > 0 ? Math.round((utilidadDisplay / ingresosDisplay) * 10000) / 100 : 0; const formatCurrency = (value: number) => new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', minimumFractionDigits: 0, }).format(value); // Helper para construir URLs de drill-down const drillUrl = (titulo: string, filters: Record) => { const p = new URLSearchParams({ titulo, fechaInicio, fechaFin, status: 'vigente', ...filters }); // Dashboard no tiene toggles considerarActivos/considerarNCs — siempre // pasa los defaults true (omitir = backend usa true). Si en el futuro se // agregan toggles aquí, propagarlos como hace /impuestos. if (regimenSeleccionado) { if (filters.type === 'EMITIDO') p.set('regimenEmisor', regimenSeleccionado); else if (filters.type === 'RECIBIDO') p.set('regimenReceptor', regimenSeleccionado); // Por bucket — 605 es receptor en bucket=ingresos (nómina recibida). else if (filters.bucket === 'ingresos') { if (regimenSeleccionado === '605') p.set('regimenReceptor', '605'); else p.set('regimenEmisor', regimenSeleccionado); } else if (filters.bucket === 'causado') p.set('regimenEmisor', regimenSeleccionado); else if (filters.bucket === 'gastos' || filters.bucket === 'acreditable') p.set('regimenReceptor', regimenSeleccionado); } return `/drill-down?${p}`; }; // Año anterior para labels const añoAnterior = new Date(anterior.fechaInicio + 'T00:00:00').getFullYear(); // Reset régimen si ya no existe en el periodo const regimenesDisponibles = regimenesPeriodo || []; if (regimenSeleccionado && regimenesDisponibles.length > 0 && !regimenesDisponibles.find(r => r.clave === regimenSeleccionado)) { setRegimenSeleccionado(null); } return ( <>
{/* Filtros */}
{/* KPIs */}
} trend={ingresosVariacion !== null ? (ingresosVariacion >= 0 ? 'up' : 'down') : 'neutral'} trendValue={ ingresosVariacion !== null ? `${ingresosVariacion >= 0 ? '+' : ''}${ingresosVariacion}% vs ${añoAnterior}` : 'Sin datos del periodo anterior' } href={drillUrl('Ingresos del Mes - CFDIs', { bucket: 'ingresos' })} /> } trend={egresosVariacion !== null ? (egresosVariacion >= 0 ? 'up' : 'down') : 'neutral'} trendValue={ egresosVariacion !== null ? `${egresosVariacion >= 0 ? '+' : ''}${egresosVariacion}% vs ${añoAnterior}` : 'Sin datos del periodo anterior' } href={drillUrl('Gastos del Mes - CFDIs', { bucket: 'gastos' })} /> } trend={utilidadDisplay > 0 ? 'up' : 'down'} trendValue={`${margenDisplay}% margen`} /> } trend={ivaDisplay > 0 ? 'up' : ivaDisplay < 0 ? 'down' : 'neutral'} trendValue={ivaDisplay > 0 ? 'Por pagar' : ivaDisplay < 0 ? 'A favor' : 'Neutro'} subtitle={ ivaVariacion !== null ? `${ivaVariacion >= 0 ? '+' : ''}${ivaVariacion}% vs ${añoAnterior}` : undefined } href={drillUrl('Balance IVA - CFDIs', {})} /> } trend="neutral" trendValue="Notas de crédito emitidas" /> } trend="neutral" trendValue="Notas de crédito recibidas" />
{/* Desglose por régimen */} {!regimenSeleccionado && kpis && ( (kpis.ingresosPorRegimen.length > 1 || kpis.egresosPorRegimen.length > 1 || kpis.ivaBalancePorRegimen.length > 1 || kpis.ncsEmitidasPorRegimen.length > 1 || kpis.ncsRecibidasPorRegimen.length > 1) && (
{kpis.ingresosPorRegimen.length > 1 && ( Ingresos por Regimen
{kpis.ingresosPorRegimen.map((r) => (
{r.regimenClave} {r.regimenDescripcion}
{formatCurrency(r.monto)}
))}
)} {kpis.egresosPorRegimen.length > 1 && ( Gastos por Regimen
{kpis.egresosPorRegimen.map((r) => (
{r.regimenClave} {r.regimenDescripcion}
{formatCurrency(r.monto)}
))}
)} {kpis.ivaBalancePorRegimen.length > 1 && ( Balance IVA por Regimen
{kpis.ivaBalancePorRegimen.map((r) => (
{r.regimenClave} {r.regimenDescripcion}
0 ? 'text-destructive' : 'text-success'}`}> {formatCurrency(r.monto)}
))}
)} {kpis.ncsEmitidasPorRegimen.length > 1 && ( NCs Emitidas por Regimen
{kpis.ncsEmitidasPorRegimen.map((r) => (
{r.regimenClave} {r.regimenDescripcion}
{formatCurrency(r.monto)}
))}
)} {kpis.ncsRecibidasPorRegimen.length > 1 && ( NCs Recibidas por Regimen
{kpis.ncsRecibidasPorRegimen.map((r) => (
{r.regimenClave} {r.regimenDescripcion}
{formatCurrency(r.monto)}
))}
)}
))} {/* Charts and Alerts */}
Alertas {alertasLoading ? (

Cargando...

) : alertas?.length === 0 ? (

No hay alertas pendientes

) : ( alertas?.map((alerta) => (

{alerta.titulo}

{alerta.mensaje}

)) )}
{/* Resumen Fiscal */}

CFDIs Emitidos

{ regimenSeleccionado ? kpis?.cfdisEmitidosPorRegimen?.find(r => r.regimen === regimenSeleccionado)?.total || 0 : kpis?.cfdisEmitidos || 0 }

CFDIs Recibidos

{ regimenSeleccionado ? kpis?.cfdisRecibidosPorRegimen?.find(r => r.regimen === regimenSeleccionado)?.total || 0 : kpis?.cfdisRecibidos || 0 }

Adquisición de Mercancías

{formatCurrency(adquisicionDisplay)}

Uso CFDI G01

IVA a Favor ({añoChart})

{formatCurrency(kpis?.ivaAFavorAcumulado || 0)}

IVA a Favor Historico

{formatCurrency(kpis?.ivaAFavorHistorico || 0)}

{añoChart - 5} — {añoChart}

); }