Oleada UX 3: POS (repetir ultima venta, aviso de paquetes, atajos, caja desde POS), Ventas (cancelacion auditada con reversion de monedero y stock, detalle inline, presets de fecha), Cortes (historial comparativo y export)

This commit is contained in:
2026-09-17 19:26:02 +00:00
parent b4f91527f1
commit ac5896b1fc
10 changed files with 1328 additions and 57 deletions

View File

@@ -0,0 +1,87 @@
/**
* Presets de rango de fecha compartidos (Ventas, y reutilizables por otras
* vistas). El rango elegido se persiste en localStorage para que al volver a
* la pantalla se restaure, igual que los presets de la agenda.
*/
export type DatePreset = 'hoy' | 'ayer' | 'semana' | 'mes' | 'mes_pasado' | 'custom';
export interface DateRange {
start: string;
end: string;
}
export const DATE_PRESETS: { value: DatePreset; label: string }[] = [
{ value: 'hoy', label: 'Hoy' },
{ value: 'ayer', label: 'Ayer' },
{ value: 'semana', label: 'Esta semana' },
{ value: 'mes', label: 'Este mes' },
{ value: 'mes_pasado', label: 'Mes pasado' },
{ value: 'custom', label: 'Rango personalizado' },
];
export const isoDate = (d: Date): string => {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
};
export const rangeForPreset = (preset: DatePreset, today = new Date()): DateRange => {
const t = new Date(today.getFullYear(), today.getMonth(), today.getDate());
switch (preset) {
case 'hoy':
return { start: isoDate(t), end: isoDate(t) };
case 'ayer': {
const y = new Date(t);
y.setDate(y.getDate() - 1);
return { start: isoDate(y), end: isoDate(y) };
}
case 'semana': {
// Semana en curso, de lunes a domingo
const dow = (t.getDay() + 6) % 7; // 0 = lunes
const monday = new Date(t);
monday.setDate(t.getDate() - dow);
const sunday = new Date(monday);
sunday.setDate(monday.getDate() + 6);
return { start: isoDate(monday), end: isoDate(sunday) };
}
case 'mes': {
const first = new Date(t.getFullYear(), t.getMonth(), 1);
const last = new Date(t.getFullYear(), t.getMonth() + 1, 0);
return { start: isoDate(first), end: isoDate(last) };
}
case 'mes_pasado': {
const first = new Date(t.getFullYear(), t.getMonth() - 1, 1);
const last = new Date(t.getFullYear(), t.getMonth(), 0);
return { start: isoDate(first), end: isoDate(last) };
}
default:
return { start: isoDate(t), end: isoDate(t) };
}
};
export interface DateFilterPrefs extends DateRange {
preset: DatePreset;
}
export const readDateFilterPrefs = (key: string): DateFilterPrefs | null => {
try {
const raw = localStorage.getItem(key);
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<DateFilterPrefs>;
if (!parsed || typeof parsed.start !== 'string' || typeof parsed.end !== 'string') return null;
const preset = (DATE_PRESETS.some((p) => p.value === parsed.preset) ? parsed.preset : 'custom') as DatePreset;
return { preset, start: parsed.start, end: parsed.end };
} catch {
return null;
}
};
export const writeDateFilterPrefs = (key: string, prefs: DateFilterPrefs): void => {
try {
localStorage.setItem(key, JSON.stringify(prefs));
} catch {
// localStorage no disponible: se mantiene solo en memoria
}
};