Grandioso Maquinas para hacer ejercicio

Maquinas para hacer ejercicio calidad premium para realizar tus ejercicios, elige dentro de nuestra gran variedad de productos

CONOCE TODOS NUESTROS PRODUCTOS

TENEMOS LOS MEJORES EQUIPOS

Descubre nuestra tienda especializada en productos de gimnasio de alta calidad para entrenamiento en casa o en gimnasios. Encuentra máquinas multifuncionales, pesas, bancos, caminadoras, bicicletas estáticas, accesorios y mucho más. Diseñados para mejorar tu rendimiento, resistencia y fuerza, nuestros equipos se adaptan a tus objetivos y espacio. Invierte en salud, transforma tu entrenamiento y alcanza tus metas con productos duraderos, seguros y eficientes.

Descubre más

Carrito de compra
CARRITO DE COMPRAS0
No hay productos en el carrito!
0
Scroll al inicio
// --- Self-executing function to encapsulate the entire widget --- (() => { // Wait for the main document to be fully loaded before running the script. document.addEventListener('DOMContentLoaded', () => {// 1. CREATE THE HOST ELEMENT AND ATTACH SHADOW DOM // This is the container on your main page where the widget will live. const host = document.createElement('div'); host.id = 'falcon-chat-host'; document.body.appendChild(host);// Attach a shadow root, which isolates the widget's HTML and CSS. const shadowRoot = host.attachShadow({ mode: 'open' });// 2. DEFINE THE WIDGET'S CSS AND HTML // All styles are defined here and will only apply inside the shadow DOM. const widgetCSS = ` :host { /* Ensures the widget itself doesn't have strange default styles */ all: initial; } /* --- Fonts and Base Styles --- */ .font-inter { font-family: 'Inter', sans-serif; } /* --- Custom Scrollbar --- */ #chat-messages::-webkit-scrollbar { width: 8px; } #chat-messages::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } #chat-messages::-webkit-scrollbar-thumb { background: #888; border-radius: 10px; } #chat-messages::-webkit-scrollbar-thumb:hover { background: #555; }/* --- Animations --- */ .chat-window-enter { opacity: 0; transform: translateY(20px) scale(0.95); transition: opacity 0.3s ease-out, transform 0.3s ease-out; } .chat-window-enter-active { opacity: 1; transform: translateY(0) scale(1); } .chat-window-leave { opacity: 1; transform: translateY(0) scale(1); transition: opacity 0.2s ease-in, transform 0.2s ease-in; } .chat-window-leave-active { opacity: 0; transform: translateY(20px) scale(0.95); }/* --- Loading Pulse Animation --- */ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } `; // The complete HTML structure for the chat widget. const widgetHTML = `
`; // 3. INJECT RESOURCES INTO THE SHADOW DOM // This adds the necessary external stylesheets and scripts inside the isolated widget. const fontLink = document.createElement('link'); fontLink.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'; fontLink.rel = 'stylesheet'; shadowRoot.appendChild(fontLink); const tailwindScript = document.createElement('script'); tailwindScript.src = 'https://cdn.tailwindcss.com'; shadowRoot.appendChild(tailwindScript);const styleSheet = document.createElement('style'); styleSheet.textContent = widgetCSS; shadowRoot.appendChild(styleSheet); const htmlWrapper = document.createElement('div'); htmlWrapper.innerHTML = widgetHTML; shadowRoot.appendChild(htmlWrapper);// 4. WIDGET LOGIC (SCOPED TO THE SHADOW DOM) // All event listeners and functions will now only find elements inside the shadow root. const chatWindow = shadowRoot.querySelector('#chat-window'); const chatToggle = shadowRoot.querySelector('#chat-toggle'); const closeChat = shadowRoot.querySelector('#close-chat'); const chatMessages = shadowRoot.querySelector('#chat-messages'); const chatForm = shadowRoot.querySelector('#chat-form'); const chatInput = shadowRoot.querySelector('#chat-input'); const sendButton = shadowRoot.querySelector('#send-button');const toggleChatWindow = () => { const isHidden = chatWindow.classList.contains('hidden'); if (isHidden) { chatWindow.classList.remove('hidden', 'chat-window-leave-active'); chatWindow.classList.add('flex', 'chat-window-enter'); setTimeout(() => chatWindow.classList.add('chat-window-enter-active'), 10); chatToggle.classList.add('hidden'); } else { chatWindow.classList.remove('chat-window-enter-active'); chatWindow.classList.add('chat-window-leave'); setTimeout(() => { chatWindow.classList.add('chat-window-leave-active', 'hidden'); chatWindow.classList.remove('chat-window-leave', 'flex'); }, 200); chatToggle.classList.remove('hidden'); } };chatToggle.addEventListener('click', toggleChatWindow); closeChat.addEventListener('click', toggleChatWindow); chatForm.addEventListener('submit', async (e) => { e.preventDefault(); const userMessage = chatInput.value.trim(); if (!userMessage) return;addMessage(userMessage, 'user'); chatInput.value = ''; setFormDisabled(true); showLoadingIndicator();try { const aiResponse = await getAiResponseWithRetry(userMessage); removeLoadingIndicator(); addMessage(aiResponse.text, 'ai', aiResponse.sources); } catch (error) { removeLoadingIndicator(); console.error('Error fetching AI response:', error); addMessage('Lo siento, un error inesperado ocurrió. Por favor, intenta de nuevo más tarde.', 'ai'); } finally { setFormDisabled(false); } });const setFormDisabled = (disabled) => { chatInput.disabled = disabled; sendButton.disabled = disabled; if (!disabled) chatInput.focus(); };const addMessage = (text, sender, sources = []) => { const messageContainer = document.createElement('div'); messageContainer.className = `flex mb-4 ${sender === 'user' ? 'justify-end' : 'justify-start'}`;const tempDiv = document.createElement('div'); tempDiv.innerText = text; const sanitizedText = tempDiv.innerHTML.replace(/\n/g, '
');let sourcesHtml = ''; if (sources.length > 0) { sourcesHtml = '
Fuentes:
'; }messageContainer.innerHTML = `

