С чего всё началось

Мы периодически летаем на Бали, и с каждым годом наша компания растёт. Сначала летали вдвоём с женой, потом втроём с дочкой, а в этом году полетит вообще 4 взрослых + 1 ребёнок.

В декабре 2025, устав от уральской зимы, я открыл сервис по поиску дешёвых авиабилетов. Билеты по маршруту Екатеринбург–Москва–Бали стоили дорого. Подписался на уведомления о ценах и стал ждать.

Через две недели приходит пуш: «Твои билеты подешевели!» Открываю, смотрю цену… а там не учитывался детский билет. Решил, что я сам накосячил. Удалил маршрут, создал новый — тщательно проверил все фильтры. Через неделю история повторяется. Цена приходит без учёта части заданных параметров. Обидно.

Проблема: гибкие даты, которые не гибкие

Пуши не помогли, начал мониторить вручную. Мне важно:

  • Диапазон вылета 25 февраля – 15 марта (хочу попасть на балийский Новый год 19 марта)

  • Длительность пребывания 27–29 дней (чтобы не продлевать визу VOA и не переплачивать за жильё)

У всех агрегаторов есть «гибкие даты», но я никогда не видел в них пользы. Вбиваю вылет 25.02–15.03, хочу быть в стране 27–29 дней → возврат должен быть 24.03–15.04.

Что я, ожидаемо, вижу:

  • «Вылет 15.03, возврат 24.03» — 9 дней. Мало.

  • «Вылет 25.02, возврат 15.04» — 49 дней. Много.

Единственный способ найти билеты с пребыванием на Бали 27–29 дней — это искать руками. Спустя неделю ручной мониторинг начал бесить.


Версия 1.0: OpenAPI и разочарование

Я тимлид, но в последнее время отошёл от написания кода. Давно хотел проверить, может ли AI написать настоящий продукт. Поставил себе челлендж: весь код пишут нейронки, я ни строчки не пишу сам.

Промпт в ChatGPT: «Напиши скрипт на JavaScript, который ходит в OpenAPI агрегатора и ищет билеты». AI за 5 минут накидал код. Запускаю — работает! Сравниваю цену из скрипта с ценой на сайте.

Цены разные. Существенно.

Спрашиваю у ChatGPT. Поясняет: OpenAPI отдаёт кэшированные данные. То есть какой-то пользователь искал такой же маршрут, результат записался в кэш, и теперь отдаётся мне. При этом на сайте цена уже другая.

Грустно. Надо переделывать.


Версия 2.0: Puppeteer и настоящий мониторинг

Раз уж через OpenAPI цены не актуальные, а актуальные цены получает реальный пользователь, то я выбрал эмулировать поведение реального пользователя через автотесты.

Промпт AI: «Напиши скрипт с использованием Puppeteer, где он: открывает сайт, устанавливает фильтры, сортирует по цене, делает скриншот (пруф) и сохраняет результат в SQLite.

AI накидал базовую реализацию. Запустил — работает! Цены актуальные.

Чтобы перебрать варианты с длительностью 27–29 дней, генерирую все возможные пары дат по маршруту:

  • Вылет 25.02 + 27 дней = возврат 24.03

  • Вылет 25.02 + 28 дней = возврат 25.03

  • Вылет 26.02 + 27 дней = возврат 25.03

  • … и так далее

Математика:

  • Диапазон дат вылета: 18 дней

  • Вариантов длительности: 3 (27, 28, 29)

  • Авиакомпании: 2 (Etihad и Emirates)

Итого: 108 комбинаций. Проверка запускалась каждый час на локальной машине, чтобы не упустить выгодные билеты. Скрипт работал по 40 минут на все комбинации.

Первая блокировка

Через 4 часа глянул логи — ошибки. Система защиты агрегатора забанила мой IP.

Я подождал снятия блокировки и вместе с AI подобрал:

  • Адекватные тайминги между действиями

  • Эмуляцию действия пользователя

  • Задержку между проверками

Система была примерно такая:

Категория

Механизм

Назначение

Анти-детект

delete navigator.webdriver

Скрыть признак автоматизации

window.chrome = {..}

Имитация настоящего Chrome

Fake plugins

Подмена списка плагинов

Задержки

sleep(2000-4000)

Случайная пауза перед загрузкой

sleep(500-1000)

Пауза после каждого фильтра

sleep(5000-8000)

Пауза между batch URL

Клики

checkbox.click()

Эмуляция клика по чекбоксу

