🏨 Отели и цены. Болгария 2026 — подберём идеальный вариант






ЭкспертТур | Туры в Болгарию 2026 | Вылет из Варшавы


🇧🇬 ЭКСПЕРТТУР

✈️ Авиатур в Болгарию из Варшавы

📁 Загрузите XML файлы с ценами от туроператора

Перетащите файлы сюда или нажмите для выбора (можно несколько)

📐 Формула расчёта:
① Цена туроператора за 1 ночь → умножаем на 9 ночей
② Применяем акции (бесплатные ночи из XML)
③ Наценка +3% → округление ВВЕРХ
④ Трансфер: 140 € на человека (туда и обратно)
⑤ Прибыль: 40 € за взрослого, 30 € за ребёнка
📅 ВЫБЕРИТЕ ДАТУ ВЫЛЕТА (9 ночей)

0 отелей
📋 ЗАБРОНИРОВАТЬ ТУР

🏨 Отель:

🛏️

💰 € за 9 ночей

✅ В стоимость включено:
🏨 9 ночей + 🚐 Трансфер Гродно–Варшава–Гродно (140€ на человека)
🚌 Трансфер Бургас–отель–Бургас + ✈️ Авиаперелёт


📍 Мостовая 31, офис 28.1, Гродно
📞 +375 29 782-14-27

let allPrices = []; let hotelsData = []; let selectedHotel = null; let selectedRoom = null; let selectedPrice = 0;

// ===================================================== // ФУНКЦИЯ РАСЧЁТА (С УЧЁТОМ БЕСПЛАТНЫХ НОЧЕЙ) // ===================================================== function calculateFinalPrice(pricePerNight, freeNights, adults, children) { const paidNights = Math.max(0, NIGHTS - freeNights); const hotelCost = pricePerNight * paidNights; const afterMarkup = hotelCost * (1 + MARKUP / 100); const afterRound = Math.ceil(afterMarkup); const totalPersons = adults + children; const transfer = TRANSFER_PER_PERSON * totalPersons; const profit = (adults * PROFIT_ADULT) + (children * PROFIT_CHILD); return afterRound + transfer + profit; }

// ===================================================== // ПАРСЕР XML // ===================================================== function parseXML(xmlString, fileName) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlString, "text/xml");

if (xmlDoc.querySelector('parsererror')) { console.warn(`Ошибка в файле ${fileName}`); return []; }

const results = []; const offers = xmlDoc.querySelectorAll('offer');

for (let offer of offers) { const offerSeasons = offer.querySelectorAll('offer_season');

for (let os of offerSeasons) { // ===== ОСНОВНАЯ ИНФОРМАЦИЯ ===== let hotelName = os.querySelector('Hotel')?.textContent?.trim() || "Неизвестный отель"; let stars = os.querySelector('IdStar')?.textContent?.trim() || "3*"; stars = parseInt(stars.replace('*', '')) || 3;

let resort = "Солнечный берег"; const market = os.querySelector('Market')?.textContent?.trim(); if (market && market !== "ALL MARKETS") resort = market;

// ===== ПИТАНИЕ ===== let board = "BB"; const firstBoard = os.querySelector('season_price IdBoard')?.textContent?.trim(); if (firstBoard) board = firstBoard; if (board === "BO") board = "BO"; if (board === "All Inclusive") board = "All Inclusive";

// ===== АКЦИИ: БЕСПЛАТНЫЕ НОЧИ ===== let freeNights = 0; const extrasGroup = os.querySelector('extras_group'); if (extrasGroup) { const extras = extrasGroup.querySelectorAll('extras'); for (let ex of extras) { const fromDays = parseInt(ex.querySelector('FromDays')?.textContent) || 0; const toDays = parseInt(ex.querySelector('ToDays')?.textContent) || 0; const freeDays = parseInt(ex.querySelector('FreeDays')?.textContent) || 0; if (NIGHTS >= fromDays && NIGHTS <= toDays && freeDays > 0) { freeNights = freeDays; } } }

// ===== ДАТЫ СЕЗОНОВ ===== const seasons = {}; const seasonTags = os.querySelectorAll('season'); for (let st of seasonTags) { const no = st.querySelector('No')?.textContent; const from = st.querySelector('FromDate')?.textContent; const to = st.querySelector('ToDate')?.textContent; if (no && from && to) { seasons[no] = { from, to }; } }

// ===== ЦЕНЫ ===== const seasonPrices = os.querySelectorAll('season_price');

for (let sp of seasonPrices) { const roomName = sp.querySelector('IdRoom')?.textContent?.trim() || "Standard";

let adults = 0, children = 0; const adt = sp.querySelector('adt')?.textContent; const chd = sp.querySelector('chd')?.textContent; if (adt) adults = parseInt(adt); if (chd) children = parseInt(chd);

if (adults === 0 && children === 0) continue;

const persText = sp.querySelector('pers')?.textContent || `${adults} Adults, ${children} Children`;

for (let s = 1; s <= 5; s++) { const priceElem = sp.querySelector(`season${s}`); if (priceElem) { let pricePerNight = parseFloat(priceElem.textContent); if (pricePerNight > 0) { const finalPrice = calculateFinalPrice(pricePerNight, freeNights, adults, children);

let seasonName = `Сезон ${s}`; if (seasons[s]) { seasonName = `${seasons[s].from} → ${seasons[s].to}`; }

results.push({ hotel: hotelName, stars: stars, resort: resort, board: board, roomName: roomName, viewName: persText, adults: adults, children: children, season: s, seasonName: seasonName, pricePerNight: pricePerNight, finalPrice: finalPrice, freeNights: freeNights }); } } } } } }

