feat: bootstrap taller-turnos project

Estructura inicial con Next.js 14, PostgreSQL/Prisma 7, autenticación por
magic link (clientes) y JWT (admins), lógica de scheduling con 4 slots
paralelos y API routes completas para cliente y panel admin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Beldor
2026-06-18 11:51:19 -03:00
parent 4fde77e3ed
commit 93ad1cb8c1
28 changed files with 2626 additions and 23 deletions
+102
View File
@@ -0,0 +1,102 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model Cliente {
id String @id @default(cuid())
email String @unique
nombre String
apellido String
telefono String
createdAt DateTime @default(now())
autos Auto[]
turnos Turno[]
magicTokens MagicToken[]
}
model Auto {
id String @id @default(cuid())
clienteId String
patente String @unique
marca String
modelo String
anio Int
createdAt DateTime @default(now())
cliente Cliente @relation(fields: [clienteId], references: [id])
turnos Turno[]
historial HistorialServicio[]
}
model TipoServicio {
id String @id @default(cuid())
nombre String
duracionHoras Float
activo Boolean @default(true)
turnos Turno[]
historial HistorialServicio[]
}
model Turno {
id String @id @default(cuid())
clienteId String
autoId String
tipoServicioId String
fechaInicio DateTime
fechaFin DateTime
estado EstadoTurno @default(PENDIENTE)
createdAt DateTime @default(now())
cliente Cliente @relation(fields: [clienteId], references: [id])
auto Auto @relation(fields: [autoId], references: [id])
tipoServicio TipoServicio @relation(fields: [tipoServicioId], references: [id])
}
model HistorialServicio {
id String @id @default(cuid())
autoId String
adminId String
tipoServicioId String?
descripcion String
observaciones String?
fechaRealizado DateTime
createdAt DateTime @default(now())
auto Auto @relation(fields: [autoId], references: [id])
admin Admin @relation(fields: [adminId], references: [id])
tipoServicio TipoServicio? @relation(fields: [tipoServicioId], references: [id])
}
model Admin {
id String @id @default(cuid())
email String @unique
nombre String
passwordHash String
createdAt DateTime @default(now())
historial HistorialServicio[]
}
model MagicToken {
id String @id @default(cuid())
clienteId String
token String @unique
expiresAt DateTime
usado Boolean @default(false)
createdAt DateTime @default(now())
cliente Cliente @relation(fields: [clienteId], references: [id])
}
enum EstadoTurno {
PENDIENTE
CONFIRMADO
CANCELADO
COMPLETADO
}
+51
View File
@@ -0,0 +1,51 @@
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcryptjs";
const prisma = new PrismaClient();
async function main() {
const hash = (pwd: string) => bcrypt.hash(pwd, 10);
await prisma.admin.upsert({
where: { email: "jefe1@taller.com" },
update: {},
create: {
email: "jefe1@taller.com",
nombre: "Jefe 1",
passwordHash: await hash("jefe1password"),
},
});
await prisma.admin.upsert({
where: { email: "jefe2@taller.com" },
update: {},
create: {
email: "jefe2@taller.com",
nombre: "Jefe 2",
passwordHash: await hash("jefe2password"),
},
});
// Tipos de servicio de ejemplo
const servicios = [
{ nombre: "Cambio de aceite y filtro", duracionHoras: 1 },
{ nombre: "Cambio de bujías", duracionHoras: 1 },
{ nombre: "Cambio de frenos", duracionHoras: 2 },
{ nombre: "Alineación y balanceo", duracionHoras: 1.5 },
{ nombre: "Cambio de correa de distribución", duracionHoras: 4 },
{ nombre: "Cambio de distribución con bomba de agua", duracionHoras: 8 },
{ nombre: "Revisión general", duracionHoras: 2 },
];
for (const s of servicios) {
await prisma.tipoServicio.upsert({
where: { id: s.nombre },
update: {},
create: s,
}).catch(() => prisma.tipoServicio.create({ data: s }));
}
console.log("Seed completado");
}
main().finally(() => prisma.$disconnect());