label.click()

Клик по label элемента

Drag & Drop

MouseDown → Move → Up

Полная эмуляция перетаскивания слайдера

Расчет координат

Преобразование значения в пиксели

setTimeout chain

Задержки между событиями мыши

Ожидания

waitForPriceStability

Ожидание загрузки всех источников цен

waitForSelector

Ожидание появления DOM элементов

Adaptive retry

Повторные попытки при изменениях

Сработало — блокировок больше нет. Скрипт крутился локально, я проверял результаты руками.


Версия 2.5: Telegram-бот и первые пользователи

Подходить к ноутбуку каждый час — не вариант. Создал новый промпт для AI: «Добавь отправку уведомлений в Telegram». За вечер бот был готов. Оповещения приходят на телефон, блокировок нет. Ноутбук пашет 24/7.

Появилась потребность купить билеты в Питер для родственника на 3 недели. Да еще и бюджет на билеты был ограничен. За час скрипт нашёл билеты вдвое дешевле бюджета. Родственник купил, улетел довольный, скоро уже вернётся домой.

Тогда до меня дошло: такая проблема не только у меня.

Исследование рынка

Нашёл десяток ботов в Telegram и сервисы с подписками. Изучил, вс�� работают через OpenAPI. То есть отдают кэшированные цены.

Рассказал о своём боте коллегам. Люди заинтересовались, нагрузка начала расти, а текущая архитектура не масштабируется. Пользователи создали маршруты на 400 комбинаций, которые прогонялись 5+ часов, а это долго. Можно упустить дешевый билет.

Нужно переделывать с нуля, но уже проектировать масштабируемую систему.


Версия 3.0: текущая

Новая система должна отвечать требованиям:

  1. VPS и прокси

  2. Производительность — проверять 1000+ комбинаций в час

  3. Тарификация комбинаций

  4. Админка как система мониторинга

  5. Система уведомлений — не должна заспамить пользователей

  6. UX — простой интерфейс для создания маршрутов

  7. Оплата тарифов

1. VPS и прокси

Чтобы не убивать свою локальную машину нашёл VPS за 800 ₽/мес, залил код. AI помог настроить окружение и PM2.

Прокси: всего купил 17 резидентских прокси на 1300 ₽/мес, настроил ротацию прокси под авторизационные куки.

Запустил проверки на VPS сразу в 3 потока из-за чего VPS стал уходить в перегрузку (CPU+RAM под 200%). Решил оптимизировать алгоритм проверки цен на билеты.

2. Производительность

Узким местом оказались автотесты. Пришло время от них отказаться и реализовать поиск билетов по API. Повысить производительность удалось путём реверс-инжиниринга фронтового API агрегатора.

В DevTools нашёл основные запросы, скормил нейронке. В паре с AI исследовал основные поля, загрузку данные и прочее. Через пару дней удалось написать алгоритм получения цен по API.

Схема работы алгоритма проверки цен по API
Схема работы алгоритма проверки цен по API

В результате на проверку 400 комбинаций стало уходить всего 32 минуты (быстрее в 10 раз). Запас прочности — до 1000 комбинаций/час можно достичь увеличением числа прокси и кук.

Но надо грамотно выстроить тарификацию — количество маршрутов и комбинаций на пользователя. Чем больше комбинаций создает пользователь, тем больше требуется прокси, зависимость линейная.

3. Тарификация комбинаций

Для тарификации решил идти по пути подписок — всего 2 модели для пользователей. Такие модели отвечают на все запросы пользователей, и даже в бесплатной версии можно спокойно пользоваться ботом.

Для платной подписки уже доступно 350 комбинаций максимум, для чего требуется 2 прокси. 1 прокси обходится мне в 70 ₽. Чистая маржа - 59 ₽ с пользователя с платной подпиской и то она уходит на VPS.

Модели подписок
Модели подписок

4. Админка как система мониторинга

Количество пользователей постепенно росло. Для проактивного отслеживания основных метрик создал админку. Всё было написал AI. К тому моменту перешёл на AI-агента Claude Code с моделью Claude Opus 4.6 — это, конечно, зверь.

В админке отображается:

  • длительность проверок цен

  • количество комбинаций по маршрутам

  • CTR, retention, DAU/WAU/MAU

  • информация по маршрутам пользователей

Главная страница админки
Главная страница админки
Главная страница админки

