fix(web/udt-011): NuevaVigenciaModal preview usa prevCivilDate+formatCivilDate sin Date() (fix BUG-FE-04)
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
import { useNuevaVersionIngresosBrutos } from '../hooks/useIngresosBrutos'
|
||||
import type { IngresosBrutos } from '../types/ingresosBrutos.types'
|
||||
import { toast } from 'sonner'
|
||||
import { prevCivilDate, formatCivilDate } from '@/lib/dateFormat'
|
||||
|
||||
const formSchema = z.object({
|
||||
alicuota: z.coerce
|
||||
@@ -48,11 +49,12 @@ interface NuevaVigenciaIibbModalProps {
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
function fechaCierre(vigenciaDesde: string): string {
|
||||
/** Formatea la fecha de cierre (vigenciaDesde - 1 día) para display "dd/MM/yyyy".
|
||||
* Usa prevCivilDate (Date.UTC pura, sin TZ) + formatCivilDate (split manual).
|
||||
*/
|
||||
function fechaCierreDisplay(vigenciaDesde: string): string {
|
||||
if (!vigenciaDesde || !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaDesde)) return '—'
|
||||
const d = new Date(vigenciaDesde + 'T00:00:00')
|
||||
d.setDate(d.getDate() - 1)
|
||||
return d.toISOString().slice(0, 10)
|
||||
return formatCivilDate(prevCivilDate(vigenciaDesde))
|
||||
}
|
||||
|
||||
function resolveBackendError(err: unknown): string | null {
|
||||
@@ -212,7 +214,7 @@ export function NuevaVigenciaIibbModal({
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
Versión actual ({item.alicuota}%) quedará cerrada el{' '}
|
||||
<strong>{fechaCierre(watchedVigencia)}</strong>.
|
||||
<strong>{fechaCierreDisplay(watchedVigencia)}</strong>.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
Esta acción no se puede deshacer.
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { useNuevaVersionTipoDeIva } from '../hooks/useTiposDeIva'
|
||||
import type { TipoDeIva } from '../types/tipoDeIva.types'
|
||||
import { toast } from 'sonner'
|
||||
import { prevCivilDate, formatCivilDate } from '@/lib/dateFormat'
|
||||
|
||||
const formSchema = z.object({
|
||||
porcentaje: z.coerce
|
||||
@@ -49,12 +50,12 @@ interface NuevaVigenciaModalProps {
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
/** Devuelve la fecha anterior (vigenciaDesde - 1 día) como string "yyyy-MM-dd" */
|
||||
function fechaCierre(vigenciaDesde: string): string {
|
||||
/** Formatea la fecha de cierre (vigenciaDesde - 1 día) para display "dd/MM/yyyy".
|
||||
* Usa prevCivilDate (Date.UTC pura, sin TZ) + formatCivilDate (split manual).
|
||||
*/
|
||||
function fechaCierreDisplay(vigenciaDesde: string): string {
|
||||
if (!vigenciaDesde || !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaDesde)) return '—'
|
||||
const d = new Date(vigenciaDesde + 'T00:00:00')
|
||||
d.setDate(d.getDate() - 1)
|
||||
return d.toISOString().slice(0, 10)
|
||||
return formatCivilDate(prevCivilDate(vigenciaDesde))
|
||||
}
|
||||
|
||||
function resolveBackendError(err: unknown): string | null {
|
||||
@@ -218,7 +219,7 @@ export function NuevaVigenciaModal({
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
Versión actual ({item.porcentaje}%) quedará cerrada el{' '}
|
||||
<strong>{fechaCierre(watchedVigencia)}</strong>.
|
||||
<strong>{fechaCierreDisplay(watchedVigencia)}</strong>.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
Esta acción no se puede deshacer.
|
||||
|
||||
@@ -87,6 +87,20 @@ export function parseCivilDate(yyyyMmDd: string): { year: number; month: number;
|
||||
return { year: y, month: m, day: d };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna el día anterior a una fecha civil Argentina en formato "yyyy-MM-dd".
|
||||
* Usa Date.UTC para aritmética pura — sin conversión de timezone en ningún momento.
|
||||
* Output: "yyyy-MM-dd"
|
||||
*/
|
||||
export function prevCivilDate(yyyyMmDd: string): string {
|
||||
const [y, m, d] = yyyyMmDd.split('-').map(Number);
|
||||
const prevDay = new Date(Date.UTC(y, m - 1, d - 1));
|
||||
const yy = prevDay.getUTCFullYear();
|
||||
const mm = String(prevDay.getUTCMonth() + 1).padStart(2, '0');
|
||||
const dd = String(prevDay.getUTCDate()).padStart(2, '0');
|
||||
return `${yy}-${mm}-${dd}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte un valor de <input type="datetime-local"> (ART implícito) a ISO UTC.
|
||||
* Input: "2026-05-01T22:30" (sin TZ) → interpretado como ART → "2026-05-02T01:30:00.000Z".
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('NuevaVigenciaModal — preview con fechas correctas [REQ-UI-004]', ()
|
||||
)
|
||||
})
|
||||
|
||||
it('preview muestra fecha de cierre = vigenciaDesde - 1 día', async () => {
|
||||
it('preview muestra fecha de cierre = vigenciaDesde - 1 día (formato dd/MM/yyyy, BUG-FE-04)', async () => {
|
||||
renderModal()
|
||||
|
||||
const porcentajeInput = screen.getByLabelText(/porcentaje nuevo/i)
|
||||
@@ -132,9 +132,9 @@ describe('NuevaVigenciaModal — preview con fechas correctas [REQ-UI-004]', ()
|
||||
const vigenciaInput = screen.getByLabelText(/vigencia desde/i)
|
||||
await userEvent.type(vigenciaInput, '2026-05-01')
|
||||
|
||||
// La versión anterior cierra el día anterior → 2026-04-30
|
||||
// La versión anterior cierra el día anterior → 30/04/2026 (formato AR civil)
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(/2026-04-30/)).toBeInTheDocument(),
|
||||
expect(screen.getByText(/30\/04\/2026/)).toBeInTheDocument(),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
formatCivilDateRange,
|
||||
todayArgentina,
|
||||
parseCivilDate,
|
||||
prevCivilDate,
|
||||
} from '@/lib/dateFormat';
|
||||
|
||||
describe('dateFormat.ts', () => {
|
||||
@@ -108,4 +109,22 @@ describe('dateFormat.ts', () => {
|
||||
expect(result.day).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prevCivilDate (BUG-FE-04 — fecha cierre NuevaVigencia)', () => {
|
||||
it('returns day before (2026-05-01 → 2026-04-30)', () => {
|
||||
expect(prevCivilDate('2026-05-01')).toBe('2026-04-30');
|
||||
});
|
||||
|
||||
it('crosses month boundary (2026-05-31 → 2026-05-30)', () => {
|
||||
expect(prevCivilDate('2026-06-01')).toBe('2026-05-31');
|
||||
});
|
||||
|
||||
it('crosses year boundary (2026-01-01 → 2025-12-31)', () => {
|
||||
expect(prevCivilDate('2026-01-01')).toBe('2025-12-31');
|
||||
});
|
||||
|
||||
it('handles leap year (2024-03-01 → 2024-02-29)', () => {
|
||||
expect(prevCivilDate('2024-03-01')).toBe('2024-02-29');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user