return results; }

// ===================================================== // ГРУППИРОВКА ПО ОТЕЛЯМ // ===================================================== function groupByHotel(prices) { const grouped = new Map();

for (let item of prices) { const key = `${item.hotel}|${item.stars}|${item.resort}|${item.board}`;

if (!grouped.has(key)) { grouped.set(key, { name: item.hotel, stars: item.stars, resort: item.resort, board: item.board, rooms: [] }); }

const hotel = grouped.get(key); const roomExists = hotel.rooms.some(r => r.name === item.roomName && r.adults === item.adults && r.children === item.children );

if (!roomExists) { hotel.rooms.push({ name: item.roomName, view: item.viewName, adults: item.adults, children: item.children, price: item.finalPrice, freeNights: item.freeNights }); } }

return Array.from(grouped.values()); }

// ===================================================== // ЗАГРУЗКА XML (несколько файлов) // ===================================================== let filesProcessed = 0; let totalFiles = 0;

function loadXMLFiles(files) { allPrices = []; filesProcessed = 0; totalFiles = files.length;

const fileInfo = document.getElementById('fileInfo'); fileInfo.innerHTML = `⏳ Загружено 0 из ${totalFiles} файлов...`; fileInfo.style.display = 'block'; fileInfo.style.background = '#fff3cd'; fileInfo.style.color = '#856404';

for (let file of files) { const reader = new FileReader(); reader.onload = function(e) { try { const parsed = parseXML(e.target.result, file.name); allPrices.push(...parsed); filesProcessed++; fileInfo.innerHTML = `⏳ Загружено ${filesProcessed} из ${totalFiles} файлов... Найдено ${allPrices.length} позиций`;

if (filesProcessed === totalFiles) { finishLoading(); } } catch(err) { console.error(err); filesProcessed++; if (filesProcessed === totalFiles) { finishLoading(); } } }; reader.readAsText(file, 'UTF-8'); } }

function finishLoading() { if (allPrices.length === 0) { document.getElementById('fileInfo').innerHTML = '⚠️ Не найдено ни одной цены. Проверьте XML файлы.'; document.getElementById('fileInfo').style.background = '#ffebee'; document.getElementById('fileInfo').style.color = '#c62828'; return; }

hotelsData = groupByHotel(allPrices);

document.getElementById('fileInfo').innerHTML = `✅ Загружено ${hotelsData.length} отелей, ${allPrices.length} вариантов номеров.`; document.getElementById('fileInfo').style.background = '#e8f5e9'; document.getElementById('fileInfo').style.color = '#2e7d32';

document.getElementById('formulaPanel').style.display = 'block'; document.getElementById('datesBlock').style.display = 'block'; document.getElementById('filtersSection').style.display = 'block'; document.getElementById('formCard').style.display = 'block';

initFilters(); renderDates(); renderHotels(); }

