✅ FASE 5 COMPLETADA: Analytics y Administración
Implementados 5 módulos de analytics con agent swarm: 1. DASHBOARD ADMINISTRATIVO - Resumen ejecutivo (reservas, ingresos, usuarios) - Vista del día con alertas - Calendario semanal de ocupación 2. MÉTRICAS DE OCUPACIÓN - Ocupación por fecha, cancha, franja horaria - Horas pico (top 5 demandados) - Comparativa entre períodos - Tendencias de uso 3. MÉTRICAS FINANCIERAS - Ingresos por período, cancha, tipo - Métodos de pago más usados - Estadísticas de reembolsos - Tendencias de crecimiento - Top días de ingresos 4. MÉTRICAS DE USUARIOS - Stats generales y actividad - Top jugadores (por partidos/victorias/puntos) - Detección de churn (riesgo de abandono) - Tasa de retención - Crecimiento mensual 5. EXPORTACIÓN DE DATOS - Exportar a CSV (separado por ;) - Exportar a JSON - Exportar a Excel (múltiples hojas) - Reportes completos descargables Endpoints nuevos (solo admin): - /analytics/dashboard/* - /analytics/occupancy/* - /analytics/revenue/* - /analytics/reports/* - /analytics/users/* - /analytics/exports/* Dependencias: - xlsx - Generación de archivos Excel Utilidades: - Cálculo de crecimiento porcentual - Formateo de moneda - Agrupación por fechas - Relleno de fechas faltantes
This commit is contained in:
142
backend/src/controllers/analytics/report.controller.ts
Normal file
142
backend/src/controllers/analytics/report.controller.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { ReportService } from '../../services/analytics/report.service';
|
||||
import { ApiError } from '../../middleware/errorHandler';
|
||||
|
||||
// Validar fechas
|
||||
const validateDates = (startDateStr: string, endDateStr: string): { startDate: Date; endDate: Date } => {
|
||||
const startDate = new Date(startDateStr);
|
||||
const endDate = new Date(endDateStr);
|
||||
|
||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
throw new ApiError('Fechas inválidas', 400);
|
||||
}
|
||||
|
||||
if (startDate > endDate) {
|
||||
throw new ApiError('La fecha de inicio debe ser anterior a la fecha de fin', 400);
|
||||
}
|
||||
|
||||
// Ajustar endDate para incluir todo el día
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
|
||||
return { startDate, endDate };
|
||||
};
|
||||
|
||||
export class ReportController {
|
||||
/**
|
||||
* GET /analytics/reports/revenue
|
||||
* Reporte completo de ingresos
|
||||
*/
|
||||
static async getRevenueReport(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { startDate: startDateStr, endDate: endDateStr } = req.query;
|
||||
|
||||
if (!startDateStr || !endDateStr) {
|
||||
throw new ApiError('Se requieren fechas de inicio y fin', 400);
|
||||
}
|
||||
|
||||
const { startDate, endDate } = validateDates(startDateStr as string, endDateStr as string);
|
||||
|
||||
const report = await ReportService.generateRevenueReport(startDate, endDate);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: report,
|
||||
meta: {
|
||||
reportType: 'revenue',
|
||||
generatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /analytics/reports/occupancy
|
||||
* Reporte de ocupación
|
||||
*/
|
||||
static async getOccupancyReport(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { startDate: startDateStr, endDate: endDateStr } = req.query;
|
||||
|
||||
if (!startDateStr || !endDateStr) {
|
||||
throw new ApiError('Se requieren fechas de inicio y fin', 400);
|
||||
}
|
||||
|
||||
const { startDate, endDate } = validateDates(startDateStr as string, endDateStr as string);
|
||||
|
||||
const report = await ReportService.generateOccupancyReport(startDate, endDate);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: report,
|
||||
meta: {
|
||||
reportType: 'occupancy',
|
||||
generatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /analytics/reports/users
|
||||
* Reporte de usuarios
|
||||
*/
|
||||
static async getUserReport(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { startDate: startDateStr, endDate: endDateStr } = req.query;
|
||||
|
||||
if (!startDateStr || !endDateStr) {
|
||||
throw new ApiError('Se requieren fechas de inicio y fin', 400);
|
||||
}
|
||||
|
||||
const { startDate, endDate } = validateDates(startDateStr as string, endDateStr as string);
|
||||
|
||||
const report = await ReportService.generateUserReport(startDate, endDate);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: report,
|
||||
meta: {
|
||||
reportType: 'users',
|
||||
generatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /analytics/reports/summary
|
||||
* Resumen ejecutivo
|
||||
*/
|
||||
static async getExecutiveSummary(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { startDate: startDateStr, endDate: endDateStr } = req.query;
|
||||
|
||||
if (!startDateStr || !endDateStr) {
|
||||
throw new ApiError('Se requieren fechas de inicio y fin', 400);
|
||||
}
|
||||
|
||||
const { startDate, endDate } = validateDates(startDateStr as string, endDateStr as string);
|
||||
|
||||
const summary = await ReportService.getReportSummary(startDate, endDate);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: summary,
|
||||
meta: {
|
||||
reportType: 'executive-summary',
|
||||
generatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ReportController;
|
||||
Reference in New Issue
Block a user