Commit Graph

531 Commits

Author SHA1 Message Date
Дмитрий 2f6e88e784 feat(sales): замечания владельца по воронке — богатая карточка, сортировка созвонов, счётчики фильтра
Три правки после демо Этапа 1 (все по TDD):
1. Карточка показывает ВСЕ данные поиска из payload (тип ProspectPayload 1:1 с
   dataclass Firm; computed infoRows рендерит юрлицо, директора+личный ИНН,
   контакты, бюджет Директа вилкой, каналы/коллтрекинг, оценку). Демо-сидер
   кладёт полный синтетический payload.
2. Начальнику отдаётся manager_counts; фильтр показывает «Имя (N)».
3. Колонка «Переговоры» сортируется по next_call_at ASC (просроченные сверху).

Гейты: бэк 12/12, фронт 19/19, Larastan 0. НЕ выкачено (ждём разрешения владельца).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:52:54 +03:00
Дмитрий 869590a1b2 feat(sales): маршруты + пункты меню воронки (менеджер/начальник)
Этап 1 Task 14. /sales/prospects (менеджер), /sales/prospects/board
(начальник, salesBossOnly). Пункты «Потенциальные клиенты» и «Воронка отдела».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:46:54 +03:00
Дмитрий 4b5afacfb7 feat(sales): экраны воронки — менеджер (свои) + начальник (все + фильтр)
Этап 1 Task 12–13. SalesProspectsView (менеджер, listProspects свои),
SalesProspectsBoardView (начальник, listSalesManagers фильтр, все карточки).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:44:29 +03:00
Дмитрий 4212380d6c feat(sales): доска-канбан прогнозов (8 колонок, клик→диалог, просрочка)
Этап 1 Task 11. Без drag: карточки раскладываются по стадиям, клик эмитит
open, просроченный next_call_at подсвечивается.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:42:08 +03:00
Дмитрий bb2fe2f34d feat(sales): диалог карточки прогноза — результат разговора + валидация
Этап 1 Task 10. Селектор результата, поля по стадии, отказ скрыт при user,
обязательные причина/время. Тесты через vm (v-dialog телепортит контент).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:40:46 +03:00
Дмитрий ead1335242 feat(sales): фронт — API прогнозов + утилита стадий/просрочки
Этап 1 Task 9. PROSPECT_STAGES (8 колонок), stageMeta, isOverdue;
listProspects/updateProspect в api/sales.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:38:07 +03:00
Дмитрий aab142c381 feat(sales): ссылка «Поиск клиентов» в кабинете начальника → /finder/
В боковом меню кабинета отдела продаж (блок «Начальник», только role=head)
добавлена внешняя ссылка на Python-поиск клиентов (2ГИС), развёрнутый отдельной
службой под lk.liderra.ru/finder/. Открывается в новой вкладке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 14:02:08 +03:00
Дмитрий b1b1686b0e fix(bot): прогон 250 вопросов × 2 режима — 11 находок закрыто, все проверены перепрогоном
Владелец: «проверь их обоих на тех 250 вопросах» → «правь все».
Разбор: docs/superpowers/findings/2026-07-14-bot-250-both-modes-run.md

Цены оказались вылечены полностью (0 выдуманных цен на 500 ответов), но вылез другой
пласт — бот путал гостя с вошедшим клиентом и импровизировал там, где правды нет в статьях.

ЧТО ПОЧИНЕНО (было → стало на перепрогоне):
- Гостю приписывали счёт и подарок («у вас уже есть 1000 ₽, они лежат на счёте») 2 → 0.
  У гостя нет ни счёта, ни подарка: он не зарегистрирован.
- Вошедшему называли его баланс подарком портала 2 → 0 и подсовывали сценарий новичка
  («живите на подарок», «создайте первый проект») — подарок он потратил давно.
- Вошедшему врали про состояние проектов («два работают», а работает один) 4 → 0
  и про заявки («сегодня 12», а сегодня 0) 1 → 0.
- «Сотая заявка уже дешевле» 2 → 0: ступень меняется по ОБЪЁМУ (первая — 500 заявок).
- «1000 ₽ хватит на несколько десятков заявок» 1 → 0. По решению владельца число заявок
  на подарок НЕ называем вообще: сколько придёт, зависит от источника.
- «Поднимите лимит — заявок будет больше» 1 → 0: лимит это ПОТОЛОК, а не источник.
- «Такого не бывает» про списание без заявки 1 → 0: на проде фантомные списания БЫЛИ.
- «Для Москвы — Московскую область» 1 → 0: Москва и СПб — самостоятельные субъекты.
- Обрубки фраз после чистки сторожем («Они лежат…», «Нажмите её…») 3 → 0.

ЗАМЕЧАНИЕ ВЛАДЕЛЬЦА ПО ЖИВОМУ ЛОГУ: «номера это и есть заявки, а он их делит».
Фраза «мы не продаём номера поштучно» была ВЫДУМКОЙ бота — в статьях её нет.
Теперь: номер = заявка = лид = контакт, продаём любой объём (хоть одну штуку),
минимального заказа нет; спросили цену — бот называет цифру ПЕРВОЙ фразой.
Живьём: «Сколько стоит один номер?» → «Заявка стоит от 55 ₽ до 25 ₽».

ПРАВДА ПРО ВОЗВРАТ ДЕНЕГ вынесена в статью vozvrat-deneg.md — дословно из «Политики
возврата» (/legal/refund): остаток по заявлению на почту, до 3 рабочих дней, за вычетом
комиссий; стоимость переданных заявок не возвращается. Раньше бот придумывал процедуру.

Плюс: сторож больше не считает выдумкой число, названное САМИМ клиентом («можно купить
всего 5 номеров?»); демо-стенд перестал сам себе противоречить (счётчик проекта «сегодня»
разошёлся с числом сделок); добавлен прогонщик bot:run-questions для таких проверок.

