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:
@@ -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);
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { crearSesionJefe } from "@/lib/auth-admin";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const { email, password } = await req.json();
|
||||
if (!email || !password)
|
||||
return NextResponse.json({ error: "Credenciales requeridas" }, { status: 400 });
|
||||
|
||||
const jefe = await prisma.jefeMecanico.findUnique({
|
||||
where: { email: String(email).toLowerCase().trim() },
|
||||
});
|
||||
if (!jefe || !jefe.activo)
|
||||
return NextResponse.json({ error: "Credenciales incorrectas" }, { status: 401 });
|
||||
|
||||
const ok = await bcrypt.compare(password, jefe.passwordHash);
|
||||
if (!ok) return NextResponse.json({ error: "Credenciales incorrectas" }, { status: 401 });
|
||||
|
||||
const jwt = await crearSesionJefe(jefe.id);
|
||||
|
||||
const response = NextResponse.json({ ok: true, jefe: { id: jefe.id, nombre: jefe.nombre } });
|
||||
response.cookies.set("jefe_session", jwt, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
maxAge: 60 * 60 * 8,
|
||||
path: "/",
|
||||
});
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST() {
|
||||
const response = NextResponse.json({ ok: true });
|
||||
response.cookies.set("jefe_session", "", { maxAge: 0, path: "/" });
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { verificarSesion } from "@/lib/auth-admin";
|
||||
|
||||
export async function GET() {
|
||||
const sesion = await verificarSesion("cliente_session");
|
||||
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
|
||||
|
||||
const jefes = await prisma.jefeMecanico.findMany({
|
||||
where: { activo: true },
|
||||
orderBy: { nombre: "asc" },
|
||||
select: { id: true, nombre: true },
|
||||
});
|
||||
return NextResponse.json(jefes);
|
||||
}
|
||||
@@ -9,13 +9,16 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const tipoServicioId = searchParams.get("tipoServicioId");
|
||||
const jefeMecanicoId = searchParams.get("jefeMecanicoId");
|
||||
if (!tipoServicioId)
|
||||
return NextResponse.json({ error: "tipoServicioId requerido" }, { status: 400 });
|
||||
if (!jefeMecanicoId)
|
||||
return NextResponse.json({ error: "jefeMecanicoId requerido" }, { status: 400 });
|
||||
|
||||
const servicio = await prisma.tipoServicio.findUnique({ where: { id: tipoServicioId } });
|
||||
if (!servicio || !servicio.activo)
|
||||
return NextResponse.json({ error: "Servicio no encontrado" }, { status: 404 });
|
||||
|
||||
const slots = await obtenerSlotsDisponibles(servicio.duracionHoras, 60, 500);
|
||||
const slots = await obtenerSlotsDisponibles(jefeMecanicoId, servicio.duracionHoras, 60, 500);
|
||||
return NextResponse.json(slots);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export async function GET() {
|
||||
|
||||
const turnos = await prisma.turno.findMany({
|
||||
where: { clienteId: sesion.sub },
|
||||
include: { auto: true, tipoServicio: true },
|
||||
include: { auto: true, tipoServicio: true, jefeMecanico: { select: { id: true, nombre: true } } },
|
||||
orderBy: { fechaInicio: "desc" },
|
||||
});
|
||||
|
||||
@@ -20,8 +20,8 @@ export async function POST(req: NextRequest) {
|
||||
const sesion = await verificarSesion("cliente_session");
|
||||
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
|
||||
|
||||
const { autoId, tipoServicioId, fechaInicio, kilometros } = await req.json();
|
||||
if (!autoId || !tipoServicioId || !fechaInicio || !kilometros)
|
||||
const { autoId, tipoServicioId, jefeMecanicoId, fechaInicio, kilometros } = await req.json();
|
||||
if (!autoId || !tipoServicioId || !jefeMecanicoId || !fechaInicio || !kilometros)
|
||||
return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
|
||||
|
||||
const km = Number(kilometros);
|
||||
@@ -38,16 +38,28 @@ export async function POST(req: NextRequest) {
|
||||
if (!servicio || !servicio.activo)
|
||||
return NextResponse.json({ error: "Servicio no disponible" }, { status: 404 });
|
||||
|
||||
const jefe = await prisma.jefeMecanico.findUnique({ where: { id: jefeMecanicoId } });
|
||||
if (!jefe || !jefe.activo)
|
||||
return NextResponse.json({ error: "Jefe mecánico no disponible" }, { status: 404 });
|
||||
|
||||
const inicio = new Date(fechaInicio);
|
||||
const fin = new Date(inicio.getTime() + servicio.duracionHoras * 60 * 60 * 1000);
|
||||
|
||||
const disponible = await slotEstaDisponible(inicio, fin);
|
||||
const disponible = await slotEstaDisponible(inicio, fin, jefeMecanicoId);
|
||||
if (!disponible)
|
||||
return NextResponse.json({ error: "El horario ya no está disponible" }, { status: 409 });
|
||||
|
||||
const turno = await prisma.turno.create({
|
||||
data: { clienteId: sesion.sub, autoId, tipoServicioId, fechaInicio: inicio, fechaFin: fin, kilometros: km },
|
||||
include: { auto: true, tipoServicio: true },
|
||||
data: {
|
||||
clienteId: sesion.sub,
|
||||
autoId,
|
||||
tipoServicioId,
|
||||
jefeMecanicoId,
|
||||
fechaInicio: inicio,
|
||||
fechaFin: fin,
|
||||
kilometros: km,
|
||||
},
|
||||
include: { auto: true, tipoServicio: true, jefeMecanico: { select: { id: true, nombre: true } } },
|
||||
});
|
||||
|
||||
return NextResponse.json(turno, { status: 201 });
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { verificarSesion } from "@/lib/auth-admin";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const sesion = await verificarSesion("jefe_session");
|
||||
if (!sesion || sesion.role !== "jefe")
|
||||
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const fecha = searchParams.get("fecha"); // YYYY-MM-DD
|
||||
|
||||
const where: Record<string, unknown> = { jefeMecanicoId: sesion.sub };
|
||||
if (fecha) {
|
||||
where.fechaInicio = {
|
||||
gte: new Date(`${fecha}T00:00:00`),
|
||||
lte: new Date(`${fecha}T23:59:59`),
|
||||
};
|
||||
}
|
||||
|
||||
const turnos = await prisma.turno.findMany({
|
||||
where,
|
||||
include: {
|
||||
cliente: { select: { nombre: true, apellido: true, telefono: true } },
|
||||
auto: true,
|
||||
tipoServicio: true,
|
||||
},
|
||||
orderBy: { fechaInicio: "asc" },
|
||||
});
|
||||
|
||||
return NextResponse.json(turnos);
|
||||
}
|
||||
Reference in New Issue
Block a user