UDT-011: Localización Temporal Argentina (infra transversal) #25

Merged
dmolinari merged 24 commits from feature/UDT-011 into main 2026-04-18 13:57:49 +00:00
Showing only changes of commit 2ea7678129 - Show all commits

View File

@@ -0,0 +1,99 @@
/**
* Localización temporal Argentina — utility centralizada.
* Ver: Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.17 ⏰ Localización Temporal Argentina.md
* Engram topic_key: sig-cm2/conventions/fechas-timezones
*
* REGLAS PROHIBIDAS (no usar fuera de este módulo):
* - new Date(civilDateString) → aplica UTC, pierde días
* - toISOString().slice(0, 10) → UTC creep
* - toLocaleString('es-AR', {...}) sin timeZone → depende del navegador
*/
export const AR_TZ = 'America/Argentina/Buenos_Aires';
type FormatStyle = 'short' | 'medium' | 'long' | 'full';
interface FormatInstantOptions {
dateStyle?: FormatStyle;
timeStyle?: FormatStyle;
}
/**
* Formatea un instante UTC (Cat1) como string legible en zona horaria Argentina.
* Usa partes explícitas para garantizar año 4 dígitos y hora 24h independientemente del entorno.
* Output: "dd/MM/yyyy, HH:mm:ss"
*/
export function formatInstant(
iso: string,
_opts: FormatInstantOptions = { dateStyle: 'short', timeStyle: 'medium' }
): string {
const parts = new Intl.DateTimeFormat('es-AR', {
timeZone: AR_TZ,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
}).formatToParts(new Date(iso));
const get = (type: string): string => parts.find(p => p.type === type)!.value;
return `${get('day')}/${get('month')}/${get('year')}, ${get('hour')}:${get('minute')}:${get('second')}`;
}
/**
* Formatea una fecha civil Argentina (Cat2, formato "yyyy-MM-dd") a "dd/MM/yyyy".
* Split manual — NO usa new Date() para evitar UTC creep.
*/
export function formatCivilDate(yyyyMmDd: string): string {
const [y, m, d] = yyyyMmDd.split('-');
return `${d}/${m}/${y}`;
}
/**
* Formatea un rango de fechas civiles Argentinas.
*/
export function formatCivilDateRange(from: string, to: string | null): string {
return to ? `${formatCivilDate(from)}${formatCivilDate(to)}` : `desde ${formatCivilDate(from)}`;
}
/**
* Retorna la fecha civil Argentina de hoy en formato "yyyy-MM-dd".
* Fix de BUG-FE-03: usa Intl.DateTimeFormat con timeZone, NO toISOString().slice(0, 10).
*/
export function todayArgentina(): string {
const now = new Date();
const parts = new Intl.DateTimeFormat('en-CA', {
timeZone: AR_TZ,
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).formatToParts(now);
const y = parts.find(p => p.type === 'year')!.value;
const m = parts.find(p => p.type === 'month')!.value;
const d = parts.find(p => p.type === 'day')!.value;
return `${y}-${m}-${d}`;
}
/**
* Parsea una fecha civil Argentina ("yyyy-MM-dd") a partes numéricas.
* Split manual — NO usa new Date().
*/
export function parseCivilDate(yyyyMmDd: string): { year: number; month: number; day: number } {
const [y, m, d] = yyyyMmDd.split('-').map(Number);
return { year: y, month: m, day: d };
}
/**
* 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".
*
* Útil para AuditFilters que envía filtros de rango al backend.
*/
export function parseArgentinaDateTimeToUtc(localDateTime: string): string {
// Parse "yyyy-MM-ddTHH:mm" como ART (offset -03:00) y convertir a ISO UTC
return new Date(`${localDateTime}:00-03:00`).toISOString();
}