Тесты: бот 258/258, весь бэкенд 2738/2744 (0 падений). Сторож не перестарался: из 250
старых одобренных ответов режет целиком 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:32:40 +03:00
Дмитрий 42e907c882 Merge gitea/main into feat/sales-finder — сведение с боевым перед выкатом
Ветка разошлась с боевым 28.06 (334 коммита в main / 215 у нас). Влито ВСЁ боевое:
автоподбор конкурентов, мобильный адаптив портала, свой учёт посетителей, мониторинг
внешних сервисов, фиксы поставщика/биллинга/бота/разборов.

ПОБАЙТОВАЯ СВЕРКА: из 1008 файлов, изменённых боевым, 989 совпадают точно;
19 отличаются — все с нашей законной работой (обе стороны внутри). Затёртых — 0.

24 конфликта разобраны вручную. Ключевое:
- VerifySupplierOrderJob — взята БОЕВАЯ версия (фикс инцидента 11-12.07: площадка
  берётся из src, а не из имени; наша была старой и вернула бы баг, терявший заявки).
- SyncSupplierProjectsJobTest — 15 боевых тестов + наш уникальный (limit-1 → только B1).
- routes/web, router/index, config/services, bootstrap/app — обе стороны сложены.
- NewProjectDialog — зелёные дни недели (наше) + мобильная раскладка (боевое).
- CHANGELOG схемы — номера версий столкнулись, наши перенумерованы в v8.67-v8.70.
- composer — обе зависимости (laravel-dompdf наш + geoip2 боевой).

Гейты: бэкенд 2907/2911 (0 падений), Larastan 0, фронт 1333/1333, сборка OK.
Baseline статанализа принял пре-существующий долг боевого кода (автоподбор/чат).
@mixin в 26 моделях — требование статанализа, dev-докблок, на рантайм не влияет.

Откат: git reset --hard pre-merge-main-20260714

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:49:28 +03:00
Дмитрий 7b7718d319 fix(bot): бот врал про цены живым гостям — брал ВСЕ версии сетки тарифов разом
Живой разговор на боевом 14.07.2026: гость спросил цену, бот назвал «500 ₽ за первую
заявку месяца» (такого тарифа нет — сетка 55→25 ₽), клиент поймал на вранье и ушёл.

Три поломки из одного разговора:

1. ЦЕНЫ. В pricing_tiers лежат ЧЕТЫРЕ версии сетки (старые хранятся с датой начала
   действия). Портал берёт свежую через PricingTierRepository, а LivePrices читал
   таблицу напрямую и склеивал все версии: «ступень 1 — 500 ₽; ступень 1 — 70 ₽;
   ступень 1 — 55 ₽…». Сторож вранья был бессилен — 500 ₽ и правда лежало в поданных
   модели статьях. Теперь сетку берём тем же способом, что и «Биллинг» в кабинете.

2. ССЫЛКИ МИМО ТЕМЫ. У статьи «Собрать источники — цена, очередь…» слово «цена» стоит
   в заголовке, а в синонимах «50 рублей» — она перебивала «Тарифы» на любом денежном
   вопросе. Теперь берём САМУЮ совпавшую статью и только при уверенном совпадении
   (совпавшие слова покрывают хотя бы половину вопроса); на коротком уточнении новую
   ссылку не подсовываем, если человек уже получил одну.

3. ОТВЕТ С СЕРЕДИНЫ ФРАЗЫ. «Но если вы хотите понять…» — клиент решил, что ему хамят.
   Висящий союз снимался ДО того, как выбрасывалась отговорка «в инструкции этого нет».
   Порядок исправлен (AnswerGuard::polishStart).

Плюс по решению владельца: на «сколько стоит заявка?» бот сразу называет вилку
«от 55 ₽ за заявку до 25 ₽ при большом объёме» (метка {{вилка}} из живой сетки).

Тесты бота: 240/240, из них 6 новых — воспроизводят тот разговор и падали на старом коде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 15:32:15 +03:00
Дмитрий 9233bf9960 Merge remote-tracking branch 'gitea/main' into worktree-jivo-bot-core
# Conflicts:
#	app/routes/console.php
2026-07-14 13:27:46 +03:00
Дмитрий c0487ddaa5 fix(billing,ux): портал видит отмену платежа и перестаёт молчать в формах
Разбор живого клиента (стоматология, Красноярск, 14.07): 2 часа настраивал 26
проектов, получил 14 отказов в трёх формах и ушёл, не заплатив.

Деньги:
- отменённый шлюзом платёж больше не висит «ожидает» вечно: закрываем как failed
  с причиной (PaymentSettlementService — общий путь для webhook и крона);
- billing:reconcile-payments каждые 5 минут сам спрашивает шлюз про зависшие
  pending. Побочно страхует от ПОТЕРИ ДЕНЕГ: если webhook не дойдёт, оплаченный
  платёж всё равно зачислится;
- кабинет говорит правду: «Оплата не завершена» + «Оплатить снова» вместо
  «баланс обновится автоматически» (GET /api/billing/last-payment).

🔴 RLS-мина (поймана валидатором ДО выката): UPDATE при отмене шёл без
tenant-контекста → на проде тронул бы 0 строк, а портал рапортовал бы «отменено».
Тесты слепы (тестовая БД под postgres). Регресс-тест проверяет ПОРЯДОК:
SET LOCAL tenant ДО UPDATE. Тот же класс, что инциденты 07.07 и 12.07.

Формы (клиент бился и уходил):
- удаление проекта со сделками: причина показывается на месте + кнопка
  «Поставить на паузу» (раньше 422 улетал в никуда — 4 попытки впустую);
- создание проекта: ошибка по дням недели больше не молчит (у поля не было
  места для показа — 2 немых отказа);
- автоподбор «Добавить вручную»: показываем причину от сервера (был голый
  catch {}), длинные ссылки 2ГИС/Яндекс.Карт принимаются — трекинг-хвост срезаем
  сами. Воспроизведено тестом: именно длинная ссылка давала 3 отказа подряд.