С самого начала я логировал некоторые события нажатия кнопок в боте. Никакой глубокой цели в этом не было — просто чтобы после релиза новой фичи быстро понять, всё ли работает. Логи нигде не хранились, смотрел в моменте.

Почему бы не сохранять часть этой информации в базу? Всё равно уже собираю DAU/WAU/MAU, а добавить несколько полей в базу — не проблема. Добавил события на создание маршрута, визуализировал, и тут нашлась проблема в воронке создания маршрута.

Схема работы системы логирования
Схема сбора данных для аналитики
Схема сбора данных для аналитики
Воронка создания маршрута
Воронка создания маршрута
Воронка создания маршрута

В воронке увидел, что часть пользователей не завершают создание маршрута и спотыкаются на этапе ввода дат. Именно в этот момент срабатывала валидация и сообщала, что комбинаций слишком много.

Фатальная ошибка. Изначально в бесплатном тарифе было ограничение — 20 комбинаций для маршрутов с гибкими датами. Пытаясь побудить пользователей взять платную подписку, я просто не давал клиенту "родиться" и пользователь уходил даже не создав свой первый маршрут.

Я увеличил лимит в бесплатном тарифе до 50 комбинаций. Узкое место в воронке исчезло — процент успешно созданных маршрутов вырос, а отток на этом шаге снизился.

Вывод: даже простая аналитика действий пользователя помогает находить узкие места, о которых не догадываешься на этапе проектирования.

Теперь предстояло выстроить адекватную систему уведомлений.

5. Система уведомлений: как не заспамить

Первая версия системы уведомлений присылала вообще все результаты проверок. А проверки выполняются каждый час. Один пользователь получил 15 уведомлений за 2 часа (много маршрутов) и попросил их отключить.

Во второй версии уведомления только если цена снижалась, но динамика снижения цен разная. Бывало, что каждая проверка с каждым разом находила билеты всё дешевле и дешевле и это спамило пользователей.

Переделывал еще несколько раз. Сейчас у меня сложный скоринговый алгоритм, учитывающий:

  • процент снижения

  • частоту изменений

  • время суток

  • историю уведомлений пользователя

  • среднюю цену

Это, если честно, тянет на отдельную статью. Но для затравки скину схему работы текущей системы уведомлений.

Система уведомлений
Алгоритм для отправки уведомлений
Алгоритм для отправки уведомлений

С последней системой уведомлений заспам пользователей уменьшился и ключевые метрики улучшились:

  • CTR: 11.4% (каждое 9-е уведомление ведёт к открытию)

  • Retention: ~20% (показатель низковат, но в целом для подобного продукта это хороший показатель)

Главные метрики
Главные метрики
Главные метрики

6. UX — простой интерфейс для создания маршрутов

Мой принцип: бот должен быть простым. Только поиск билетов. Никакого лишнего функционала. Бот не хранит даже имя пользователя, только chat_id.

AI реализовал:

  • создание маршрутов через диалог

  • тепловую карту цен (какой час/день выгоднее)

  • график цен

  • историю найденных предложений

Графики изменения цен
Тепловая карта цен по моему маршруту
Тепловая карта цен по моему маршруту
История изменений цены по моему маршруту
История изменений цены по моему маршруту

7. Оплата тарифов

Для приёма платежей подключил ЮKassa через BotFather. По умолчанию была доступна только оплата картам, ЮMoney, SberPay.

Но: BotFather не умеет в СБП. Кто в 2026 оплачивает не через СБП?!

Пришлось через API ЮKassa делать оплату самостоятельно. AI справился за день. Промпт в скрытом. Этот и подобные промпты я храню в репозитории для истории.

Промпт
**Задача**: Мигрировать оплату подписки с Telegram Payments API на прямую интеграцию ЮКасса API

**Контекст**:
- Текущий код: `handlers/subscriptionHandlers.js` использует токены через BotFather
- Проблема: нет поддержки СБП (доступен в личном кабинете ЮКассы)
- Решение: прямое API ЮКассы с webhook

**Что сделать**:
1. Переписать `handlers/subscriptionHandlers.js`:
    - Заменить `sendInvoice` на создание платежа через ЮКасса API
    - Генерировать URL оплаты, отправлять пользователю
    - Обрабатывать статусы платежей

2. Добавить webhook в `web/server.js`:
    - Принимать уведомления от ЮКассы о статусе платежа
    - Валидировать подпись запроса
    - Обновлять статус подписки в БД

3. Дополнить `.env.example`:
    - Добавить переменные для ключей ЮКассы с примерами

