Files
taller_automotriz/src/app/api/cliente/turnos/route.ts
T
Beldor 0046b6c3cf feat(whatsapp): máquina de conversación del bot de reservas (F2) + test
- lib/turnos.ts: crearTurno() compartido (valida servicio/jefe/slot); cliente/turnos POST lo reutiliza
- lib/telefono.ts: normalización de teléfono (E.164 sin '+') para matchear el wa_id
- lib/whatsapp-bot.ts: procesarMensaje(telefono, texto), state machine completa:
  identidad por teléfono + verificación por email registrado / alta nueva,
  elegir mecánico (botones) -> trabajo (letras) -> horario (letras, con "más") -> confirmar,
  crea turno sin auto/km; expiración 30min, "cancelar" global, manejo de inválidos y 409.
  Devuelve objetos-respuesta (texto/botones) -> independiente de Meta (lo traduce F0).
- scripts/test-whatsapp-bot.ts + npm run test:bot: corre la máquina de estados con un
  Prisma en memoria (sin DB ni Meta), ejercitando scheduling y crearTurno reales. 27/27 OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 09:55:47 -03:00

51 lines
1.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
import { crearTurno } from "@/lib/turnos";
export async function GET() {
const sesion = await verificarSesion("cliente_session");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const turnos = await prisma.turno.findMany({
where: { clienteId: sesion.sub },
include: { auto: true, tipoServicio: true, jefeMecanico: { select: { id: true, nombre: true } } },
orderBy: { fechaInicio: "desc" },
});
return NextResponse.json(turnos);
}
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, jefeMecanicoId, fechaInicio, kilometros } = await req.json();
if (!autoId || !tipoServicioId || !jefeMecanicoId || !fechaInicio || !kilometros)
return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
const km = Number(kilometros);
if (!Number.isInteger(km) || km <= 0 || km > 1000000)
return NextResponse.json({ error: "Kilometraje inválido (debe ser entre 1 y 1.000.000)" }, { status: 400 });
// Verificar que el auto pertenece al cliente
const auto = await prisma.auto.findFirst({
where: { id: autoId, clienteId: sesion.sub },
});
if (!auto) return NextResponse.json({ error: "Auto no encontrado" }, { status: 404 });
const resultado = await crearTurno({
clienteId: sesion.sub,
jefeMecanicoId,
tipoServicioId,
fechaInicio: new Date(fechaInicio),
autoId,
kilometros: km,
});
if (!resultado.ok)
return NextResponse.json({ error: resultado.error }, { status: resultado.status });
return NextResponse.json(resultado.turno, { status: 201 });
}