Наблюдаемость: причины отказов пишутся в журнал (маршрут, tenant, ИМЕНА полей;
значений нет — 152-ФЗ). Уровень warning: на проде LOG_LEVEL=warning, info в
журнал не попадает вовсе. Робот-сверщик добавлен в реестр пульса.

Тесты: Pest 2475/2475, Vitest 1215/1215.
Выкачено на боевой 14.07.2026 ~13:00 МСК; сверка сразу закрыла 3 мёртвых платежа
(10 000 ₽, 5 000 ₽, 1 000 ₽).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 13:14:49 +03:00
Дмитрий e517b7a256 Merge remote-tracking branch 'gitea/main' into worktree-jivo-bot-core
# Conflicts:
#	app/bootstrap/app.php
#	app/config/services.php
#	app/tests/Feature/Plan4/Schema/SchemaDeltaTest.php
#	db/CHANGELOG_schema.md
#	db/schema.sql
2026-07-14 09:44:56 +03:00
Дмитрий c687694f43 fix(portal): убраны машинные слова, которые видит клиент
В «Отчётах» клиенту показывали «retry-failed для owner отчёта», «3 попыток
retry» и «Готовится асинхронно». В «Конкурентном поле» подсказка звала
элементы «подкомпаниями / ЖК» (чужая ниша). Переписано по-человечески:
перезапустить до 3 раз за 7 дней, готовится в фоне, элементы — филиалы или
отдельные бренды.

Тест на баннер отчётов теперь запрещает слово «retry» в интерфейсе.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:36:32 +03:00
Дмитрий 5cb742926b fix(portal): дата в истории слияний слипалась с названием + чужая подсказка в форме
«Объединено в Белый Зуб14.07.2026» — между названием и датой не было разделителя.
Подсказка поля «Название элемента» звала пример из чужой ниши («ЖК Нанжуль-
Солнечный») — заменена на нейтральную.

Демо-стенд: лента «Активность» в карточке заявки была пустой (событий у демо-заявок
не было вовсе), журнал кошелька копил кривую хронологию прошлых прогонов — списания
стояли раньше пополнений. Журнал денег защищён от подделки (только дописывать), поэтому
сброс демо-журнала делается ЯВНО и только на локальной машине.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:17:34 +03:00
Дмитрий 261a3cd46f fix(portal): проект на паузе больше не пишет «Собирает заявки» + итог колонки в «Канбане»
Два бага, которых не видел ни один тест — их нашёл робот, снимавший инструкции
для гостей.

1) Карточка остановленного проекта показывала одновременно «На паузе» и зелёную
плашку «Собирает заявки»: плашка смотрела только на связь с поставщиком (там
всё «ок») и про паузу не знала. Теперь серым: «Остановлен вами — заявки не
приходят».

2) В «Канбане» сумма в шапке колонки всегда была прочерком: считалась по старому
демо-полю, которого у живых заявок нет (там ноль), хотя на карточках стояла
честная цена. Теперь считает по настоящему списанию.

Тесты: сначала падающие, потом фикс. Весь фронт 1229 зелёных.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 08:58:33 +03:00
Дмитрий b6b8d6e0d6 feat(visitors): раздел «Посетители» в админке — воронка, каналы, визиты, кабинет
Воронка подсвечивает красным шаг, где теряем больше всего людей. Гости с VPN
и хостингов показаны отдельным числом, а не подмешаны в конверсию. Город при
VPN честно помечается как недостоверный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 05:03:38 +03:00
Дмитрий 9f1b2ccb8d fix(bot): гость получает ссылку на разбор по всем частым вопросам (канбан, баланс, заявки) 2026-07-13 20:38:12 +03:00
Дмитрий 157f277130 feat(visitors): маячок портала (view/alive/register_open/portal_screen) 2026-07-13 20:02:56 +03:00
Дмитрий 6e930400cd feat(portal): подсказка про качество источника при создании проекта из чужого источника 2026-07-13 14:04:49 +03:00
Дмитрий 303bca9081 fix(bot): свой разговор на каждый вход — чужие цифры не остаются в браузере
Ключ разговора в localStorage теперь привязан к тому, кто вошёл:
'liderra_chat_id_u<id>' в кабинете, 'liderra_chat_id_guest' на лендинге.
Смена вошедшего (вход/выход/вход под другим) очищает переписку на экране
и заводит новый ключ — иначе следующий человек за тем же компьютером
видел бы баланс и проекты прошлого клиента (окошко долбилось бы в чужой
разговор и получало 404 от серверной проверки владельца).
2026-07-13 12:11:48 +03:00
Дмитрий 6a3a9e5d3a fix(bot): хвосты — экскурсии по теме и только из кабинета, приветствие, доступы
1. Экскурсии «Показать на портале» включены обратно. Прежнее правило давало ссылку,
   если совпало ХОТЯ БЫ ОДНО слово, — бот вёл на уведомления в ответ на «первое
   сообщение подряд». Теперь два условия: реплика должна быть ВОПРОСОМ и слово из
   него должно попасть в заголовок статьи или её синонимы. Плюс экскурсия предлагается
   ТОЛЬКО клиенту из кабинета: гостю с лендинга ссылка внутрь кабинета бесполезна.
   Ссылки в окошке стали кликабельными (без innerHTML — текст ответа от модели).

2. На «hello» бот отвечал «в моей инструкции нет ответа» и просил телефон: заготовка
   знала только русские приветствия. Приветствие — не вопрос, эскалировать его глупо.

3. «Хочу, чтобы менеджер не видел биллинг» бот принимал за просьбу позвать НАШЕГО
   менеджера и уводил к специалисту, хотя ответ есть в статье, — и через реплику сам
   же отвечал, отчего выглядел противоречащим себе. Правило про доступ дополнено.
   Статья «Команда и доступ» дописана: сколько человек можно пустить, почему нельзя
   скрыть раздел, баланс общий на всех.

