← Мои проекты

Собственный бизнес · Краснодар

Ушки на подушке

Гостиница для кошек в Краснодаре и мой собственный бизнес. Сайт, CRM, звонки и реклама здесь работают как одна система, которую я разрабатываю.

Моя роль
Владелец и разработчик системы
Основа
Laravel 11, SQLite, Vite
Полосатая кошка сидит в номере гостиницы «Ушки на подушке»

Ниже я провожу одну бронь через всю систему: от клика по рекламе до оплаты. Тиша и его хозяйка вымышлены. Экраны CRM настоящие, но сняты на демонстрационной базе, а правила и тексты уведомлений взяты из кода.

За вопросом есть места на шесть ночей? стоят тарифы, календарь боксов, переезды, звонки и реклама. Я собрал это в одну цепочку, где данные не теряются между этапами.

  • 10боксов в онлайн-продаже: восемь «Премиум» и два «Стандарт»
  • 3виртуальных номера, чтобы знать, откуда звонок: сайт, Яндекс Карты, 2ГИС
  • 1формула цены для сайта, CRM и уведомлений
  • 197автотестов: от расчёта цены до разбора звонков

Клик по рекламе не теряется по дороге

Метки Директа и ClientID Метрики запоминаются при заходе с рекламы и уходят вместе с заявкой: в CRM, в уведомление и обратно в Метрику. Так видно, какая реклама приводит оплаченные брони, а не только клики.

ушки-на-подушке.рф/?utm_source=yandex&utm_medium=cpc&utm_campaign=zoogostinica_krasnodar&yclid=7200000000000015838

  • Заявка в CRMколонки yclid, ym_client_id и utm у брони
  • Telegramблок «Реклама / источник» в уведомлении
  • Метрикаофлайн-конверсия после оплаты
Пример адреса из объявления. Номер клика вымышленный.
  • 30 дней метки хранятся в браузере: человек может вернуться через неделю, и заявка всё равно свяжется с объявлением.
  • Строкой хранится ClientID: длинный номер легко теряет последние цифры, если превратить его в обычное число.
  • Во всех формах метки лежат в скрытых полях, поэтому их видят и CRM, и сотрудник в уведомлении.

В броньyclid, UTM и ClientID

Первый экран отвечает на рекламный запрос

Что это, сколько стоит и есть ли места на мои даты: ответы видны сразу. Около 78 % визитов приходят со смартфонов, так показывала Метрика, когда я переделывал первый экран. Поэтому на телефоне даты выбираются в шторке снизу, а окно расчёта помещается даже на экран 320 × 568 без внутренней прокрутки.

Главная страница сайта гостиницы: первый экран с расчётом на даты и карточки номеров
Первый экран сайта на телефоне: кнопка выбора дат, цена и фото номеров
  • 22 ширины экрана от 320 до 1920 px в проверке перед выкладкой
  • 18 сценариев воронки бронирования в той же проверке
  • Без JavaScript заявка тоже уходит: обычной отправкой формы с возвратом ошибок

В броньпосетитель считал стоимость ×2

Одна формула цены на сайте, в CRM и в Telegram

Ставка зависит от номера и длины проживания, вторая кошка добавляет половину, ранняя бронь снимает ещё 10 %. Считает только сервер: если он не ответил, сайт не покажет «примерную» сумму, которая потом не совпадёт с CRM.

Номер
Кошек
6

Расчёт

  1. 6 ночей × 550 ₽3 300 ₽
  2. Ранняя бронь −10 %−330 ₽

Итого2 970 ₽

  • Сайт, окно расчёта2 970 ₽
  • CRM, карточка брони2 970 ₽
  • Telegram, заявкаПредварительная сумма: 2 970 ₽

Цены по тарифу от 2 сентября 2026 года. Каждая бронь помнит свою версию тарифа, поэтому смена цен не пересчитывает старые брони; с апреля таких версий было шесть. С октября можно задать праздничную цену ночи на конкретные даты.

В бронь2 970 ₽ с ранней бронью

Ряд боксов «Премиум» со стеклянными дверцами и зелёными полками

Это боксы. В CRM каждый становится строкой календаря, а каждая ночь столбцом.

Шесть ночей и ни одного бокса на весь срок

Октябрь в примере почти полон. Тиша просится на 9–15 октября. Каждую ночь какой-нибудь «Премиум» свободен, но ни один не свободен все шесть подряд. Простой календарь ответил бы «мест нет».

Каждая ночь проверяется отдельно

Сайт открывает даты, если на каждую ночь есть свободный бокс нужного типа. Один бокс на весь срок для этого не нужен. Строка внизу показывает, сколько «Премиумов» свободно в каждую ночь.

Берётся самая длинная свободная серия

С первой ночи подходят два бокса: Б2 свободен одну ночь, Б3 три. Алгоритм выбирает тот, что свободен дольше, и держит его, пока серия не кончится.

Потом следующий бокс

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

Маршрут видит сотрудник, а не клиент

