feat(db): add seed script with demo data
Add comprehensive seed script for testing the application with realistic demo data: - Organization "Padel Pro Demo" - 3 sites (Norte, Sur, Centro) with different schedules - 2 courts per site (standard and premium pricing) - Admin users (1 super admin + 3 site admins) - Product categories (Bebidas, Snacks, Equipamiento, Alquiler) - Sample products with pricing - 3 membership plans (Basico, Premium, VIP) - 5 sample clients with one Premium membership Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
452
apps/web/prisma/seed.ts
Normal file
452
apps/web/prisma/seed.ts
Normal file
@@ -0,0 +1,452 @@
|
||||
import { PrismaClient, UserRole, CourtType, CourtStatus, MembershipStatus } from '@prisma/client';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('Starting seed...');
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// CLEANUP - Delete existing data in correct order
|
||||
// =============================================================================
|
||||
console.log('Cleaning up existing data...');
|
||||
|
||||
await prisma.payment.deleteMany();
|
||||
await prisma.saleItem.deleteMany();
|
||||
await prisma.sale.deleteMany();
|
||||
await prisma.cashRegister.deleteMany();
|
||||
await prisma.match.deleteMany();
|
||||
await prisma.tournamentInscription.deleteMany();
|
||||
await prisma.tournament.deleteMany();
|
||||
await prisma.booking.deleteMany();
|
||||
await prisma.membership.deleteMany();
|
||||
await prisma.membershipPlan.deleteMany();
|
||||
await prisma.product.deleteMany();
|
||||
await prisma.productCategory.deleteMany();
|
||||
await prisma.client.deleteMany();
|
||||
await prisma.user.deleteMany();
|
||||
await prisma.court.deleteMany();
|
||||
await prisma.site.deleteMany();
|
||||
await prisma.organization.deleteMany();
|
||||
|
||||
console.log('Cleanup complete.');
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// ORGANIZATION
|
||||
// =============================================================================
|
||||
console.log('Creating organization...');
|
||||
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
name: 'Padel Pro Demo',
|
||||
slug: 'padel-pro-demo',
|
||||
settings: {
|
||||
currency: 'MXN',
|
||||
timezone: 'America/Mexico_City',
|
||||
language: 'es',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(` Created organization: ${organization.name}`);
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// SITES
|
||||
// =============================================================================
|
||||
console.log('Creating sites...');
|
||||
|
||||
const sitesData = [
|
||||
{
|
||||
name: 'Sede Norte',
|
||||
slug: 'sede-norte',
|
||||
address: 'Av. Universidad 1000, Col. Del Valle',
|
||||
phone: '+52 55 1234 5678',
|
||||
email: 'norte@padelpro.com',
|
||||
timezone: 'America/Mexico_City',
|
||||
openTime: '07:00',
|
||||
closeTime: '23:00',
|
||||
},
|
||||
{
|
||||
name: 'Sede Sur',
|
||||
slug: 'sede-sur',
|
||||
address: 'Av. Insurgentes 2000, Col. Roma',
|
||||
phone: '+52 55 2345 6789',
|
||||
email: 'sur@padelpro.com',
|
||||
timezone: 'America/Mexico_City',
|
||||
openTime: '08:00',
|
||||
closeTime: '22:00',
|
||||
},
|
||||
{
|
||||
name: 'Sede Centro',
|
||||
slug: 'sede-centro',
|
||||
address: 'Calle Reforma 500, Centro Historico',
|
||||
phone: '+52 55 3456 7890',
|
||||
email: 'centro@padelpro.com',
|
||||
timezone: 'America/Mexico_City',
|
||||
openTime: '06:00',
|
||||
closeTime: '24:00',
|
||||
},
|
||||
];
|
||||
|
||||
const sites = await Promise.all(
|
||||
sitesData.map(async (siteData) => {
|
||||
const site = await prisma.site.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
...siteData,
|
||||
},
|
||||
});
|
||||
console.log(` Created site: ${site.name}`);
|
||||
return site;
|
||||
})
|
||||
);
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// COURTS (2 per site)
|
||||
// =============================================================================
|
||||
console.log('Creating courts...');
|
||||
|
||||
const courts: { id: string; name: string; siteId: string }[] = [];
|
||||
|
||||
for (const site of sites) {
|
||||
const courtData = [
|
||||
{
|
||||
name: 'Cancha 1',
|
||||
type: CourtType.INDOOR,
|
||||
status: CourtStatus.AVAILABLE,
|
||||
pricePerHour: 350,
|
||||
description: 'Cancha techada con iluminacion LED',
|
||||
features: ['Iluminacion LED', 'Techada', 'Cristal panoramico'],
|
||||
displayOrder: 1,
|
||||
},
|
||||
{
|
||||
name: 'Cancha 2',
|
||||
type: CourtType.INDOOR,
|
||||
status: CourtStatus.AVAILABLE,
|
||||
pricePerHour: 450,
|
||||
description: 'Cancha premium con aire acondicionado',
|
||||
features: ['Iluminacion LED', 'Techada', 'Aire acondicionado', 'Cristal panoramico', 'Premium'],
|
||||
displayOrder: 2,
|
||||
},
|
||||
];
|
||||
|
||||
for (const court of courtData) {
|
||||
const created = await prisma.court.create({
|
||||
data: {
|
||||
siteId: site.id,
|
||||
...court,
|
||||
},
|
||||
});
|
||||
courts.push(created);
|
||||
console.log(` Created court: ${site.name} - ${created.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// ADMIN USER (SUPER_ADMIN)
|
||||
// =============================================================================
|
||||
console.log('Creating admin users...');
|
||||
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
|
||||
const adminUser = await prisma.user.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
email: 'admin@padelpro.com',
|
||||
password: hashedPassword,
|
||||
firstName: 'Administrador',
|
||||
lastName: 'Sistema',
|
||||
role: UserRole.SUPER_ADMIN,
|
||||
phone: '+52 55 9999 0000',
|
||||
siteIds: sites.map(s => s.id),
|
||||
},
|
||||
});
|
||||
|
||||
console.log(` Created super admin: ${adminUser.email}`);
|
||||
|
||||
// =============================================================================
|
||||
// SITE ADMINS (one per site)
|
||||
// =============================================================================
|
||||
const siteAdminsData = [
|
||||
{ email: 'norte@padelpro.com', firstName: 'Carlos', lastName: 'Rodriguez', site: sites[0] },
|
||||
{ email: 'sur@padelpro.com', firstName: 'Maria', lastName: 'Gonzalez', site: sites[1] },
|
||||
{ email: 'centro@padelpro.com', firstName: 'Luis', lastName: 'Hernandez', site: sites[2] },
|
||||
];
|
||||
|
||||
for (const adminData of siteAdminsData) {
|
||||
const siteAdmin = await prisma.user.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
email: adminData.email,
|
||||
password: hashedPassword,
|
||||
firstName: adminData.firstName,
|
||||
lastName: adminData.lastName,
|
||||
role: UserRole.SITE_ADMIN,
|
||||
siteIds: [adminData.site.id],
|
||||
},
|
||||
});
|
||||
|
||||
// Connect user to site
|
||||
await prisma.site.update({
|
||||
where: { id: adminData.site.id },
|
||||
data: {
|
||||
users: {
|
||||
connect: { id: siteAdmin.id },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(` Created site admin: ${siteAdmin.email} (${adminData.site.name})`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// PRODUCT CATEGORIES
|
||||
// =============================================================================
|
||||
console.log('Creating product categories...');
|
||||
|
||||
const categoriesData = [
|
||||
{ name: 'Bebidas', description: 'Bebidas y refrescos', displayOrder: 1 },
|
||||
{ name: 'Snacks', description: 'Botanas y snacks', displayOrder: 2 },
|
||||
{ name: 'Equipamiento', description: 'Equipo y accesorios de padel', displayOrder: 3 },
|
||||
{ name: 'Alquiler', description: 'Articulos en renta', displayOrder: 4 },
|
||||
];
|
||||
|
||||
const categories: { id: string; name: string }[] = [];
|
||||
|
||||
for (const catData of categoriesData) {
|
||||
const category = await prisma.productCategory.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
...catData,
|
||||
},
|
||||
});
|
||||
categories.push(category);
|
||||
console.log(` Created category: ${category.name}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// PRODUCTS (for organization, shown in Sede Norte initially)
|
||||
// =============================================================================
|
||||
console.log('Creating products...');
|
||||
|
||||
const bebidasCategory = categories.find(c => c.name === 'Bebidas');
|
||||
const snacksCategory = categories.find(c => c.name === 'Snacks');
|
||||
const equipamientoCategory = categories.find(c => c.name === 'Equipamiento');
|
||||
const alquilerCategory = categories.find(c => c.name === 'Alquiler');
|
||||
|
||||
const productsData = [
|
||||
// Bebidas
|
||||
{ name: 'Agua', description: 'Agua natural 600ml', price: 20, costPrice: 8, stock: 100, categoryId: bebidasCategory?.id, sku: 'BEB-001' },
|
||||
{ name: 'Gatorade', description: 'Bebida deportiva 500ml', price: 35, costPrice: 18, stock: 50, categoryId: bebidasCategory?.id, sku: 'BEB-002' },
|
||||
{ name: 'Cerveza', description: 'Cerveza artesanal 355ml', price: 45, costPrice: 22, stock: 48, categoryId: bebidasCategory?.id, sku: 'BEB-003' },
|
||||
// Snacks
|
||||
{ name: 'Papas', description: 'Papas fritas 45g', price: 25, costPrice: 12, stock: 30, categoryId: snacksCategory?.id, sku: 'SNK-001' },
|
||||
{ name: 'Barra energetica', description: 'Barra de proteina 50g', price: 30, costPrice: 15, stock: 25, categoryId: snacksCategory?.id, sku: 'SNK-002' },
|
||||
// Equipamiento
|
||||
{ name: 'Pelotas HEAD', description: 'Tubo de 3 pelotas HEAD Pro', price: 180, costPrice: 90, stock: 20, categoryId: equipamientoCategory?.id, sku: 'EQP-001' },
|
||||
{ name: 'Grip', description: 'Overgrip Wilson Pro', price: 50, costPrice: 25, stock: 40, categoryId: equipamientoCategory?.id, sku: 'EQP-002' },
|
||||
// Alquiler
|
||||
{ name: 'Raqueta alquiler', description: 'Raqueta de padel (por hora)', price: 100, costPrice: 0, stock: 10, categoryId: alquilerCategory?.id, sku: 'ALQ-001', trackStock: false },
|
||||
];
|
||||
|
||||
for (const productData of productsData) {
|
||||
const product = await prisma.product.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
...productData,
|
||||
},
|
||||
});
|
||||
console.log(` Created product: ${product.name} - $${product.price}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// MEMBERSHIP PLANS
|
||||
// =============================================================================
|
||||
console.log('Creating membership plans...');
|
||||
|
||||
const membershipPlansData = [
|
||||
{
|
||||
name: 'Basico',
|
||||
description: 'Plan basico mensual con beneficios esenciales',
|
||||
price: 499,
|
||||
durationMonths: 1,
|
||||
courtHours: 2,
|
||||
discountPercent: 10,
|
||||
benefits: ['2 horas gratis de cancha al mes', '10% descuento en reservas', '5% descuento en tienda'],
|
||||
},
|
||||
{
|
||||
name: 'Premium',
|
||||
description: 'Plan premium con mayores beneficios',
|
||||
price: 899,
|
||||
durationMonths: 1,
|
||||
courtHours: 5,
|
||||
discountPercent: 20,
|
||||
benefits: ['5 horas gratis de cancha al mes', '20% descuento en reservas', '10% descuento en tienda', 'Acceso prioritario a torneos'],
|
||||
},
|
||||
{
|
||||
name: 'VIP',
|
||||
description: 'Plan VIP con todos los beneficios',
|
||||
price: 1499,
|
||||
durationMonths: 1,
|
||||
courtHours: 10,
|
||||
discountPercent: 30,
|
||||
benefits: ['10 horas gratis de cancha al mes', '30% descuento en reservas', '15% descuento en tienda', 'Acceso prioritario a torneos', 'Invitados con descuento', 'Casillero incluido'],
|
||||
},
|
||||
];
|
||||
|
||||
const membershipPlans: { id: string; name: string; courtHours: number | null }[] = [];
|
||||
|
||||
for (const planData of membershipPlansData) {
|
||||
const plan = await prisma.membershipPlan.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
...planData,
|
||||
},
|
||||
});
|
||||
membershipPlans.push(plan);
|
||||
console.log(` Created membership plan: ${plan.name} - $${plan.price}/mes`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// SAMPLE CLIENTS
|
||||
// =============================================================================
|
||||
console.log('Creating sample clients...');
|
||||
|
||||
const clientsData = [
|
||||
{
|
||||
firstName: 'Juan',
|
||||
lastName: 'Perez',
|
||||
email: 'juan.perez@email.com',
|
||||
phone: '+52 55 1111 2222',
|
||||
level: 'Intermedio',
|
||||
preferredHand: 'Derecha',
|
||||
},
|
||||
{
|
||||
firstName: 'Maria',
|
||||
lastName: 'Garcia',
|
||||
email: 'maria.garcia@email.com',
|
||||
phone: '+52 55 2222 3333',
|
||||
level: 'Avanzado',
|
||||
preferredHand: 'Derecha',
|
||||
},
|
||||
{
|
||||
firstName: 'Carlos',
|
||||
lastName: 'Lopez',
|
||||
email: 'carlos.lopez@email.com',
|
||||
phone: '+52 55 3333 4444',
|
||||
level: 'Principiante',
|
||||
preferredHand: 'Izquierda',
|
||||
},
|
||||
{
|
||||
firstName: 'Ana',
|
||||
lastName: 'Martinez',
|
||||
email: 'ana.martinez@email.com',
|
||||
phone: '+52 55 4444 5555',
|
||||
level: 'Intermedio',
|
||||
preferredHand: 'Derecha',
|
||||
},
|
||||
{
|
||||
firstName: 'Roberto',
|
||||
lastName: 'Sanchez',
|
||||
email: 'roberto.sanchez@email.com',
|
||||
phone: '+52 55 5555 6666',
|
||||
level: 'Avanzado',
|
||||
preferredHand: 'Derecha',
|
||||
},
|
||||
];
|
||||
|
||||
const clients: { id: string; firstName: string; lastName: string }[] = [];
|
||||
|
||||
for (const clientData of clientsData) {
|
||||
const client = await prisma.client.create({
|
||||
data: {
|
||||
organizationId: organization.id,
|
||||
...clientData,
|
||||
},
|
||||
});
|
||||
clients.push(client);
|
||||
console.log(` Created client: ${client.firstName} ${client.lastName}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// MEMBERSHIP FOR ONE CLIENT (Maria Garcia with Premium)
|
||||
// =============================================================================
|
||||
console.log('Creating sample membership...');
|
||||
|
||||
const premiumPlan = membershipPlans.find(p => p.name === 'Premium');
|
||||
const mariaClient = clients.find(c => c.firstName === 'Maria');
|
||||
|
||||
if (premiumPlan && mariaClient) {
|
||||
const startDate = new Date();
|
||||
const endDate = new Date();
|
||||
endDate.setMonth(endDate.getMonth() + 1);
|
||||
|
||||
const membership = await prisma.membership.create({
|
||||
data: {
|
||||
planId: premiumPlan.id,
|
||||
clientId: mariaClient.id,
|
||||
startDate,
|
||||
endDate,
|
||||
status: MembershipStatus.ACTIVE,
|
||||
remainingHours: premiumPlan.courtHours,
|
||||
autoRenew: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(` Created Premium membership for ${mariaClient.firstName} ${mariaClient.lastName}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// =============================================================================
|
||||
// SUMMARY
|
||||
// =============================================================================
|
||||
console.log('='.repeat(60));
|
||||
console.log('Seed completed successfully!');
|
||||
console.log('='.repeat(60));
|
||||
console.log('');
|
||||
console.log('Summary:');
|
||||
console.log(` - 1 Organization: ${organization.name}`);
|
||||
console.log(` - ${sites.length} Sites`);
|
||||
console.log(` - ${courts.length} Courts (${courts.length / sites.length} per site)`);
|
||||
console.log(` - 4 Users (1 super admin + 3 site admins)`);
|
||||
console.log(` - ${categories.length} Product Categories`);
|
||||
console.log(` - ${productsData.length} Products`);
|
||||
console.log(` - ${membershipPlans.length} Membership Plans`);
|
||||
console.log(` - ${clients.length} Sample Clients`);
|
||||
console.log(` - 1 Active Membership`);
|
||||
console.log('');
|
||||
console.log('Login credentials:');
|
||||
console.log(' Super Admin: admin@padelpro.com / admin123');
|
||||
console.log(' Site Admins: norte@padelpro.com, sur@padelpro.com, centro@padelpro.com / admin123');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error('');
|
||||
console.error('Error during seed:');
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user