Тесты 161/161.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:14:52 +03:00
Дмитрий 8c3fa9a96b feat(bot): ответственность за источник — новая статья + сторож
Правило владельца 13.07.2026. Лидерра отдаёт ВСЕ номера, прошедшие через
выбранный клиентом источник (и входящие, и исходящие), и не сортирует людей
по темам. Источник конкурента может быть шире ниши клиента (клиника с 30
врачами против одной стоматологии); конкурент может расширить профиль,
ошибиться в рекламе или прозванивать купленную базу — это вне нашего контроля.

Заявка не по теме = не брак, замены за неё нет; но тон мягкий — объяснить
и развернуть клиента к работе с ИСТОЧНИКОМ (сузить, сменить, пауза).

Статья kachestvo-istochnika.md + мягкие напоминания в трёх местах, где речь
заходит об источниках. Сторож AnswerGuard::promisesNicheRelevance режет
обещания «только целевые / отсеиваем нецелевых / гарантируем трафик».
50 статей, тесты 153/153.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:49:31 +03:00
Дмитрий e969b7700a chore(chat): убрать настройки Jivo — чат теперь свой; экскурсии выключены
Config: jivo_bot -> bot (убраны неиспользуемые webhook_secret/outbound_url/token
- транспорт Jivo уже удалён), jivosite убран целиком (виджет больше не нужен).
Env: JIVO_BOT_* -> BOT_*; JIVO_WIDGET_ID/VITE_JIVO_WIDGET_ID удалены.
tours_enabled выключен по умолчанию (BOT_TOURS_ENABLED=false) — экскурсии
«Показать на портале» уходили не по теме вопроса (живая проверка 13.07.2026),
чинить релевантность отдельно; сторож-тест не даёт включить незаметно.

Заодно убраны обнаруженные хвосты того же виджета, оставшиеся от прежних
задач: JivoLivenessProbe (класс+тест+регистрация в мониторинге внешних
сервисов), плитка «JivoSite» в админ-дашборде, встроенный скрипт виджета
в welcome.blade.php, осиротевший тест resources/js/.../JivoWidget.vue
(компонент уже был удалён ранее), declaration VITE_JIVO_WIDGET_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:32:21 +03:00
Дмитрий ac0c8f74fc feat(chat): окошко чата на портале и лендинге вместо виджета Jivo 2026-07-13 08:09:51 +03:00
Дмитрий dddf7fbd83 feat(chat): в письме владельцу вся переписка и откуда клиент 2026-07-13 07:54:37 +03:00
Дмитрий 09b980f5ac feat(chat): три уровня счётчиков + письмо-тревога о суточном потолке
Защита денег для публичного чата (лендинг открыт всему интернету,
каждый ответ бота ~0,44 руб): пауза 2 сек между репликами, лимит
вопросов в час (гость 20 / вошедший 60), потолок реплик на разговор
(40), лимит по IP в час (60) и общий суточный потолок ответов (1500)
с одноразовым письмом-тревогой владельцу при пробитии. Отказ всегда
вежливый — клиент получает текст, а не молчание.
2026-07-13 07:29:47 +03:00
Дмитрий 9cd12f179f fix(bot): замены заявки НЕТ — недозвон не брак (правило владельца 13.07.2026)
В трёх статьях было написано «не берёт трубку → заменим». Это ошибка: замену
мы не делаем, и бот честно обещал её клиентам.

Тон тонкий, тему сглаживаем:
- не обещать замену/возврат («заменим», «вернём деньги») — AnswerGuard::promisesReplacement;
- но и не рубить дверью («замена не полагается», «деньги не возвращаются») — тоже режем;
- мягко: недозвон — обычное дело; совет СМС → звонок позже → мессенджер; спорный
  случай на support@liderra.ru, специалист разбирает индивидуально, без обещаний;
- не выдумывать срок ответа поддержки (она 24/7).

Статьи: zamena-zayavki.md переписана, поправлены kak-rabotat-s-nomerom.md и sdelki.md.
Два старых теста охраняли прежнее (неверное) правило — переписаны под новое.

Тесты 130/130. Бот НЕ на проде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 05:59:11 +03:00
Дмитрий c320e095e1 fix(bot): ювелирная доводка после второго круга судей
- «скидка ЗА ОБЪЁМ» больше не считается просьбой скидки: бот отвечает про
  ступени тарифа, а не уводит к специалисту.
- «где посмотреть мою ступень / мой лимит» — вопрос про портал, а не про
  личные данные клиента: эскалация снята.
- Цена ↔ ниша/регион: правило по СМЫСЛУ (AnswerGuard::priceTiedToNiche).
  Враньё «цена зависит от ниши» возвращалось ПЯТЬЮ формулировками —
  регулярка на каждую не работает. Честное «цена НЕ зависит от региона»
  сохраняется.
- Вырезано: «на тысячу выйдет от 2 до 20 заявок» (реально максимум 4),
  «работаем не со всеми нишами» / «есть направления, с которыми мы не
  сотрудничаем» (ограничений по нишам НЕТ), «не берёт трубку — забей»
  (такую заявку МЕНЯЮТ), «баланс на паузе не потратится», выдуманные сроки
  зачисления по счёту.
- Промпт: цена только от ступени объёма; запретных ниш нет.
- Статьи: тронута ТОЛЬКО строка topics (синонимы поиска), тексты не менялись.

Тесты 126/126. Бот НЕ на проде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 05:35:11 +03:00
Дмитрий bd918c1dce fix(bot): держим нить разговора и не теряем телефон клиента
Прогон 46 диалогами (250 реплик подряд в одном чате) вскрыл то, чего не видел
прогон отдельными вопросами:

- Клиент оставил телефон → бот не отвечал ВООБЩЕ: письмо в поддержку слалось
  синхронно внутри ответа, SMTP не отозвался → упала вся задача (tries=1).
  Теперь номер в чате = заявка на звонок: принимаем всегда (даже без просьбы
  бота), письмо уходит через очередь и не может уронить ответ.
- Бот «забывал» себя: сторож резал цену, взятую из истории чата, ответ пустел
  → эскалация. Числа из своих прошлых ответов теперь разрешены; память чата
  3 → 5 пар реплик; в эскалационную ветку тоже передаётся история.