***

1. **Структура данных**: в файле [database.js](../../config/database.js)
2. **База данных**: тут: [database.js](../../config/database.js)
3. **Тарифные планы**: в БД в таблице subscription_types или в документации в [subs.md](../subs.md)
4. **Текущий флоу**: посмотри в файле [subscriptionHandlers.js](../../handlers/subscriptionHandlers.js)
5. Старый функционал оплаты сохранять не надо

Схема алгоритма оплаты.

В результате получилась такая логика
В результате получилась такая логика

Claude Сode + Opus 4.6: составные маршруты

В версии 3.0 было два типа маршрутов: фиксированные даты и гибкие (диапазон + дни). Но я бы сильно хотел один раз вбить свой полный маршрут — Екатеринбург → Москва (1д) → Бали(27-29д) → Москва(1д) → Екатеринбург и просто получать оповещение о полной цене.

Можно, конечно, создать два маршрута, но стыковать остановку придётся вручную. А ведь здесь бот может проявить себя на полную.

Магия Opus 4.6

Сам промпт писал в Claude Sonnet, дабы не съедать много токенов. Перед этим, конечно, сам накидал драфт, затем скормил уже Opus.

Opus в режиме планирования выел все токены за 4-часовой лимит. С корректировкой плана ушло 3 сессии.

Потом приступил к реализации.

Я выпал. Opus реализовал всё корректно. Написал тесты (включая интеграционные). Мне оставалось только запустить и наслаждаться.

Использую Opus только для сложных задач, где страшно сломать обратную совместимость. Подписка на два аккаунта спасает — при лимите переключаешься и просишь продолжить. Магия.

Открытие: билет туда-обратно ≠ два в один конец

Запустил фичу локально, получил общую цену маршрута ~600 к. В проде же бот выдал цену за отдельные участки: Москва (80 к) + Бали (350 к) = 430 к. Не понял почему суммы отличаются и пустил в релиз.

На следующий день решил проверить, что именно нашёл бот:

  • Москва → Бали: 239 к

  • Бали → Москва: 341 к

  • Сумма: 580 к

Открываю сервис-агрегатор, вбиваю те же даты сам, но ищу не два отдельных билета в один конец, а указываю дату возврата (билет туда-обратно). Вижу цену значительно ниже — около 435 к.

Такая же проблема с билетами Екатеринбург – Москва: бот нашёл 35 427 ₽ (туда) + 47 155 ₽ (обратно) = 82 582 ₽. С датой возврата на сайте: 80 000 ₽.

ОТКРЫТИЕ: сумма двух one-way билетов НЕ равна цене round-trip. Разница может быть огромной.

Версия 3.1: умный поиск

Попросил Opus переписать алгоритм следующим промптом.

Промпт для исправления составных маршрутов
Сейчас в файле @TripOptimizer есть функция generateBatchItems. Она генерирует комбинации мо ногам трип.
Если я создаю такой трип:
🗺️ SVX → MOW → DPS → MOW → SVX
📅 25.02.2026 – 15.03.2026

1️⃣ Екатеринбург → Москва: 1-1 дн. | 👥4взр+1дет | ✈️Любая | 🧳багаж | 🔄любые пер.
2️⃣ Москва → Денпасар (Бали): 28-29 дн. | 👥4взр+1дет | ✈️Etihad Airways | 🧳багаж | 🔄до 1 пер.(5ч)
3️⃣ Денпасар (Бали) → Москва: 1-1 дн. | 👥4взр+1дет | ✈️Etihad Airways | 🧳багаж | 🔄до 1 пер.(5ч)
4️⃣ Москва → Екатеринбург | 👥4взр+1дет | ✈️Любая | 🧳багаж | 🔄любые пер.

🔍 78 проверок (API-вызовов)

то это 78 комбинаций на выходе.
И потом будет найдено 4 билета. Найденные цены такие:
1. 25.02 — 35427 ₽
2. 26.02 — 239982 ₽
3. 27.03 — 341662 ₽
4. 28.03 — 47155 ₽

Билеты в один конец все. То есть отдельно билеты идет от екб до москвы, потом с Москвы до Бали, потом с бали до москвы, потом с москвы до екатеринбурга.

