'use client'; import { useState, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Card, CardContent, CardHeader, CardTitle, Button, Input, Label, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, KpiCard, cn, } from '@horux/shared-ui'; import { apiClient } from '@/lib/api/client'; import { useContribuyenteStore } from '@/stores/contribuyente-store'; import { Wallet, Calendar, AlertTriangle, CheckCircle2, Trash2, RotateCcw, Building2, TrendingUp, Clock, CircleSlash, Filter, } from 'lucide-react'; import { formatCurrency, toCfdiDate } from '@/lib/utils'; interface ActivoFijoItem { cfdiId: number; uuid: string; fechaEmision: string; rfcEmisor: string; nombreEmisor: string; usoCfdi: string; concepto: string; porcentajeAnual: number; porcentajeMensual: number; total: number; iva: number; moi: number; acumuladoHastaMesAnterior: number; acreditableEsteMes: number; saldoPendiente: number; estado: 'activo' | 'agotado' | 'baja_venta' | 'baja_desecho' | 'baja_otro'; baja: { fechaBaja: string; motivo: string; comentario: string | null } | null; } interface Totales { cantidad: number; totalMoi: number; totalAcumuladoPrevio: number; totalEsteMes: number; totalSaldoPendiente: number; cantidadActivos: number; cantidadAgotados: number; cantidadDeBaja: number; } interface Response { items: ActivoFijoItem[]; totales: Totales; usosExcluidos: string[]; } const USOS_DISPONIBLES: { clave: string; concepto: string }[] = [ { clave: 'I01', concepto: 'Construcciones' }, { clave: 'I02', concepto: 'Mobiliario y equipo de oficina' }, { clave: 'I03', concepto: 'Equipo de transporte' }, { clave: 'I04', concepto: 'Equipo de cómputo y accesorios' }, { clave: 'I05', concepto: 'Dados, troqueles, moldes, matrices' }, { clave: 'I06', concepto: 'Comunicaciones telefónicas' }, { clave: 'I07', concepto: 'Comunicaciones satelitales' }, { clave: 'I08', concepto: 'Otra maquinaria y equipo' }, ]; const ESTADO_LABEL: Record = { activo: { label: 'Activo', color: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' }, agotado: { label: 'Agotado', color: 'bg-muted text-muted-foreground' }, baja_venta: { label: 'Vendido', color: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400' }, baja_desecho: { label: 'Desechado', color: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400' }, baja_otro: { label: 'Baja', color: 'bg-zinc-100 text-zinc-800 dark:bg-zinc-900/30 dark:text-zinc-400' }, }; export function ActivosFijosTab({ año, mes }: { año: number; mes: number }) { const queryClient = useQueryClient(); const { selectedContribuyenteId } = useContribuyenteStore(); const [filtroEstado, setFiltroEstado] = useState<'todos' | 'activos' | 'baja' | 'agotados'>('todos'); const [bajaModal, setBajaModal] = useState(null); const [bajaForm, setBajaForm] = useState({ fechaBaja: new Date().toISOString().slice(0, 10), motivo: 'venta' as 'venta' | 'desecho' | 'otro', comentario: '', }); const [conceptosModal, setConceptosModal] = useState(false); const [conceptosDraft, setConceptosDraft] = useState>(new Set()); const { data, isLoading } = useQuery({ queryKey: ['activos-fijos', año, mes, selectedContribuyenteId, filtroEstado], queryFn: async () => { const p = new URLSearchParams({ año: String(año), mes: String(mes), estado: filtroEstado }); if (selectedContribuyenteId) p.set('contribuyenteId', selectedContribuyenteId); const res = await apiClient.get(`/impuestos/activos-fijos?${p}`); return res.data; }, }); const invalidate = () => queryClient.invalidateQueries({ queryKey: ['activos-fijos'] }); const bajaMutation = useMutation({ mutationFn: async () => { if (!bajaModal) return; await apiClient.post(`/impuestos/activos-fijos/${bajaModal.cfdiId}/baja`, { fechaBaja: bajaForm.fechaBaja, motivo: bajaForm.motivo, comentario: bajaForm.comentario || null, }); }, onSuccess: () => { setBajaModal(null); invalidate(); }, }); const revertirMutation = useMutation({ mutationFn: async (cfdiId: number) => apiClient.delete(`/impuestos/activos-fijos/${cfdiId}/baja`), onSuccess: invalidate, }); const conceptosMutation = useMutation({ mutationFn: async (excluidos: string[]) => { if (!selectedContribuyenteId) return; await apiClient.put('/impuestos/activos-fijos/usos-excluidos', { contribuyenteId: selectedContribuyenteId, usos: excluidos, }); }, onSuccess: () => { setConceptosModal(false); invalidate(); }, }); const openConceptos = () => { setConceptosDraft(new Set(data?.usosExcluidos ?? [])); setConceptosModal(true); }; const toggleConcepto = (clave: string) => { setConceptosDraft(prev => { const next = new Set(prev); if (next.has(clave)) next.delete(clave); else next.add(clave); return next; }); }; const items = data?.items ?? []; const t = data?.totales; const openBaja = (a: ActivoFijoItem) => { setBajaForm({ fechaBaja: a.baja?.fechaBaja ?? new Date().toISOString().slice(0, 10), motivo: (a.baja?.motivo as 'venta' | 'desecho' | 'otro') ?? 'venta', comentario: a.baja?.comentario ?? '', }); setBajaModal(a); }; return (
{/* Disclaimer */}
Vista informativa. El sistema considera estos CFDIs como gasto del periodo (igual que el SAT), por lo que ya están en tu Dashboard y en tu cálculo de ISR. Esta vista te sirve para llevar el seguimiento de la deducción mensual proporcional (% anual ÷ 12) y decidir manualmente si la aplicas en tu declaración anual.
{/* KPIs */}
} subtitle={`${t?.cantidad ?? 0} CFDIs`} /> } subtitle="Ya deducible" /> } subtitle="A aplicar este mes" /> } subtitle="Por deducir en futuro" />
{/* Filtros */}
{t ? `${t.cantidadActivos} activos · ${t.cantidadAgotados} agotados · ${t.cantidadDeBaja} bajas` : ''}
{/* Tabla */} {isLoading ? (

Cargando...

) : items.length === 0 ? (

No hay activos fijos en el periodo seleccionado.

) : (
{items.map(a => { const estadoMeta = ESTADO_LABEL[a.estado] ?? ESTADO_LABEL.activo; const esBaja = a.estado.startsWith('baja_'); return ( ); })}
Fecha Emisor Concepto MOI % anual Acum. previo Este mes Saldo Estado
{toCfdiDate(a.fechaEmision).toLocaleDateString('es-MX', { day: 'numeric', month: 'short', year: 'numeric' })}
{a.rfcEmisor}
{a.nombreEmisor}
{a.usoCfdi}
{a.concepto}
{formatCurrency(a.moi)} {(a.porcentajeAnual * 100).toFixed(0)}% {formatCurrency(a.acumuladoHastaMesAnterior)} {formatCurrency(a.acreditableEsteMes)} {formatCurrency(a.saldoPendiente)} {estadoMeta.label} {esBaja ? ( ) : a.estado === 'activo' ? ( ) : null}
)}
{/* Modal conceptos: excluir usos CFDI que el contador no quiere ver */} { if (!o) setConceptosModal(false); }}> Conceptos a considerar como activos fijos

Desmarca los conceptos cuyos CFDIs en este contribuyente NO sean adquisiciones de activos fijos (ej. servicio telefónico mensual con uso I06). Por default todos están considerados.

{USOS_DISPONIBLES.map(u => { const excluido = conceptosDraft.has(u.clave); return ( ); })}
{/* Modal baja */} { if (!o) setBajaModal(null); }}> Dar de baja activo fijo

{bajaModal?.concepto} — {bajaModal?.nombreEmisor}

setBajaForm(f => ({ ...f, fechaBaja: e.target.value }))} />
setBajaForm(f => ({ ...f, comentario: e.target.value }))} />
); }