${match.team1} – ${match.team2}
${match.day}
${match.date}
${match.time}
`;
}
// Función para mostrar estado de carga
function showLoadingState(container) {
container.innerHTML = `
`;
}
// Función para mostrar estado de error
function showErrorState(container, error) {
console.log(‘Error al cargar los partidos’, error);
container.innerHTML = `
`;
}
// Función para comparar datos y detectar cambios reales
function hasRealChanges(oldData, newData) {
if (!oldData || !newData) return true;
// Filtrar solo partidos válidos (excluir cached_at)
const oldMatches = oldData.filter(match => match && match.id && !match.cached_at);
const newMatches = newData.filter(match => match && match.id && !match.cached_at);
if (oldMatches.length !== newMatches.length) {
console.log(‘Cambio en cantidad de partidos:’, oldMatches.length, ‘→’, newMatches.length);
return true;
}
// Comparar cada partido
for (let i = 0; i < oldMatches.length; i++) {
const oldMatch = oldMatches[i];
const newMatch = newMatches[i];
// Comparar campos críticos
if (oldMatch.id !== newMatch.id ||
oldMatch.score1 !== newMatch.score1 ||
oldMatch.score2 !== newMatch.score2 ||
oldMatch.status !== newMatch.status ||
oldMatch.day !== newMatch.day) {
console.log(‘Cambio detectado en partido:’, oldMatch.id);
console.log(‘Score:’, `${oldMatch.score1}-${oldMatch.score2} → ${newMatch.score1}-${newMatch.score2}`);
console.log(‘Status:’, `${oldMatch.status} → ${newMatch.status}`);
console.log(‘Day:’, `${oldMatch.day} → ${newMatch.day}`);
return true;
}
}
return false;
}
// Función para verificar si un elemento está visible
function isElementVisible(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
return rect.top < windowHeight && rect.bottom > 0;
}
// Función para lazy loading de imágenes
function lazyLoadImages(container) {
if (‘IntersectionObserver’ in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src || img.src;
img.classList.remove(‘lazy’);
observer.unobserve(img);
}
});
}, {
rootMargin: ’50px 0px’,
threshold: WIDGET_CONFIG.lazyLoadThreshold
});
container.querySelectorAll(‘img[loading=»lazy»]’).forEach(img => {
imageObserver.observe(img);
});
}
}
// Función para precargar widget (opcional)
window.preloadGoleagueWidget = function(agendaId, domainId) {
const cacheKey = `${agendaId}-${domainId}`;
if (!widgetCache.has(cacheKey)) {
cargarAgenda(agendaId, domainId);
}
};
// Función fetch con retry para alta demanda
async function fetchWithRetry(url, options, config) {
let lastError;
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 8000); // 8s timeout
const response = await fetch(url, {
…options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
lastError = error;
if (attempt < config.maxRetries) {
console.warn(`Intento ${attempt + 1} falló, reintentando en ${config.retryDelay}ms…`);
await new Promise(resolve => setTimeout(resolve, config.retryDelay));
}
}
}
throw lastError;
}
// Función para iniciar polling de cambios (optimizada para alta demanda)
function startPolling(agendaId, domainId, config) {
const cacheKey = `${agendaId}-${domainId}`;
// Detener polling existente si hay uno
stopPolling(agendaId, domainId);
// Solo hacer polling si no hay errores recientes
if (errorCount.get(cacheKey) >= 3) {
console.log(‘Polling deshabilitado por errores recientes’);
return;
}
const timer = setInterval(async () => {
try {
// Verificar si el widget sigue visible
const container = document.getElementById(config.containerId);
if (!container || !isElementVisible(container)) {
return; // No hacer polling si no está visible
}
const lastETag = lastETags.get(cacheKey);
const response = await fetchWithRetry(`${config.baseUrl}/api/widgets/${agendaId}/matches/domain/${domainId}`, {
headers: {
‘Accept’: ‘application/json’,
‘If-None-Match’: lastETag || »
}
}, config);
// Si hay cambios (no es 304), actualizar widget
if (response.status !== 304) {
//console.log(‘=== DIAGNÓSTICO DE CAMBIOS ===’);
//console.log(‘Status:’, response.status);
//console.log(‘ETag enviado:’, lastETag);
//console.log(‘ETag recibido:’, response.headers.get(‘ETag’));
//console.log(‘Last-Modified:’, response.headers.get(‘Last-Modified’));
//console.log(‘Cache-Control:’, response.headers.get(‘Cache-Control’));
const data = await response.json();
//console.log(‘Datos recibidos:’, data.length, ‘elementos’);
//console.log(‘Primer elemento:’, data[0]);
//console.log(‘Último elemento:’, data[data.length – 1]);
//console.log(‘=== FIN DIAGNÓSTICO ===’);
// Verificar si hay cambios reales en los datos
const cached = widgetCache.get(cacheKey);
const hasChanges = hasRealChanges(cached ? cached.data : null, data);
if (hasChanges) {
console.log(‘✅ Cambios reales detectados, actualizando widget…’);
// Actualizar cache y ETag (si existe)
const etag = response.headers.get(‘ETag’);
if (etag) {
lastETags.set(cacheKey, etag);
}
widgetCache.set(cacheKey, {
data,
timestamp: Date.now()
});
// Re-renderizar widget
if (container) {
renderWidget(container, data, agendaId, domainId);
// Emitir evento de actualización
document.dispatchEvent(new CustomEvent(‘goleagueWidgetUpdated’, {
detail: {
agendaId,
domainId,
matchCount: data.length,
timestamp: Date.now()
}
}));
}
} else {
console.log(‘✅ No hay cambios reales en los datos’);
//console.log(‘Actualizando cache con datos frescos (sin re-renderizar)’);
// Actualizar solo el cache con datos frescos (sin re-renderizar)
widgetCache.set(cacheKey, {
data,
timestamp: Date.now()
});
// Actualizar ETag si existe
const etag = response.headers.get(‘ETag’);
if (etag) {
lastETags.set(cacheKey, etag);
}
}
} else {
console.log(‘✅ No hay cambios (304), usando cache existente’);
}
} catch (error) {
console.warn(‘Error en polling:’, error);
// Incrementar contador de errores y detener polling si hay muchos errores
const currentErrors = errorCount.get(cacheKey) || 0;
errorCount.set(cacheKey, currentErrors + 1);
if (currentErrors >= 3) {
stopPolling(agendaId, domainId);
}
}
}, config.pollingInterval);
pollingTimers.set(cacheKey, timer);
}
// Función para detener polling
function stopPolling(agendaId, domainId) {
const cacheKey = `${agendaId}-${domainId}`;
const timer = pollingTimers.get(cacheKey);
if (timer) {
clearInterval(timer);
pollingTimers.delete(cacheKey);
}
}
// Función para limpiar cache
window.clearGoleagueWidgetCache = function() {
widgetCache.clear();
lastETags.clear();
// Detener todos los polling
pollingTimers.forEach(timer => clearInterval(timer));
pollingTimers.clear();
};
// Función para forzar recarga sin cache
window.reloadGoleagueWidget = async function(agendaId, domainId, options = {}) {
const config = { …WIDGET_CONFIG, …options };
// Limpiar cache específico
const cacheKey = `${agendaId}-${domainId}`;
widgetCache.delete(cacheKey);
lastETags.delete(cacheKey);
stopPolling(agendaId, domainId);
// Recargar con datos frescos
return await cargarAgenda(agendaId, domainId, options);
};
// Función para detener polling específico
window.stopGoleagueWidgetPolling = function(agendaId, domainId) {
stopPolling(agendaId, domainId);
};
// Función para habilitar polling manualmente (para casos especiales)
window.enableGoleagueWidgetPolling = function(agendaId, domainId, options = {}) {
const config = { …WIDGET_CONFIG, …options, enablePolling: true };
const cacheKey = `${agendaId}-${domainId}`;
// Resetear contador de errores
errorCount.delete(cacheKey);
// Iniciar polling
startPolling(agendaId, domainId, config);
};
// Función para detectar partidos en vivo y ajustar polling automáticamente
function adjustPollingForLiveMatches(data, agendaId, domainId, config) {
if (!data || !Array.isArray(data)) return;
const liveStates = [‘primer-tiempo’, ‘segundo-tiempo’, ‘tiempo-extra’, ‘penales’, ‘descanso’];
const liveMatches = data.filter(match => match && match.id && !match.cached_at &&
liveStates.includes(match.status.toLowerCase().replace(/\s+/g, ‘-‘)) &&
match.day && match.day.toLowerCase() === ‘hoy’
);
if (liveMatches.length > 0) {
console.log(`Detectados ${liveMatches.length} partidos en vivo, ajustando polling…`);
// Habilitar polling más frecuente para partidos en vivo
const liveConfig = {
…config,
enablePolling: true,
pollingInterval: 30 * 1000, // 30 segundos para partidos en vivo
cacheTimeout: 30 * 1000
};
startPolling(agendaId, domainId, liveConfig);
// Emitir evento de partidos en vivo detectados
document.dispatchEvent(new CustomEvent(‘goleagueWidgetLiveMatchesDetected’, {
detail: {
agendaId,
domainId,
liveMatches: liveMatches.length,
matches: liveMatches
}
}));
}
}
// Auto-inicialización cuando el DOM esté listo
if (document.readyState === ‘loading’) {
document.addEventListener(‘DOMContentLoaded’, () => {
document.dispatchEvent(new CustomEvent(‘goleagueWidgetReady’));
});
} else {
document.dispatchEvent(new CustomEvent(‘goleagueWidgetReady’));
}
})();
