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:
2026-01-31 09:02:25 +00:00
parent 6494e2b38b
commit b8a964dc2c
44 changed files with 7084 additions and 9 deletions

View File

@@ -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")
}