import { prisma } from "./prisma"; import { slotEstaDisponible } from "./scheduling"; import { trabajoEsDeJefe } from "./servicios"; export interface CrearTurnoInput { clienteId: string; jefeMecanicoId: string; tipoServicioId: string; fechaInicio: Date; autoId?: string | null; kilometros?: number | null; } export type CrearTurnoResultado = | { ok: true; turno: Awaited> } | { ok: false; error: string; status: number }; /** * Crea un turno validando servicio/jefe activos y disponibilidad del slot. * Lo usan tanto el flujo web (con auto+km) como el bot de WhatsApp (sin auto+km). * La duración del turno la define el TipoServicio. */ export async function crearTurno(input: CrearTurnoInput): Promise { const { clienteId, jefeMecanicoId, tipoServicioId, fechaInicio } = input; const servicio = await prisma.tipoServicio.findUnique({ where: { id: tipoServicioId } }); if (!servicio || !servicio.activo) return { ok: false, error: "Servicio no disponible", status: 404 }; const jefe = await prisma.jefeMecanico.findUnique({ where: { id: jefeMecanicoId } }); if (!jefe || !jefe.activo) return { ok: false, error: "Jefe mecánico no disponible", status: 404 }; // Un trabajo propio de otro jefe no se reserva con éste (los globales sí). if (!trabajoEsDeJefe(servicio, jefeMecanicoId)) return { ok: false, error: "Ese trabajo no lo hace este mecánico", status: 400 }; const fin = new Date(fechaInicio.getTime() + servicio.duracionHoras * 60 * 60 * 1000); // Verificar y crear en una sola transacción, serializando por jefe con un lock // de Postgres: si no, dos clientes pueden pasar la verificación al mismo tiempo // y quedar los dos reservados sobre la misma capacidad. const turno = await prisma.$transaction(async (tx) => { await tx.$executeRaw`SELECT pg_advisory_xact_lock(hashtext(${jefeMecanicoId}))`; const disponible = await slotEstaDisponible(fechaInicio, fin, jefeMecanicoId, tx); if (!disponible) return null; return tx.turno.create({ data: { clienteId, jefeMecanicoId, tipoServicioId, fechaInicio, fechaFin: fin, autoId: input.autoId ?? null, kilometros: input.kilometros ?? null, }, include: { auto: true, tipoServicio: true, jefeMecanico: { select: { id: true, nombre: true } }, }, }); }); if (!turno) return { ok: false, error: "El horario ya no está disponible", status: 409 }; return { ok: true, turno }; }