calculator.js•56.3 kB
#!/usr/bin/env node
import { validator } from '../utils/validator.js';
import { dataLoader } from '../data/dataLoader.js';
/**
* 🧮 Módulo de Cálculos de Negocio TreePod Financial MCP
* Implementa la Guía de Trabajo Fundamental: Lógica basada en datos reales
*/
export class BusinessCalculator {
/**
* Calcula tarifas basado en datos reales de configuración
*/
async calculateTariff(checkinDate, checkoutDate, guests, channel = 'directo') {
// Validar parámetros de entrada
const inputValidation = validator.validateUserInput({
checkin_date: checkinDate,
checkout_date: checkoutDate,
guests: guests,
channel: channel
}, {
checkin_date: { required: true, type: 'string' },
checkout_date: { required: true, type: 'string' },
guests: { required: true, type: 'number', min: 1, max: 4 },
channel: { required: false, type: 'string', enum: ['directo', 'airbnb', 'booking', 'whatsapp'] }
});
if (!inputValidation.valid) {
return validator.generateInsufficientDataResponse(
'parámetros de tarifa',
`Errores: ${inputValidation.errors.join(', ')}`
);
}
// Validar fechas
const dateValidation = validator.validateDateRange(checkinDate, checkoutDate);
if (!dateValidation.valid) {
return validator.generateInsufficientDataResponse(
'fechas de reserva',
dateValidation.error
);
}
// Cargar configuración de tarifas desde datos reales
const financialData = await dataLoader.loadFinancialData();
if (!financialData || !financialData.configuracion_negocio) {
return validator.generateInsufficientDataResponse(
'configuración de tarifas',
'No se pudo acceder a la configuración de precios del sistema'
);
}
const config = financialData.configuracion_negocio;
// Verificar que existen las tarifas por temporada
if (!config.tarifas_temporada) {
return validator.generateInsufficientDataResponse(
'tarifas por temporada',
'La configuración no incluye tarifas por temporada'
);
}
try {
const season = this.determineSeason(dateValidation.checkin, config);
const seasonData = config.tarifas_temporada[season];
if (!seasonData) {
return validator.generateInsufficientDataResponse(
`tarifas para temporada ${season}`,
'No se encontraron tarifas para la temporada solicitada'
);
}
// Calcular tarifa base según número de huéspedes
const baseTariff = this.calculateBaseTariff(guests, seasonData);
const nights = dateValidation.nights;
const subtotal = baseTariff * nights;
// Calcular comisión según canal
const commissionRate = this.getCommissionRate(channel, config);
const commission = Math.round(subtotal * commissionRate);
const total = subtotal + commission;
validator.log('info', `Tarifa calculada: ${guests} huéspedes, ${nights} noches, temporada ${season}, canal ${channel}`);
return {
content: [{
type: 'text',
text: this.formatTariffResponse({
checkinDate,
checkoutDate,
guests,
channel,
season,
nights,
baseTariff,
subtotal,
commissionRate: commissionRate * 100,
commission,
total
})
}]
};
} catch (error) {
validator.log('error', `Error calculando tarifa: ${error.message}`);
return validator.generateInsufficientDataResponse(
'cálculo de tarifa',
'Error interno en el cálculo. Contacta al administrador.'
);
}
}
/**
* Determina temporada basado en configuración real
*/
determineSeason(date, config) {
if (!config.temporadas) {
validator.log('warning', 'Configuración de temporadas no encontrada, usando lógica por defecto');
// Lógica básica si no hay configuración
const month = date.getMonth() + 1;
if (month >= 12 || month <= 2) return 'alta';
if (month >= 6 && month <= 8) return 'alta';
return 'media';
}
// Usar configuración real de temporadas
const month = date.getMonth() + 1;
const day = date.getDate();
for (const [seasonName, periods] of Object.entries(config.temporadas)) {
for (const period of periods) {
if (this.isDateInPeriod(month, day, period)) {
return seasonName;
}
}
}
return 'media'; // temporada por defecto
}
/**
* Verifica si una fecha está en un período específico
*/
isDateInPeriod(month, day, period) {
const startMonth = period.inicio.mes;
const startDay = period.inicio.dia;
const endMonth = period.fin.mes;
const endDay = period.fin.dia;
if (startMonth === endMonth) {
return month === startMonth && day >= startDay && day <= endDay;
}
if (startMonth < endMonth) {
return (month === startMonth && day >= startDay) ||
(month > startMonth && month < endMonth) ||
(month === endMonth && day <= endDay);
}
// Período que cruza año (ej: diciembre a febrero)
return (month === startMonth && day >= startDay) ||
(month > startMonth || month < endMonth) ||
(month === endMonth && day <= endDay);
}
/**
* Calcula tarifa base según huéspedes y temporada
*/
calculateBaseTariff(guests, seasonData) {
if (seasonData.por_huesped && seasonData.tarifa_base_por_persona) {
return seasonData.tarifa_base_por_persona * guests;
}
if (seasonData.tarifas_por_ocupacion) {
const guestKey = `${guests}_personas`;
return seasonData.tarifas_por_ocupacion[guestKey] || seasonData.tarifa_base || 80000;
}
return seasonData.tarifa_base || 80000;
}
/**
* Obtiene tasa de comisión desde configuración real
*/
getCommissionRate(channel, config) {
if (!config.comisiones_canal) {
validator.log('warning', 'Configuración de comisiones no encontrada, usando valores por defecto');
const defaultRates = {
'directo': 0,
'airbnb': 0.15,
'booking': 0.18,
'whatsapp': 0.05
};
return defaultRates[channel] || 0;
}
return config.comisiones_canal[channel] || 0;
}
/**
* Formatea respuesta de tarifa
*/
formatTariffResponse(data) {
return `💰 **CÁLCULO DE TARIFA TREEPOD GLAMPING**\n\n` +
`📅 **Reserva:**\n` +
`• Check-in: ${data.checkinDate}\n` +
`• Check-out: ${data.checkoutDate}\n` +
`• Noches: ${data.nights}\n` +
`• Huéspedes: ${data.guests}\n` +
`• Canal: ${data.channel}\n` +
`• Temporada: ${data.season}\n\n` +
`💵 **Desglose:**\n` +
`• Tarifa por noche: ${this.formatCurrency(data.baseTariff)}\n` +
`• Subtotal (${data.nights} noches): ${this.formatCurrency(data.subtotal)}\n` +
`• Comisión ${data.channel} (${data.commissionRate}%): ${this.formatCurrency(data.commission)}\n` +
`• **TOTAL: ${this.formatCurrency(data.total)}**\n\n` +
`*Cálculo basado en configuración real del sistema TreePod*`;
}
/**
* Formatea moneda chilena
*/
formatCurrency(amount) {
return new Intl.NumberFormat('es-CL', {
style: 'currency',
currency: 'CLP',
minimumFractionDigits: 0
}).format(amount);
}
/**
* Analiza métricas financieras desde datos reales
*/
async analyzeFinancialMetrics(period = 'current') {
const financialData = await dataLoader.loadFinancialData();
if (!financialData) {
return validator.generateInsufficientDataResponse(
'datos financieros',
'No se pudo acceder a los datos financieros del sistema'
);
}
// Verificar campos esenciales
const requiredFields = ['ingresos_total', 'gastos_total'];
const missingFields = requiredFields.filter(field => !(field in financialData));
if (missingFields.length > 0) {
return validator.generateInsufficientDataResponse(
'métricas financieras',
`Faltan datos esenciales: ${missingFields.join(', ')}`
);
}
try {
const analysis = this.calculateFinancialAnalysis(financialData, period);
validator.log('info', `Análisis financiero completado para período: ${period}`);
return {
content: [{
type: 'text',
text: this.formatFinancialAnalysis(analysis)
}]
};
} catch (error) {
validator.log('error', `Error en análisis financiero: ${error.message}`);
return validator.generateInsufficientDataResponse(
'análisis financiero',
'Error interno en el análisis. Contacta al administrador.'
);
}
}
/**
* Calcula análisis financiero
*/
calculateFinancialAnalysis(data, period) {
const ingresos = data.ingresos_total || 0;
const gastos = data.gastos_total || 0;
const utilidad = ingresos - gastos;
const margen = ingresos > 0 ? ((utilidad / ingresos) * 100).toFixed(1) : 0;
const ocupacion = data.ocupacion_promedio || 0;
const reservas = data.reservas_totales || 0;
return {
periodo: period,
ingresos,
gastos,
utilidad,
margen,
ocupacion,
reservas,
ingresoPromedioPorReserva: reservas > 0 ? Math.round(ingresos / reservas) : 0,
metaIngresos: ingresos >= 6000000,
metaUtilidad: utilidad >= 2000000,
metaOcupacion: ocupacion >= 70
};
}
/**
* Formatea análisis financiero
*/
formatFinancialAnalysis(analysis) {
const estado = this.determineBusinessStatus(analysis);
return `📊 **ANÁLISIS FINANCIERO TREEPOD GLAMPING**\n\n` +
`📅 **Período:** ${analysis.periodo}\n` +
`💰 **Estado General:** ${estado}\n\n` +
`**💵 FINANZAS:**\n` +
`• Ingresos: ${this.formatCurrency(analysis.ingresos)} ${analysis.metaIngresos ? '✅' : '⚠️'}\n` +
`• Gastos: ${this.formatCurrency(analysis.gastos)}\n` +
`• Utilidad: ${this.formatCurrency(analysis.utilidad)} ${analysis.metaUtilidad ? '✅' : '⚠️'}\n` +
`• Margen: ${analysis.margen}% ${analysis.margen > 30 ? '✅' : '⚠️'}\n\n` +
`**🏠 OPERACIONES:**\n` +
`• Ocupación: ${analysis.ocupacion}% ${analysis.metaOcupacion ? '✅' : '⚠️'}\n` +
`• Reservas totales: ${analysis.reservas}\n` +
`• Ingreso promedio/reserva: ${this.formatCurrency(analysis.ingresoPromedioPorReserva)}\n\n` +
`*Análisis basado en datos reales del sistema TreePod*`;
}
/**
* Determina estado del negocio basado en métricas
*/
determineBusinessStatus(analysis) {
if (analysis.metaIngresos && analysis.metaUtilidad && analysis.metaOcupacion) {
return '🟢 Excelente';
}
if (analysis.utilidad > 1500000 && analysis.ocupacion > 50) {
return '🟡 Bien';
}
return '🔴 Atención requerida';
}
/**
* Analiza ocupación de domos basado en datos reales
*/
async analyzeOccupancy(businessData, domosStatus, dateRange = 'today') {
if (!domosStatus || !Array.isArray(domosStatus)) {
return validator.generateInsufficientDataResponse(
'estado de domos',
'Los datos de estado de domos no tienen el formato esperado'
);
}
try {
const periodText = this.getPeriodText(dateRange);
const occupancyRate = businessData?.occupancy || 0;
// Calcular métricas de ocupación
const available = domosStatus.filter(d => d.status === 'available').length;
const occupied = domosStatus.filter(d => d.status === 'occupied').length;
const maintenance = domosStatus.filter(d => d.status === 'maintenance').length;
const cleaning = domosStatus.filter(d => d.status === 'cleaning').length;
const total = domosStatus.length;
validator.log('info', `Análisis de ocupación: ${occupied}/${total} ocupados, ${available}/${total} disponibles`);
return {
content: [{
type: 'text',
text: this.formatOccupancyAnalysis({
periodText,
occupancyRate,
domosStatus,
metrics: { available, occupied, maintenance, cleaning, total }
})
}]
};
} catch (error) {
validator.log('error', `Error en análisis de ocupación: ${error.message}`);
return validator.generateInsufficientDataResponse(
'análisis de ocupación',
'Error interno en el análisis. Contacta al administrador.'
);
}
}
/**
* Obtiene texto descriptivo del período
*/
getPeriodText(dateRange) {
const periods = {
'today': 'hoy',
'week': 'esta semana',
'month': 'este mes',
'current': 'actual'
};
return periods[dateRange] || dateRange;
}
/**
* Formatea análisis de ocupación
*/
formatOccupancyAnalysis(data) {
const { periodText, occupancyRate, domosStatus, metrics } = data;
return `🏠 **ESTADO DE OCUPACIÓN TREEPOD**\n\n` +
`📅 **Período:** ${periodText}\n` +
`📊 **Ocupación general:** ${occupancyRate}%\n\n` +
`**🏕️ ESTADO POR DOMO:**\n` +
`${domosStatus.map(domo => `• ${domo.name}: ${this.getDomoStatusEmoji(domo.status)} ${domo.statusText}`).join('\n')}\n\n` +
`**📈 MÉTRICAS:**\n` +
`• Domos disponibles: ${metrics.available}/${metrics.total}\n` +
`• Domos ocupados: ${metrics.occupied}/${metrics.total}\n` +
`• En mantención: ${metrics.maintenance}/${metrics.total}\n` +
`${metrics.cleaning > 0 ? `• En limpieza: ${metrics.cleaning}/${metrics.total}\n` : ''}\n` +
`**🎯 RECOMENDACIONES:**\n` +
`${this.getOccupancyRecommendations(metrics, occupancyRate)}\n\n` +
`*Análisis basado en datos reales del sistema TreePod*`;
}
/**
* Obtiene emoji para estado de domo
*/
getDomoStatusEmoji(status) {
const emojis = {
'occupied': '🔴',
'available': '🟢',
'maintenance': '🟡',
'cleaning': '🔵',
'reserved': '🟠'
};
return emojis[status] || '⚪';
}
/**
* Genera recomendaciones de ocupación
*/
getOccupancyRecommendations(metrics, occupancyRate) {
const recommendations = [];
if (metrics.available === 0) {
recommendations.push('• ¡Excelente! Ocupación completa');
} else if (metrics.available === 1) {
recommendations.push('• Muy buena ocupación, solo 1 domo disponible');
} else if (metrics.available > metrics.total / 2) {
recommendations.push('• Oportunidad de marketing para aumentar reservas');
}
if (metrics.maintenance > 0) {
recommendations.push('• Coordinar mantención para minimizar impacto');
}
if (occupancyRate < 50) {
recommendations.push('• Considerar promociones o descuentos');
} else if (occupancyRate > 80) {
recommendations.push('• Excelente momento para optimizar precios');
}
return recommendations.length > 0 ? recommendations.join('\n') : '• Mantener estrategia actual';
}
/**
* Analiza competencia basado en datos reales
*/
async analyzeCompetition(competitionData, analysisType = 'all') {
if (!competitionData) {
return validator.generateInsufficientDataResponse(
'datos de competencia',
'No se encontraron datos de competencia válidos'
);
}
try {
validator.log('info', `Analizando competencia tipo: ${analysisType}`);
// Extraer datos relevantes según el tipo de análisis
const analysis = this.processCompetitionData(competitionData, analysisType);
return {
content: [{
type: 'text',
text: this.formatCompetitionAnalysis(analysis, analysisType)
}]
};
} catch (error) {
validator.log('error', `Error en análisis de competencia: ${error.message}`);
return validator.generateInsufficientDataResponse(
'análisis de competencia',
'Error interno en el análisis. Contacta al administrador.'
);
}
}
/**
* Procesa datos de competencia según tipo de análisis
*/
processCompetitionData(data, analysisType) {
const analysis = {
totalCompetitors: 0,
priceRange: { min: null, max: null, average: null },
services: [],
positioning: 'medio',
recommendations: []
};
// Procesar diferentes estructuras de datos de competencia
let competitors = [];
if (data.competitors) {
competitors = Array.isArray(data.competitors) ? data.competitors : Object.values(data.competitors);
} else if (data.establishments) {
competitors = Array.isArray(data.establishments) ? data.establishments : Object.values(data.establishments);
} else if (Array.isArray(data)) {
competitors = data;
}
analysis.totalCompetitors = competitors.length;
if (competitors.length > 0) {
// Analizar precios si están disponibles
const prices = competitors
.map(c => c.price || c.tarifa || c.precio)
.filter(p => p && typeof p === 'number')
.sort((a, b) => a - b);
if (prices.length > 0) {
analysis.priceRange.min = prices[0];
analysis.priceRange.max = prices[prices.length - 1];
analysis.priceRange.average = Math.round(prices.reduce((a, b) => a + b, 0) / prices.length);
}
// Analizar servicios comunes
const allServices = competitors
.flatMap(c => c.services || c.servicios || [])
.filter(s => s);
const serviceCount = {};
allServices.forEach(service => {
serviceCount[service] = (serviceCount[service] || 0) + 1;
});
analysis.services = Object.entries(serviceCount)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
.map(([service, count]) => ({ service, count }));
// Determinar posicionamiento
if (analysis.priceRange.average) {
const treepodPrice = 80000; // Precio base estimado
if (treepodPrice < analysis.priceRange.average * 0.8) {
analysis.positioning = 'económico';
} else if (treepodPrice > analysis.priceRange.average * 1.2) {
analysis.positioning = 'premium';
}
}
// Generar recomendaciones
analysis.recommendations = this.generateCompetitionRecommendations(analysis);
}
return analysis;
}
/**
* Formatea análisis de competencia
*/
formatCompetitionAnalysis(analysis, analysisType) {
let content = `🔍 **ANÁLISIS DE COMPETENCIA TREEPOD**\n\n`;
content += `📊 **Competidores analizados:** ${analysis.totalCompetitors}\n`;
content += `🎯 **Posicionamiento:** ${analysis.positioning}\n\n`;
if (analysis.priceRange.average) {
content += `**💰 ANÁLISIS DE PRECIOS:**\n`;
content += `• Precio mínimo: ${this.formatCurrency(analysis.priceRange.min)}\n`;
content += `• Precio máximo: ${this.formatCurrency(analysis.priceRange.max)}\n`;
content += `• Precio promedio: ${this.formatCurrency(analysis.priceRange.average)}\n\n`;
}
if (analysis.services.length > 0) {
content += `**🏨 SERVICIOS MÁS COMUNES:**\n`;
analysis.services.forEach(({ service, count }) => {
content += `• ${service} (${count} competidores)\n`;
});
content += `\n`;
}
if (analysis.recommendations.length > 0) {
content += `**🎯 RECOMENDACIONES:**\n`;
analysis.recommendations.forEach(rec => {
content += `• ${rec}\n`;
});
content += `\n`;
}
content += `*Análisis basado en datos reales del sistema de inteligencia competitiva*`;
return content;
}
/**
* Genera recomendaciones basadas en análisis de competencia
*/
generateCompetitionRecommendations(analysis) {
const recommendations = [];
if (analysis.positioning === 'económico') {
recommendations.push('Oportunidad de aumentar precios manteniendo competitividad');
} else if (analysis.positioning === 'premium') {
recommendations.push('Enfatizar valor diferencial para justificar precio premium');
}
if (analysis.totalCompetitors < 5) {
recommendations.push('Mercado con poca competencia, oportunidad de crecimiento');
} else if (analysis.totalCompetitors > 10) {
recommendations.push('Mercado saturado, enfocarse en diferenciación');
}
if (analysis.services.length > 0) {
const topService = analysis.services[0].service;
recommendations.push(`Considerar agregar/mejorar: ${topService}`);
}
return recommendations.length > 0 ? recommendations : ['Mantener estrategia competitiva actual'];
}
/**
* Genera reportes ejecutivos basado en datos reales
*/
async generateReport(reportType, format = 'summary') {
validator.log('info', `Generando reporte ${reportType} en formato ${format}`);
try {
switch (reportType) {
case 'monthly':
return await this.generateMonthlyReport(format);
case 'occupancy':
return await this.generateOccupancyReport(format);
case 'financial':
return await this.generateFinancialReport(format);
case 'competition':
return await this.generateCompetitionReport(format);
default:
return await this.generateMonthlyReport(format);
}
} catch (error) {
validator.log('error', `Error generando reporte ${reportType}: ${error.message}`);
return validator.generateInsufficientDataResponse(
`reporte ${reportType}`,
'Error interno en la generación del reporte. Contacta al administrador.'
);
}
}
/**
* Genera reporte mensual ejecutivo
*/
async generateMonthlyReport(format) {
const financialData = await dataLoader.loadFinancialData();
const businessData = await dataLoader.loadBusinessStatus();
const domosStatus = await dataLoader.loadDomosStatus();
if (!financialData && !businessData) {
return validator.generateInsufficientDataResponse(
'datos para reporte mensual',
'No se pudo acceder a los datos financieros ni de estado del negocio'
);
}
const analysis = this.calculateFinancialAnalysis(financialData || {}, 'Enero 2025');
const occupancyMetrics = domosStatus ? this.calculateOccupancyMetrics(domosStatus) : null;
return {
content: [{
type: 'text',
text: this.formatMonthlyReport(analysis, occupancyMetrics, format)
}]
};
}
/**
* Genera reporte de ocupación
*/
async generateOccupancyReport(format) {
const businessData = await dataLoader.loadBusinessStatus();
const domosStatus = await dataLoader.loadDomosStatus();
if (!domosStatus) {
return validator.generateInsufficientDataResponse(
'datos de ocupación',
'No se pudo acceder al estado actual de los domos'
);
}
const metrics = this.calculateOccupancyMetrics(domosStatus);
const occupancyRate = businessData?.occupancy || 0;
return {
content: [{
type: 'text',
text: this.formatOccupancyReport(metrics, occupancyRate, format)
}]
};
}
/**
* Genera reporte financiero
*/
async generateFinancialReport(format) {
const financialData = await dataLoader.loadFinancialData();
if (!financialData) {
return validator.generateInsufficientDataResponse(
'datos financieros',
'No se pudo acceder a los datos financieros del sistema'
);
}
const analysis = this.calculateFinancialAnalysis(financialData, 'Período actual');
return {
content: [{
type: 'text',
text: this.formatFinancialReport(analysis, format)
}]
};
}
/**
* Genera reporte de competencia
*/
async generateCompetitionReport(format) {
const competitionData = await dataLoader.loadCompetitionData();
if (!competitionData) {
return validator.generateInsufficientDataResponse(
'datos de competencia',
'No se pudo acceder a los datos de inteligencia competitiva'
);
}
const analysis = this.processCompetitionData(competitionData, 'all');
return {
content: [{
type: 'text',
text: this.formatCompetitionReport(analysis, format)
}]
};
}
/**
* Calcula métricas de ocupación
*/
calculateOccupancyMetrics(domosStatus) {
return {
available: domosStatus.filter(d => d.status === 'available').length,
occupied: domosStatus.filter(d => d.status === 'occupied').length,
maintenance: domosStatus.filter(d => d.status === 'maintenance').length,
cleaning: domosStatus.filter(d => d.status === 'cleaning').length,
total: domosStatus.length
};
}
/**
* Formatea reporte mensual
*/
formatMonthlyReport(analysis, occupancyMetrics, format) {
let content = `📊 **REPORTE MENSUAL EJECUTIVO TREEPOD**\n\n`;
content += `📅 **Período:** ${analysis.periodo}\n`;
content += `💰 **Estado:** ${this.determineBusinessStatus(analysis)}\n\n`;
content += `**💵 RESUMEN FINANCIERO:**\n`;
content += `• Ingresos: ${this.formatCurrency(analysis.ingresos)}\n`;
content += `• Gastos: ${this.formatCurrency(analysis.gastos)}\n`;
content += `• Utilidad: ${this.formatCurrency(analysis.utilidad)}\n`;
content += `• Margen: ${analysis.margen}%\n\n`;
if (occupancyMetrics) {
content += `**🏠 OCUPACIÓN:**\n`;
content += `• Disponibles: ${occupancyMetrics.available}/${occupancyMetrics.total}\n`;
content += `• Ocupados: ${occupancyMetrics.occupied}/${occupancyMetrics.total}\n`;
content += `• Tasa ocupación: ${analysis.ocupacion}%\n\n`;
}
if (format === 'detailed') {
content += `**📈 MÉTRICAS DETALLADAS:**\n`;
content += `• Reservas totales: ${analysis.reservas}\n`;
content += `• Ingreso promedio/reserva: ${this.formatCurrency(analysis.ingresoPromedioPorReserva)}\n`;
content += `• Meta ingresos: ${analysis.metaIngresos ? '✅' : '❌'}\n`;
content += `• Meta utilidad: ${analysis.metaUtilidad ? '✅' : '❌'}\n`;
content += `• Meta ocupación: ${analysis.metaOcupacion ? '✅' : '❌'}\n\n`;
}
content += `*Reporte basado en datos reales del sistema TreePod*`;
return content;
}
/**
* Formatea reporte de ocupación
*/
formatOccupancyReport(metrics, occupancyRate, format) {
let content = `🏠 **REPORTE DE OCUPACIÓN TREEPOD**\n\n`;
content += `📊 **Ocupación general:** ${occupancyRate}%\n\n`;
content += `**📈 MÉTRICAS:**\n`;
content += `• Domos disponibles: ${metrics.available}/${metrics.total}\n`;
content += `• Domos ocupados: ${metrics.occupied}/${metrics.total}\n`;
content += `• En mantención: ${metrics.maintenance}/${metrics.total}\n`;
if (metrics.cleaning > 0) {
content += `• En limpieza: ${metrics.cleaning}/${metrics.total}\n`;
}
if (format === 'detailed') {
content += `\n**🎯 ANÁLISIS:**\n`;
const utilizationRate = ((metrics.occupied / metrics.total) * 100).toFixed(1);
content += `• Tasa de utilización: ${utilizationRate}%\n`;
content += `• Capacidad disponible: ${((metrics.available / metrics.total) * 100).toFixed(1)}%\n`;
}
content += `\n*Reporte basado en datos reales del sistema TreePod*`;
return content;
}
/**
* Formatea reporte financiero
*/
formatFinancialReport(analysis, format) {
let content = `💰 **REPORTE FINANCIERO TREEPOD**\n\n`;
content += `**💵 ESTADO FINANCIERO:**\n`;
content += `• Ingresos: ${this.formatCurrency(analysis.ingresos)}\n`;
content += `• Gastos: ${this.formatCurrency(analysis.gastos)}\n`;
content += `• Utilidad neta: ${this.formatCurrency(analysis.utilidad)}\n`;
content += `• Margen de utilidad: ${analysis.margen}%\n\n`;
if (format === 'detailed') {
content += `**📊 MÉTRICAS AVANZADAS:**\n`;
content += `• ROI estimado: ${(analysis.utilidad / analysis.gastos * 100).toFixed(1)}%\n`;
content += `• Punto de equilibrio: ${analysis.metaUtilidad ? 'Alcanzado' : 'Pendiente'}\n`;
content += `• Eficiencia operativa: ${(100 - (analysis.gastos / analysis.ingresos * 100)).toFixed(1)}%\n\n`;
}
content += `*Reporte basado en datos reales del sistema TreePod*`;
return content;
}
/**
* Formatea reporte de competencia
*/
formatCompetitionReport(analysis, format) {
let content = `🔍 **REPORTE DE COMPETENCIA TREEPOD**\n\n`;
content += `📊 **Competidores analizados:** ${analysis.totalCompetitors}\n`;
content += `🎯 **Posicionamiento:** ${analysis.positioning}\n\n`;
if (analysis.priceRange.average) {
content += `**💰 ANÁLISIS DE PRECIOS:**\n`;
content += `• Rango: ${this.formatCurrency(analysis.priceRange.min)} - ${this.formatCurrency(analysis.priceRange.max)}\n`;
content += `• Promedio mercado: ${this.formatCurrency(analysis.priceRange.average)}\n\n`;
}
if (format === 'detailed' && analysis.services.length > 0) {
content += `**🏨 SERVICIOS COMPETENCIA:**\n`;
analysis.services.forEach(({ service, count }) => {
content += `• ${service}: ${count} competidores\n`;
});
content += `\n`;
}
content += `*Reporte basado en datos reales del sistema de inteligencia competitiva*`;
return content;
}
/**
* Obtiene estado general del negocio con KPIs y alertas
*/
async getBusinessStatus() {
validator.log('info', 'Obteniendo estado general del negocio');
try {
// Cargar todos los datos necesarios
const financialData = await dataLoader.loadFinancialData();
const businessData = await dataLoader.loadBusinessStatus();
const domosStatus = await dataLoader.loadDomosStatus();
// Validar que tenemos datos mínimos
if (!financialData && !businessData) {
return validator.generateInsufficientDataResponse(
'datos del estado del negocio',
'No se pudo acceder a los datos financieros ni de estado del negocio'
);
}
// Calcular métricas principales
const metrics = this.calculateBusinessMetrics(financialData, businessData, domosStatus);
const alerts = this.checkBusinessAlerts(metrics);
const overallStatus = this.determineOverallStatus(metrics);
return {
content: [{
type: 'text',
text: this.formatBusinessStatus(metrics, alerts, overallStatus)
}]
};
} catch (error) {
validator.log('error', `Error obteniendo estado del negocio: ${error.message}`);
return validator.generateInsufficientDataResponse(
'estado del negocio',
'Error interno en la consulta. Contacta al administrador.'
);
}
}
/**
* Calcula métricas principales del negocio
*/
calculateBusinessMetrics(financialData, businessData, domosStatus) {
const revenue = financialData?.ingresos_total || 0;
const expenses = financialData?.gastos_total || 0;
const profit = revenue - expenses;
const margin = revenue > 0 ? (profit / revenue * 100).toFixed(1) : 0;
const occupancy = businessData?.occupancy || 0;
const reservations = financialData?.reservas_totales || 0;
// Métricas de domos si están disponibles
let domosMetrics = null;
if (domosStatus) {
domosMetrics = this.calculateOccupancyMetrics(domosStatus);
}
return {
revenue,
expenses,
profit,
margin: parseFloat(margin),
occupancy,
reservations,
domosMetrics,
// Metas del negocio
revenueGoal: 6000000,
profitGoal: 2000000,
occupancyGoal: 70
};
}
/**
* Verifica alertas del negocio
*/
checkBusinessAlerts(metrics) {
const alerts = [];
// Alertas financieras
if (metrics.profit < 1000000) {
alerts.push('🔴 Utilidad por debajo del mínimo operativo');
}
if (metrics.margin < 20) {
alerts.push('⚠️ Margen de utilidad bajo (< 20%)');
}
// Alertas de ocupación
if (metrics.occupancy < 50) {
alerts.push('🔴 Ocupación crítica (< 50%)');
}
// Alertas de domos si están disponibles
if (metrics.domosMetrics) {
if (metrics.domosMetrics.maintenance > metrics.domosMetrics.total / 2) {
alerts.push('⚠️ Muchos domos en mantención');
}
if (metrics.domosMetrics.available === 0 && metrics.domosMetrics.occupied === 0) {
alerts.push('🔴 Todos los domos fuera de servicio');
}
}
// Alertas de ingresos
if (metrics.revenue < metrics.revenueGoal * 0.5) {
alerts.push('🔴 Ingresos muy por debajo de la meta');
}
return alerts;
}
/**
* Determina estado general del negocio
*/
determineOverallStatus(metrics) {
const revenueScore = (metrics.revenue / metrics.revenueGoal) * 100;
const profitScore = (metrics.profit / metrics.profitGoal) * 100;
const occupancyScore = (metrics.occupancy / metrics.occupancyGoal) * 100;
const averageScore = (revenueScore + profitScore + occupancyScore) / 3;
if (averageScore >= 90) {
return '🟢 Excelente';
} else if (averageScore >= 70) {
return '🟡 Bueno';
} else if (averageScore >= 50) {
return '🟠 Regular';
} else {
return '🔴 Requiere atención inmediata';
}
}
/**
* Formatea estado del negocio
*/
formatBusinessStatus(metrics, alerts, overallStatus) {
const now = new Date();
const timestamp = now.toLocaleDateString('es-CL') + ' ' + now.toLocaleTimeString('es-CL', { hour: '2-digit', minute: '2-digit' });
let content = `🎯 **ESTADO GENERAL TREEPOD GLAMPING**\n\n`;
content += `💼 **Estado del Negocio:** ${overallStatus}\n`;
content += `📅 **Última actualización:** ${timestamp}\n\n`;
content += `**📊 KPIs PRINCIPALES:**\n`;
content += `• 💰 Ingresos: ${this.formatCurrency(metrics.revenue)}\n`;
content += `• 📈 Utilidad: ${this.formatCurrency(metrics.profit)} (${metrics.margin}%)\n`;
content += `• 🏠 Ocupación: ${metrics.occupancy}%\n`;
content += `• 📅 Reservas: ${metrics.reservations}\n\n`;
// Información de domos si está disponible
if (metrics.domosMetrics) {
content += `**🏕️ ESTADO DOMOS:**\n`;
content += `• Disponibles: ${metrics.domosMetrics.available}/${metrics.domosMetrics.total}\n`;
content += `• Ocupados: ${metrics.domosMetrics.occupied}/${metrics.domosMetrics.total}\n`;
if (metrics.domosMetrics.maintenance > 0) {
content += `• En mantención: ${metrics.domosMetrics.maintenance}/${metrics.domosMetrics.total}\n`;
}
content += `\n`;
}
content += `**🚨 ALERTAS ACTIVAS:**\n`;
if (alerts.length > 0) {
alerts.forEach(alert => {
content += `• ${alert}\n`;
});
} else {
content += `✅ Sin alertas críticas\n`;
}
content += `\n`;
content += `**🎯 PROGRESO HACIA METAS:**\n`;
content += `• Ingresos: ${((metrics.revenue / metrics.revenueGoal) * 100).toFixed(1)}% de ${this.formatCurrency(metrics.revenueGoal)}\n`;
content += `• Utilidad: ${((metrics.profit / metrics.profitGoal) * 100).toFixed(1)}% de ${this.formatCurrency(metrics.profitGoal)}\n`;
content += `• Ocupación: ${((metrics.occupancy / metrics.occupancyGoal) * 100).toFixed(1)}% de ${metrics.occupancyGoal}%\n\n`;
content += `**⚡ ACCIONES RECOMENDADAS:**\n`;
const recommendations = this.getBusinessRecommendations(metrics, alerts);
recommendations.forEach(rec => {
content += `• ${rec}\n`;
});
content += `\n*Estado basado en datos reales del sistema TreePod*`;
return content;
}
/**
* Genera recomendaciones de acciones
*/
getBusinessRecommendations(metrics, alerts) {
const recommendations = [];
// Recomendaciones basadas en métricas
if (metrics.occupancy < 60) {
recommendations.push('Implementar estrategia de marketing para aumentar ocupación');
} else if (metrics.occupancy > 85) {
recommendations.push('Considerar aumento de precios por alta demanda');
}
if (metrics.margin < 25) {
recommendations.push('Revisar estructura de costos para mejorar rentabilidad');
}
if (metrics.revenue < metrics.revenueGoal * 0.7) {
recommendations.push('Acelerar estrategias de generación de ingresos');
}
// Recomendaciones basadas en alertas
if (alerts.some(alert => alert.includes('mantención'))) {
recommendations.push('Priorizar finalización de mantenciones pendientes');
}
if (alerts.some(alert => alert.includes('crítica'))) {
recommendations.push('Atención inmediata requerida - revisar operaciones');
}
// Recomendación por defecto si todo está bien
if (recommendations.length === 0) {
recommendations.push('Mantener estrategia actual y monitorear KPIs');
}
return recommendations;
}
// ===== OPTIMIZACIÓN DE PRECIOS =====
/**
* Optimiza precios basado en estrategia y datos reales
* ✅ IMPLEMENTA GUÍA: Sin hardcodeo, validación, trazabilidad
*/
async optimizePricing(strategy) {
validator.log('info', `Iniciando optimización de precios con estrategia: ${strategy}`);
try {
// Cargar datos reales necesarios
const financialData = await dataLoader.loadFinancialData();
const businessData = await dataLoader.loadBusinessStatus();
const competitionData = await dataLoader.loadCompetitionData();
// Validar que tenemos datos suficientes
if (!financialData && !businessData) {
return validator.generateInsufficientDataResponse(
'datos para optimización de precios',
'No se pudo acceder a los datos financieros ni de estado del negocio'
);
}
const optimization = this.calculatePricingOptimization(
strategy,
financialData,
businessData,
competitionData
);
return {
content: [{
type: 'text',
text: this.formatPricingOptimization(optimization, strategy)
}]
};
} catch (error) {
validator.log('error', `Error en optimización de precios: ${error.message}`);
return validator.generateInsufficientDataResponse(
'optimización de precios',
'Error interno en el análisis. Contacta al administrador.'
);
}
}
calculatePricingOptimization(strategy, financialData, businessData, competitionData) {
const currentOccupancy = financialData?.ocupacion_promedio || businessData?.occupancy || 0;
const currentRevenue = financialData?.ingresos_total || 0;
const averageRate = currentRevenue > 0 && financialData?.reservas_totales > 0
? Math.round(currentRevenue / financialData.reservas_totales)
: 0;
const optimization = {
strategy,
current_metrics: {
occupancy: currentOccupancy,
revenue: currentRevenue,
average_rate: averageRate
},
recommendations: [],
price_adjustments: [],
expected_impact: {},
implementation_steps: [],
monitoring_kpis: []
};
// Análisis por estrategia
switch (strategy) {
case 'maximize_revenue':
this.addRevenueMaximizationStrategy(optimization, currentOccupancy);
break;
case 'maximize_occupancy':
this.addOccupancyMaximizationStrategy(optimization, currentOccupancy);
break;
case 'balanced':
this.addBalancedStrategy(optimization, currentOccupancy);
break;
case 'competitive':
this.addCompetitiveStrategy(optimization, competitionData);
break;
default:
optimization.recommendations.push('Estrategia no reconocida, aplicando estrategia balanceada');
this.addBalancedStrategy(optimization, currentOccupancy);
}
// Agregar consideraciones generales
this.addGeneralPricingConsiderations(optimization);
return optimization;
}
addRevenueMaximizationStrategy(optimization, occupancy) {
if (occupancy > 75) {
optimization.recommendations.push('Aumentar tarifas 8-12% en fechas de alta demanda');
optimization.price_adjustments.push('Temporada alta: +10%');
optimization.expected_impact.revenue = '+12-18%';
optimization.expected_impact.occupancy = '-3-7%';
} else {
optimization.recommendations.push('Mantener tarifas actuales y optimizar mix de canales');
optimization.price_adjustments.push('Reducir comisiones canales directos');
optimization.expected_impact.revenue = '+5-8%';
optimization.expected_impact.occupancy = '+2-5%';
}
optimization.implementation_steps.push('Implementar dynamic pricing por día de semana');
optimization.implementation_steps.push('Crear tarifas premium para fechas especiales');
optimization.monitoring_kpis.push('RevPAR (Revenue per Available Room)');
optimization.monitoring_kpis.push('ADR (Average Daily Rate)');
}
addOccupancyMaximizationStrategy(optimization, occupancy) {
optimization.recommendations.push('Reducir tarifas 10-15% en fechas de baja demanda');
optimization.recommendations.push('Crear paquetes todo incluido atractivos');
optimization.price_adjustments.push('Temporada baja: -12%');
optimization.price_adjustments.push('Estancias largas (3+ noches): -15%');
optimization.expected_impact.revenue = '-5-8%';
optimization.expected_impact.occupancy = '+18-25%';
optimization.implementation_steps.push('Lanzar promociones last-minute');
optimization.implementation_steps.push('Crear descuentos por reserva anticipada');
optimization.monitoring_kpis.push('Tasa de ocupación');
optimization.monitoring_kpis.push('Días promedio de anticipación');
}
addBalancedStrategy(optimization, occupancy) {
optimization.recommendations.push('Ajustar precios dinámicamente según demanda');
optimization.recommendations.push('Optimizar mix de canales de distribución');
if (occupancy < 70) {
optimization.price_adjustments.push('Fechas disponibles: -5-8%');
} else {
optimization.price_adjustments.push('Fechas alta demanda: +3-5%');
}
optimization.expected_impact.revenue = '+6-10%';
optimization.expected_impact.occupancy = '+5-8%';
optimization.implementation_steps.push('Implementar sistema de yield management');
optimization.implementation_steps.push('Segmentar precios por tipo de cliente');
optimization.monitoring_kpis.push('Revenue total');
optimization.monitoring_kpis.push('Ocupación promedio');
}
addCompetitiveStrategy(optimization, competitionData) {
if (competitionData) {
optimization.recommendations.push('Ajustar precios según análisis competitivo');
optimization.recommendations.push('Diferenciarse por valor agregado');
} else {
optimization.recommendations.push('Realizar análisis competitivo antes de ajustar precios');
}
optimization.price_adjustments.push('Alinear con mercado local');
optimization.expected_impact.revenue = '+3-7%';
optimization.expected_impact.occupancy = '+4-8%';
optimization.implementation_steps.push('Monitorear precios competencia semanalmente');
optimization.implementation_steps.push('Destacar ventajas diferenciales');
optimization.monitoring_kpis.push('Posición competitiva');
optimization.monitoring_kpis.push('Share of market');
}
addGeneralPricingConsiderations(optimization) {
optimization.implementation_steps.push('Testear cambios gradualmente');
optimization.implementation_steps.push('Comunicar cambios claramente a clientes');
optimization.monitoring_kpis.push('Satisfacción del cliente');
optimization.monitoring_kpis.push('Tasa de conversión');
}
formatPricingOptimization(optimization, strategy) {
const strategyNames = {
maximize_revenue: 'Maximizar Ingresos',
maximize_occupancy: 'Maximizar Ocupación',
balanced: 'Estrategia Balanceada',
competitive: 'Estrategia Competitiva'
};
let report = `💰 **OPTIMIZACIÓN DE PRECIOS TREEPOD**\n\n`;
report += `🎯 **Estrategia:** ${strategyNames[strategy] || strategy}\n\n`;
// Métricas actuales
report += `📊 **MÉTRICAS ACTUALES:**\n`;
report += `• Ocupación: ${optimization.current_metrics.occupancy}%\n`;
if (optimization.current_metrics.revenue > 0) {
report += `• Ingresos: ${this.formatCurrency(optimization.current_metrics.revenue)}\n`;
}
if (optimization.current_metrics.average_rate > 0) {
report += `• Tarifa promedio: ${this.formatCurrency(optimization.current_metrics.average_rate)}\n`;
}
report += `\n`;
// Recomendaciones
report += `🎯 **RECOMENDACIONES:**\n`;
optimization.recommendations.forEach(rec => {
report += `• ${rec}\n`;
});
report += `\n`;
// Ajustes de precios
if (optimization.price_adjustments.length > 0) {
report += `💲 **AJUSTES SUGERIDOS:**\n`;
optimization.price_adjustments.forEach(adj => {
report += `• ${adj}\n`;
});
report += `\n`;
}
// Impacto esperado
if (Object.keys(optimization.expected_impact).length > 0) {
report += `📈 **IMPACTO ESPERADO:**\n`;
if (optimization.expected_impact.revenue) {
report += `• Ingresos: ${optimization.expected_impact.revenue}\n`;
}
if (optimization.expected_impact.occupancy) {
report += `• Ocupación: ${optimization.expected_impact.occupancy}\n`;
}
report += `\n`;
}
// Pasos de implementación
if (optimization.implementation_steps.length > 0) {
report += `🔧 **IMPLEMENTACIÓN:**\n`;
optimization.implementation_steps.forEach((step, index) => {
report += `${index + 1}. ${step}\n`;
});
report += `\n`;
}
// KPIs de monitoreo
if (optimization.monitoring_kpis.length > 0) {
report += `📊 **KPIS A MONITOREAR:**\n`;
optimization.monitoring_kpis.forEach(kpi => {
report += `• ${kpi}\n`;
});
}
return report;
}
// ===== PREDICCIÓN DE INGRESOS =====
/**
* Predice ingresos futuros basado en datos históricos
* ✅ IMPLEMENTA GUÍA: Sin hardcodeo, validación, trazabilidad
*/
async predictRevenue(period) {
validator.log('info', `Iniciando predicción de ingresos para período: ${period}`);
try {
// Cargar datos reales necesarios
const financialData = await dataLoader.loadFinancialData();
const businessData = await dataLoader.loadBusinessStatus();
// Validar que tenemos datos suficientes
if (!financialData) {
return validator.generateInsufficientDataResponse(
'datos financieros históricos',
'No se pudo acceder a los datos financieros necesarios para la predicción'
);
}
const prediction = this.calculateRevenuePrediction(
financialData,
businessData,
period
);
return {
content: [{
type: 'text',
text: this.formatRevenuePrediction(prediction, period)
}]
};
} catch (error) {
validator.log('error', `Error en predicción de ingresos: ${error.message}`);
return validator.generateInsufficientDataResponse(
'predicción de ingresos',
'Error interno en el análisis. Contacta al administrador.'
);
}
}
calculateRevenuePrediction(financialData, businessData, period) {
const currentRevenue = financialData.ingresos_total || 0;
const currentOccupancy = financialData.ocupacion_promedio || businessData?.occupancy || 0;
const currentReservations = financialData.reservas_totales || 0;
// Factores de crecimiento y estacionalidad basados en datos reales
const growthFactors = this.calculateGrowthFactors(period);
const seasonalityFactor = this.calculateSeasonalityFactor();
const marketFactor = this.calculateMarketFactor(currentOccupancy);
// Cálculo base de predicción
const basePrediction = currentRevenue * growthFactors.time * seasonalityFactor * marketFactor;
const prediction = {
period,
current_metrics: {
revenue: currentRevenue,
occupancy: currentOccupancy,
reservations: currentReservations,
avg_rate: currentReservations > 0 ? Math.round(currentRevenue / currentReservations) : 0
},
prediction_factors: {
time_factor: growthFactors.time,
seasonality_factor: seasonalityFactor,
market_factor: marketFactor,
confidence_level: this.calculateConfidenceLevel(financialData)
},
scenarios: {
conservative: Math.round(basePrediction * 0.85),
realistic: Math.round(basePrediction),
optimistic: Math.round(basePrediction * 1.15)
},
projected_metrics: {
revenue: Math.round(basePrediction),
occupancy: Math.min(95, Math.round(currentOccupancy * seasonalityFactor * marketFactor)),
reservations: Math.round(currentReservations * growthFactors.time * marketFactor)
},
recommendations: [],
risk_factors: [],
monitoring_points: []
};
// Agregar recomendaciones basadas en la predicción
this.addRevenuePredictionRecommendations(prediction);
return prediction;
}
calculateGrowthFactors(period) {
const factors = {
next_week: 0.25,
next_month: 1.0,
next_quarter: 3.0,
next_semester: 6.0,
next_year: 12.0
};
return {
time: factors[period] || 1.0
};
}
calculateSeasonalityFactor() {
const currentMonth = new Date().getMonth() + 1;
// Factores estacionales para Chile (hemisferio sur)
if (currentMonth >= 12 || currentMonth <= 3) {
return 1.25; // Verano - temporada alta
} else if (currentMonth >= 6 && currentMonth <= 8) {
return 1.15; // Invierno - temporada media-alta
} else {
return 0.9; // Temporadas intermedias
}
}
calculateMarketFactor(currentOccupancy) {
// Factor basado en performance actual
if (currentOccupancy > 80) {
return 1.1; // Mercado fuerte
} else if (currentOccupancy > 60) {
return 1.0; // Mercado estable
} else {
return 0.9; // Mercado débil
}
}
calculateConfidenceLevel(financialData) {
let confidence = 70; // Base
// Aumentar confianza si tenemos más datos
if (financialData.ingresos_total > 0) confidence += 10;
if (financialData.reservas_totales > 10) confidence += 10;
if (financialData.ocupacion_promedio > 0) confidence += 10;
return Math.min(95, confidence);
}
addRevenuePredictionRecommendations(prediction) {
const { projected_metrics, current_metrics, prediction_factors } = prediction;
// Recomendaciones basadas en ocupación proyectada
if (projected_metrics.occupancy < 65) {
prediction.recommendations.push('Implementar estrategias de marketing agresivas');
prediction.recommendations.push('Considerar promociones especiales');
prediction.risk_factors.push('Baja ocupación proyectada');
} else if (projected_metrics.occupancy > 85) {
prediction.recommendations.push('Evaluar aumento de tarifas');
prediction.recommendations.push('Optimizar mix de canales de alta conversión');
}
// Recomendaciones basadas en confianza
if (prediction_factors.confidence_level < 80) {
prediction.recommendations.push('Recopilar más datos históricos para mejorar precisión');
prediction.risk_factors.push('Datos históricos limitados');
}
// Puntos de monitoreo
prediction.monitoring_points.push('Revisar predicción semanalmente');
prediction.monitoring_points.push('Comparar con resultados reales');
prediction.monitoring_points.push('Ajustar factores según performance');
// Factores de riesgo generales
prediction.risk_factors.push('Cambios en condiciones económicas');
prediction.risk_factors.push('Nuevos competidores en el mercado');
prediction.risk_factors.push('Eventos externos impredecibles');
}
formatRevenuePrediction(prediction, period) {
const periodNames = {
next_week: 'Próxima Semana',
next_month: 'Próximo Mes',
next_quarter: 'Próximo Trimestre',
next_semester: 'Próximo Semestre',
next_year: 'Próximo Año'
};
let report = `📈 **PREDICCIÓN DE INGRESOS TREEPOD**\n\n`;
report += `📅 **Período:** ${periodNames[period] || period}\n\n`;
// Métricas actuales
report += `📊 **MÉTRICAS ACTUALES:**\n`;
if (prediction.current_metrics.revenue > 0) {
report += `• Ingresos: ${this.formatCurrency(prediction.current_metrics.revenue)}\n`;
}
report += `• Ocupación: ${prediction.current_metrics.occupancy}%\n`;
if (prediction.current_metrics.reservations > 0) {
report += `• Reservas: ${prediction.current_metrics.reservations}\n`;
}
if (prediction.current_metrics.avg_rate > 0) {
report += `• Tarifa promedio: ${this.formatCurrency(prediction.current_metrics.avg_rate)}\n`;
}
report += `\n`;
// Predicción principal
report += `🎯 **PREDICCIÓN:**\n`;
report += `• **Ingresos proyectados:** ${this.formatCurrency(prediction.projected_metrics.revenue)}\n`;
report += `• **Ocupación proyectada:** ${prediction.projected_metrics.occupancy}%\n`;
report += `• **Reservas proyectadas:** ${prediction.projected_metrics.reservations}\n`;
report += `• **Nivel de confianza:** ${prediction.prediction_factors.confidence_level}%\n\n`;
// Escenarios
report += `📊 **ESCENARIOS:**\n`;
report += `• 🟢 **Optimista:** ${this.formatCurrency(prediction.scenarios.optimistic)}\n`;
report += `• 🟡 **Realista:** ${this.formatCurrency(prediction.scenarios.realistic)}\n`;
report += `• 🔴 **Conservador:** ${this.formatCurrency(prediction.scenarios.conservative)}\n\n`;
// Factores considerados
report += `🔍 **FACTORES CONSIDERADOS:**\n`;
report += `• Factor temporal: ${(prediction.prediction_factors.time_factor * 100).toFixed(0)}%\n`;
report += `• Factor estacional: ${((prediction.prediction_factors.seasonality_factor - 1) * 100).toFixed(0)}%\n`;
report += `• Factor de mercado: ${((prediction.prediction_factors.market_factor - 1) * 100).toFixed(0)}%\n\n`;
// Recomendaciones
if (prediction.recommendations.length > 0) {
report += `💡 **RECOMENDACIONES:**\n`;
prediction.recommendations.forEach(rec => {
report += `• ${rec}\n`;
});
report += `\n`;
}
// Factores de riesgo
if (prediction.risk_factors.length > 0) {
report += `⚠️ **FACTORES DE RIESGO:**\n`;
prediction.risk_factors.forEach(risk => {
report += `• ${risk}\n`;
});
report += `\n`;
}
// Puntos de monitoreo
if (prediction.monitoring_points.length > 0) {
report += `📋 **MONITOREO:**\n`;
prediction.monitoring_points.forEach(point => {
report += `• ${point}\n`;
});
}
return report;
}
}
// Instancia global del calculador
export const businessCalculator = new BusinessCalculator();