✅ 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:
136
backend/_future_services/bonusPack.service.ts
Normal file
136
backend/_future_services/bonusPack.service.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import prisma from '../config/database';
|
||||
import { ApiError } from '../middleware/errorHandler';
|
||||
import logger from '../config/logger';
|
||||
|
||||
export interface CreateBonusPackInput {
|
||||
name: string;
|
||||
description?: string;
|
||||
numberOfBookings: number;
|
||||
price: number;
|
||||
validityDays: number;
|
||||
}
|
||||
|
||||
export interface UpdateBonusPackInput {
|
||||
name?: string;
|
||||
description?: string;
|
||||
numberOfBookings?: number;
|
||||
price?: number;
|
||||
validityDays?: number;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export class BonusPackService {
|
||||
// Crear un tipo de bono (admin)
|
||||
static async createBonusPack(adminId: string, data: CreateBonusPackInput) {
|
||||
// Validar que el precio sea positivo
|
||||
if (data.price < 0) {
|
||||
throw new ApiError('El precio no puede ser negativo', 400);
|
||||
}
|
||||
|
||||
// Validar que la cantidad de reservas sea positiva
|
||||
if (data.numberOfBookings <= 0) {
|
||||
throw new ApiError('La cantidad de reservas debe ser mayor a 0', 400);
|
||||
}
|
||||
|
||||
// Validar que los días de validez sean positivos
|
||||
if (data.validityDays <= 0) {
|
||||
throw new ApiError('Los días de validez deben ser mayor a 0', 400);
|
||||
}
|
||||
|
||||
const bonusPack = await prisma.bonusPack.create({
|
||||
data: {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
numberOfBookings: data.numberOfBookings,
|
||||
price: data.price,
|
||||
validityDays: data.validityDays,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info(`BonusPack creado: ${bonusPack.id} por admin: ${adminId}`);
|
||||
|
||||
return bonusPack;
|
||||
}
|
||||
|
||||
// Obtener todos los bonos activos (público)
|
||||
static async getBonusPacks(includeInactive = false) {
|
||||
const where = includeInactive ? {} : { isActive: true };
|
||||
|
||||
return prisma.bonusPack.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
price: 'asc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Obtener un bono por ID
|
||||
static async getBonusPackById(id: string) {
|
||||
const bonusPack = await prisma.bonusPack.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!bonusPack) {
|
||||
throw new ApiError('Pack de bonos no encontrado', 404);
|
||||
}
|
||||
|
||||
return bonusPack;
|
||||
}
|
||||
|
||||
// Actualizar un tipo de bono (admin)
|
||||
static async updateBonusPack(id: string, adminId: string, data: UpdateBonusPackInput) {
|
||||
const bonusPack = await prisma.bonusPack.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!bonusPack) {
|
||||
throw new ApiError('Pack de bonos no encontrado', 404);
|
||||
}
|
||||
|
||||
// Validaciones si se actualizan ciertos campos
|
||||
if (data.price !== undefined && data.price < 0) {
|
||||
throw new ApiError('El precio no puede ser negativo', 400);
|
||||
}
|
||||
|
||||
if (data.numberOfBookings !== undefined && data.numberOfBookings <= 0) {
|
||||
throw new ApiError('La cantidad de reservas debe ser mayor a 0', 400);
|
||||
}
|
||||
|
||||
if (data.validityDays !== undefined && data.validityDays <= 0) {
|
||||
throw new ApiError('Los días de validez deben ser mayor a 0', 400);
|
||||
}
|
||||
|
||||
const updated = await prisma.bonusPack.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
|
||||
logger.info(`BonusPack actualizado: ${id} por admin: ${adminId}`);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
// Eliminar (desactivar) un tipo de bono (admin)
|
||||
static async deleteBonusPack(id: string, adminId: string) {
|
||||
const bonusPack = await prisma.bonusPack.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!bonusPack) {
|
||||
throw new ApiError('Pack de bonos no encontrado', 404);
|
||||
}
|
||||
|
||||
// Desactivar en lugar de eliminar físicamente
|
||||
const updated = await prisma.bonusPack.update({
|
||||
where: { id },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
logger.info(`BonusPack desactivado: ${id} por admin: ${adminId}`);
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
export default BonusPackService;
|
||||
Reference in New Issue
Block a user