fix: scheduling en UTC explícito para Argentina (UTC-3), sin depender del TZ del container
Deploy / deploy (push) Successful in 3m57s
Deploy / deploy (push) Successful in 3m57s
getHours() en Docker devuelve UTC → slots aparecían 3hs antes (5:30 en vez de 8:30). Ahora usa getUTCHours/getUTCMinutes + offset -180min en todas las comparaciones. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+49
-28
@@ -8,31 +8,54 @@ const FIN_M = 0;
|
|||||||
const INICIO_MINUTOS = INICIO_H * 60 + INICIO_M; // 510
|
const INICIO_MINUTOS = INICIO_H * 60 + INICIO_M; // 510
|
||||||
const FIN_MINUTOS = FIN_H * 60 + FIN_M; // 1020
|
const FIN_MINUTOS = FIN_H * 60 + FIN_M; // 1020
|
||||||
|
|
||||||
|
// Argentina es siempre UTC-3 (sin horario de verano desde 2008)
|
||||||
|
const ARG_OFFSET_MIN = -180;
|
||||||
|
|
||||||
|
// Minutos del día en hora Argentina (0-1439)
|
||||||
|
function minutosArgentina(fecha: Date): number {
|
||||||
|
const utcMin = fecha.getUTCHours() * 60 + fecha.getUTCMinutes();
|
||||||
|
return ((utcMin + ARG_OFFSET_MIN) + 1440) % 1440;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Día de la semana en Argentina (0=Dom, 6=Sab)
|
||||||
|
function diaArgentina(fecha: Date): number {
|
||||||
|
const arg = new Date(fecha.getTime() + ARG_OFFSET_MIN * 60 * 1000);
|
||||||
|
return arg.getUTCDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Componentes de fecha Argentina (para construir fechas UTC)
|
||||||
|
function fechaArgentina(fecha: Date): { year: number; month: number; date: number } {
|
||||||
|
const arg = new Date(fecha.getTime() + ARG_OFFSET_MIN * 60 * 1000);
|
||||||
|
return { year: arg.getUTCFullYear(), month: arg.getUTCMonth(), date: arg.getUTCDate() };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve la fecha UTC correspondiente a las 8:30 Argentina del día que contiene `fecha`
|
||||||
|
function inicioJornada(fecha: Date): Date {
|
||||||
|
const { year, month, date } = fechaArgentina(fecha);
|
||||||
|
// 8:30 Argentina + 3h = 11:30 UTC
|
||||||
|
return new Date(Date.UTC(year, month, date, INICIO_H - ARG_OFFSET_MIN / 60, INICIO_M, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
function esDiaHabil(fecha: Date): boolean {
|
function esDiaHabil(fecha: Date): boolean {
|
||||||
const dia = fecha.getDay();
|
const dia = diaArgentina(fecha);
|
||||||
return dia >= 1 && dia <= 5;
|
return dia >= 1 && dia <= 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
function minutosDelDia(fecha: Date): number {
|
|
||||||
return fecha.getHours() * 60 + fecha.getMinutes();
|
|
||||||
}
|
|
||||||
|
|
||||||
function horasDentroDeJornada(inicio: Date, fin: Date): boolean {
|
function horasDentroDeJornada(inicio: Date, fin: Date): boolean {
|
||||||
return (
|
const iMin = minutosArgentina(inicio);
|
||||||
minutosDelDia(inicio) >= INICIO_MINUTOS &&
|
const fMin = minutosArgentina(fin);
|
||||||
minutosDelDia(fin) <= FIN_MINUTOS &&
|
const iArg = fechaArgentina(inicio);
|
||||||
inicio.toDateString() === fin.toDateString()
|
const fArg = fechaArgentina(fin);
|
||||||
);
|
const mismodia =
|
||||||
|
iArg.year === fArg.year && iArg.month === fArg.month && iArg.date === fArg.date;
|
||||||
|
return mismodia && iMin >= INICIO_MINUTOS && fMin <= FIN_MINUTOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function turnosSolapados(inicio: Date, fin: Date): Promise<number> {
|
async function turnosSolapados(inicio: Date, fin: Date): Promise<number> {
|
||||||
const count = await prisma.turno.count({
|
const count = await prisma.turno.count({
|
||||||
where: {
|
where: {
|
||||||
estado: { in: ["PENDIENTE", "CONFIRMADO"] },
|
estado: { in: ["PENDIENTE", "CONFIRMADO"] },
|
||||||
AND: [
|
AND: [{ fechaInicio: { lt: fin } }, { fechaFin: { gt: inicio } }],
|
||||||
{ fechaInicio: { lt: fin } },
|
|
||||||
{ fechaFin: { gt: inicio } },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return count;
|
return count;
|
||||||
@@ -51,33 +74,31 @@ export async function obtenerSlotsDisponibles(
|
|||||||
const ahora = new Date();
|
const ahora = new Date();
|
||||||
const duracionMs = duracionHoras * 60 * 60 * 1000;
|
const duracionMs = duracionHoras * 60 * 60 * 1000;
|
||||||
|
|
||||||
const fechaLimite = new Date(ahora);
|
const fechaLimite = new Date(ahora.getTime() + diasAdelante * 24 * 60 * 60 * 1000);
|
||||||
fechaLimite.setDate(fechaLimite.getDate() + diasAdelante);
|
|
||||||
|
|
||||||
|
// Avanzar al próximo slot (:00 o :30 Argentina) a partir de ahora
|
||||||
let cursor = new Date(ahora);
|
let cursor = new Date(ahora);
|
||||||
// Avanzar al siguiente slot de :00 o :30
|
const argMNow = minutosArgentina(cursor) % 60;
|
||||||
cursor.setMinutes(cursor.getMinutes() >= 30 ? 60 : 30, 0, 0);
|
const avanzarMin = argMNow < 30 ? 30 - argMNow : 60 - argMNow;
|
||||||
|
cursor = new Date(cursor.getTime() + avanzarMin * 60 * 1000);
|
||||||
|
cursor.setUTCSeconds(0, 0);
|
||||||
|
|
||||||
while (cursor < fechaLimite && slots.length < 20) {
|
while (cursor < fechaLimite && slots.length < 20) {
|
||||||
if (!esDiaHabil(cursor)) {
|
if (!esDiaHabil(cursor)) {
|
||||||
cursor.setDate(cursor.getDate() + 1);
|
cursor = inicioJornada(new Date(cursor.getTime() + 24 * 60 * 60 * 1000));
|
||||||
cursor.setHours(INICIO_H, INICIO_M, 0, 0);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minutosDelDia(cursor) < INICIO_MINUTOS) {
|
const minActual = minutosArgentina(cursor);
|
||||||
cursor.setHours(INICIO_H, INICIO_M, 0, 0);
|
if (minActual < INICIO_MINUTOS) {
|
||||||
|
cursor = inicioJornada(cursor);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fin = new Date(cursor.getTime() + duracionMs);
|
const fin = new Date(cursor.getTime() + duracionMs);
|
||||||
|
|
||||||
if (
|
if (!horasDentroDeJornada(cursor, fin)) {
|
||||||
fin.toDateString() !== cursor.toDateString() ||
|
cursor = inicioJornada(new Date(cursor.getTime() + 24 * 60 * 60 * 1000));
|
||||||
minutosDelDia(fin) > FIN_MINUTOS
|
|
||||||
) {
|
|
||||||
cursor.setDate(cursor.getDate() + 1);
|
|
||||||
cursor.setHours(INICIO_H, INICIO_M, 0, 0);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user