- AnswerGuard: + телефон поддержки (его нет), + юр-гарантии в новых оборотах
  («Да, законно», «Штраф не прилетит»), + враньё «на паузе списаний нет»,
  + выдумки про 18:00/ночь, + «цена зависит от ниши»; снимает висящее «Но…»
  и голое «Правильно, не будут» после вырезанной лжи.
- Промпт: не повторять сказанное, не поддакивать клиенту, без нумерованных списков.
- pravilo-18-00.md: 18:00 — граница для ИЗМЕНЕНИЙ, а не конец приёма заявок.

Тесты 120/120. Бот НЕ на проде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 04:52:09 +03:00
Дмитрий c5125e981e feat(bot): выбор модели + три замка безопасности + инструкция 51 статья
Прогнал 250 вопросов через настоящий вебхук шесть раз и сравнил четыре модели
вслепую силами судей-агентов. Итоги — в docs findings.

Модель. Появился переключатель services.jivo_bot.llm: yandex (данные не покидают
РФ) либо aitunnel (зарубежные модели через российский шлюз). По слепому сравнению
Claude Haiku 4.5 выиграл финал у Gemini 157:24 и у обеих моделей Яндекса: он
полнее, точнее по кнопкам и цифрам и говорит как человек, а не справочник.

Три замка, потому что живая модель = самоуверенная модель:
- PersonalDataMask — телефоны и почта вырезаются ДО отправки в любую модель
  (клиент оставляет номер в чате; за границу ПДн уходить не должны).
- ChatTextCleaner — markdown и смайлики (Jivo их не рисует): было 191 ответ
  с разметкой, стало 0.
- AnswerGuard — режет юридические гарантии («вас не оштрафуют», «мы гарантируем»,
  «не нарушаем закон») и ЛЮБУЮ цену, которой нет в выданных статьях. Пусто после
  чистки → зовём живого специалиста.

Поиск по инструкции переписан: выбрасываются вопросительные слова (из-за них
«какие у вас тарифы» находило выгрузку в Excel), ранжирование сперва по заголовку
и синонимам, выдача — три РАЗНЫЕ статьи и каждая целиком, плюс починка опечаток
(«каг сазадь праэкт») и учёт прошлой реплики в коротких «а это платно?».

Инструкция: 40 → 51 статья. Новые — по фактам владельца: бесплатный тест и
подарок 1000 ₽, «мы канал, а не реклама», СМС → через час звонок, средние
показатели (8–12 из 100 разговаривают, ещё 5–7 по СМС), номер не вернётся
30 дней, замена брака, вывод остатка, поддержка 24/7, удаление данных, ниши.
Вычищено враньё: минус баланса, «списали без заявки», пауза вечером, выходные,
«Вся РФ», статус «Отказ» (его нет — есть «Не реализовано»), amoCRM, платный
повторный сбор, нарастающий итог тарифных ступеней.

Тесты: 106 из 106. Каждая найденная тупость закреплена тестом.

НЕ НА ПРОДЕ. Выкат — только по прямой команде владельца.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:44:48 +03:00
Дмитрий b28f858419 fix(supplier): метка канала B1_/B2_/B3_ не стирается при обновлении заказа
Прод-инцидент 11-12.07.2026: робот итоговой проверки слал письма «нет заказа у
поставщика» на строки, которые в кабинете ЕСТЬ, включены и с верным лимитом.

Корень: кабинет дописывает метку канала к имени строки только при СОЗДАНИИ, а при
обновлении сохраняет имя ровно как прислали. Наш ежедневный updateProject слал голый
uniqueKey и каждый прогон стирал метку. Последствия:
  - итоговая проверка выводила площадку из префикса имени и переставала узнавать
    строку -> ложное missing 11.07 и 12.07;
  - лид от такой строки приходил с project без метки -> webhook не мог определить
    канал и писал platform=DIRECT вместо B1/B2/B3, то есть терялась атрибуция канала.

Что сделано:
  - SupplierPortalClient::toPayload — на update имя уходит с меткой канала; на create
    остаётся голым, там метку ставит сам кабинет и один save с тремя флагами рождает
    три строки, общего префикса у них нет.
  - VerifySupplierOrderJob::normalizeLive — площадка берётся из служебного поля src
    rt/bl/mt, а не из префикса имени; сверка больше не зависит от имени вообще.
  - Новая разовая команда supplier:repair-project-names — возвращает метку строкам,
    у которых её уже стёрли. Payload собирается ИЗ ЖИВОЙ строки кабинета, меняется
    ровно одно поле name; по умолчанию сухой прогон, запись только с --apply.

Ветка пересобрана на gitea/main — закрывает follow-up «фича итоговой проверки заказа
не сведена в main». Попутно возвращён CsvReconcileJobTest, отставший от кода после
сведения main 09.07: он не фейкал fetchDeliveredLeads и падал 9 из 11.

Боевой liderra.ru: выкачено, починена 81 строка, робот показывает 0 расхождений
138 наших строк вместо 57. Двум лидам восстановлен канал по журналу выдач поставщика.

Тесты: Pest supplier 277/277, Pint clean, Larastan 0 новых ошибок.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 19:14:22 +03:00
Дмитрий beb0528612 fix(bot): продукт, 50% номеров и договор-оферта — по живым замечаниям владельца
1. 🔴 МОЙ КОСЯК: на «как получить номер человека, заходящего на сайт» бот отвечал
   «нельзя» — то есть отговаривал клиента от того, ЧТО МЫ И ПРОДАЁМ. Владелец:
   «как нельзя, а мы чем занимаемся?!». Теперь: «это ровно то, что делает Лидерра —
   создайте проект, укажите сайт, контакты пойдут в Сделки».
2. «Все ли номера с источника цепляет система?» — нет, ПРИМЕРНО ПОЛОВИНУ: передавать
   можно только абонентов с согласием (требование закона). Формулировка владельца.
