feat: bulk XML upload, period selector, and session persistence

- Add bulk XML CFDI upload support (up to 300MB)
- Add period selector component for month/year navigation
- Fix session persistence on page refresh (Zustand hydration)
- Fix income/expense classification based on tenant RFC
- Fix IVA calculation from XML (correct Impuestos element)
- Add error handling to reportes page
- Support multiple CORS origins
- Update reportes service with proper Decimal/BigInt handling
- Add RFC to tenant view store for proper CFDI classification
- Update README with changelog and new features

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-01-22 06:51:53 +00:00
parent 0c10c887d2
commit c3ce7199af
37 changed files with 1680 additions and 216 deletions

View File

@@ -0,0 +1,104 @@
'use client';
import { Button } from '@/components/ui/button';
import { ChevronLeft, ChevronRight } from 'lucide-react';
const meses = [
'Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun',
'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'
];
interface PeriodSelectorProps {
año: number;
mes?: number;
onAñoChange: (año: number) => void;
onMesChange?: (mes: number) => void;
showMonth?: boolean;
minYear?: number;
maxYear?: number;
}
export function PeriodSelector({
año,
mes,
onAñoChange,
onMesChange,
showMonth = true,
minYear = 2020,
maxYear = new Date().getFullYear(),
}: PeriodSelectorProps) {
const handlePrev = () => {
if (showMonth && mes && onMesChange) {
if (mes === 1) {
if (año > minYear) {
onMesChange(12);
onAñoChange(año - 1);
}
} else {
onMesChange(mes - 1);
}
} else {
if (año > minYear) {
onAñoChange(año - 1);
}
}
};
const handleNext = () => {
if (showMonth && mes && onMesChange) {
if (mes === 12) {
if (año < maxYear) {
onMesChange(1);
onAñoChange(año + 1);
}
} else {
onMesChange(mes + 1);
}
} else {
if (año < maxYear) {
onAñoChange(año + 1);
}
}
};
const canGoPrev = showMonth && mes
? !(año === minYear && mes === 1)
: año > minYear;
const canGoNext = showMonth && mes
? !(año === maxYear && mes === 12)
: año < maxYear;
const displayText = showMonth && mes
? `${meses[mes - 1]} ${año}`
: `${año}`;
return (
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={handlePrev}
disabled={!canGoPrev}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="min-w-[90px] text-center text-sm font-medium">
{displayText}
</span>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={handleNext}
disabled={!canGoNext}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
);
}