fix: validar km máximo 1M y mostrar error si falla la reserva
Deploy / deploy (push) Successful in 4m39s
Deploy / deploy (push) Successful in 4m39s
Si alguien ingresaba un km fuera del rango integer de Postgres la app crasheaba silenciosamente — el cliente no veía ningún mensaje. Se agrega validación de rango (1–1.000.000) en frontend y backend, y se muestra el error de la API debajo del botón de confirmar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,10 @@ export async function POST(req: NextRequest) {
|
|||||||
if (!autoId || !tipoServicioId || !fechaInicio || !kilometros)
|
if (!autoId || !tipoServicioId || !fechaInicio || !kilometros)
|
||||||
return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
|
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
|
// Verificar que el auto pertenece al cliente
|
||||||
const auto = await prisma.auto.findFirst({
|
const auto = await prisma.auto.findFirst({
|
||||||
where: { id: autoId, clienteId: sesion.sub },
|
where: { id: autoId, clienteId: sesion.sub },
|
||||||
@@ -42,7 +46,7 @@ export async function POST(req: NextRequest) {
|
|||||||
return NextResponse.json({ error: "El horario ya no está disponible" }, { status: 409 });
|
return NextResponse.json({ error: "El horario ya no está disponible" }, { status: 409 });
|
||||||
|
|
||||||
const turno = await prisma.turno.create({
|
const turno = await prisma.turno.create({
|
||||||
data: { clienteId: sesion.sub, autoId, tipoServicioId, fechaInicio: inicio, fechaFin: fin, kilometros: Number(kilometros) },
|
data: { clienteId: sesion.sub, autoId, tipoServicioId, fechaInicio: inicio, fechaFin: fin, kilometros: km },
|
||||||
include: { auto: true, tipoServicio: true },
|
include: { auto: true, tipoServicio: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ function TurnosContent() {
|
|||||||
const [kilometros, setKilometros] = useState("");
|
const [kilometros, setKilometros] = useState("");
|
||||||
const [kmError, setKmError] = useState("");
|
const [kmError, setKmError] = useState("");
|
||||||
const [reservando, setReservando] = useState(false);
|
const [reservando, setReservando] = useState(false);
|
||||||
|
const [reservaError, setReservaError] = useState("");
|
||||||
const [turnoCreado, setTurnoCreado] = useState<Turno | null>(null);
|
const [turnoCreado, setTurnoCreado] = useState<Turno | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -127,8 +128,10 @@ function TurnosContent() {
|
|||||||
setKilometros(val);
|
setKilometros(val);
|
||||||
if (val === "") {
|
if (val === "") {
|
||||||
setKmError("");
|
setKmError("");
|
||||||
} else if (!/^\d+$/.test(val) || Number(val) < 0) {
|
} else if (!/^\d+$/.test(val) || Number(val) <= 0) {
|
||||||
setKmError("Ingresá solo números enteros positivos");
|
setKmError("Ingresá solo números enteros positivos");
|
||||||
|
} else if (Number(val) > 1000000) {
|
||||||
|
setKmError("El kilometraje no puede superar 1.000.000 km");
|
||||||
} else {
|
} else {
|
||||||
setKmError("");
|
setKmError("");
|
||||||
}
|
}
|
||||||
@@ -137,6 +140,7 @@ function TurnosContent() {
|
|||||||
async function reservar() {
|
async function reservar() {
|
||||||
if (!slotSel || !servicioSel || !autoId || !kilometros || kmError) return;
|
if (!slotSel || !servicioSel || !autoId || !kilometros || kmError) return;
|
||||||
setReservando(true);
|
setReservando(true);
|
||||||
|
setReservaError("");
|
||||||
const res = await fetch("/api/cliente/turnos", {
|
const res = await fetch("/api/cliente/turnos", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -144,7 +148,13 @@ function TurnosContent() {
|
|||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setReservando(false);
|
setReservando(false);
|
||||||
if (res.ok) { setTurnoCreado(data); setDiaSeleccionado(null); setStep("confirmado"); }
|
if (res.ok) {
|
||||||
|
setTurnoCreado(data);
|
||||||
|
setDiaSeleccionado(null);
|
||||||
|
setStep("confirmado");
|
||||||
|
} else {
|
||||||
|
setReservaError(data?.error || "Ocurrió un error al reservar. Intentá de nuevo.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navegación de mes
|
// Navegación de mes
|
||||||
@@ -364,6 +374,7 @@ function TurnosContent() {
|
|||||||
<Button onClick={reservar} disabled={!kilometros || !!kmError} loading={reservando} className="w-full">
|
<Button onClick={reservar} disabled={!kilometros || !!kmError} loading={reservando} className="w-full">
|
||||||
Confirmar turno · {horaArg(slotSel.fechaInicio)} – {horaArg(slotSel.fechaFin)}
|
Confirmar turno · {horaArg(slotSel.fechaInicio)} – {horaArg(slotSel.fechaFin)}
|
||||||
</Button>
|
</Button>
|
||||||
|
{reservaError && <p className="text-xs text-red-500 mt-2 text-center">{reservaError}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user