3. «Как подписать договор с юрлицом?» — у нас публичная ОФЕРТА, подписывать нечего.
   Слово «договор» убрано из стоп-тем; новая статья: оферта (/legal/offer), политика,
   возврат + закрывающие документы (счёт → оплата → акт, УСН без НДС).
   Деньги/жалобы/претензии — по-прежнему живой специалист.

Все три проверены ЖИВЬЁМ через настоящий вебхук чата (а не в обход очереди).
Инструкция: 41 статья. Pest бот 58/58.

🪤 Урок: после правки кода бота ОБЯЗАТЕЛЬНО перезапускать queue:work — воркер
кэширует бут. Владелец видел старые ответы, пока я радовался зелёным тестам.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 16:13:34 +03:00
Дмитрий aa2ceb8a13 fix(bot): 9 тупостей из живого прогона владельца (журнал bot_dialogs 127-182)
Владелец прогнал бота вживую: «там тупежа жуть какая-то». Разобрал журнал —
каждая тупость закрыта тестом.

1. 🔴 «откуда приходят номера телефонов?» — бот плёл про Конкурентное поле вместо
   честного ответа (операторы + согласие абонента). Статья усилена.
2. «как получить номер человека, зашедшего на сайт» — теперь прямой ответ: НЕЛЬЗЯ,
   мы не ставим счётчик на чужой сайт и не снимаем посетителей.
3. «законно ли брать телефоны с сайтов конкурентов» — да: это контакты фирм
   из открытых источников, не персональные данные граждан.
4. Формат сайта и номера в проекте — бот сдавался, хотя это в статье. Дописано явно.
5. Клиентское слово «выгрузка» (= поставка заявок) — новая статья «Когда пойдут
   заявки и когда прекратятся».
6. 🔴 «могут ли работать 2-3 человека?» — ЛОЖНАЯ эскалация: голое слово «человек»
   стояло в стоп-темах. Стоп-паттерн переписан: ловим просьбу позвать ЖИВОГО
   (позовите/соедините/живой человек), а не любое упоминание. Плюс статья про команду.
7. «а что ты вообще знаешь?» — бот нёс чушь про паузу проектов. Новая статья-оглавление.
8. «придут ли СМС?» — теперь прямо: СМС/Телеграм/пуш нет, только почта и колокольчик.
9. 🔴 ИИ начинал ответ с «в инструкции этого нет», а СЛЕДОМ отвечал верно — и мы
   глушили весь ответ эскалацией. Теперь отговорку выкидываем; в промпт добавлено:
   «нет» и «нельзя» — это ТОЖЕ ответ, а не отсутствие ответа.

Проверено вручную стоп-темы: «позовите живого человека», «какие операторы», «поставщик»
— по-прежнему эскалация. Pest бот 54/54 (12 новых тестов), Vitest каталога 8/8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 15:30:49 +03:00
Дмитрий 47afefbb10 feat(bot): цены бот тянет сам из настроек — вручную актуализировать больше не надо
Вопрос владельца: «а цены в нём надо актуализировать руками или он сам тянет?»
Ответ был неприятный: руками. Цифры (300/50 руб, тарифные ступени) стояли текстом
в статьях — поменяли бы цену в админке, бот продолжил бы врать про деньги.

Теперь в статьях стоят метки {{цена_подбора}}, {{цена_изучения}}, {{тарифы}},
а сервис LivePrices подставляет живые значения из system_settings и pricing_tiers
В МОМЕНТ ОТВЕТА — без переиндексации базы знаний. Нет цены в настройках — метка
не протекает клиенту, бот отсылает к экрану, где цена написана всегда.

Из текстов экскурсий цифры цен тоже убраны (фронт не знает БД): шаг подсвечивает
строку с ценой в самом окне — она всегда актуальная.

Проверено живьём: поменял цену в настройках на 450 — бот сразу назвал 450.
Pest 42/42 (3 новых теста), Vitest каталога 8/8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:06:04 +03:00
Дмитрий fddba0a616 feat(bot): шаг «проверьте ссылки перед переносом в поле» + ревизия инструкции на грабли
Владелец: «клиентам надо подсказывать — от того, как они сформируют поле,
зависит весь дальнейший поиск». Плюс просьба вычитать инструкцию и экскурсии
на враньё и ловушки.

Добавлено предупреждение (и в экскурсии collect-competitors/proposals, и в статьи):
перед переносом конкурента в поле открыть сайт и справочник ссылками — тот ли это
бизнес, тот ли город, живой ли адрес. По грязной карточке мы найдём не те источники,
а изучение каждого конкурента стоит 50 руб.

Ревизия (агент сверил 36 статей и 31 экскурсию с кодом) — закрыто:
1. Вебхук обещал автоматическую отправку событий, которой НЕТ. Теперь честно:
   сейчас только тестовый запрос; заявки забирать по API-ключу или выгрузкой.
2. Колонка «Город»: оговорка, что в редком запасном случае там регион проекта.
3. Отчёты: фильтры «Проект»/«Менеджер» пока только «Все» — сказано прямо.
4. Цены автоподбора: «актуальная цена всегда в самом окне».
5. Статьи «Портал на телефоне» и «Откуда заявки» больше не ведут на чужую
   экскурсию про сделки (у них своей нет — кнопки просто не будет).
6. Синонимы: «портал на телефоне» и «заявки в свою CRM» находили не то.

Pest 39/39, Vitest 1227/1227. Живой прогон вопросов — отвечает верно.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:50:19 +03:00
Дмитрий 3e9ce2091e fix(tours): карточка экскурсии не уезжает за экран и не тонет под виджетом чата
Владелец застрял на шаге «Цена» (экскурсия про сбор конкурентов): карточка встала
ниже видимой части окна и вдобавок была накрыта виджетом Jivo — кнопку «Далее»
нажать было нечем, экскурсия превращалась в тупик.

1. Позиция карточки прижимается к экрану (clampTop): она всегда целиком видна,
   какой бы низкой ни была цель шага.
2. z-index экскурсии поднят выше Jivo (виджет рисует себя поверх всего).