${sanitizedText}

${sourcesHtml}
`; chatMessages.appendChild(messageContainer); chatMessages.scrollTop = chatMessages.scrollHeight; };const showLoadingIndicator = () => { const loadingElement = document.createElement('div'); loadingElement.id = 'loading-indicator'; loadingElement.className = 'flex justify-start mb-4'; loadingElement.innerHTML = `
`; chatMessages.appendChild(loadingElement); chatMessages.scrollTop = chatMessages.scrollHeight; };const removeLoadingIndicator = () => { shadowRoot.getElementById('loading-indicator')?.remove(); }; const getAiResponseWithRetry = async (userQuery, retries = 4) => { let delay = 1000; for (let i = 0; i <= retries; i++) { try { return await getAiResponse(userQuery); } catch (error) { if (error.status === 429 && i < retries) { await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; } else { throw error; } } } };const getAiResponse = async (userQuery) => { const systemPrompt = `Eres un asistente de ventas experto para "Falcon Fitness". Tu única fuente de información es el sitio web www.falconfitness.mx. Tu objetivo es responder las preguntas de los usuarios sobre los productos que se encuentran EXCLUSIVAMENTE en ese sitio web. Reglas estrictas: 1. Basa TODAS tus respuestas únicamente en la información encontrada en www.falconfitness.mx. No uses conocimiento general. 2. Si la respuesta no se encuentra en el sitio web, responde amablemente: "No encontré información sobre eso en www.falconfitness.mx. ¿Puedo ayudarte con algún otro equipo?". 3. Sé amable, profesional y conversacional. Responde siempre en español. 4. No inventes precios, especificaciones o disponibilidad. Si no está en el sitio, no lo sabes.`; const apiKey = ""; const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: userQuery }] }], tools: [{ "google_search": {} }], systemInstruction: { parts: [{ text: systemPrompt }] }, }; const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { const error = new Error(`API request failed with status ${response.status}`); error.status = response.status; throw error; } const result = await response.json(); const candidate = result.candidates?.[0]; if (candidate?.content?.parts?.[0]?.text) { const text = candidate.content.parts[0].text; let sources = []; const groundingMetadata = candidate.groundingMetadata; if (groundingMetadata?.groundingAttributions) { const uniqueUris = new Set(); sources = groundingMetadata.groundingAttributions .map(attr => ({ uri: attr.web?.uri, title: attr.web?.title })) .filter(source => { if (source.uri && source.uri.includes('falconfitness.mx') && !uniqueUris.has(source.uri)) { uniqueUris.add(source.uri); return true; } return false; }); } return { text, sources }; } else { throw new Error(`Invalid response structure from API: ${JSON.stringify(result)}`); } }; }); })();