284 lines
12 KiB
TypeScript
284 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { DashboardShell } from '@/components/layouts/dashboard-shell';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { useEstadoResultados, useFlujoEfectivo, useComparativo, useConcentradoRfc } from '@/lib/hooks/use-reportes';
|
|
import { BarChart } from '@/components/charts/bar-chart';
|
|
import { formatCurrency } from '@/lib/utils';
|
|
import { FileText, TrendingUp, TrendingDown, Users } from 'lucide-react';
|
|
|
|
export default function ReportesPage() {
|
|
const [año] = useState(new Date().getFullYear());
|
|
const fechaInicio = `${año}-01-01`;
|
|
const fechaFin = `${año}-12-31`;
|
|
|
|
const { data: estadoResultados, isLoading: loadingER } = useEstadoResultados(fechaInicio, fechaFin);
|
|
const { data: flujoEfectivo, isLoading: loadingFE } = useFlujoEfectivo(fechaInicio, fechaFin);
|
|
const { data: comparativo, isLoading: loadingComp } = useComparativo(año);
|
|
const { data: clientes } = useConcentradoRfc('cliente', fechaInicio, fechaFin);
|
|
const { data: proveedores } = useConcentradoRfc('proveedor', fechaInicio, fechaFin);
|
|
|
|
return (
|
|
<DashboardShell
|
|
title="Reportes"
|
|
description="Analisis financiero y reportes fiscales"
|
|
>
|
|
<Tabs defaultValue="estado-resultados" className="space-y-4">
|
|
<TabsList>
|
|
<TabsTrigger value="estado-resultados">Estado de Resultados</TabsTrigger>
|
|
<TabsTrigger value="flujo-efectivo">Flujo de Efectivo</TabsTrigger>
|
|
<TabsTrigger value="comparativo">Comparativo</TabsTrigger>
|
|
<TabsTrigger value="concentrado">Concentrado RFC</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="estado-resultados" className="space-y-4">
|
|
{loadingER ? (
|
|
<div className="text-center py-8 text-muted-foreground">Cargando...</div>
|
|
) : estadoResultados ? (
|
|
<>
|
|
<div className="grid gap-4 md:grid-cols-4">
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-sm font-medium">Total Ingresos</CardTitle>
|
|
<TrendingUp className="h-4 w-4 text-success" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-success">
|
|
{formatCurrency(estadoResultados.totalIngresos)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-sm font-medium">Total Egresos</CardTitle>
|
|
<TrendingDown className="h-4 w-4 text-destructive" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-destructive">
|
|
{formatCurrency(estadoResultados.totalEgresos)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-sm font-medium">Utilidad Bruta</CardTitle>
|
|
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={`text-2xl font-bold ${estadoResultados.utilidadBruta >= 0 ? 'text-success' : 'text-destructive'}`}>
|
|
{formatCurrency(estadoResultados.utilidadBruta)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-sm font-medium">Utilidad Neta</CardTitle>
|
|
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={`text-2xl font-bold ${estadoResultados.utilidadNeta >= 0 ? 'text-success' : 'text-destructive'}`}>
|
|
{formatCurrency(estadoResultados.utilidadNeta)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Top 10 Ingresos por Cliente</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
{estadoResultados.ingresos.map((item, i) => (
|
|
<div key={i} className="flex justify-between items-center py-2 border-b last:border-0">
|
|
<span className="text-sm truncate max-w-[200px]">{item.concepto}</span>
|
|
<span className="font-medium">{formatCurrency(item.monto)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Top 10 Egresos por Proveedor</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
{estadoResultados.egresos.map((item, i) => (
|
|
<div key={i} className="flex justify-between items-center py-2 border-b last:border-0">
|
|
<span className="text-sm truncate max-w-[200px]">{item.concepto}</span>
|
|
<span className="font-medium">{formatCurrency(item.monto)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</>
|
|
) : null}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="flujo-efectivo" className="space-y-4">
|
|
{loadingFE ? (
|
|
<div className="text-center py-8 text-muted-foreground">Cargando...</div>
|
|
) : flujoEfectivo ? (
|
|
<>
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Total Entradas</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-success">
|
|
{formatCurrency(flujoEfectivo.totalEntradas)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Total Salidas</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-destructive">
|
|
{formatCurrency(flujoEfectivo.totalSalidas)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Flujo Neto</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={`text-2xl font-bold ${flujoEfectivo.flujoNeto >= 0 ? 'text-success' : 'text-destructive'}`}>
|
|
{formatCurrency(flujoEfectivo.flujoNeto)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Flujo de Efectivo Mensual</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<BarChart
|
|
data={flujoEfectivo.entradas.map((e, i) => ({
|
|
mes: e.concepto,
|
|
ingresos: e.monto,
|
|
egresos: flujoEfectivo.salidas[i]?.monto || 0,
|
|
}))}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</>
|
|
) : null}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="comparativo" className="space-y-4">
|
|
{loadingComp ? (
|
|
<div className="text-center py-8 text-muted-foreground">Cargando...</div>
|
|
) : comparativo ? (
|
|
<>
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Var. Ingresos vs Ano Anterior</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={`text-2xl font-bold ${comparativo.variacionIngresos >= 0 ? 'text-success' : 'text-destructive'}`}>
|
|
{comparativo.variacionIngresos >= 0 ? '+' : ''}{comparativo.variacionIngresos.toFixed(1)}%
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Var. Egresos vs Ano Anterior</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className={`text-2xl font-bold ${comparativo.variacionEgresos <= 0 ? 'text-success' : 'text-destructive'}`}>
|
|
{comparativo.variacionEgresos >= 0 ? '+' : ''}{comparativo.variacionEgresos.toFixed(1)}%
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium">Ano Actual</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{año}</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Comparativo Mensual {año}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<BarChart
|
|
data={comparativo.periodos.map((mes, i) => ({
|
|
mes,
|
|
ingresos: comparativo.ingresos[i],
|
|
egresos: comparativo.egresos[i],
|
|
}))}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</>
|
|
) : null}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="concentrado" className="space-y-4">
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Users className="h-5 w-5" />
|
|
Clientes
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
{clientes?.slice(0, 10).map((c, i) => (
|
|
<div key={i} className="flex justify-between items-center py-2 border-b last:border-0">
|
|
<div>
|
|
<div className="font-medium text-sm">{c.nombre}</div>
|
|
<div className="text-xs text-muted-foreground">{c.rfc} - {c.cantidadCfdis} CFDIs</div>
|
|
</div>
|
|
<span className="font-medium">{formatCurrency(c.totalFacturado)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Users className="h-5 w-5" />
|
|
Proveedores
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
{proveedores?.slice(0, 10).map((p, i) => (
|
|
<div key={i} className="flex justify-between items-center py-2 border-b last:border-0">
|
|
<div>
|
|
<div className="font-medium text-sm">{p.nombre}</div>
|
|
<div className="text-xs text-muted-foreground">{p.rfc} - {p.cantidadCfdis} CFDIs</div>
|
|
</div>
|
|
<span className="font-medium">{formatCurrency(p.totalFacturado)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</DashboardShell>
|
|
);
|
|
}
|