Оба бага сначала воспроизведены тестами. Vitest 1227/1227. Живьём: все 4 шага
экскурсии collect-competitors — карточка в экране, «Далее» кликабельна.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:18:18 +03:00
Дмитрий d1f3b0581e fix(tours): ссылка бота на экскурсию переживает вход и не перебивается приветственным туром
Владелец нажал «Показать на портале» в чате и не смог посмотреть: его выкинуло
на «Вход», после входа увело на дашборд (адрес экскурсии потерян), а сверху сел
приветственный тур «Добро пожаловать в Лидерру».

1. LoginView теперь возвращает клиента по redirect (роутер его уже сохранял) —
   /dashboard?tour=dupes открывается сразу после входа. Чужой адрес в redirect
   игнорируется (защита от подмены ссылки).
2. WelcomeTour молчит, если в адресе есть ?tour= — экскурсия из чата важнее.

Оба бага сначала воспроизведены тестами. Vitest 1225/1225. Проверено живьём:
гость → ссылка бота → форма входа → сразу «Шаг 1 из 4» нужной экскурсии.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:10:00 +03:00
Дмитрий 9ff19f3cc3 @
feat(bot): всеобъемлющая инструкция (36 статей) + 31 узкая экскурсия, которая открывает окна

Живой урок владельца 12.07.2026: «дубли чистятся в 2-х местах, а бот выдумал третье»,
«стыдно такого бота показывать», «видео показываем полное, а я спрашиваю про одно —
надо резать», «надо не только кнопку показывать, а и что за ней».

База знаний: 17 обзорных статей → 36 узких по темам. Портал обойдён целиком
(5 агентов): все экраны, окна, подсказки «(?)», тексты ошибок, внутренние правила.
Новое, чего бот не знал: дубли в ДВУХ местах (Поле и Предложения) + окно «источник
уже есть»; правило 18:00; почему приходит меньше лимита; оплата по счёту и акты;
списания; безопасность и 2FA; отчёты; массовые действия; регистрация.

Экскурсии: 15 → 31, каждая на свою тему (3–7 шагов). Раннер GuidedTour научен
шагу `open` — сам открывает окно/вкладку и подсвечивает то, что ВНУТРИ
(окно дублей, форму подбора, диалог пополнения, панель тарифов, карточку сделки).
99 новых якорей data-tour расставлено по всему порталу (4 агента).

Поиск: веса в tsvector (заголовок A / синонимы B / текст C) — иначе на «чистка
дублей» первой всплывала статья «Списания» из-за мимоходного упоминания «поля».

Тесты: Pest бот 39/39, Vitest 1222/1222. Новые сторожа: у каждой статьи есть
экскурсия; экскурсия не короче 3 и не длиннее 7 шагов; каждая цель экскурсии
реально существует в разметке; экскурсии про действия обязаны открывать окно.
Живая проверка в браузере: экскурсия про дубли сама открыла окно объединения
и перешла на второе место чистки.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@
2026-07-12 12:56:22 +03:00
Дмитрий 561096cc84 feat(tours): 15 полноценных экскурсий вместо 6 обрубленных + раннер водит по экранам
Претензия владельца 12.07.2026: «видео мало и они обрубленные».

Причина «обрубленности» найдена живым прогоном: раннер НЕ переключал экраны —
шаг про «Проекты» показывался поверх «Биллинга». Фикс GuidedTour: при переходе
на шаг с другим route зовём router.push (TDD-тест «шаг на другом экране»).

Каталог: 6 → 15 экскурсий, все минимум по 3 шага (тест-сторож в TourCatalog.spec):
start (4 шага), create-project (6), top-up-balance (6), tariffs (4), balance-pause (4),
deals (4), kanban (3), konkurentnoe-pole (6), change-source (4), security (4),
notifications (3), import (4), api (3), dashboard (4), support (3).

Якоря data-tour расставлены по портале (3 агента): биллинг (кошелёк, запас,
ступени, вкладки, сумма, способ оплаты), проекты (фильтры, карточка, меню, тип
источника, лимит, регионы, дни), сделки (фильтры, таблица, экспорт), канбан,
импорт, дашборд, настройки (вкладки, 2FA, матрица уведомлений), Поле (собрать,
вручную, дубли, вкладки, карточка, легенда, изучение), помощь (FAQ, форма).

Каждая из 17 статей инструкции получила свою экскурсию (frontmatter tour:).

Vitest 1216/1216, Pest бот 46/46, живая проверка в браузере: экскурсия сама
переходит между экранами и подсвечивает цели.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:48:46 +03:00
Дмитрий f33d1a1638 feat(jivo-bot): эскалация = просьба телефона + письмо с контактом в поддержку
Правило владельца 12.07.2026: оператора в чате может не быть, поэтому бот НЕ
переводит диалог (INVITE_AGENT убран из авто-флоу). Не смог ответить (личный
вопрос, стоп-тема, незнание) → просит номер телефона → как только клиент его
оставил, контакт уходит письмом в поддержку, а клиенту — «Спасибо, передал».

- ContactCapture: распознаёт номер в любом формате (+7/8/скобки/дефисы) → 79161234567
- BotContactRequestMail + шаблон: телефон, вопрос-на-котором-спасовал, id чата,
  переписка целиком, время → services.support.email
- ProcessJivoMessageJob: ждём контакт, если прошлый ответ бота просил номер;
  номера нет (клиент передумал/спросил другое) — отвечаем как обычно
- тексты эскалации переписаны под сбор контакта

TDD: BotContactCaptureTest (4 новых) + обновлены 4 теста прежней механики. 46/46.
Живая проверка на стенде: вопрос → просьба → номер → письмо с полной перепиской.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:30:33 +03:00
Дмитрий 9b83b2e035 feat(jivo-bot): статья «Откуда берутся заявки и законно ли это» (формулировка владельца)
Владелец 12.07.2026 дал суть: номера передают операторы связи, и только по тем
абонентам, кто дал согласие на передачу данных третьим лицам и на рекламные
предложения. Раньше бот на «а где вы берёте эти номера?» честно эскалировал —
теперь отвечает сам, юридически корректно.

