From 1dcc98aadabec75a458421093b2a215c5ec2e149 Mon Sep 17 00:00:00 2001 From: Consultoria AS Date: Thu, 22 Jan 2026 02:34:34 +0000 Subject: [PATCH] feat(web): add impuestos page with IVA/ISR control --- apps/web/app/(dashboard)/impuestos/page.tsx | 246 ++++++++++++++++++++ apps/web/lib/api/impuestos.ts | 32 +++ apps/web/lib/hooks/use-impuestos.ts | 30 +++ 3 files changed, 308 insertions(+) create mode 100644 apps/web/app/(dashboard)/impuestos/page.tsx create mode 100644 apps/web/lib/api/impuestos.ts create mode 100644 apps/web/lib/hooks/use-impuestos.ts diff --git a/apps/web/app/(dashboard)/impuestos/page.tsx b/apps/web/app/(dashboard)/impuestos/page.tsx new file mode 100644 index 0000000..779d559 --- /dev/null +++ b/apps/web/app/(dashboard)/impuestos/page.tsx @@ -0,0 +1,246 @@ +'use client'; + +import { useState } from 'react'; +import { Header } from '@/components/layouts/header'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { KpiCard } from '@/components/charts/kpi-card'; +import { useIvaMensual, useResumenIva, useResumenIsr } from '@/lib/hooks/use-impuestos'; +import { Calculator, TrendingUp, TrendingDown, Receipt } from 'lucide-react'; + +const meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']; + +export default function ImpuestosPage() { + const currentYear = new Date().getFullYear(); + const currentMonth = new Date().getMonth() + 1; + const [año] = useState(currentYear); + const [activeTab, setActiveTab] = useState<'iva' | 'isr'>('iva'); + + const { data: ivaMensual, isLoading: ivaLoading } = useIvaMensual(año); + const { data: resumenIva } = useResumenIva(año, currentMonth); + const { data: resumenIsr } = useResumenIsr(año, currentMonth); + + const formatCurrency = (value: number) => + new Intl.NumberFormat('es-MX', { + style: 'currency', + currency: 'MXN', + minimumFractionDigits: 0, + }).format(value); + + return ( + <> +
+
+ {/* Tabs */} +
+ + +
+ + {activeTab === 'iva' && ( + <> + {/* IVA KPIs */} +
+ } + subtitle="Cobrado a clientes" + /> + } + subtitle="Pagado a proveedores" + /> + } + trend={(resumenIva?.resultado || 0) > 0 ? 'up' : 'down'} + trendValue={(resumenIva?.resultado || 0) > 0 ? 'Por pagar' : 'A favor'} + /> + } + trend={(resumenIva?.acumuladoAnual || 0) < 0 ? 'up' : 'neutral'} + trendValue={(resumenIva?.acumuladoAnual || 0) < 0 ? 'Saldo a favor' : ''} + /> +
+ + {/* IVA Mensual Table */} + + + Histórico IVA {año} + + + {ivaLoading ? ( +
+ Cargando... +
+ ) : ( +
+ + + + + + + + + + + + + + {ivaMensual?.map((row) => ( + + + + + + + + + + ))} + {(!ivaMensual || ivaMensual.length === 0) && ( + + + + )} + +
MesTrasladadoAcreditableRetenidoResultadoAcumuladoEstado
{meses[row.mes - 1]} + {formatCurrency(row.ivaTrasladado)} + + {formatCurrency(row.ivaAcreditable)} + + {formatCurrency(row.ivaRetenido)} + 0 + ? 'text-destructive' + : 'text-success' + }`} + > + {formatCurrency(row.resultado)} + 0 + ? 'text-destructive' + : 'text-success' + }`} + > + {formatCurrency(row.acumulado)} + + + {row.estado === 'declarado' ? 'Declarado' : 'Pendiente'} + +
+ No hay registros de IVA para este año +
+
+ )} +
+
+ + )} + + {activeTab === 'isr' && ( + <> + {/* ISR KPIs */} +
+ } + /> + } + /> + } + /> + } + trend={(resumenIsr?.isrAPagar || 0) > 0 ? 'up' : 'neutral'} + /> +
+ + {/* ISR Info Card */} + + + Cálculo de ISR Acumulado + + +
+
+ Ingresos acumulados + + {formatCurrency(resumenIsr?.ingresosAcumulados || 0)} + +
+
+ (-) Deducciones autorizadas + + {formatCurrency(resumenIsr?.deducciones || 0)} + +
+
+ (=) Base gravable + + {formatCurrency(resumenIsr?.baseGravable || 0)} + +
+
+ ISR causado (estimado) + + {formatCurrency(resumenIsr?.isrCausado || 0)} + +
+
+ (-) ISR retenido + + {formatCurrency(resumenIsr?.isrRetenido || 0)} + +
+
+ ISR a pagar + + {formatCurrency(resumenIsr?.isrAPagar || 0)} + +
+
+
+
+ + )} +
+ + ); +} diff --git a/apps/web/lib/api/impuestos.ts b/apps/web/lib/api/impuestos.ts new file mode 100644 index 0000000..a2c401c --- /dev/null +++ b/apps/web/lib/api/impuestos.ts @@ -0,0 +1,32 @@ +import { apiClient } from './client'; +import type { IvaMensual, IsrMensual, ResumenIva, ResumenIsr } from '@horux/shared'; + +export async function getIvaMensual(año?: number): Promise { + const params = año ? `?año=${año}` : ''; + const response = await apiClient.get(`/impuestos/iva/mensual${params}`); + return response.data; +} + +export async function getResumenIva(año?: number, mes?: number): Promise { + const params = new URLSearchParams(); + if (año) params.set('año', año.toString()); + if (mes) params.set('mes', mes.toString()); + + const response = await apiClient.get(`/impuestos/iva/resumen?${params}`); + return response.data; +} + +export async function getIsrMensual(año?: number): Promise { + const params = año ? `?año=${año}` : ''; + const response = await apiClient.get(`/impuestos/isr/mensual${params}`); + return response.data; +} + +export async function getResumenIsr(año?: number, mes?: number): Promise { + const params = new URLSearchParams(); + if (año) params.set('año', año.toString()); + if (mes) params.set('mes', mes.toString()); + + const response = await apiClient.get(`/impuestos/isr/resumen?${params}`); + return response.data; +} diff --git a/apps/web/lib/hooks/use-impuestos.ts b/apps/web/lib/hooks/use-impuestos.ts new file mode 100644 index 0000000..718fd53 --- /dev/null +++ b/apps/web/lib/hooks/use-impuestos.ts @@ -0,0 +1,30 @@ +import { useQuery } from '@tanstack/react-query'; +import * as impuestosApi from '@/lib/api/impuestos'; + +export function useIvaMensual(año?: number) { + return useQuery({ + queryKey: ['iva-mensual', año], + queryFn: () => impuestosApi.getIvaMensual(año), + }); +} + +export function useResumenIva(año?: number, mes?: number) { + return useQuery({ + queryKey: ['iva-resumen', año, mes], + queryFn: () => impuestosApi.getResumenIva(año, mes), + }); +} + +export function useIsrMensual(año?: number) { + return useQuery({ + queryKey: ['isr-mensual', año], + queryFn: () => impuestosApi.getIsrMensual(año), + }); +} + +export function useResumenIsr(año?: number, mes?: number) { + return useQuery({ + queryKey: ['isr-resumen', año, mes], + queryFn: () => impuestosApi.getResumenIsr(año, mes), + }); +}