НО
если я на сайте смотрю билеты не в один конец, то суммарная цена ниже.
К примеру, на те же даты я ищу билеты с Москвы до Бали и еще указываю дату возврата (26.02-27.03), то я получаю цену 435893. Но если я просуммирую два отдельных билета в один конец на те же даты, то это будет 239982 (26.02 до бали из москвы)+341662(27.03 до москвы из бали), что существенно выше.
Такая же проблема по билетам екатеринбург москва. Бот нашёл билеты на 25.02 из Екб в Мск и на 28.03 из мск в екб по цене 35427 + 47155, но если я указываю дату туда-обратно, то получаю цену 80000.

В общем моё, предложение: если в трипе указаны ноги где совпадают пары вылет-прилет, то надо генерировать еще дополнительно комбинации с датой возврата. 
То есть в моем примере будет 77 комбинаций + сколько-то комбинаций для билетов екб-мск-екб и мск-бали-мск.
Важно: не факт, что билеты с датой возврата будут дешевле, поэтому считать самый дешевый вариант в функции findBestCombination надо, перебирая все комбинации

Внеси исправления и посчитай мне количество комбинаций в таком случае. 

Новая логика: если в маршруте есть пары A→B и B→A, бот ищет два отдельных one-way билета и один round-trip билет и выбирает самый дешёвый.

Результат: пользователи начали находить билеты на 30-40% дешевле, чем раньше.

Проверка других сервисов.

Открыл другой популярный сервис, вбил маршрут с гибкими датами — цена 492 000 ₽.

Тот же сервис в соседней вкладке, те же даты, но явно указал, что нужен билет с датой возврата (round-trip) — цена 352 000 ₽. На 35% дешевле! Тот же рейс, те же даты.

Пруфы
Тут поиск по фиксированным датам
Тут поиск по фиксированным дат��м
Тут поиск через гибкие даты
Тут поиск через гибкие даты

Метрики проекта на текущий момент

  • 185 активных пользователей (за последние 2 недели)

  • 77 созданных маршрутов

  • 3 билета куплено (пользователи подтвердили)

  • 5 платных подписок (995 ₽/мес — окупают часть расходов)

  • ~20% средняя экономия от средней цены

  • Лучшее предложение: билет за 52% от средней цены агрегатора

Список самых дешевых маршрутов за неделю

Технический стек:

  • JavaScript (без фреймворков)

  • SQLite

  • Puppeteer (получение кук)

  • PM2 (управление процессами)

  • Telegram Bot API

  • ЮKassa (платежи через СБП и карты)

Расходы vs Доходы:

  • Расходы: 2100 ₽/мес (VPS 800 ₽ + прокси 1300 ₽)

  • Доходы: 995 ₽/мес (5 подписок × 199 ₽)

  • До полной окупаемости: ещё 6 подписок

Время разработки:

  • Версия 1.0–2.5: 1 неделя (вечера)

  • Версия 3.0: 2 недели (вечера + выходные)

  • Версия 3.1: 3 дня

Процент кода от AI: ~90%
Мой вклад: промпты, тестирование, проектирование архитектуры.


Чему я научился за 3 недели

  1. AI может написать настоящий продукт. Не игрушку, не MVP на коленке, а рабочий SaaS с монетизацией.

  2. Реверс-инжиниринг API с AI-ассистентом — это весело. Скармливаешь данные из DevTools, AI описывает логику и пишет код.

  3. Несмотря на это, нужно развиваться в промпт-инжениринге. На мой взгляд, тут все как в продуктовой разработке - чем лучше сформулирована задача, тем дешевле будет разработка. Пишу корявые промпты — трачу токены на то, чтобы допилить напильником, но если вдумчиво написать промпт, то получаю результат с первого раза.


Что дальше?

Я продолжаю разработку бота, придерживаясь своих принципов:

  • Бот решает одну задачу — ищет билеты. Никакого feature bloat.

  • Своевременные уведомления — без спама.

  • Весь код пишет AI — челлендж для себя и проверка возможностей моделей 2026.

Ближайшие планы:

  • Полная окупаемость (ещё 6 подписок)

  • Улучшить систему уведомлений (сейчас CTR 11.4%, хочу 15%+)

  • Возможно, web-app хотя бы для создания маршрутов — это явно удобнее, но я скептически отношусь к web-app.

Долгосрочные планы:

  • Надеюсь, найду силы написать отдельную статью про систему умных уведомлений

  • Масштабирование на 500+ пользователей


P.S. Если интересно, как устроена система уведомлений (алгоритм баланса спама и полезности) или хотите увидеть промпты для Claude Opus — дайте знать, будет для меня дополнительной мотивацией.