+ otkuda-zayavki.md: источник данных (операторы связи), согласие абонента как
основание, 152-ФЗ, никаких «слитых» баз; практика для клиента (звонить можно
и нужно быстро, вежливо завершать при отказе); честная оговорка «заявка —
интерес, не гарантия продажи»; юрвопросы → живой специалист.

Границы держатся (проверено живьём): «какой конкретно оператор — Билайн? МТС?» →
эскалация; «эти номера только мне достаются?» → эскалация (эксклюзивность —
жёсткая стоп-тема). Поставщик и модель распределения не раскрываются.

TDD: тест на РЕАЛЬНОЙ базе знаний (3 формулировки вопроса → статья про операторов).
42/42 зелёные.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:18:11 +03:00
Дмитрий 27c863aa73 feat(jivo-bot): статья «О сервисе» — зачем нужна Лидерра и чем отличается
Живой урок владельца 12.07.2026: на «зачем лидерра нужна и чем отличается
от конкурентов» бот понял «конкурентов» буквально и рассказал про раздел
«Конкурентное поле». Ценностного предложения в базе знаний просто не было.

+ o-servise.md: оплата за результат (нет абонплаты), 7 ступеней, прозрачные
списания, управление лимитами/регионами/паузой, никаких обязательств, старт
за минуты + 1000₽ новичку, заявки сразу в CRM, кому подходит, честная оговорка
«заявка — это интерес, а не гарантированная продажа».

TDD: тест ищет на РЕАЛЬНОЙ базе знаний (help:rebuild-knowledge внутри теста) —
топ-1 по вопросу не должен быть «Конкурентное поле». 41/41 зелёные.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:11:24 +03:00
Дмитрий 1a686d07c8 feat(jivo-bot): всеобъемлющая клиентская инструкция — 15 статей + защита секретов
Разведка портала 5 агентами (экраны, биллинг/настройки, Конкурентное поле,
все подсказки (?), внутренние правила) → база знаний бота выросла 4 → 15 статей:
начало работы (что такое заявка, подарок 1000₽), проекты (лимиты/регионы/дни,
правило 18:00 МСК), тарифы (7 ступеней, списание по факту), пополнение (100₽,
карта/счёт/акт), баланс и пауза (разморозка «всё-или-ничего»), сделки и канбан
(5 статусов, экспорт), Конкурентное поле (сбор 300₽/изучение 50₽, очередь,
ручной режим, дубли, метки номеров), смена источника, настройки и 2FA,
уведомления, импорт CSV, API/webhook, мобильная версия, дашборд, поддержка.

Защита коммерческой тайны (проверено агентом-цензором — 15/15 статей чисто):
ни слова про поставщика/каналы/закупку и про то, что заявка может уйти не только
одному клиенту; вопросы об эксклюзивности и перепродаже — жёсткая стоп-тема
(бот не отвечает НИ В КАКУЮ сторону, зовёт живого специалиста), внутренние
движки не упоминаются, ложных обещаний (авто-возврат, «Команда») нет.

Стоп-темы разделены: HARD (деньги/скидки/юр/эксклюзив — всегда) и PERSONAL
(«мой баланс») с исключением HOWTO — «перенести мои заявки из амоцрм» больше
не уходит к человеку, бот рассказывает про импорт.

TDD: 40/40 зелёные. Живая проверка 20+ вопросов на стенде.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:02:23 +03:00
Дмитрий dd16edde8e fix(jivo-bot): «не знаю» без кнопки экскурсии + статья про объединение дублей
Живой урок владельца 12.07.2026 («а что такое объединить дубли?»):
- LLM признался «в инструкции этого нет» (слабое ИЛИ-совпадение по общим словам),
  а мы лепили кнопку «Показать на портале» → теперь такой ответ = честная
  эскалация без кнопок (регэксп-детектор признания)
- статья konkurentnoe-pole: «Найти и объединить дубли» 🧹 — пары-двойники,
  подтверждение клиента, бесплатно, счётчик на кнопке
- TDD: тест «не знаю без кнопки», 38/38 зелёные

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 10:24:01 +03:00
Дмитрий 185df2d810 feat(jivo-bot): память разговора + статья про ручное добавление конкурентов
Живой урок владельца 12.07.2026 («а сам руками я могу?» — бот талдычил про ИИ за 300₽):
- ProcessJivoMessageJob: последние 6 реплик чата → история для LLM (хвосты
  «👉 Показать…» срезаются), только СВОЙ чат
- BotAnswerService/YandexGptClient: история между system и текущим вопросом;
  пустой поиск досыпается прошлой репликой клиента
- статья konkurentnoe-pole: ручное добавление конкурента/точек/источников — бесплатно,
  кнопка «+ Добавить вручную» (фича существует, бот о ней не знал)
- TDD: 3 новых теста, все 37 зелёные

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 10:15:09 +03:00
Дмитрий e5f1ff9a4f fix(tours): карточка подсказки не уезжает за экран — справа/слева/снизу от цели
Найдено живым показом: у кнопки «Создать проект» (правый край) карточка
позиционировалась за пределами окна. Vitest GuidedTour 5/5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:29:05 +03:00
Дмитрий 93f2307aec feat(bot): дообучение под нововведения — Конкурентное поле, счета, подарок 1000₽, новые Сделки, мобильный
Статьи: +konkurentnoe-pole (сбор 300₽/изучение/пакет), пополнение (счета+акт+подарок),
проект (второй путь через Поле, колонки Конкурент/Источник, мобильные вкладки).
Экскурсия konkurentnoe-pole (nav-autopodbor). Pest Bot|Tour|Help 49/49.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:01:29 +03:00
Дмитрий cd62806da9 feat(tours): data-tour якоря — кнопки создания проекта и пополнения 2026-07-10 08:49:41 +03:00
Дмитрий bf8174dbfc feat(tours): запуск экскурсии по ?tour= из ссылки бота 2026-07-10 08:49:40 +03:00