✅ FASE 4 COMPLETADA: Pagos y Monetización con MercadoPago
Implementados 4 módulos con agent swarm: 1. MERCADOPAGO INTEGRADO - SDK oficial de MercadoPago - Crear preferencias de pago - Webhooks para notificaciones - Reembolsos y cancelaciones - Estados: PENDING, PROCESSING, COMPLETED, REFUNDED 2. SISTEMA DE BONOS Y PACKS - Pack 5, Pack 10, Pack Mensual - Compra online con MP - Uso FIFO automático - Control de expiración - Aplicación en reservas 3. SUSCRIPCIONES/MEMBRESÍAS - Planes: Básico, Premium, Anual VIP - Beneficios: descuentos, reservas gratis, prioridad - Cobro recurrente vía MP - Estados: ACTIVE, PAUSED, CANCELLED - Aplicación automática en reservas 4. CLASES CON PROFESORES - Registro de coaches con verificación - Tipos: Individual, Grupal, Clínica - Horarios y disponibilidad - Reservas con pago integrado - Sistema de reseñas Endpoints nuevos: - /payments/* - Pagos MercadoPago - /bonus-packs/*, /bonuses/* - Bonos - /subscription-plans/*, /subscriptions/* - Suscripciones - /coaches/* - Profesores - /classes/*, /class-enrollments/* - Clases Variables de entorno: - MERCADOPAGO_ACCESS_TOKEN - MERCADOPAGO_PUBLIC_KEY - MERCADOPAGO_WEBHOOK_SECRET Datos de prueba: - 3 Bonus Packs - 3 Planes de suscripción - 1 Coach verificado (admin) - 3 Clases disponibles
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,166 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "payments" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"referenceId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'ARS',
|
||||
"provider" TEXT NOT NULL DEFAULT 'MERCADOPAGO',
|
||||
"providerPaymentId" TEXT,
|
||||
"providerPreferenceId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"paymentMethod" TEXT,
|
||||
"installments" INTEGER,
|
||||
"metadata" TEXT,
|
||||
"paidAt" DATETIME,
|
||||
"refundedAt" DATETIME,
|
||||
"refundAmount" INTEGER,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "payments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "bonus_packs" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"numberOfBookings" INTEGER NOT NULL,
|
||||
"price" INTEGER NOT NULL,
|
||||
"validityDays" INTEGER NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_bonuses" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"bonusPackId" TEXT NOT NULL,
|
||||
"totalBookings" INTEGER NOT NULL,
|
||||
"usedBookings" INTEGER NOT NULL DEFAULT 0,
|
||||
"remainingBookings" INTEGER NOT NULL,
|
||||
"purchaseDate" DATETIME NOT NULL,
|
||||
"expirationDate" DATETIME NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
|
||||
"paymentId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "user_bonuses_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "user_bonuses_bonusPackId_fkey" FOREIGN KEY ("bonusPackId") REFERENCES "bonus_packs" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "bonus_usages" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userBonusId" TEXT NOT NULL,
|
||||
"bookingId" TEXT NOT NULL,
|
||||
"usedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "bonus_usages_userBonusId_fkey" FOREIGN KEY ("userBonusId") REFERENCES "user_bonuses" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "bonus_usages_bookingId_fkey" FOREIGN KEY ("bookingId") REFERENCES "bookings" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "subscription_plans" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"price" INTEGER NOT NULL,
|
||||
"features" TEXT,
|
||||
"benefits" TEXT NOT NULL,
|
||||
"mercadoPagoPlanId" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_subscriptions" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"planId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"startDate" DATETIME,
|
||||
"endDate" DATETIME,
|
||||
"currentPeriodStart" DATETIME,
|
||||
"currentPeriodEnd" DATETIME,
|
||||
"cancelAtPeriodEnd" BOOLEAN NOT NULL DEFAULT false,
|
||||
"mercadoPagoSubscriptionId" TEXT,
|
||||
"paymentMethodId" TEXT,
|
||||
"lastPaymentDate" DATETIME,
|
||||
"nextPaymentDate" DATETIME,
|
||||
"freeBookingsUsed" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "user_subscriptions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "user_subscriptions_planId_fkey" FOREIGN KEY ("planId") REFERENCES "subscription_plans" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "payments_providerPreferenceId_key" ON "payments"("providerPreferenceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payments_userId_idx" ON "payments"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payments_status_idx" ON "payments"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payments_type_referenceId_idx" ON "payments"("type", "referenceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payments_providerPaymentId_idx" ON "payments"("providerPaymentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payments_providerPreferenceId_idx" ON "payments"("providerPreferenceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payments_createdAt_idx" ON "payments"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "bonus_packs_isActive_idx" ON "bonus_packs"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_bonuses_userId_idx" ON "user_bonuses"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_bonuses_status_idx" ON "user_bonuses"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_bonuses_expirationDate_idx" ON "user_bonuses"("expirationDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_bonuses_userId_status_idx" ON "user_bonuses"("userId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "bonus_usages_userBonusId_idx" ON "bonus_usages"("userBonusId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "bonus_usages_usedAt_idx" ON "bonus_usages"("usedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "bonus_usages_bookingId_key" ON "bonus_usages"("bookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_plans_type_idx" ON "subscription_plans"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_plans_isActive_idx" ON "subscription_plans"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_userId_idx" ON "user_subscriptions"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_planId_idx" ON "user_subscriptions"("planId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_status_idx" ON "user_subscriptions"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_mercadoPagoSubscriptionId_idx" ON "user_subscriptions"("mercadoPagoSubscriptionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_subscriptions_userId_status_key" ON "user_subscriptions"("userId", "status");
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `subscription_plans` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the `user_subscriptions` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropTable
|
||||
PRAGMA foreign_keys=off;
|
||||
DROP TABLE "subscription_plans";
|
||||
PRAGMA foreign_keys=on;
|
||||
|
||||
-- DropTable
|
||||
PRAGMA foreign_keys=off;
|
||||
DROP TABLE "user_subscriptions";
|
||||
PRAGMA foreign_keys=on;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "coaches" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"bio" TEXT,
|
||||
"specialties" TEXT,
|
||||
"certifications" TEXT,
|
||||
"yearsExperience" INTEGER NOT NULL DEFAULT 0,
|
||||
"hourlyRate" INTEGER NOT NULL DEFAULT 0,
|
||||
"photoUrl" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"rating" REAL,
|
||||
"reviewCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "coaches_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "coach_availabilities" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"coachId" TEXT NOT NULL,
|
||||
"dayOfWeek" INTEGER NOT NULL,
|
||||
"startTime" TEXT NOT NULL,
|
||||
"endTime" TEXT NOT NULL,
|
||||
"isAvailable" BOOLEAN NOT NULL DEFAULT true,
|
||||
CONSTRAINT "coach_availabilities_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "coaches" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "classes" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"coachId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" TEXT NOT NULL DEFAULT 'INDIVIDUAL',
|
||||
"maxStudents" INTEGER NOT NULL DEFAULT 1,
|
||||
"price" INTEGER NOT NULL DEFAULT 0,
|
||||
"duration" INTEGER NOT NULL DEFAULT 60,
|
||||
"levelRequired" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "classes_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "coaches" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "class_bookings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"classId" TEXT NOT NULL,
|
||||
"coachId" TEXT NOT NULL,
|
||||
"courtId" TEXT,
|
||||
"date" DATETIME NOT NULL,
|
||||
"startTime" TEXT NOT NULL,
|
||||
"students" TEXT NOT NULL DEFAULT '[]',
|
||||
"maxStudents" INTEGER NOT NULL DEFAULT 1,
|
||||
"enrolledStudents" INTEGER NOT NULL DEFAULT 0,
|
||||
"status" TEXT NOT NULL DEFAULT 'AVAILABLE',
|
||||
"price" INTEGER NOT NULL DEFAULT 0,
|
||||
"paymentId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "class_bookings_classId_fkey" FOREIGN KEY ("classId") REFERENCES "classes" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "class_bookings_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "coaches" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "class_bookings_courtId_fkey" FOREIGN KEY ("courtId") REFERENCES "courts" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "student_enrollments" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"classBookingId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"paymentId" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"enrolledAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"cancelledAt" DATETIME,
|
||||
CONSTRAINT "student_enrollments_classBookingId_fkey" FOREIGN KEY ("classBookingId") REFERENCES "class_bookings" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "student_enrollments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "coach_reviews" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"coachId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"rating" INTEGER NOT NULL,
|
||||
"comment" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "coach_reviews_coachId_fkey" FOREIGN KEY ("coachId") REFERENCES "coaches" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "coach_reviews_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "coaches_userId_key" ON "coaches"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coaches_isActive_idx" ON "coaches"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coaches_isVerified_idx" ON "coaches"("isVerified");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coaches_userId_idx" ON "coaches"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coach_availabilities_coachId_idx" ON "coach_availabilities"("coachId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coach_availabilities_coachId_dayOfWeek_idx" ON "coach_availabilities"("coachId", "dayOfWeek");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coach_availabilities_dayOfWeek_idx" ON "coach_availabilities"("dayOfWeek");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "classes_coachId_idx" ON "classes"("coachId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "classes_type_idx" ON "classes"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "classes_isActive_idx" ON "classes"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "class_bookings_classId_idx" ON "class_bookings"("classId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "class_bookings_coachId_idx" ON "class_bookings"("coachId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "class_bookings_courtId_idx" ON "class_bookings"("courtId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "class_bookings_date_idx" ON "class_bookings"("date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "class_bookings_status_idx" ON "class_bookings"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "student_enrollments_userId_idx" ON "student_enrollments"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "student_enrollments_status_idx" ON "student_enrollments"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "student_enrollments_classBookingId_idx" ON "student_enrollments"("classBookingId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "student_enrollments_classBookingId_userId_key" ON "student_enrollments"("classBookingId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coach_reviews_coachId_idx" ON "coach_reviews"("coachId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coach_reviews_userId_idx" ON "coach_reviews"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "coach_reviews_rating_idx" ON "coach_reviews"("rating");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "coach_reviews_coachId_userId_key" ON "coach_reviews"("coachId", "userId");
|
||||
@@ -0,0 +1,57 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "subscription_plans" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"price" INTEGER NOT NULL,
|
||||
"features" TEXT,
|
||||
"benefits" TEXT NOT NULL,
|
||||
"mercadoPagoPlanId" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_subscriptions" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"planId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"startDate" DATETIME,
|
||||
"endDate" DATETIME,
|
||||
"currentPeriodStart" DATETIME,
|
||||
"currentPeriodEnd" DATETIME,
|
||||
"cancelAtPeriodEnd" BOOLEAN NOT NULL DEFAULT false,
|
||||
"mercadoPagoSubscriptionId" TEXT,
|
||||
"paymentMethodId" TEXT,
|
||||
"lastPaymentDate" DATETIME,
|
||||
"nextPaymentDate" DATETIME,
|
||||
"freeBookingsUsed" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "user_subscriptions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "user_subscriptions_planId_fkey" FOREIGN KEY ("planId") REFERENCES "subscription_plans" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_plans_type_idx" ON "subscription_plans"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "subscription_plans_isActive_idx" ON "subscription_plans"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_userId_idx" ON "user_subscriptions"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_planId_idx" ON "user_subscriptions"("planId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_status_idx" ON "user_subscriptions"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_subscriptions_mercadoPagoSubscriptionId_idx" ON "user_subscriptions"("mercadoPagoSubscriptionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_subscriptions_userId_status_key" ON "user_subscriptions"("userId", "status");
|
||||
@@ -76,6 +76,20 @@ model User {
|
||||
tournamentsCreated Tournament[] @relation("TournamentsCreated")
|
||||
tournamentParticipations TournamentParticipant[]
|
||||
|
||||
// Bonos (Fase 4.2)
|
||||
userBonuses UserBonus[]
|
||||
|
||||
// Pagos (Fase 4.1)
|
||||
payments Payment[]
|
||||
|
||||
// Suscripciones (Fase 4.3)
|
||||
subscriptions UserSubscription[]
|
||||
|
||||
// Clases con profesores (Fase 4.4)
|
||||
coach Coach?
|
||||
studentEnrollments StudentEnrollment[]
|
||||
coachReviews CoachReview[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -132,6 +146,7 @@ model Court {
|
||||
recurringBookings RecurringBooking[]
|
||||
leagueMatches LeagueMatch[]
|
||||
tournamentMatches TournamentMatch[]
|
||||
classBookings ClassBooking[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -193,6 +208,9 @@ model Booking {
|
||||
recurringBooking RecurringBooking? @relation(fields: [recurringBookingId], references: [id])
|
||||
recurringBookingId String?
|
||||
|
||||
// Uso de bonos
|
||||
bonusUsages BonusUsage[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -757,3 +775,449 @@ model LeagueStanding {
|
||||
@@index([points])
|
||||
@@map("league_standings")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Modelo de Pagos (Fase 4.1)
|
||||
// ============================================
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Usuario que realiza el pago
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
// Tipo de pago: BOOKING, TOURNAMENT, BONUS, SUBSCRIPTION, CLASS
|
||||
type String
|
||||
|
||||
// ID de la entidad relacionada (booking, tournament, etc.)
|
||||
referenceId String
|
||||
|
||||
// Monto en centavos (para evitar decimales)
|
||||
amount Int
|
||||
|
||||
// Moneda (ARS, MXN, etc.)
|
||||
currency String @default("ARS")
|
||||
|
||||
// Proveedor de pago
|
||||
provider String @default("MERCADOPAGO")
|
||||
|
||||
// IDs de MercadoPago
|
||||
providerPaymentId String? // ID del pago en MP (cuando se confirma)
|
||||
providerPreferenceId String @unique // ID de la preferencia MP
|
||||
|
||||
// Estado del pago
|
||||
status String @default("PENDING") // PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED, CANCELLED
|
||||
|
||||
// Información del método de pago
|
||||
paymentMethod String?
|
||||
installments Int? // Cantidad de cuotas
|
||||
|
||||
// Metadata adicional (JSON)
|
||||
metadata String?
|
||||
|
||||
// Fechas
|
||||
paidAt DateTime?
|
||||
refundedAt DateTime?
|
||||
refundAmount Int? // Monto reembolsado en centavos
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@index([type, referenceId])
|
||||
@@index([providerPaymentId])
|
||||
@@index([providerPreferenceId])
|
||||
@@index([createdAt])
|
||||
@@map("payments")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Modelos de Sistema de Bonos (Fase 4.2)
|
||||
// ============================================
|
||||
|
||||
// Modelo de Pack de Bonos (tipos de bonos disponibles)
|
||||
model BonusPack {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
|
||||
// Configuración del bono
|
||||
numberOfBookings Int // Cantidad de reservas incluidas
|
||||
price Int // Precio del bono en centavos
|
||||
validityDays Int // Días de validez desde la compra
|
||||
|
||||
// Estado
|
||||
isActive Boolean @default(true)
|
||||
|
||||
// Relaciones
|
||||
userBonuses UserBonus[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([isActive])
|
||||
@@map("bonus_packs")
|
||||
}
|
||||
|
||||
// Modelo de Bono de Usuario (bonos comprados)
|
||||
model UserBonus {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relaciones
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
bonusPack BonusPack @relation(fields: [bonusPackId], references: [id])
|
||||
bonusPackId String
|
||||
|
||||
// Uso del bono
|
||||
totalBookings Int
|
||||
usedBookings Int @default(0)
|
||||
remainingBookings Int
|
||||
|
||||
// Fechas
|
||||
purchaseDate DateTime
|
||||
expirationDate DateTime
|
||||
|
||||
// Estado: ACTIVE, EXPIRED, DEPLETED
|
||||
status String @default("ACTIVE")
|
||||
|
||||
// Referencia al pago
|
||||
paymentId String?
|
||||
|
||||
// Relaciones
|
||||
usages BonusUsage[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@index([expirationDate])
|
||||
@@index([userId, status])
|
||||
@@map("user_bonuses")
|
||||
}
|
||||
|
||||
// Modelo de Uso de Bono (registro de usos)
|
||||
model BonusUsage {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relaciones
|
||||
userBonus UserBonus @relation(fields: [userBonusId], references: [id], onDelete: Cascade)
|
||||
userBonusId String
|
||||
|
||||
// Reserva asociada
|
||||
booking Booking @relation(fields: [bookingId], references: [id])
|
||||
bookingId String
|
||||
|
||||
// Fecha de uso
|
||||
usedAt DateTime @default(now())
|
||||
|
||||
@@unique([bookingId])
|
||||
@@index([userBonusId])
|
||||
@@index([usedAt])
|
||||
@@map("bonus_usages")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Modelos de Sistema de Suscripciones (Fase 4.3)
|
||||
// ============================================
|
||||
|
||||
// Modelo de Plan de Suscripción (planes disponibles)
|
||||
model SubscriptionPlan {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
|
||||
// Tipo de plan
|
||||
type String // MONTHLY, QUARTERLY, YEARLY
|
||||
|
||||
// Precio en centavos
|
||||
price Int
|
||||
|
||||
// Características (JSON array de strings)
|
||||
features String? // Ej: ["Reservas ilimitadas", "Prioridad en reservas"]
|
||||
|
||||
// Beneficios del plan (almacenados como JSON)
|
||||
// discountPercentage: porcentaje de descuento en reservas
|
||||
// freeBookingsPerMonth: cantidad de reservas gratis por mes
|
||||
// priorityBooking: prioridad en reservas
|
||||
// tournamentDiscount: descuento en torneos
|
||||
benefits String // JSON: { discountPercentage, freeBookingsPerMonth, priorityBooking, tournamentDiscount }
|
||||
|
||||
// ID del plan en MercadoPago
|
||||
mercadoPagoPlanId String?
|
||||
|
||||
// Estado
|
||||
isActive Boolean @default(true)
|
||||
|
||||
// Relaciones
|
||||
subscriptions UserSubscription[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([type])
|
||||
@@index([isActive])
|
||||
@@map("subscription_plans")
|
||||
}
|
||||
|
||||
// Modelo de Suscripción de Usuario
|
||||
model UserSubscription {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Usuario
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
// Plan
|
||||
plan SubscriptionPlan @relation(fields: [planId], references: [id])
|
||||
planId String
|
||||
|
||||
// Estado: PENDING, ACTIVE, PAUSED, CANCELLED, EXPIRED
|
||||
status String @default("PENDING")
|
||||
|
||||
// Fechas de suscripción
|
||||
startDate DateTime?
|
||||
endDate DateTime?
|
||||
|
||||
// Período actual
|
||||
currentPeriodStart DateTime?
|
||||
currentPeriodEnd DateTime?
|
||||
|
||||
// Cancelar al final del período
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
|
||||
// Referencia a MercadoPago
|
||||
mercadoPagoSubscriptionId String?
|
||||
|
||||
// Método de pago vinculado
|
||||
paymentMethodId String?
|
||||
|
||||
// Fechas de pagos
|
||||
lastPaymentDate DateTime?
|
||||
nextPaymentDate DateTime?
|
||||
|
||||
// Contador de reservas gratis usadas en el período actual
|
||||
freeBookingsUsed Int @default(0)
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([userId, status])
|
||||
@@index([userId])
|
||||
@@index([planId])
|
||||
@@index([status])
|
||||
@@index([mercadoPagoSubscriptionId])
|
||||
@@map("user_subscriptions")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Modelos de Clases con Profesores (Fase 4.4)
|
||||
// ============================================
|
||||
|
||||
// Modelo de Profesor (Coach)
|
||||
model Coach {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relación con usuario
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String @unique
|
||||
|
||||
// Perfil profesional
|
||||
bio String?
|
||||
specialties String? // JSON array de especialidades
|
||||
certifications String? // JSON array de certificaciones
|
||||
yearsExperience Int @default(0)
|
||||
hourlyRate Int @default(0) // en centavos
|
||||
photoUrl String?
|
||||
|
||||
// Estado
|
||||
isActive Boolean @default(true)
|
||||
isVerified Boolean @default(false)
|
||||
|
||||
// Calificaciones
|
||||
rating Float?
|
||||
reviewCount Int @default(0)
|
||||
|
||||
// Relaciones
|
||||
availabilities CoachAvailability[]
|
||||
classes Class[]
|
||||
classBookings ClassBooking[]
|
||||
coachReviews CoachReview[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([isActive])
|
||||
@@index([isVerified])
|
||||
@@index([userId])
|
||||
@@map("coaches")
|
||||
}
|
||||
|
||||
// Modelo de Disponibilidad del Coach
|
||||
model CoachAvailability {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relación con coach
|
||||
coach Coach @relation(fields: [coachId], references: [id], onDelete: Cascade)
|
||||
coachId String
|
||||
|
||||
// Día de la semana (0=Domingo, 1=Lunes, ..., 6=Sábado)
|
||||
dayOfWeek Int
|
||||
|
||||
// Horario
|
||||
startTime String
|
||||
endTime String
|
||||
|
||||
// Estado
|
||||
isAvailable Boolean @default(true)
|
||||
|
||||
@@index([coachId])
|
||||
@@index([coachId, dayOfWeek])
|
||||
@@index([dayOfWeek])
|
||||
@@map("coach_availabilities")
|
||||
}
|
||||
|
||||
// Modelo de Clase (programa/tipo de clase)
|
||||
model Class {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relación con coach
|
||||
coach Coach @relation(fields: [coachId], references: [id], onDelete: Cascade)
|
||||
coachId String
|
||||
|
||||
// Información de la clase
|
||||
title String
|
||||
description String?
|
||||
|
||||
// Tipo: INDIVIDUAL, GROUP, CLINIC
|
||||
type String @default("INDIVIDUAL")
|
||||
|
||||
// Configuración
|
||||
maxStudents Int @default(1) // Máximo de alumnos
|
||||
price Int @default(0) // Precio por persona en centavos
|
||||
duration Int @default(60) // Duración en minutos
|
||||
|
||||
// Nivel mínimo requerido
|
||||
levelRequired String?
|
||||
|
||||
// Estado
|
||||
isActive Boolean @default(true)
|
||||
|
||||
// Relaciones
|
||||
sessions ClassBooking[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([coachId])
|
||||
@@index([type])
|
||||
@@index([isActive])
|
||||
@@map("classes")
|
||||
}
|
||||
|
||||
// Modelo de Sesión de Clase (instancia específica de una clase)
|
||||
model ClassBooking {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relaciones
|
||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||
classId String
|
||||
|
||||
coach Coach @relation(fields: [coachId], references: [id])
|
||||
coachId String
|
||||
|
||||
court Court? @relation(fields: [courtId], references: [id], onDelete: SetNull)
|
||||
courtId String?
|
||||
|
||||
// Fecha y hora
|
||||
date DateTime
|
||||
startTime String
|
||||
|
||||
// Estudiantes (JSON array de userIds)
|
||||
students String @default("[]")
|
||||
|
||||
// Cupo
|
||||
maxStudents Int @default(1)
|
||||
enrolledStudents Int @default(0)
|
||||
|
||||
// Estado: AVAILABLE, FULL, COMPLETED, CANCELLED
|
||||
status String @default("AVAILABLE")
|
||||
|
||||
// Precio
|
||||
price Int @default(0)
|
||||
|
||||
// Pago
|
||||
paymentId String?
|
||||
|
||||
// Relaciones
|
||||
enrollments StudentEnrollment[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([classId])
|
||||
@@index([coachId])
|
||||
@@index([courtId])
|
||||
@@index([date])
|
||||
@@index([status])
|
||||
@@map("class_bookings")
|
||||
}
|
||||
|
||||
// Modelo de Inscripción de Estudiante
|
||||
model StudentEnrollment {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relaciones
|
||||
classBooking ClassBooking @relation(fields: [classBookingId], references: [id], onDelete: Cascade)
|
||||
classBookingId String
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
// Referencia al pago
|
||||
paymentId String?
|
||||
|
||||
// Estado: PENDING, CONFIRMED, CANCELLED, ATTENDED
|
||||
status String @default("PENDING")
|
||||
|
||||
// Timestamps
|
||||
enrolledAt DateTime @default(now())
|
||||
cancelledAt DateTime?
|
||||
|
||||
@@unique([classBookingId, userId])
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@index([classBookingId])
|
||||
@@map("student_enrollments")
|
||||
}
|
||||
|
||||
// Modelo de Reseña de Coach
|
||||
model CoachReview {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relaciones
|
||||
coach Coach @relation(fields: [coachId], references: [id], onDelete: Cascade)
|
||||
coachId String
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
// Calificación (1-5)
|
||||
rating Int
|
||||
comment String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([coachId, userId])
|
||||
@@index([coachId])
|
||||
@@index([userId])
|
||||
@@index([rating])
|
||||
@@map("coach_reviews")
|
||||
}
|
||||
|
||||
159
backend/prisma/seed-fase4.ts
Normal file
159
backend/prisma/seed-fase4.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding Fase 4 - Pagos y Monetización...\n');
|
||||
|
||||
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) {
|
||||
console.log('❌ Usuarios no encontrados. Ejecuta seed.ts primero.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Crear Bonus Packs
|
||||
const bonusPacks = [
|
||||
{ name: 'Pack 5 Clases', bookings: 5, price: 9000, validity: 90 },
|
||||
{ name: 'Pack 10 Clases', bookings: 10, price: 16000, validity: 180 },
|
||||
{ name: 'Pack Mensual', bookings: 30, price: 40000, validity: 30 },
|
||||
];
|
||||
|
||||
for (const pack of bonusPacks) {
|
||||
await prisma.bonusPack.upsert({
|
||||
where: { id: `bonus-${pack.bookings}` },
|
||||
update: {},
|
||||
create: {
|
||||
id: `bonus-${pack.bookings}`,
|
||||
name: pack.name,
|
||||
description: `${pack.bookings} reservas de 1 hora cada una`,
|
||||
numberOfBookings: pack.bookings,
|
||||
price: pack.price,
|
||||
validityDays: pack.validity,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
console.log(`✅ Bonus Pack creado: ${pack.name}`);
|
||||
}
|
||||
|
||||
// Crear planes de suscripción
|
||||
const plans = [
|
||||
{
|
||||
name: 'Básico',
|
||||
type: 'MONTHLY',
|
||||
price: 15000,
|
||||
benefits: { discountPercentage: 10, freeBookingsPerMonth: 2, priorityBooking: false, tournamentDiscount: 5 },
|
||||
desc: '10% off en reservas, 2 reservas gratis/mes'
|
||||
},
|
||||
{
|
||||
name: 'Premium',
|
||||
type: 'MONTHLY',
|
||||
price: 25000,
|
||||
benefits: { discountPercentage: 20, freeBookingsPerMonth: 5, priorityBooking: true, tournamentDiscount: 10 },
|
||||
desc: '20% off en reservas, 5 reservas gratis/mes, prioridad'
|
||||
},
|
||||
{
|
||||
name: 'Anual VIP',
|
||||
type: 'YEARLY',
|
||||
price: 250000,
|
||||
benefits: { discountPercentage: 30, freeBookingsPerMonth: 10, priorityBooking: true, tournamentDiscount: 15 },
|
||||
desc: '30% off en reservas, 10 reservas gratis/mes'
|
||||
},
|
||||
];
|
||||
|
||||
for (const plan of plans) {
|
||||
await prisma.subscriptionPlan.upsert({
|
||||
where: { id: `plan-${plan.name.toLowerCase().replace(/ /g, '-')}` },
|
||||
update: {},
|
||||
create: {
|
||||
id: `plan-${plan.name.toLowerCase().replace(/ /g, '-')}`,
|
||||
name: plan.name,
|
||||
description: plan.desc,
|
||||
type: plan.type,
|
||||
price: plan.price,
|
||||
benefits: JSON.stringify(plan.benefits),
|
||||
features: JSON.stringify([`${plan.benefits.discountPercentage}% descuento`, `${plan.benefits.freeBookingsPerMonth} reservas gratis`, plan.benefits.priorityBooking ? 'Prioridad de reserva' : 'Sin prioridad']),
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
console.log(`✅ Plan de suscripción creado: ${plan.name}`);
|
||||
}
|
||||
|
||||
// Registrar usuario como coach
|
||||
const coach = await prisma.coach.upsert({
|
||||
where: { id: 'coach-1' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'coach-1',
|
||||
userId: admin.id,
|
||||
bio: 'Profesor de pádel con 10 años de experiencia. Especialista en técnica y táctica.',
|
||||
specialties: JSON.stringify(['Técnica', 'Táctica', 'Volea', 'Smash']),
|
||||
certifications: 'Entrenador Nacional Nivel 3',
|
||||
yearsExperience: 10,
|
||||
hourlyRate: 5000,
|
||||
isActive: true,
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
console.log(`✅ Coach creado: ${coach.id}`);
|
||||
|
||||
// Crear disponibilidad del coach
|
||||
for (let day = 1; day <= 5; day++) { // Lunes a Viernes
|
||||
await prisma.coachAvailability.upsert({
|
||||
where: { id: `avail-${coach.id}-${day}` },
|
||||
update: {},
|
||||
create: {
|
||||
id: `avail-${coach.id}-${day}`,
|
||||
coachId: coach.id,
|
||||
dayOfWeek: day,
|
||||
startTime: '09:00',
|
||||
endTime: '18:00',
|
||||
isAvailable: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('✅ Disponibilidad del coach creada');
|
||||
|
||||
// Crear clases
|
||||
const classes = [
|
||||
{ name: 'Clase Individual', type: 'INDIVIDUAL', max: 1, price: 5000, duration: 60 },
|
||||
{ name: 'Clase en Pareja', type: 'GROUP', max: 2, price: 3500, duration: 60 },
|
||||
{ name: 'Clínica de Volea', type: 'CLINIC', max: 8, price: 2000, duration: 90 },
|
||||
];
|
||||
|
||||
for (const cls of classes) {
|
||||
await prisma.class.upsert({
|
||||
where: { id: `class-${cls.name.toLowerCase().replace(/ /g, '-')}` },
|
||||
update: {},
|
||||
create: {
|
||||
id: `class-${cls.name.toLowerCase().replace(/ /g, '-')}`,
|
||||
coachId: coach.id,
|
||||
title: cls.name,
|
||||
description: `Clase especializada de ${cls.name.toLowerCase()}`,
|
||||
type: cls.type,
|
||||
maxStudents: cls.max,
|
||||
price: cls.price,
|
||||
duration: cls.duration,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
console.log(`✅ Clase creada: ${cls.name}`);
|
||||
}
|
||||
|
||||
console.log('\n🎾 Fase 4 seed completado!');
|
||||
console.log('\nDatos creados:');
|
||||
console.log(` - 3 Bonus Packs`);
|
||||
console.log(` - 3 Planes de suscripción`);
|
||||
console.log(` - 1 Coach verificado`);
|
||||
console.log(` - 3 Clases disponibles`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user