feat: agrega km y chasis al turno/auto, combobox de marcas y modelos
Deploy / deploy (push) Failing after 13m41s

- Turno.kilometros (requerido al reservar)
- Auto.nroChasis (opcional)
- Combobox con 60 marcas mundiales y modelos precargados
- Modelo se habilita al seleccionar marca

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Beldor
2026-06-22 16:01:19 -03:00
parent b6705239a1
commit 4d259558c6
7 changed files with 198 additions and 11 deletions
+2 -1
View File
@@ -18,7 +18,7 @@ export async function POST(req: NextRequest) {
const sesion = await verificarSesion("cliente_session");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { patente, marca, modelo, anio } = await req.json();
const { patente, marca, modelo, anio, nroChasis } = await req.json();
if (!patente || !marca || !modelo || !anio)
return NextResponse.json({ error: "Todos los campos son requeridos" }, { status: 400 });
@@ -32,6 +32,7 @@ export async function POST(req: NextRequest) {
marca,
modelo,
anio: Number(anio),
nroChasis: nroChasis?.trim() || null,
},
});
+3 -3
View File
@@ -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 } = await req.json();
if (!autoId || !tipoServicioId || !fechaInicio)
const { autoId, tipoServicioId, fechaInicio, kilometros } = await req.json();
if (!autoId || !tipoServicioId || !fechaInicio || !kilometros)
return NextResponse.json({ error: "Datos incompletos" }, { status: 400 });
// Verificar que el auto pertenece al cliente
@@ -42,7 +42,7 @@ export async function POST(req: NextRequest) {
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 },
data: { clienteId: sesion.sub, autoId, tipoServicioId, fechaInicio: inicio, fechaFin: fin, kilometros: Number(kilometros) },
include: { auto: true, tipoServicio: true },
});
+28 -4
View File
@@ -5,6 +5,8 @@ import Link from "next/link";
import Button from "@/components/ui/Button";
import Card from "@/components/ui/Card";
import Input from "@/components/ui/Input";
import Combobox from "@/components/ui/Combobox";
import { MARCAS, getModelos } from "@/lib/marcas-modelos";
interface Auto {
id: string;
@@ -12,6 +14,7 @@ interface Auto {
marca: string;
modelo: string;
anio: number;
nroChasis?: string | null;
}
export default function DashboardPage() {
@@ -19,7 +22,8 @@ export default function DashboardPage() {
const [autos, setAutos] = useState<Auto[]>([]);
const [loading, setLoading] = useState(true);
const [mostrarForm, setMostrarForm] = useState(false);
const [form, setForm] = useState({ patente: "", marca: "", modelo: "", anio: "" });
const [form, setForm] = useState({ patente: "", marca: "", modelo: "", anio: "", nroChasis: "" });
const modelosDisponibles = getModelos(form.marca);
const [guardando, setGuardando] = useState(false);
const [errorForm, setErrorForm] = useState("");
@@ -51,7 +55,7 @@ export default function DashboardPage() {
setGuardando(false);
if (!res.ok) { setErrorForm(data.error || "Error al guardar"); return; }
setMostrarForm(false);
setForm({ patente: "", marca: "", modelo: "", anio: "" });
setForm({ patente: "", marca: "", modelo: "", anio: "", nroChasis: "" });
cargarAutos();
}
@@ -108,10 +112,30 @@ export default function DashboardPage() {
<Card title="Agregar auto">
<form onSubmit={agregarAuto} className="flex flex-col gap-4">
<div className="grid grid-cols-2 gap-4">
<Input id="marca" label="Marca" placeholder="Ford" value={form.marca} onChange={set("marca")} required />
<Input id="modelo" label="Modelo" placeholder="Focus" value={form.modelo} onChange={set("modelo")} required />
<Combobox
id="marca"
label="Marca"
options={MARCAS}
value={form.marca}
onChange={(v) => setForm((f) => ({ ...f, marca: v, modelo: "" }))}
onSelect={(v) => setForm((f) => ({ ...f, marca: v, modelo: "" }))}
placeholder="Ford"
required
/>
<Combobox
id="modelo"
label="Modelo"
options={modelosDisponibles}
value={form.modelo}
onChange={(v) => setForm((f) => ({ ...f, modelo: v }))}
onSelect={(v) => setForm((f) => ({ ...f, modelo: v }))}
placeholder={form.marca ? "Focus" : "Primero elegí la marca"}
disabled={modelosDisponibles.length === 0}
required
/>
<Input id="anio" label="Año" type="number" placeholder="2018" value={form.anio} onChange={set("anio")} required min={1960} max={new Date().getFullYear() + 1} />
<Input id="patente" label="Patente" placeholder="AB123CD" value={form.patente} onChange={set("patente")} required className="uppercase" />
<Input id="nroChasis" label="Nº Chasis (opcional)" placeholder="8AFZZZ11ZYM569874" value={form.nroChasis} onChange={set("nroChasis")} className="col-span-2" />
</div>
{errorForm && <p className="text-sm text-red-500">{errorForm}</p>}
<div className="flex gap-2 justify-end">
+18 -3
View File
@@ -30,6 +30,7 @@ function TurnosContent() {
const [slots, setSlots] = useState<Slot[]>([]);
const [slotSel, setSlotSel] = useState<Slot | null>(null);
const [loadingSlots, setLoadingSlots] = useState(false);
const [kilometros, setKilometros] = useState("");
const [reservando, setReservando] = useState(false);
const [turnoCreado, setTurnoCreado] = useState<Turno | null>(null);
const [misTurnos, setMisTurnos] = useState<Turno[]>([]);
@@ -56,12 +57,12 @@ function TurnosContent() {
}
async function reservar() {
if (!slotSel || !servicioSel || !autoId) return;
if (!slotSel || !servicioSel || !autoId || !kilometros) return;
setReservando(true);
const res = await fetch("/api/cliente/turnos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ autoId, tipoServicioId: servicioSel.id, fechaInicio: slotSel.fechaInicio }),
body: JSON.stringify({ autoId, tipoServicioId: servicioSel.id, fechaInicio: slotSel.fechaInicio, kilometros: Number(kilometros) }),
});
const data = await res.json();
setReservando(false);
@@ -189,9 +190,23 @@ function TurnosContent() {
);
})}
</div>
<div className="mt-2">
<label htmlFor="kilometros" className="block text-sm font-medium text-gray-700 mb-1">
Kilometraje actual <span className="text-red-500">*</span>
</label>
<input
id="kilometros"
type="number"
min={0}
placeholder="Ej: 85000"
value={kilometros}
onChange={(e) => setKilometros(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<Button
onClick={reservar}
disabled={!slotSel}
disabled={!slotSel || !kilometros}
loading={reservando}
className="w-full mt-2"
>