✅ 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:
20
backend/_future/class.routes.ts
Normal file
20
backend/_future/class.routes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Router } from 'express';
|
||||
import { ClassController } from '../controllers/class.controller';
|
||||
import { authenticate, authorize } from '../middleware/auth';
|
||||
import { validate } from '../middleware/validate';
|
||||
import { createClassSchema, createClassBookingSchema } from '../validators/class.validator';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Rutas públicas
|
||||
router.get('/', ClassController.getClasses);
|
||||
router.get('/:id', ClassController.getClassById);
|
||||
router.get('/:id/sessions', ClassController.getClassBookings);
|
||||
|
||||
// Rutas protegidas (solo coaches)
|
||||
router.post('/', authenticate, validate(createClassSchema), ClassController.createClass);
|
||||
router.put('/:id', authenticate, ClassController.updateClass);
|
||||
router.delete('/:id', authenticate, ClassController.deleteClass);
|
||||
router.post('/:id/sessions', authenticate, validate(createClassBookingSchema), ClassController.createClassBooking);
|
||||
|
||||
export default router;
|
||||
103
backend/_future/classEnrollment.controller.ts
Normal file
103
backend/_future/classEnrollment.controller.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { ClassEnrollmentService } from '../services/classEnrollment.service';
|
||||
import { ApiError } from '../middleware/errorHandler';
|
||||
|
||||
export class ClassEnrollmentController {
|
||||
// Inscribirse en una clase
|
||||
static async enrollInClass(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.userId;
|
||||
const result = await ClassEnrollmentService.enrollInClass(userId, req.body);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Inscripción creada exitosamente',
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Webhook de MercadoPago
|
||||
static async webhook(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
// Responder inmediatamente a MP
|
||||
res.status(200).send('OK');
|
||||
|
||||
// Procesar el webhook de forma asíncrona
|
||||
await ClassEnrollmentService.processPaymentWebhook(req.body);
|
||||
} catch (error) {
|
||||
// Loggear error pero no enviar respuesta (ya se envió 200)
|
||||
console.error('Error procesando webhook:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Cancelar inscripción
|
||||
static async cancelEnrollment(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.userId;
|
||||
const { id } = req.params;
|
||||
const result = await ClassEnrollmentService.cancelEnrollment(userId, id);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener mis inscripciones
|
||||
static async getMyEnrollments(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.userId;
|
||||
const status = req.query.status as string | undefined;
|
||||
const enrollments = await ClassEnrollmentService.getMyEnrollments(userId, status);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: enrollments.length,
|
||||
data: enrollments,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener inscripción por ID
|
||||
static async getEnrollmentById(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.userId;
|
||||
const { id } = req.params;
|
||||
const enrollment = await ClassEnrollmentService.getEnrollmentById(id, userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: enrollment,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Marcar asistencia (solo coach)
|
||||
static async markAttendance(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.userId;
|
||||
const { id } = req.params;
|
||||
const enrollment = await ClassEnrollmentService.markAttendance(id, userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Asistencia marcada exitosamente',
|
||||
data: enrollment,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ClassEnrollmentController;
|
||||
19
backend/_future/classEnrollment.routes.ts
Normal file
19
backend/_future/classEnrollment.routes.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Router } from 'express';
|
||||
import { ClassEnrollmentController } from '../controllers/classEnrollment.controller';
|
||||
import { authenticate, authorize } from '../middleware/auth';
|
||||
import { validate } from '../middleware/validate';
|
||||
import { enrollmentSchema } from '../validators/class.validator';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Webhook de MercadoPago (público)
|
||||
router.post('/webhook', ClassEnrollmentController.webhook);
|
||||
|
||||
// Rutas protegidas
|
||||
router.post('/', authenticate, validate(enrollmentSchema), ClassEnrollmentController.enrollInClass);
|
||||
router.get('/my', authenticate, ClassEnrollmentController.getMyEnrollments);
|
||||
router.get('/:id', authenticate, ClassEnrollmentController.getEnrollmentById);
|
||||
router.delete('/:id', authenticate, ClassEnrollmentController.cancelEnrollment);
|
||||
router.put('/:id/attend', authenticate, ClassEnrollmentController.markAttendance);
|
||||
|
||||
export default router;
|
||||
29
backend/_future/coach.routes.ts
Normal file
29
backend/_future/coach.routes.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Router } from 'express';
|
||||
import { CoachController } from '../controllers/coach.controller';
|
||||
import { authenticate, authorize } from '../middleware/auth';
|
||||
import { validate } from '../middleware/validate';
|
||||
import { registerCoachSchema, reviewSchema } from '../validators/class.validator';
|
||||
import { UserRole } from '../utils/constants';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Rutas públicas
|
||||
router.get('/', CoachController.getCoaches);
|
||||
router.get('/:id', CoachController.getCoachById);
|
||||
router.get('/:id/availability', CoachController.getAvailability);
|
||||
router.get('/:id/reviews', CoachController.getReviews);
|
||||
|
||||
// Rutas protegidas (usuarios autenticados)
|
||||
router.post('/register', authenticate, validate(registerCoachSchema), CoachController.registerAsCoach);
|
||||
router.get('/me/profile', authenticate, CoachController.getMyProfile);
|
||||
router.put('/me', authenticate, CoachController.updateMyProfile);
|
||||
router.post('/me/availability', authenticate, CoachController.addAvailability);
|
||||
router.post('/:id/reviews', authenticate, validate(reviewSchema), CoachController.addReview);
|
||||
|
||||
// Eliminar disponibilidad
|
||||
router.delete('/availability/:id', authenticate, CoachController.removeAvailability);
|
||||
|
||||
// Rutas de admin
|
||||
router.put('/:id/verify', authenticate, authorize(UserRole.ADMIN, UserRole.SUPERADMIN), CoachController.verifyCoach);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user