refactor(web/udt-011): eliminar 4 funciones formatDate duplicadas y formatOccurredAt, usar dateFormat utility (fix BUG-FE-01, BUG-FE-02)

This commit is contained in:
2026-04-18 10:26:29 -03:00
parent 71d0928389
commit 03a02c63d5
7 changed files with 38 additions and 58 deletions

View File

@@ -5,15 +5,7 @@ import { CanPerform } from '@/components/auth/CanPerform'
import { useMedio } from '../hooks/useMedio'
import { DeactivateMedioModal } from '../components/DeactivateMedioModal'
import { tipoMedioLabel } from '../tipoMedio'
function formatDate(iso: string | null): string {
if (!iso) return '—'
return new Date(iso).toLocaleDateString('es-AR', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
import { formatInstantOrDash } from '@/lib/dateFormat'
export function MedioDetailPage() {
const { id } = useParams<{ id: string }>()
@@ -69,11 +61,11 @@ export function MedioDetailPage() {
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Creado</span>
<span>{formatDate(medio.fechaCreacion)}</span>
<span>{formatInstantOrDash(medio.fechaCreacion)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Modificado</span>
<span>{formatDate(medio.fechaModificacion)}</span>
<span>{formatInstantOrDash(medio.fechaModificacion)}</span>
</div>
</div>

View File

@@ -7,15 +7,7 @@ import { useMedio } from '../../medios/hooks/useMedio'
import { DeactivatePuntoDeVentaModal } from '../components/DeactivatePuntoDeVentaModal'
import { MedioInactivoBanner } from '../components/MedioInactivoBanner'
import { PdvInactivoBanner } from '../components/PdvInactivoBanner'
function formatDate(iso: string | null): string {
if (!iso) return '—'
return new Date(iso).toLocaleDateString('es-AR', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
import { formatInstantOrDash } from '@/lib/dateFormat'
export function PuntoDeVentaDetailPage() {
const { id } = useParams<{ id: string }>()
@@ -70,11 +62,11 @@ export function PuntoDeVentaDetailPage() {
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Creado</span>
<span>{formatDate(pdv.fechaCreacion)}</span>
<span>{formatInstantOrDash(pdv.fechaCreacion)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Modificado</span>
<span>{formatDate(pdv.fechaModificacion)}</span>
<span>{formatInstantOrDash(pdv.fechaModificacion)}</span>
</div>
</div>

View File

@@ -7,15 +7,7 @@ import { DeactivateSeccionModal } from '../components/DeactivateSeccionModal'
import { MedioInactivoBanner } from '../components/MedioInactivoBanner'
import { tipoSeccionLabel } from '../tipoSeccion'
import { useMedio } from '../../medios/hooks/useMedio'
function formatDate(iso: string | null): string {
if (!iso) return '—'
return new Date(iso).toLocaleDateString('es-AR', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
import { formatInstantOrDash } from '@/lib/dateFormat'
export function SeccionDetailPage() {
const { id } = useParams<{ id: string }>()
@@ -75,11 +67,11 @@ export function SeccionDetailPage() {
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Creado</span>
<span>{formatDate(seccion.fechaCreacion)}</span>
<span>{formatInstantOrDash(seccion.fechaCreacion)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Modificado</span>
<span>{formatDate(seccion.fechaModificacion)}</span>
<span>{formatInstantOrDash(seccion.fechaModificacion)}</span>
</div>
</div>

View File

@@ -3,21 +3,13 @@ import type { ColumnDef } from '@tanstack/react-table'
import { Badge } from '@/components/ui/badge'
import { DataTable } from '@/components/ui/data-table'
import type { UserListItem } from '../types'
import { formatInstantOrDash } from '@/lib/dateFormat'
interface UsersTableProps {
rows: UserListItem[]
onRowClick: (user: UserListItem) => void
}
function formatDate(iso: string | null): string {
if (!iso) return '—'
return new Date(iso).toLocaleDateString('es-AR', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
export function UsersTable({ rows, onRowClick }: UsersTableProps) {
const columns = useMemo<ColumnDef<UserListItem>[]>(
() => [
@@ -81,7 +73,7 @@ export function UsersTable({ rows, onRowClick }: UsersTableProps) {
header: 'Último login',
cell: ({ row }) => (
<span className="text-muted-foreground">
{formatDate(row.original.ultimoLogin)}
{formatInstantOrDash(row.original.ultimoLogin)}
</span>
),
meta: { priority: 'low' },

View File

@@ -87,6 +87,15 @@ export function parseCivilDate(yyyyMmDd: string): { year: number; month: number;
return { year: y, month: m, day: d };
}
/**
* Formatea un instante UTC (Cat1) nullable a string legible en zona horaria Argentina.
* Retorna '—' cuando el valor es null o undefined.
*/
export function formatInstantOrDash(iso: string | null | undefined): string {
if (!iso) return '—';
return formatInstant(iso);
}
/**
* 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.

View File

@@ -18,20 +18,7 @@ import {
toApiFilter,
type AuditFiltersValue,
} from './AuditFilters'
/** Formatea un ISO datetime a hora local AR (dd/mm/yyyy HH:mm:ss). */
function formatOccurredAt(iso: string): string {
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return iso
return d.toLocaleString('es-AR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
import { formatInstant } from '@/lib/dateFormat'
/** Copia texto al clipboard con fallback + toast. */
async function copyToClipboard(text: string, label: string): Promise<void> {
@@ -123,7 +110,7 @@ export function AuditPage() {
header: 'Fecha',
cell: ({ row }) => (
<span className="font-mono text-xs text-foreground">
{formatOccurredAt(row.original.occurredAt)}
{formatInstant(row.original.occurredAt)}
</span>
),
meta: { priority: 'high' },

View File

@@ -2,6 +2,7 @@ import { describe, it, expect, afterEach, vi } from 'vitest';
import {
AR_TZ,
formatInstant,
formatInstantOrDash,
formatCivilDate,
formatCivilDateRange,
todayArgentina,
@@ -33,6 +34,21 @@ describe('dateFormat.ts', () => {
});
});
describe('formatInstantOrDash (Cat1 nullable)', () => {
it('returns "—" for null', () => {
expect(formatInstantOrDash(null)).toBe('—');
});
it('returns "—" for undefined', () => {
expect(formatInstantOrDash(undefined)).toBe('—');
});
it('delegates to formatInstant for valid ISO', () => {
const iso = '2026-05-01T01:30:00.000Z';
expect(formatInstantOrDash(iso)).toBe(formatInstant(iso));
});
});
describe('formatCivilDate (Cat2 — yyyy-MM-dd → dd/MM/yyyy)', () => {
it('splits manually without new Date()', () => {
expect(formatCivilDate('2026-05-01')).toBe('01/05/2026');