import { prisma } from "./prisma"; const INICIO_H = 8; const INICIO_M = 30; const FIN_H = 17; const FIN_M = 0; const INICIO_MINUTOS = INICIO_H * 60 + INICIO_M; // 510 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 { const dia = diaArgentina(fecha); return dia >= 1 && dia <= 5; } function horasDentroDeJornada(inicio: Date, fin: Date): boolean { const iMin = minutosArgentina(inicio); const fMin = minutosArgentina(fin); const iArg = fechaArgentina(inicio); 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, jefeMecanicoId: string ): Promise { const count = await prisma.turno.count({ where: { jefeMecanicoId, estado: { in: ["PENDIENTE", "CONFIRMADO"] }, AND: [{ fechaInicio: { lt: fin } }, { fechaFin: { gt: inicio } }], }, }); return count; } async function obtenerJefeActivo(jefeMecanicoId: string) { return prisma.jefeMecanico.findUnique({ where: { id: jefeMecanicoId }, select: { slotsParalelos: true, activo: true }, }); } export interface SlotDisponible { fechaInicio: Date; fechaFin: Date; } export async function obtenerSlotsDisponibles( jefeMecanicoId: string, duracionHoras: number, diasAdelante = 14, limite = 300 ): Promise { const jefe = await obtenerJefeActivo(jefeMecanicoId); if (!jefe || !jefe.activo) return []; const cap = jefe.slotsParalelos; const slots: SlotDisponible[] = []; const ahora = new Date(); const duracionMs = duracionHoras * 60 * 60 * 1000; const fechaLimite = new Date(ahora.getTime() + diasAdelante * 24 * 60 * 60 * 1000); // Avanzar al próximo slot (:00 o :30 Argentina) a partir de ahora let cursor = new Date(ahora); const argMNow = minutosArgentina(cursor) % 60; 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 < limite) { if (!esDiaHabil(cursor)) { cursor = inicioJornada(new Date(cursor.getTime() + 24 * 60 * 60 * 1000)); continue; } const minActual = minutosArgentina(cursor); if (minActual < INICIO_MINUTOS) { cursor = inicioJornada(cursor); continue; } const fin = new Date(cursor.getTime() + duracionMs); if (!horasDentroDeJornada(cursor, fin)) { cursor = inicioJornada(new Date(cursor.getTime() + 24 * 60 * 60 * 1000)); continue; } const ocupados = await turnosSolapados(cursor, fin, jefeMecanicoId); if (ocupados < cap) { slots.push({ fechaInicio: new Date(cursor), fechaFin: new Date(fin) }); } cursor = new Date(cursor.getTime() + 30 * 60 * 1000); } return slots; } export async function slotEstaDisponible( inicio: Date, fin: Date, jefeMecanicoId: string ): Promise { if (!esDiaHabil(inicio) || !horasDentroDeJornada(inicio, fin)) return false; const jefe = await obtenerJefeActivo(jefeMecanicoId); if (!jefe || !jefe.activo) return false; const ocupados = await turnosSolapados(inicio, fin, jefeMecanicoId); return ocupados < jefe.slotsParalelos; }