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
+41
View File
@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
import bcrypt from "bcryptjs";
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const sesion = await verificarSesion("admin_session");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { id } = await params;
const data = await req.json();
const update: {
nombre?: string;
email?: string;
slotsParalelos?: number;
activo?: boolean;
passwordHash?: string;
} = {};
if (data.nombre !== undefined) update.nombre = data.nombre;
if (data.email !== undefined) update.email = String(data.email).toLowerCase().trim();
if (data.slotsParalelos !== undefined) {
const slots = Number(data.slotsParalelos);
if (!Number.isInteger(slots) || slots < 1)
return NextResponse.json({ error: "slotsParalelos debe ser un entero >= 1" }, { status: 400 });
update.slotsParalelos = slots;
}
if (data.activo !== undefined) update.activo = !!data.activo;
if (data.password) {
update.passwordHash = await bcrypt.hash(data.password, 10);
}
const jefe = await prisma.jefeMecanico.update({
where: { id },
data: update,
select: { id: true, nombre: true, email: true, slotsParalelos: true, activo: true, createdAt: true },
});
return NextResponse.json(jefe);
}
+40
View File
@@ -0,0 +1,40 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
import bcrypt from "bcryptjs";
export async function GET() {
const sesion = await verificarSesion("admin_session");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const jefes = await prisma.jefeMecanico.findMany({
orderBy: { nombre: "asc" },
select: { id: true, nombre: true, email: true, slotsParalelos: true, activo: true, createdAt: true },
});
return NextResponse.json(jefes);
}
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { nombre, email, password, slotsParalelos } = await req.json();
if (!nombre || !email || !password)
return NextResponse.json({ error: "nombre, email y password requeridos" }, { status: 400 });
const slots = Number(slotsParalelos);
if (!Number.isInteger(slots) || slots < 1)
return NextResponse.json({ error: "slotsParalelos debe ser un entero >= 1" }, { status: 400 });
const emailNorm = String(email).toLowerCase().trim();
const existente = await prisma.jefeMecanico.findUnique({ where: { email: emailNorm } });
if (existente) return NextResponse.json({ error: "Ya existe un jefe con ese email" }, { status: 409 });
const passwordHash = await bcrypt.hash(password, 10);
const jefe = await prisma.jefeMecanico.create({
data: { nombre, email: emailNorm, passwordHash, slotsParalelos: slots },
select: { id: true, nombre: true, email: true, slotsParalelos: true, activo: true, createdAt: true },
});
return NextResponse.json(jefe, { status: 201 });
}