// ===================================================== // ДАТЫ ВЫЛЕТА // ===================================================== const FLIGHT_DATES = [ "06.07 – 15.07", "13.07 – 22.07", "20.07 – 29.07", "27.07 – 05.08", "03.08 – 12.08", "10.08 – 19.08", "17.08 – 26.08", "24.08 – 02.09", "31.08 – 09.09" ]; let selectedDateIndex = 0;

function renderDates() { const container = document.getElementById('datesGrid'); container.innerHTML = ''; FLIGHT_DATES.forEach((date, idx) => { const badge = document.createElement('div'); badge.className = `date-badge ${idx === selectedDateIndex ? 'active' : ''}`; badge.innerText = date; badge.onclick = () => { selectedDateIndex = idx; renderDates(); renderHotels(); }; container.appendChild(badge); }); }

function getStarsHtml(stars) { return '★'.repeat(stars) + '☆'.repeat(5 - stars); }

function getBoardName(board) { const map = { 'BB': 'Завтрак', 'HB': 'Полупансион', 'BO': 'Без питания', 'All Inclusive': 'All Inclusive' }; return map[board] || board; }

function renderHotels() { const hotelFilter = document.getElementById('filterHotel').value; const starsFilter = document.getElementById('filterStars').value; const boardFilter = document.getElementById('filterBoard').value; const guestsFilter = document.getElementById('filterGuests').value;

let filtered = hotelsData.filter(hotel => { if (hotelFilter !== 'all' && hotel.name !== hotelFilter) return false; if (starsFilter !== 'all' && hotel.stars != starsFilter) return false; if (boardFilter !== 'all' && hotel.board !== boardFilter) return false; return true; });

let results = []; for (let hotel of filtered) { let availableRooms = hotel.rooms.filter(room => { if (guestsFilter === 'all') return true; if (guestsFilter === '1' && room.adults === 1 && room.children === 0) return true; if (guestsFilter === '2' && room.adults === 2 && room.children === 0) return true; if (guestsFilter === '3' && room.adults === 3 && room.children === 0) return true; if (guestsFilter === '1+1' && room.adults === 1 && room.children === 1) return true; if (guestsFilter === '2+1' && room.adults === 2 && room.children === 1) return true; if (guestsFilter === '2+2' && room.adults === 2 && room.children === 2) return true; return false; }); if (availableRooms.length > 0) { results.push({ ...hotel, availableRooms }); } }

document.getElementById('hotelsCount').innerText = `${results.length} ${getDeclension(results.length)}`;

const hotelsGrid = document.getElementById('hotelsGrid'); if (results.length === 0) { hotelsGrid.innerHTML = '

😔 Нет отелей по выбранным фильтрам

'; return; }

let html = ''; for (let hotel of results) { let roomsHtml = ''; for (let room of hotel.availableRooms) { const freeBadge = room.freeNights > 0 ? `🎁 +${room.freeNights} ночь бесплатно!` : ''; roomsHtml += `

🛏️ ${room.name} ${freeBadge}
👁️ ${room.view}
👥 ${room.adults} взр + ${room.children} дет
${room.price} € за 9 ночей

`; } html += `

🏨

✈️ Прямой перелёт + трансфер

${escapeHtml(hotel.name)}
${getStarsHtml(hotel.stars)}
📍 ${escapeHtml(hotel.resort)}
🍽️ ${getBoardName(hotel.board)}

${roomsHtml}

`; } hotelsGrid.innerHTML = html; }

function getDeclension(n) { if (n % 10 === 1 && n % 100 !== 11) return 'отель'; if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return 'отеля'; return 'отелей'; }

function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }

window.selectHotel = function(hotelName, roomName, price, adults, children) { selectedHotel = hotelName; selectedRoom = roomName; selectedPrice = price; const display = document.getElementById('selectedHotelDisplay'); display.style.display = 'block'; document.getElementById('selectedHotelName').innerText = hotelName; document.getElementById('selectedRoomName').innerText = `${roomName} (${adults} взр + ${children} дет)`; document.getElementById('selectedRoomPrice').innerText = price; document.getElementById('formCard').scrollIntoView({ behavior: 'smooth', block: 'start' }); };

window.resetFilters = function() { document.getElementById('filterHotel').value = 'all'; document.getElementById('filterStars').value = 'all'; document.getElementById('filterBoard').value = 'all'; document.getElementById('filterGuests').value = 'all'; renderHotels(); };