«Б3 (09.10–12.10) → Б6 (12.10–15.10)» появляется в карточке брони и пунктиром в календаре, а при подборе из CRM приходит ещё и в Telegram. Для хозяйки Тиши это просто свободные даты.

БоксБ1Б2Б3Б4Б5Б6Б7Б8 МуркаБарсикСоня и ЛунаМаркизМарусяБоняИриска и ПлюшаШерлокМасяДымкаСёмаРыжикФунтикПушаЛёваГрафБелкаМурка1 ночь3 ночи3 ночиТиша · Б3Тиша · Б6 Свободно11012111322212

Попробуйте сами: нажмите на первую ночь, затем на последнюю.

Демонстрация на вымышленных бронях. Подбор повторяет логику автоподбора из CRM: самая длинная свободная серия, при равенстве бокс с меньшим номером.
Календарь занятости боксов в CRM: октябрь, переезд Тиши из Б3 в Б6 выделен фиолетовым и соединён пунктиром
Тот же октябрь в CRM. Настоящий интерфейс на демонстрационных данных. День выезда разрезан по диагонали, переезд выделен своим цветом.

В броньмаршрут Б3 → Б6, один переезд

Сотрудник узнаёт о клиенте раньше звонка

Мобильная связь в гостинице нестабильна, поэтому сайт сообщает о намерениях. Нажали на телефон, открыли Макс или Telegram: сотрудник получает сообщение с источником и тем, что человек уже делал на сайте. Это сигнал быть на связи, а не подтверждение звонка.

Ушки · уведомлениябот

📞 Нажали на номер телефона — возможно, скоро позвонят.

• Страница: /
• Источник: Яндекс Директ · zoogostinica_krasnodar
• Устройство: смартфон

Этот посетитель за сутки: считал стоимость ×2, открывал форму заявки

🐾 Новая заявка на бронирование

Бронирование:
• Даты: 09.10.2026 - 15.10.2026
• Номер (выбор клиента): Премиум
• Бокс: Б3 (подобран автоматически)
• Ночей: 6
• Статус: Заявка

Финансы:
• Предварительная сумма: 2 970 ₽ (предварительно)
• Клиент видел на сайте: 2 970 ₽ (Премиум)

Тексты сообщений из кода уведомлений, данные вымышленные.

Страховка «Telegram молчит»

С 24 по 27 сентября прокси перестал пропускать Telegram, и три дня уведомления не доходили. Никто об этом не знал. Теперь каждая неудачная отправка считается: на всех страницах CRM висит красный баннер, а со второй ошибки подряд владельцу уходит SMS, не чаще раза в шесть часов. Баннер исчезает сам после первой успешной отправки.

Сводка CRM с красным баннером «Проверьте отправку в Telegram!» и списком клиентов, которым нужно перезвонить

Клиент может получать статусы брони в Макс. Бот не может написать первым, поэтому ссылка в окне «Заявка принята» передаёт ему номер брони с подписью: чужую бронь по такой ссылке не привязать.

В броньстатус «Заявка» и бокс Б3

Разговор становится записью в CRM

У сайта, Яндекс Карт и 2ГИС свои номера, поэтому канал звонка известен заранее. Запись скачивается, Whisper её расшифровывает, а GPT по строгой JSON-схеме раскладывает разговор по репликам, ставит итог и пишет, что сделать дальше.

  1. Новофонвебхук, проверка токена и повторов
  2. Записьmp3 с повторами от 10 минут до суток
  3. Whisperрасшифровка на русском
  4. GPTответ строго по JSON-схеме
  5. CRM и Telegramклиент, итог, напоминания
Разбор разговора в CRM: итог «Записан», уверенность 88 %, сводка, что сделать и что можно было сделать лучше
Диалог по репликам: сотрудник и клиентка обсуждают номер, корм и цену

Правила разбора

  • Модель не дописывает слова, а только раскладывает сказанное по репликам.
  • Перед каждой меткой нужна дословная цитата из разговора.
  • Итог выбирается из 12 вариантов: от «Записан» до «Ошиблись номером».
  • Неуверенный разбор получает флаг «Нужна проверка человеком».
  • Автоответчик на 7 секунд не считается разговором: недозвон остаётся открытым.

Одно сообщение о недозвоне живёт до конца истории

Если клиент не дозвонился, приходит 🔴. Пока никто не связался, раз в час с 9 до 21 приходит напоминание ответом на исходное сообщение, не больше пяти. После разговора все эти сообщения переписываются в ✅ с итогом из разбора звонка.

🔴 Клиент не дозвонился✅ Связались с клиентом

+79000000150
Звонок: 12.10.2026 10:52 · 2ГИС

Нужно связаться с клиентом.Дозвонились: Ольга
Разговор: 2:41
Итог разговора: Записан

🔴 Клиент не дозвонился

⏰ Нужно связаться с клиентом

Обращение открыто 1 ч.
Напоминание 1-е.
✅ Связались с клиентом

Дозвонились: Ольга

