Add jefe mecánico to client booking flow
Deploy / deploy (push) Successful in 6m52s

Cliente now picks a jefe mecánico after choosing the service so the
calendar shows that specific jefe's availability. Each jefe has its own
configurable parallel slot capacity (replacing the global SLOTS_PARALELOS
= 5). Admins manage jefes from /admin/jefes; jefes log in to /jefe to
see their daily agenda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Beldor
2026-06-29 22:28:44 -03:00
parent f79e5ce75c
commit 0792ac3767
18 changed files with 719 additions and 22 deletions
+29 -7
View File
@@ -1,6 +1,5 @@
import { prisma } from "./prisma";
const SLOTS_PARALELOS = 5;
const INICIO_H = 8;
const INICIO_M = 30;
const FIN_H = 17;
@@ -51,9 +50,14 @@ function horasDentroDeJornada(inicio: Date, fin: Date): boolean {
return mismodia && iMin >= INICIO_MINUTOS && fMin <= FIN_MINUTOS;
}
async function turnosSolapados(inicio: Date, fin: Date): Promise<number> {
async function turnosSolapados(
inicio: Date,
fin: Date,
jefeMecanicoId: string
): Promise<number> {
const count = await prisma.turno.count({
where: {
jefeMecanicoId,
estado: { in: ["PENDIENTE", "CONFIRMADO"] },
AND: [{ fechaInicio: { lt: fin } }, { fechaFin: { gt: inicio } }],
},
@@ -61,16 +65,28 @@ async function turnosSolapados(inicio: Date, fin: Date): Promise<number> {
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<SlotDisponible[]> {
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;
@@ -103,8 +119,8 @@ export async function obtenerSlotsDisponibles(
continue;
}
const ocupados = await turnosSolapados(cursor, fin);
if (ocupados < SLOTS_PARALELOS) {
const ocupados = await turnosSolapados(cursor, fin, jefeMecanicoId);
if (ocupados < cap) {
slots.push({ fechaInicio: new Date(cursor), fechaFin: new Date(fin) });
}
@@ -114,8 +130,14 @@ export async function obtenerSlotsDisponibles(
return slots;
}
export async function slotEstaDisponible(inicio: Date, fin: Date): Promise<boolean> {
export async function slotEstaDisponible(
inicio: Date,
fin: Date,
jefeMecanicoId: string
): Promise<boolean> {
if (!esDiaHabil(inicio) || !horasDentroDeJornada(inicio, fin)) return false;
const ocupados = await turnosSolapados(inicio, fin);
return ocupados < SLOTS_PARALELOS;
const jefe = await obtenerJefeActivo(jefeMecanicoId);
if (!jefe || !jefe.activo) return false;
const ocupados = await turnosSolapados(inicio, fin, jefeMecanicoId);
return ocupados < jefe.slotsParalelos;
}