FASE 2 COMPLETADA: Perfiles, Social y Ranking

Implementados 3 módulos principales:

1. PERFILES EXTENDIDOS
   - Campos adicionales: ciudad, fecha nacimiento, años jugando
   - Estadísticas: partidos jugados/ganados/perdidos
   - Historial de cambios de nivel
   - Búsqueda de usuarios con filtros

2. SISTEMA SOCIAL
   - Amigos: solicitudes, aceptar, rechazar, bloquear
   - Grupos: crear, gestionar miembros, roles
   - Reservas recurrentes: fijos semanales

3. RANKING Y ESTADÍSTICAS
   - Registro de partidos 2v2 con confirmación
   - Sistema de puntos con bonus y multiplicadores
   - Ranking mensual, anual y global
   - Estadísticas personales y globales

Nuevos endpoints:
- /users/* - Perfiles y búsqueda
- /friends/* - Gestión de amistades
- /groups/* - Grupos de jugadores
- /recurring/* - Reservas recurrentes
- /matches/* - Registro de partidos
- /ranking/* - Clasificaciones
- /stats/* - Estadísticas

Nuevos usuarios de prueba:
- carlos@padel.com / 123456
- ana@padel.com / 123456
- pedro@padel.com / 123456
- maria@padel.com / 123456
This commit is contained in:
2026-01-31 08:22:41 +00:00
parent b558372810
commit e20c5b956b
34 changed files with 6081 additions and 15 deletions

Binary file not shown.

View File

@@ -0,0 +1,228 @@
-- CreateTable
CREATE TABLE "level_history" (
"id" TEXT NOT NULL PRIMARY KEY,
"oldLevel" TEXT NOT NULL,
"newLevel" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"changedBy" TEXT NOT NULL,
"reason" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "level_history_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "friends" (
"id" TEXT NOT NULL PRIMARY KEY,
"requesterId" TEXT NOT NULL,
"addresseeId" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'PENDING',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "friends_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "friends_addresseeId_fkey" FOREIGN KEY ("addresseeId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "groups" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"description" TEXT,
"createdById" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "groups_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "group_members" (
"id" TEXT NOT NULL PRIMARY KEY,
"groupId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"role" TEXT NOT NULL DEFAULT 'MEMBER',
"joinedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "group_members_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "group_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "recurring_bookings" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"courtId" TEXT NOT NULL,
"dayOfWeek" INTEGER NOT NULL,
"startTime" TEXT NOT NULL,
"endTime" TEXT NOT NULL,
"startDate" DATETIME NOT NULL,
"endDate" DATETIME,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "recurring_bookings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "recurring_bookings_courtId_fkey" FOREIGN KEY ("courtId") REFERENCES "courts" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "match_results" (
"id" TEXT NOT NULL PRIMARY KEY,
"bookingId" TEXT,
"team1Player1Id" TEXT NOT NULL,
"team1Player2Id" TEXT NOT NULL,
"team2Player1Id" TEXT NOT NULL,
"team2Player2Id" TEXT NOT NULL,
"team1Score" INTEGER NOT NULL,
"team2Score" INTEGER NOT NULL,
"winner" TEXT NOT NULL,
"playedAt" DATETIME NOT NULL,
"confirmedBy" TEXT NOT NULL DEFAULT '[]',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "match_results_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "bookings" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "match_results_team1Player1Id_fkey" FOREIGN KEY ("team1Player1Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "match_results_team1Player2Id_fkey" FOREIGN KEY ("team1Player2Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "match_results_team2Player1Id_fkey" FOREIGN KEY ("team2Player1Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "match_results_team2Player2Id_fkey" FOREIGN KEY ("team2Player2Id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "user_stats" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"period" TEXT NOT NULL,
"periodValue" TEXT NOT NULL,
"matchesPlayed" INTEGER NOT NULL DEFAULT 0,
"matchesWon" INTEGER NOT NULL DEFAULT 0,
"matchesLost" INTEGER NOT NULL DEFAULT 0,
"tournamentsPlayed" INTEGER NOT NULL DEFAULT 0,
"tournamentsWon" INTEGER NOT NULL DEFAULT 0,
"points" INTEGER NOT NULL DEFAULT 0,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "user_stats_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_bookings" (
"id" TEXT NOT NULL PRIMARY KEY,
"date" DATETIME NOT NULL,
"startTime" TEXT NOT NULL,
"endTime" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'PENDING',
"totalPrice" INTEGER NOT NULL,
"notes" TEXT,
"userId" TEXT NOT NULL,
"courtId" TEXT NOT NULL,
"recurringBookingId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "bookings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "bookings_courtId_fkey" FOREIGN KEY ("courtId") REFERENCES "courts" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "bookings_recurringBookingId_fkey" FOREIGN KEY ("recurringBookingId") REFERENCES "recurring_bookings" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_bookings" ("courtId", "createdAt", "date", "endTime", "id", "notes", "startTime", "status", "totalPrice", "updatedAt", "userId") SELECT "courtId", "createdAt", "date", "endTime", "id", "notes", "startTime", "status", "totalPrice", "updatedAt", "userId" FROM "bookings";
DROP TABLE "bookings";
ALTER TABLE "new_bookings" RENAME TO "bookings";
CREATE INDEX "bookings_userId_idx" ON "bookings"("userId");
CREATE INDEX "bookings_courtId_idx" ON "bookings"("courtId");
CREATE INDEX "bookings_date_idx" ON "bookings"("date");
CREATE INDEX "bookings_recurringBookingId_idx" ON "bookings"("recurringBookingId");
CREATE TABLE "new_users" (
"id" TEXT NOT NULL PRIMARY KEY,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"phone" TEXT,
"avatarUrl" TEXT,
"city" TEXT,
"birthDate" DATETIME,
"role" TEXT NOT NULL DEFAULT 'PLAYER',
"playerLevel" TEXT NOT NULL DEFAULT 'BEGINNER',
"handPreference" TEXT NOT NULL DEFAULT 'RIGHT',
"positionPreference" TEXT NOT NULL DEFAULT 'BOTH',
"bio" TEXT,
"yearsPlaying" INTEGER,
"matchesPlayed" INTEGER NOT NULL DEFAULT 0,
"matchesWon" INTEGER NOT NULL DEFAULT 0,
"matchesLost" INTEGER NOT NULL DEFAULT 0,
"totalPoints" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"isVerified" BOOLEAN NOT NULL DEFAULT false,
"lastLogin" DATETIME,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_users" ("avatarUrl", "bio", "createdAt", "email", "firstName", "handPreference", "id", "isActive", "isVerified", "lastLogin", "lastName", "password", "phone", "playerLevel", "positionPreference", "role", "updatedAt") SELECT "avatarUrl", "bio", "createdAt", "email", "firstName", "handPreference", "id", "isActive", "isVerified", "lastLogin", "lastName", "password", "phone", "playerLevel", "positionPreference", "role", "updatedAt" FROM "users";
DROP TABLE "users";
ALTER TABLE "new_users" RENAME TO "users";
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
-- CreateIndex
CREATE INDEX "level_history_userId_idx" ON "level_history"("userId");
-- CreateIndex
CREATE INDEX "friends_requesterId_idx" ON "friends"("requesterId");
-- CreateIndex
CREATE INDEX "friends_addresseeId_idx" ON "friends"("addresseeId");
-- CreateIndex
CREATE INDEX "friends_status_idx" ON "friends"("status");
-- CreateIndex
CREATE UNIQUE INDEX "friends_requesterId_addresseeId_key" ON "friends"("requesterId", "addresseeId");
-- CreateIndex
CREATE INDEX "groups_createdById_idx" ON "groups"("createdById");
-- CreateIndex
CREATE INDEX "group_members_groupId_idx" ON "group_members"("groupId");
-- CreateIndex
CREATE INDEX "group_members_userId_idx" ON "group_members"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "group_members_groupId_userId_key" ON "group_members"("groupId", "userId");
-- CreateIndex
CREATE INDEX "recurring_bookings_userId_idx" ON "recurring_bookings"("userId");
-- CreateIndex
CREATE INDEX "recurring_bookings_courtId_idx" ON "recurring_bookings"("courtId");
-- CreateIndex
CREATE INDEX "recurring_bookings_dayOfWeek_idx" ON "recurring_bookings"("dayOfWeek");
-- CreateIndex
CREATE INDEX "recurring_bookings_isActive_idx" ON "recurring_bookings"("isActive");
-- CreateIndex
CREATE UNIQUE INDEX "match_results_bookingId_key" ON "match_results"("bookingId");
-- CreateIndex
CREATE INDEX "match_results_team1Player1Id_idx" ON "match_results"("team1Player1Id");
-- CreateIndex
CREATE INDEX "match_results_team1Player2Id_idx" ON "match_results"("team1Player2Id");
-- CreateIndex
CREATE INDEX "match_results_team2Player1Id_idx" ON "match_results"("team2Player1Id");
-- CreateIndex
CREATE INDEX "match_results_team2Player2Id_idx" ON "match_results"("team2Player2Id");
-- CreateIndex
CREATE INDEX "match_results_playedAt_idx" ON "match_results"("playedAt");
-- CreateIndex
CREATE INDEX "user_stats_userId_idx" ON "user_stats"("userId");
-- CreateIndex
CREATE INDEX "user_stats_period_periodValue_idx" ON "user_stats"("period", "periodValue");
-- CreateIndex
CREATE INDEX "user_stats_points_idx" ON "user_stats"("points");
-- CreateIndex
CREATE UNIQUE INDEX "user_stats_userId_period_periodValue_key" ON "user_stats"("userId", "period", "periodValue");

View File

@@ -0,0 +1 @@
-- This is an empty migration.

View File

@@ -21,6 +21,8 @@ model User {
lastName String
phone String?
avatarUrl String?
city String?
birthDate DateTime?
// Datos de juego (usamos String para simular enums en SQLite)
role String @default("PLAYER") // PLAYER, ADMIN, SUPERADMIN
@@ -28,6 +30,13 @@ model User {
handPreference String @default("RIGHT") // RIGHT, LEFT, BOTH
positionPreference String @default("BOTH") // DRIVE, BACKHAND, BOTH
bio String?
yearsPlaying Int?
// Estadísticas globales
matchesPlayed Int @default(0)
matchesWon Int @default(0)
matchesLost Int @default(0)
totalPoints Int @default(0)
// Estado
isActive Boolean @default(true)
@@ -35,13 +44,58 @@ model User {
lastLogin DateTime?
// Relaciones
bookings Booking[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bookings Booking[]
levelHistory LevelHistory[]
// Relaciones con MatchResult
team1Player1Matches MatchResult[] @relation("Team1Player1")
team1Player2Matches MatchResult[] @relation("Team1Player2")
team2Player1Matches MatchResult[] @relation("Team2Player1")
team2Player2Matches MatchResult[] @relation("Team2Player2")
// Relación con UserStats
userStats UserStats[]
// Amistades
friendsSent Friend[] @relation("FriendRequestsSent")
friendsReceived Friend[] @relation("FriendRequestsReceived")
// Grupos
groupsCreated Group[] @relation("GroupsCreated")
groupMembers GroupMember[] @relation("GroupMemberships")
// Reservas recurrentes
recurringBookings RecurringBooking[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
// Modelo de Historial de Niveles
model LevelHistory {
id String @id @default(uuid())
// Niveles
oldLevel String
newLevel String
// Referencias
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
// Quién realizó el cambio (admin)
changedBy String
// Metadata
reason String?
createdAt DateTime @default(now())
@@index([userId])
@@map("level_history")
}
// Modelo de Cancha
model Court {
id String @id @default(uuid())
@@ -66,6 +120,7 @@ model Court {
// Relaciones
bookings Booking[]
schedules CourtSchedule[]
recurringBookings RecurringBooking[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -120,6 +175,13 @@ model Booking {
court Court @relation(fields: [courtId], references: [id])
courtId String
// Relación con MatchResult
matchResult MatchResult?
// Referencia a reserva recurrente (si aplica)
recurringBooking RecurringBooking? @relation(fields: [recurringBookingId], references: [id])
recurringBookingId String?
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -127,5 +189,195 @@ model Booking {
@@index([userId])
@@index([courtId])
@@index([date])
@@index([recurringBookingId])
@@map("bookings")
}
// Modelo de Amistad
model Friend {
id String @id @default(uuid())
// Quien envía la solicitud
requester User @relation("FriendRequestsSent", fields: [requesterId], references: [id])
requesterId String
// Quien recibe la solicitud
addressee User @relation("FriendRequestsReceived", fields: [addresseeId], references: [id])
addresseeId String
// Estado (PENDING, ACCEPTED, REJECTED, BLOCKED)
status String @default("PENDING")
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([requesterId, addresseeId])
@@index([requesterId])
@@index([addresseeId])
@@index([status])
@@map("friends")
}
// Modelo de Grupo
model Group {
id String @id @default(uuid())
name String
description String?
// Creador del grupo
createdBy User @relation("GroupsCreated", fields: [createdById], references: [id])
createdById String
// Relaciones
members GroupMember[]
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([createdById])
@@map("groups")
}
// Modelo de Miembro de Grupo
model GroupMember {
id String @id @default(uuid())
// Grupo
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
groupId String
// Usuario
user User @relation("GroupMemberships", fields: [userId], references: [id])
userId String
// Rol (ADMIN, MEMBER)
role String @default("MEMBER")
// Fecha de unión
joinedAt DateTime @default(now())
@@unique([groupId, userId])
@@index([groupId])
@@index([userId])
@@map("group_members")
}
// Modelo de Reserva Recurrente
model RecurringBooking {
id String @id @default(uuid())
// Usuario que crea la reserva recurrente
user User @relation(fields: [userId], references: [id])
userId String
// Cancha
court Court @relation(fields: [courtId], references: [id])
courtId String
// Día de la semana (0=Domingo, 1=Lunes, ..., 6=Sábado)
dayOfWeek Int
// Horario
startTime String
endTime String
// Rango de fechas
startDate DateTime
endDate DateTime?
// Estado
isActive Boolean @default(true)
// Relaciones
bookings Booking[]
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([courtId])
@@index([dayOfWeek])
@@index([isActive])
@@map("recurring_bookings")
}
// Modelo de Resultado de Partido
model MatchResult {
id String @id @default(uuid())
// Relación opcional con reserva
booking Booking? @relation(fields: [bookingId], references: [id], onDelete: SetNull)
bookingId String? @unique
// Jugadores del Equipo 1
team1Player1 User @relation("Team1Player1", fields: [team1Player1Id], references: [id])
team1Player1Id String
team1Player2 User @relation("Team1Player2", fields: [team1Player2Id], references: [id])
team1Player2Id String
// Jugadores del Equipo 2
team2Player1 User @relation("Team2Player1", fields: [team2Player1Id], references: [id])
team2Player1Id String
team2Player2 User @relation("Team2Player2", fields: [team2Player2Id], references: [id])
team2Player2Id String
// Resultado
team1Score Int
team2Score Int
winner String // TEAM1, TEAM2, DRAW
// Fecha en que se jugó el partido
playedAt DateTime
// Confirmaciones (JSON array de userIds)
confirmedBy String @default("[]")
// Timestamps
createdAt DateTime @default(now())
@@index([team1Player1Id])
@@index([team1Player2Id])
@@index([team2Player1Id])
@@index([team2Player2Id])
@@index([playedAt])
@@map("match_results")
}
// Modelo de Estadísticas de Usuario por Período
model UserStats {
id String @id @default(uuid())
// Usuario
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
// Período (MONTH, YEAR, ALL_TIME)
period String
// Valor del período (ej: "2024-01" para mes, "2024" para año)
periodValue String
// Estadísticas de partidos
matchesPlayed Int @default(0)
matchesWon Int @default(0)
matchesLost Int @default(0)
// Estadísticas de torneos
tournamentsPlayed Int @default(0)
tournamentsWon Int @default(0)
// Puntos para ranking
points Int @default(0)
// Timestamps
updatedAt DateTime @updatedAt
@@unique([userId, period, periodValue])
@@index([userId])
@@index([period, periodValue])
@@index([points])
@@map("user_stats")
}

View File

@@ -0,0 +1,192 @@
import { PrismaClient } from '@prisma/client';
import { hashPassword } from '../src/utils/password';
import { UserRole, PlayerLevel, FriendStatus, GroupRole, CourtType } from '../src/utils/constants';
const prisma = new PrismaClient();
async function main() {
console.log('🌱 Seeding Fase 2 data...\n');
// Crear jugadores adicionales para pruebas sociales
const usersData = [
{ email: 'carlos@padel.com', firstName: 'Carlos', lastName: 'Martínez', level: PlayerLevel.ADVANCED },
{ email: 'ana@padel.com', firstName: 'Ana', lastName: 'López', level: PlayerLevel.INTERMEDIATE },
{ email: 'pedro@padel.com', firstName: 'Pedro', lastName: 'Sánchez', level: PlayerLevel.BEGINNER },
{ email: 'maria@padel.com', firstName: 'María', lastName: 'García', level: PlayerLevel.COMPETITION },
];
const createdUsers = [];
for (const userData of usersData) {
const password = await hashPassword('123456');
const user = await prisma.user.upsert({
where: { email: userData.email },
update: {},
create: {
email: userData.email,
password,
firstName: userData.firstName,
lastName: userData.lastName,
role: UserRole.PLAYER,
playerLevel: userData.level,
city: 'Madrid',
yearsPlaying: Math.floor(Math.random() * 10) + 1,
matchesPlayed: Math.floor(Math.random() * 50),
matchesWon: Math.floor(Math.random() * 30),
isActive: true,
},
});
createdUsers.push(user);
console.log(`✅ Usuario creado: ${user.firstName} ${user.lastName} (${user.email})`);
}
// Obtener el admin y user original
const admin = await prisma.user.findUnique({ where: { email: 'admin@padel.com' } });
const user = await prisma.user.findUnique({ where: { email: 'user@padel.com' } });
if (admin && user && createdUsers.length >= 2) {
// Crear relaciones de amistad
const friendships = [
{ requester: user.id, addressee: createdUsers[0].id }, // Juan - Carlos
{ requester: createdUsers[1].id, addressee: user.id }, // Ana - Juan (pendiente)
{ requester: user.id, addressee: createdUsers[2].id }, // Juan - Pedro
];
for (const friendship of friendships) {
await prisma.friend.upsert({
where: {
requesterId_addresseeId: {
requesterId: friendship.requester,
addresseeId: friendship.addressee,
},
},
update: {},
create: {
requesterId: friendship.requester,
addresseeId: friendship.addressee,
status: FriendStatus.ACCEPTED,
},
});
}
console.log('\n✅ Amistades creadas');
// Crear un grupo
const group = await prisma.group.upsert({
where: { id: 'group-1' },
update: {},
create: {
id: 'group-1',
name: 'Los Padelistas',
description: 'Grupo de jugadores regulares los fines de semana',
createdBy: admin.id,
members: {
create: [
{ userId: admin.id, role: GroupRole.ADMIN },
{ userId: user.id, role: GroupRole.MEMBER },
{ userId: createdUsers[0].id, role: GroupRole.MEMBER },
{ userId: createdUsers[1].id, role: GroupRole.MEMBER },
],
},
},
});
console.log(`✅ Grupo creado: ${group.name}`);
// Crear reserva recurrente
const court = await prisma.court.findFirst({ where: { isActive: true } });
if (court) {
const recurring = await prisma.recurringBooking.create({
data: {
userId: user.id,
courtId: court.id,
dayOfWeek: 6, // Sábado
startTime: '10:00',
endTime: '12:00',
startDate: new Date(),
isActive: true,
},
});
console.log(`✅ Reserva recurrente creada: Sábados ${recurring.startTime}-${recurring.endTime}`);
}
// Registrar algunos partidos
const matchResults = [
{
team1: [user.id, createdUsers[0].id],
team2: [createdUsers[1].id, createdUsers[2].id],
score1: 6,
score2: 4,
playedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Hace 1 semana
},
{
team1: [user.id, createdUsers[1].id],
team2: [createdUsers[0].id, createdUsers[2].id],
score1: 3,
score2: 6,
playedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // Hace 3 días
},
];
for (const match of matchResults) {
await prisma.matchResult.create({
data: {
team1Player1Id: match.team1[0],
team1Player2Id: match.team1[1],
team2Player1Id: match.team2[0],
team2Player2Id: match.team2[1],
team1Score: match.score1,
team2Score: match.score2,
winner: match.score1 > match.score2 ? 'TEAM1' : 'TEAM2',
playedAt: match.playedAt,
confirmedBy: [match.team1[0], match.team2[0]],
},
});
}
console.log(`✅ Partidos registrados: ${matchResults.length}`);
// Crear estadísticas de usuario
for (const u of [user, ...createdUsers.slice(0, 2)]) {
await prisma.userStats.upsert({
where: {
userId_period_periodValue: {
userId: u.id,
period: 'ALL_TIME',
periodValue: 'all',
},
},
update: {},
create: {
userId: u.id,
period: 'ALL_TIME',
periodValue: 'all',
matchesPlayed: Math.floor(Math.random() * 30) + 5,
matchesWon: Math.floor(Math.random() * 20),
matchesLost: Math.floor(Math.random() * 10),
points: Math.floor(Math.random() * 500) + 100,
},
});
}
console.log(`✅ Estadísticas de usuarios creadas`);
}
// Actualizar puntos totales de usuarios
await prisma.user.updateMany({
data: {
totalPoints: { increment: 100 },
},
});
console.log('\n🎾 Fase 2 seed completado!');
console.log('\nNuevos usuarios de prueba:');
console.log(' carlos@padel.com / 123456');
console.log(' ana@padel.com / 123456');
console.log(' pedro@padel.com / 123456');
console.log(' maria@padel.com / 123456');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});