В переписке с Авито работали подсказки ответов: GPT получал последние восемь сообщений, свободные даты и тариф и предлагал два-три варианта. Если менеджер уже ответил, подсказок нет. Сейчас канал на паузе: для API мессенджера Авито нужна платная подписка.

В броньитог «Записан», статус «Подтверждено»

День гостиницы на одном экране

Сводка «Сегодня» собирает заезды, выезды и тех, кому нужно перезвонить. Карточка брони показывает маршрут по боксам и сумму. С телефона доступен тот же день, только короче.

Сводка на сегодня: два клиента ждут перезвона, один заезд и один выезд, загрузка 9 из 10 боксов
Сводка на сегодня. Сверху те, кто не дозвонился, ниже заезды, выезды и загрузка.
Карточка брони №200: Тиша, переезд Б3 → Б6, сумма 2 970 ₽ со скидкой 330 ₽, расчёт возврата при раннем выезде
Карточка брони. Переезд, сумма со скидкой, ранний выезд с расчётом возврата по тарифу.
Календарь CRM на телефоне: заезды, выезды и проживающие на 12 октября

С телефона

Заезды, выезды и проживающие на выбранный день. Бокс показывается по сегменту: в день переезда Тиша уже в Б6.

  • Без перезагрузки изменения из карточки брони попадают в календарь, прокрутка сохраняется.
  • Ранний выезд пересчитывается по тарифу на новый срок, CRM предлагает два варианта возврата.
  • Бонусы начисляются при выезде, а при отмене брони зарезервированные возвращаются.

В бронь«Заехал» 9 октября, переезд 12 октября

Оплата возвращается в рекламу

Когда бронь создана и когда оплачена, CRM отправляет офлайн-конверсию в Метрику: с yclid или ClientID и суммой. В стратегии Директа цели из CRM стоят приоритетными, поэтому реклама учится на настоящих бронях, а не на кликах.

Цикл: Директ, сайт, заявка, CRM, оплата, Метрика и снова Директ
  1. Директклик, yclid
  2. Сайтметки и ClientID
  3. Заявкаbooking_form_sent
  4. CRMcrm_hotel_created
  5. Оплатаcrm_hotel_paid, сумма
  6. Метрикаофлайн-конверсия

21 деньза это время оплата связывается с рекламным визитом

  • Ровно один раз. Отправка идёт после ответа пользователю, отметка ставится только после успеха.
  • Без лишнего. Отменённые брони и неявки в Метрику не уходят.
  • Раздельно. Гостиница и груминг считаются разными целями.

Рекламу ведёт ИИ-агент, и каждое его действие записано

Каждый день по просьбе «разбери вчера» Claude через API смотрит расходы, поисковые запросы и цели, добавляет минус-слова и правит ставки. Каждое изменение вместе с причиной попадает в журнал рекламного кабинета. За три месяца там набралось больше 160 записей.

Записи на снимке демонстрационные: в настоящих есть бюджеты и ставки.

Журнал изменений рекламного кабинета: записи с типами «Цели», «Минус-слова», «Объявления», «Ставки» и автором Claude

В броньоплачено, конверсия в Метрике

Что держит систему

197
  • PublicBookingPromotionTest31
  • BookingPricingServiceTest26
  • MissedCallWatcherTest16
  • HotelLandingV2Test15
  • HomeContentTest12
  • CompactBookingQuoteTest9
  • TariffSeasonPricingTest9

автотестов PHPUnit: бронирование, цены, звонки, уведомления и страницы сайта. Ещё 28 проверяют маску телефона и 5 защищают деплой.

Боевая база не перезаписывается

Деплой сравнивает файлы по SHA1, выкладывает только изменённые и перед этим делает резервную копию базы.

Направление выключается одним флагом

Груминг на паузе: витрина отдаёт 404, админка работает, статьи из поиска открываются по прямым ссылкам.

Роботы не в счёт

Все «ошибки формы» за месяц оказались проверками роботов Яндекса. Фильтр нарочно узкий, чтобы не отсечь телефоны CUBOT и приложение Яндекса.

Планировщик

Каждые 5 минут напоминания о недозвонах, каждые 10 обработка записей, каждые 30 сверка с отчётом АТС.

231 маршрут

37 моделей

68 миграций

Что я вынес из собственной гостиницы

  1. Сначала сценарий, потом код. Пройти бронь с теми, кто будет ей пользоваться: где меняются даты, кто подтверждает, что будет, если до нас не дозвонились.
  2. Сбой превращать в механизм. После трёх дней тишины в Telegram появилась страховка, а не правило «проверяйте почаще».
  3. Смотреть на экономику. Стоимость клиента может съесть маржу, поэтому реклама учится на оплатах, а не на кликах.
Следующий проектUntype

Давайте знакомиться

Хорошие проекты
начинаются
с разговора.

Нужен технический партнёр, помощь команде или решение для рабочего процесса? Расскажите, что хотите изменить — обсудим, чем я могу помочь.

@work_ilyaЛично мне в Telegram
Илья Болясов
Илья БолясовРазработчик. Технический партнёр.