function initFilters() { const hotels = [...new Set(hotelsData.map(h => h.name))]; const starsSet = [...new Set(hotelsData.map(h => h.stars))].sort((a,b) => a-b); const boardSet = [...new Set(hotelsData.map(h => h.board))];

const hotelSelect = document.getElementById('filterHotel'); hotels.forEach(h => { const opt = document.createElement('option'); opt.value = h; opt.textContent = h; hotelSelect.appendChild(opt); });

const starsSelect = document.getElementById('filterStars'); starsSet.forEach(s => { const opt = document.createElement('option'); opt.value = s; opt.textContent = '★'.repeat(s) + ' ☆'.repeat(5-s); starsSelect.appendChild(opt); });

const boardSelect = document.getElementById('filterBoard'); boardSet.forEach(b => { const opt = document.createElement('option'); opt.value = b; opt.textContent = getBoardName(b); boardSelect.appendChild(opt); });

document.getElementById('filterHotel').addEventListener('change', renderHotels); document.getElementById('filterStars').addEventListener('change', renderHotels); document.getElementById('filterBoard').addEventListener('change', renderHotels); document.getElementById('filterGuests').addEventListener('change', renderHotels); }

// ===================================================== // TELEGRAM ОТПРАВКА // ===================================================== const BOT_TOKEN = '8976489216:AAH6vm4v7kU3mwgYxkozcYH_K59JpfEEOBs'; const CHAT_ID = '-5247212886';

document.getElementById('bookingForm').addEventListener('submit', async function(e) { e.preventDefault();

const name = document.getElementById('name').value.trim(); const phone = document.getElementById('phone').value.trim(); const email = document.getElementById('email').value.trim(); const childrenAges = document.getElementById('childrenAges').value.trim(); const comment = document.getElementById('comment').value.trim();

if (!name || !phone || !email) { showStatus('⚠️ Заполните имя и телефон', 'error'); return; }

const selectedDate = FLIGHT_DATES[selectedDateIndex]; const hotelInfo = selectedHotel ? `🏨 Отель: ${selectedHotel}\n🛏️ Номер: ${selectedRoom}\n💰 Цена: ${selectedPrice} €` : '🏨 Отель не выбран';

const message = `✈️ НОВАЯ ЗАЯВКА: БОЛГАРИЯ

👤 Имя: ${name} 📞 Телефон: ${phone} 📧 Email: ${email}

${hotelInfo} 📅 Даты: ${selectedDate} 👶 Возраст детей: ${childrenAges || 'нет'} 📝 Комментарий: ${comment || 'нет'}

⏰ Отправлено: ${new Date().toLocaleString('ru-RU')}`;

const submitBtn = document.querySelector('button[type="submit"]'); submitBtn.disabled = true; submitBtn.innerHTML = '⏳ Отправка...';

try { const response = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chat_id: CHAT_ID, text: message }) }); if (response.ok) { showStatus('✅ Заявка отправлена! Менеджер свяжется с вами', 'success'); document.getElementById('bookingForm').reset(); document.getElementById('selectedHotelDisplay').style.display = 'none'; selectedHotel = null; } else { showStatus('❌ Ошибка отправки', 'error'); } } catch(err) { showStatus('❌ Ошибка соединения', 'error'); } finally { submitBtn.disabled = false; submitBtn.innerHTML = '🌊 ОТПРАВИТЬ ЗАЯВКУ'; } });

function showStatus(text, type) { const div = document.getElementById('statusMsg'); div.textContent = text; div.className = `status ${type}`; div.style.display = 'block'; setTimeout(() => { div.style.display = 'none'; }, 5000); }

// ===================================================== // DRAG & DROP // ===================================================== const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput');

uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.style.background = '#fff0e6'; }); uploadArea.addEventListener('dragleave', () => { uploadArea.style.background = '#fffaf5'; }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.style.background = '#fffaf5'; const files = e.dataTransfer.files; if (files.length > 0) { const xmlFiles = Array.from(files).filter(f => f.name.endsWith('.xml')); if (xmlFiles.length > 0) { loadXMLFiles(xmlFiles); } else { alert('Пожалуйста, загрузите XML файлы'); } } });

fileInput.addEventListener('change', (e) => { if (e.target.files.length) { const xmlFiles = Array.from(e.target.files).filter(f => f.name.endsWith('.xml')); if (xmlFiles.length > 0) { loadXMLFiles(xmlFiles); } else { alert('Пожалуйста, выберите XML файлы'); } } });