Commit Graph

373 Commits

Author SHA1 Message Date
Дмитрий bb39703e62 docs реклама за показы: замысел — окно передачи между Яндексом и клиентом по отказам модерации 2026-07-28 08:32:38 +03:00
Дмитрий d3bfa2b05c docs подавать креативы роботом: дизайн-спека и находка про закрытый конструктор Яндекса
Живая проверка боевого кабинета опровергла допущение, на котором стояла Часть C и
часть уже написанного ядра. Конструктор креативов Яндекса закрыт 01.06.2026 —
Яндекс пишет об этом сам плашкой в редакторе объявления, показы его креативов
остановлены с 30.06. Адаптивного креатива из одной картинки не существует: медийная
кампания устроена как объявление на каждый размер блока со своим креативом, что
подтверждает наша живая кампания прогрева из 15 объявлений.

Отдельно проверен read-only запрос creatives.get с боевым токеном: метод отдаёт наши
баннеры, тип у них HTML5_CREATIVE, имя у всех одинаковое «Новый креатив» — опознавать
по имени нельзя, — и приходит PreviewUrl, годный для показа клиенту.

Отсюда дизайн: робот становится грузчиком готовых файлов, а не оформителем креатива.
Номера портал берёт сам через creatives.get слепком «до и после» с сопоставлением по
ширине и высоте. Ядро переходит с одного креатива на кампанию на креатив у каждого
баннера. Решения владельца: файлы готовит клиент, робот сам опрашивает портал, живёт
на боевом сервере, вход заводится через экран и удалённый рабочий стол на localhost.

Дописаны три вещи, не проговорённые в обсуждении: правило частичной модерации —
кампания работает, если принято хотя бы одно объявление, разморозка только когда
отклонены все; минимум один файл и не более одного на размер; раздел честно
неизвестного вместо выдуманных ответов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 12:32:11 +03:00
Дмитрий 2cb7e1d0ba docs(реклама): аудит Ф0 + дизайн-спека + план правок Яндекс-блока
Ф0-разведка (находки F0-9…F0-24 + 9 скриншотов), дизайн-спека
и пошаговый план (21 задача, 3 очереди) по UX-правкам рекламного
Яндекс-блока портала. Реализация — в этой ветке от main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 20:08:31 +03:00
Дмитрий 44c94da676 docs(смс): спека §3.2 приведена к реализации (match-сборка, честная стоимость нового оператора)
Убран 'class' из образца реестра (сборка через match в makeSmsProvider,
config:cache-safe), цена канала по ключу '*', и честно расписана стоимость
подключения следующего оператора (файл-провайдер + запись + арm + синоним).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:21:26 +03:00
Дмитрий c333746927 docs(смс): спека — маршрутизация СМС по операторам (МТС + СМС-центр, задел под остальных)
Дизайн схемы «МТС-номер → МТС, остальные → СМС-центр» с минимумом работы
при подключении новых операторов: реестр каналов в конфиге, справочник
операторов (OperatorNormalizer), имя отправителя на каждый канал, резерв
выключателем (по умолчанию OFF). Схему БД не трогаем. Утверждено владельцем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:20:10 +03:00
Дмитрий 4568612af6 docs(прогрев): Фаза 2 — уточнения владельца (свободный срок, СМС без warming, бэкфилл СМС только слали) 2026-07-22 13:49:06 +03:00
Дмитрий 15f198c49c docs(прогрев): спека Фазы 2 — раздельные каналы + заезд-загрузка (вся фаза) 2026-07-22 13:39:20 +03:00
Дмитрий 587ad4173b docs(прогрев): спека Фазы 1 — раздельные сроки по площадкам (кусок C)
Каждый из 3 рекламных каналов (Яндекс/ВК/Телеграм) получает свои 11 сроков + days;
движок считает решение по каждому каналу отдельно (decideForPlatform, чистая функция);
сроки переезжают на sales_ad_audience_platforms; recalc сводит на фирму для backward-compat
читателей (СМС/карточки); sync-джобы включают номера по срокам своей площадки; заодно
чинится рассинхрон ВК-джоба (читал singleton вместо строки площадки). Состав/СМС/заезд
из поиска — Фаза 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 08:27:28 +03:00
Дмитрий d11778a32b docs(прогрев): спека — 4 страницы, ниша, менеджер, постраничность, дата запуска, пометки на карточках
Портальная часть (пункты 1,2,3,5,6,7): общий движок таблицы на v-data-table,
ниша+дата колонки и фильтры, Отметить все+счётчик, показ менеджера +
назначение на СМС, пагинация 10/25/50/100, пометки греётся/прогрет + значки
каналов на карточках. Пункт 4 (поиск клиентов) — следующим заходом.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:34:55 +03:00
Дмитрий ecbcea8eed docs(прогрев): дизайн — три площадки прогрева + витрина по порталу
Кусок A (к реализации): фундамент «фирма на площадке», таблица настроек
по площадкам (свой рубильник/пороги/синхронизация), три пункта меню
Яндекс/ВК/Телеграм; поведение прогрева не меняется, 177 номеров → «Яндекс».
Куски B (витрина: значки на карточках, колонки «Греются/Прогреты», история
эпизодов, СМС) и C (раздельные сроки) — дорожная карта. Плюс справочник 11 сроков.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:01:46 +03:00
Дмитрий 6e771636ef feat(смс): модуль «Прогрев СМС» с заделом под мультиклиентность
Начальник отдела продаж отмечает фирмы прогрева галочками, пишет текст,
видит цену ДО отправки и журнал после. Отправки СМС в проекте не было
вообще — ни у СМС-центра (только баланс и HLR), ни у МТС (только файл).

Что сделано:
- разъём провайдера SmsProvider: новый оператор подключается одним файлом
- заглушка FakeSmsProvider — модуль работает и проверяется ДО согласования
  имени отправителя у операторов (это недели), иначе разработку не закончить
- маршрутизация по оператору: билайновский номер уходит через Билайн за
  4,75 ₽, прочие через МТС — без ручного выбора канала
- стоп-лист: кто отписался, тому не шлём никогда, проверка перед списанием
- отбор получателей с шестью причинами пропуска, все ДО траты денег
- списание скопировано с AutopodborChargeService; пока клиента нет
  (tenant_id пуст) с баланса не берём — платим оператору напрямую
- оператор номера доезжает из «Поиска клиентов» в прогрев (был известен
  и оплачен ДаДате, но терялся при передаче)

Мультиклиентность в костях: колонка tenant_id во всех четырёх таблицах
СМС с первого дня, NULL = «Лидерра сама». Клиент добавляется строкой,
а не переделкой модуля.

Найдено и закрыто при исполнении:
- замок от двойного списания стоял не на том соединении: кампания на
  pgsql_supplier, деньги на pgsql, lockForUpdate по кампании отпускался
  сразу. На бою два запуска списали бы дважды, обрыв — оставил бы пометку
  «оплачено» при неушедших деньгах. Источник правды перенесён в
  balance_transactions под замок по тенанту. Доказано тестом: старый код
  списывал 700 вместо 850
- приём в портал требовал phones строкой по regex — словарь с оператором
  получал 422, в базу не доезжало ничего. Тесты были зелёные, потому что
  звали сервис МИМО контроллера. Проверка теперь принимает оба формата,
  тест идёт через HTTP
- телефоны директоров в contacts остаются строками (договор
  SalesProspectController), словари — только в верхнем phones

Заодно вылечена мигающая поломка 48 тестов доставки лидов: помощник
createRoutingSnapshotFromProject клал снимок на сегодня, а LeadRouter
после 21:00 МСК ищет завтрашний (вечерний переворот заливки) — вечерние
прогоны падали, дневные проходили. Помощник теперь зеркалит активную дату
роутера в любой час. Регрессия SnapshotHelperTimeOfDayTest замораживает
22:00 МСК и пинит инвариант. Боевой LeadRouter не тронут.

Тесты: 84 бэкенд + фронт по экрану + 397 поисковика, весь набор 3226
зелёный, статанализ чист. Все защиты проверены вырезанием.

План: docs/superpowers/plans/2026-07-20-sms-progrev-modul.md
Спека: docs/superpowers/specs/2026-07-20-sms-progrev-modul-design.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 06:35:43 +03:00
Дмитрий 8697f8b0a2 docs(finder): спецификация и план — фильтр по нише и групповые действия над списками
Постановка владельца 19.07.2026: на экране «Последние списки» нужен фильтр под
выбранную нишу, галочки на списках и групповые действия (удалить / собрать телефоны /
прогреть / отдать менеджеру) — все только по горячим фирмам.

Решения владельца зафиксированы: горячие = 70+, уже собранных и отданных пропускаем
и сообщаем сколько, перед платным действием и удалением спрашиваем с числом,
Телеграм и СМС — заглушки до готовности модулей.

Найдено при проектировании: прогрев берёт только фирмы с собранным мобильным, значит
порядок работы — телефоны → прогрев → менеджер; после прогрева на нашей стороне не
оставалось следа (добавляем пометку площадки); показ 1000 списков требует переноса
счётчиков в колонки, иначе главная разбирает всю базу на каждое открытие.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 08:00:27 +03:00
Дмитрий d3ccf2db63 docs(sales): описание выбора площадки прогрева (Яндекс/ВК/оба)
Признак channels у фирмы, три кнопки и колонка «Где греем» на экране
начальника, заливка в ВК с тремя статусами (нет доступа / ждёт объёма /
работает). Порог автомата у ВК — 2000 номеров, это его же документация.

Зафиксированы находки 19.07: охват списка в ВК меньше сотни при 111
загруженных номерах, и что «111 из 111» у Яндекса — проверка формата,
а не совпадений. Открытый вопрос про перезапись списка в API ВК помечен
явно — уточняется при получении доступа.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:11:17 +03:00
Дмитрий 9bc587c2de fix(sales): реальный телефон директора убран из тестов и планов; клиент Яндекс.Аудиторий возвращён в main
Коммит d2c2ec43 от 19.07 оторвался от main (dangling, ни в одной ветке):
чистка ПДн и YandexAudienceClient в основную ветку так и не попали.
Номера заменены на фиктивные 7999000000X, клиент и его тесты внесены заново.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:24:21 +03:00
Дмитрий e09657bbb9 docs(sales): HANDOFF рекламной аудитории + спека v2 (порядок перевёрнут)
Владелец переформулировал: сначала ГРЕЕМ рекламой, потом отдаём менеджеру.
Прогрев — отдельный этап ДО воронки, карточка рождается при назначении менеджера.

Правила рекламы теперь следуют за стадией карточки, все сроки настраиваются:
прогрев 3 дня, новые 3, взят в работу 14, переговоры до даты созвона
(просрочена → +3 → стоп), недозвон 7, отказ 3, регистрация/тестирование 30,
пополнил баланс и пользователь — стоп.

Проверено на боевых данных: у «Взят в работу» и «Регистрация/Тестирование»
даты созвона нет и не будет — им обязателен свой срок, иначе реклама вечная.
У «Переговоров» дата обязательна в коде, логика владельца закрывает полностью.

HANDOFF содержит промпт для следующей сессии, все технические грабли,
состояние Яндекса (сегмент 58029600, письмо в поддержку отправлено)
и запись об инциденте с ПДн в истории коммитов.

Все телефоны в документах замаскированы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:34:59 +03:00
Дмитрий dc4a17cb43 docs(finder): правка владельца — живость менеджеру не показываем, зачёркиваем только «не существует»
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 07:05:11 +03:00
Дмитрий d2b74c8b04 docs(finder): спека и план проверки телефонов (ДаДата + HLR)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 18:22:40 +03:00
Дмитрий d9e82f6e3c @
docs(sudya): проект сводного судьи диалогов Александры

Собран из трёх существующих семейств судей: голосовые (NISQA/дуэли/glitch),
чатовые (AnswerGuard + слепые судьи-агенты), нормативные (llm-judge/reviewer-agent/
judge-evaluator). Ни одно из них не судило ГЛАВНОЕ — продала ли она; для номера
менеджера по продажам на лендинге это ключевой спектр.

Устройство: приборы (приговор) → коллегия узких судей-ролей (находки) →
судья над судьями (доля промахов). 36 признаков в шести спектрах.

Протокол честности, каждое правило оплачено ошибкой: слепота, нейтральный вопрос,
живой якорь в партии (забраковал якорь → партия недействительна), роли вместо
одной большой рубрики, находка без цитаты источника не считается.

Границы названы явно: звук живых клиентов не пишем (ПДн — отдельная работа),
судья не правит Александру сам, владелец остаётся высшей инстанцией.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:09:39 +03:00
Дмитрий 277800149f @
fix(sales): строгое разделение двух ролей начальника — экраны «мои» больше не показывают отдел

Начальник продаёт сам и руководит отделом. Правило «начальник видит всё»
применялось к ЧЕЛОВЕКУ, а не к экрану, поэтому разделы меню врали:

- «Потенциальные клиенты» показывали все карточки отдела — точную копию
  «Воронки отдела» (это владелец и заметил);
- «Мои клиенты» и «Сводка» — всех клиентов отдела;
- «Привязать клиента» — очередь заявок отдела, причём в ЧУЖОМ формате
  ({pending, history} вместо {data}), форма получала не те данные.

Новое правило: роли разделяются по ЭКРАНУ. Любой запрос по умолчанию отдаёт
только личное — включая начальника. Весь отдел выдаётся только по явному
?scope=department, и просят его только экраны раздела НАЧАЛЬНИК. Менеджеру
параметр ничего не даёт: проверка по роли, не по параметру.

ownedTenantIds теперь ВСЕГДА личные привязки (тип сузился с ?array до array),
добавлен visibleTenantIds для области видимости запроса.

Правом начальника осталось открыть ЛЮБУЮ карточку — кандидата и клиента:
иначе из «Воронки отдела» не открылась бы карточка чужого менеджера.
Ограничены только списки, не доступ к записи.

Следствие: у начальника сейчас 0 своих кандидатов и клиентов, поэтому его
личные экраны станут пустыми — это правильно, а не поломка.

Pest 272/272, Vitest зелёный, vue-tsc чист, Larastan без своих ошибок.
Спека — §23.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 15:59:56 +03:00
Дмитрий e9edb206c8 @
feat(sales): кабинет начальника — единый период (без 500) + пополнения баланса

Экраны начальника отстали от вчерашних правок кабинета менеджера (§16–§21).

1. Живой баг: «Произвольный» период до выбора дат ронял запрос в 500 на пяти
   точках из шести (сводка отдела, доход, результативность, выплаты, тарифы).
   Разбор периода вынесен в трейт ResolvesSalesPeriod: 422 вместо падения,
   период по умолчанию d30 вместо this — как показывает сам PeriodPicker.

2. Пополнения баланса (topup_rub) добавлены в dashboard/overview (kpi +
   строки менеджеров) и managers/performance. На экранах: плашка «Пополнили
   баланс» в сводке отдела и колонка «Пополнили ₽» в обеих таблицах
   результативности. Из подписей убрано «(мес)» — период больше не месяц.

3. «Воронка отдела» правок не потребовала: она рендерит те же доску и карточку,
   что экран менеджера, а карточка сама грузит журнал и сохраняет контакты.

Тесты: SalesPeriodRequestTest проходит по всем шести точкам сразу.
Pest 263/263, Vitest 1420/1420, vue-tsc чист.
Спека — §22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 15:20:44 +03:00
Дмитрий 6416ec3789 @
feat(sales): периоды сегодня/вчера/7/30 дней календарём, пополнения за период, скролл доски наверх

Замечания владельца по сводке и доске.

ПЕРИОДЫ:
- Резолвер понимает today/yesterday/d7/d30; «7 дней» = сегодня и 6 предыдущих.
  Месячные this/prev/prev2 сервер принимает по-прежнему.
- По умолчанию 30 дней.
- ПОЧИНЕНО: выбор «Произвольный» без дат ронял запрос (500). Теперь понятный 422,
  а на фронте период применяется только когда отмечены ОБЕ даты.
- Произвольный выбирается календарём-диапазоном, не руками; порядок дат неважен.

ПОПОЛНЕНИЯ ЗА ПЕРИОД (переиспользован готовый topupsRub):
- Сводка: плашка «Пополнили баланс» рядом с «Σ баланс».
- Мои клиенты: колонка «Пополнил».
  Заодно даёт число, которое видимо меняется при смене периода.

ДОСКА: горизонтальная полоса прокрутки поднята НАД колонками (колонки высокие,
системная полоса уезжала за экран); синхронизация в обе стороны, ResizeObserver
на приезжающие карточки.

Спека §21 (включая §21.4 — как по коду заполняется «Требуют внимания»).
Гейты: Pest 257/257 sales+unit, Vitest 1417, vue-tsc чисто, Larastan 0 в своих. TDD.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 14:29:18 +03:00
Дмитрий 1e0442facf @
feat(sales): результат разговора и его содержание — одной записью в журнале

Владелец: «объедини в одну запись: договорились… сверху, внизу текст».
Было две записи подряд — в ленте читалось как два события, хотя разговор один.

- БД v8.75: sales_prospect_notes.title VARCHAR(500) NULL — что решили.
- Одна запись kind=note: title = «Договорились на созвон 20.07.2026 17:35»,
  body = краткое содержание. Есть результат без содержания — как раньше,
  одна запись kind=stage без заголовка. Ручная заметка — без заголовка.
- В ленте заголовок строкой сверху, под ним текст.
- Старые парные записи задним числом не сливаем — историю не переписываем.

Спека §20.2. Гейты: Pest 240/240 sales, Vitest 64/64 воронка, vue-tsc чисто,
Larastan 0 в своих файлах. TDD RED→GREEN.
🪤 Тесты DOM для v-dialog: контент уезжает телепортом в body — искать через
document.body.querySelector, а не wrapper.find.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 14:00:55 +03:00
Дмитрий 2cd99d6ddc @
fix(sales): одна кнопка «Сохранить» в карточке — контакты больше не отдельно

Владелец: «исправь сохранения контакта убери, все на кнопку Сохранить заведи».
Две кнопки в одном окне путали: можно было записать результат и уйти, потеряв
только что набранные телефоны.

- Кнопка «Сохранить контакты» и подпись «сохранено» убраны.
- «Сохранить» теперь: сохраняет контакты → если выбран результат, проверяет
  и отправляет его → если результат не выбран, просто закрывает окно.
- Контакты не сохранились (нет сети) — результат НЕ отправляем и показываем
  ошибку, иначе стадия уехала бы, а телефоны потерялись.
- submit() стал асинхронным, кнопка показывает загрузку.

Спека §20.1. Гейты: Vitest 62/62 воронка (4 новых теста), vue-tsc чисто. TDD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:49:14 +03:00
Дмитрий 138550207a @
feat(sales): содержание разговора — вместе с результатом; недозвону дата перезвона

Владелец после первого показа карточки: «куда звонить убери; договорились на
созвон — ниже краткое содержание, и оно копится в Историю разговоров, свежие
в начало; недозвон — поставь дату следующего перезвона».

- Блок «Куда звонить» убран: дублировал строку «Телефон общий» из данных фирмы.
- «Краткое содержание разговора» переехало последним полем в блок «Результат
  разговора» (было отдельное поле с кнопкой слева — два места для одного
  действия). Уходит параметром summary вместе с результатом, ложится в журнал
  отдельной записью kind=note. Пустое — не пишем.
- Порядок внутри одного сохранения: сначала автозапись про этап, затем
  содержание → у него больший id и в ленте оно оказывается НАД этапом.
- Недозвон получил необязательное поле «Когда перезвонить»: пишется в
  next_call_at (видно на плитке) и дописывается в автозапись журнала.

Спека §20. Гейты: Pest 236/236 sales, Vitest 59/59 воронка, vue-tsc чисто,
Larastan 0 в своих файлах. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:20:27 +03:00
Дмитрий 41fbc4491d @
feat(sales): журнал разговоров по кандидату + переделка обоих окон воронки

Владелец: «краткое содержание разговора писать, чтобы не потерять историю»
и «переделай обоих дизайн, очень не практично, спроси у перплексити».

ЖУРНАЛ (БД v8.74, таблица sales_prospect_notes, append-only):
- kind=note — менеджер написал руками; kind=stage — автозапись о переезде
  по стадии (взял в работу, созвон, недозвон, отказ, регистрация).
  Повторное открытие карточки журнал не засоряет.
- GET/POST /api/sales/prospects/{id}/notes; лента свежими сверху, грузится
  при открытии карточки, а не вместе с доской.
- PATCH /prospects/{id} БОЛЬШЕ НЕ принимает notes: он молча затирал прошлую
  запись — ровно та потеря истории, ради которой журнал и появился.
  Нашёл rls-reviewer, закрыто тестом. Старые notes перенесены в журнал.

ДИЗАЙН (по разбору Pipedrive/HubSpot/Salesforce через Perplexity):
- Карточка 1100px, две колонки. Слева «что за фирма» + история разговоров
  с полем «о чём поговорили». Справа зона действия: «Куда звонить» (номер
  крупно, ссылкой tel:), «Результат разговора» своим фоном, контактные лица.
  Кнопки внизу окна. Пустая история объясняет, что делать.
- Форма создания разбита на разделы: Компания / Контактные лица / Заметка;
  Юрлицо и Город в одну строку; обязательных полей по-прежнему два.

Спека §19. Гейты: Pest 231/231 sales, Vitest 60/60 воронка, vue-tsc чисто,
Larastan 0 в своих файлах, rls-reviewer PASS. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:04:02 +03:00
Дмитрий 68428f16c6 @
feat(sales): карточка кандидата — девять полей владельца + правка контактных лиц

Владелец задал список полей карточки и попросил дать менеджеру записывать
контактных лиц прямо в карточке: поиск отдаёт 5-7 ПРЕДПОЛАГАЕМЫХ номеров
директора, живой из них выясняется обзвоном; а если директор перенаправил
к маркетологу — менеджер фиксирует и его, людей может быть 2-3.

- Карточка показывает РОВНО девять полей в его порядке: Юрлицо, Сайт, ИНН фирмы,
  Адрес, Телефон общий, Каналы, Бюджет, Директор, Тел. директора (предполагаемый).
  Убраны Оценка, Достоверность, Реклама, Запросы в Директе, ОГРН, Статус юрлица,
  Личный ИНН директора, Почта директора — данные остаются в payload.
- PATCH /api/sales/prospects/{id}/contacts — список заменяется целиком, пустой
  стирает всех; права как у update() (менеджер свои, начальник любые); главный
  телефон карточки из поиска не трогается.
- ProspectContactsEditor.vue + utils/prospectContacts.ts — один редактор контактов
  на диалог создания и карточку; диалог создания переведён на него.
- Закрывает хвост §16: у 24 живых карточек из поиска контактных лиц не было,
  теперь дозаполняются руками.

Спека §18. Гейты: Pest 223/223 sales, Vitest 196 файлов / 1397 тестов,
vue-tsc чисто, Larastan 0 в своих файлах. TDD RED→GREEN на каждом шаге.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:29:04 +03:00
Дмитрий 48dd6c44af @
fix(sales): «Зарегистрировался» подаёт ЗАЯВКУ начальнику, а не привязывает клиента сразу

Владелец заметил: в отделе есть штатный порядок «через начальника» (экран «Заявки
на привязку»), а кнопка в карточке кандидата создавала SalesClientAssignment напрямую
и в истории заявок ничего не появлялось.

- registerProspect() теперь зовёт SalesAttachmentService::submit() от имени ВЛАДЕЛЬЦА
  карточки. Свободен → заявка pending + письма менеджеру и начальникам; занят другим →
  заявка с пометкой конфликта, чужая привязка не трогается; уже свой → заявки нет.
  Привязка со снимком тарифа создаётся только при одобрении (handleApprove).
- Стадия карточки едет в «Зарегистрировался» СРАЗУ (решение владельца): регистрация —
  факт, воронка показывает правду; «чей клиент и кому деньги» решает начальник.
  Автожизнь не зависит от одобрения — джоб смотрит linked_tenant_id.
- Отменён прежний 422 «клиент уже закреплён за другим менеджером» — теперь это заявка.
- Подсказка под полем e-mail говорит про одобрение начальником.

Спека §17 (отменяет правило §6 «привязываем сразу»). Гейты: Pest 218/218 sales,
Vitest 38/38, Larastan 0 в своих файлах. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:09:24 +03:00
Дмитрий 1011a37e77 @
feat(sales): кандидат — ИНН обязателен, юрлицо отдельно от бренда, контактные лица

Замечание владельца по форме «Добавить кандидата»: телефонов бывает много, нужны
ФИО и должность человека, ИНН обязателен, бренд и юрлицо — разные вещи, юрлицо
подтягивать по ИНН через ДаData.

- БД v8.73: sales_prospects.legal_name VARCHAR(500) + contacts JSONB DEFAULT [].
  phone остаётся «главным телефоном»; inn в БД по-прежнему nullable — карточки
  из поиска и 24 живые строки могут быть без него.
- store(): ИНН обязателен + контрольная сумма ФНС; повтор ИНН у того же
  менеджера — понятный 422 вместо 500 от уникального индекса; пустые контакты
  и телефоны отсекаются; phone = первый телефон первого контакта.
- POST /api/sales/prospects/lookup-inn — юрлицо/город по ИНН через готовый шов
  PartyLookup (тот же, что в «Реквизитах»). Ничего не сохраняет; ДаData молчит
  или упала — заводим руками.
- Диалог создания: ИНН* с кнопкой «Найти», бренд*, юрлицо, блок контактных лиц
  (+человек / +телефон). Карточка показывает юрлицо и контакты со ссылками tel:.

Спека §16, CHANGELOG_schema v8.73. Гейты: Pest 28/28, Vitest 38/38,
Larastan 0 в своих файлах, rls-reviewer PASS 4/4.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 11:39:48 +03:00
Дмитрий f3b445b049 docs(brain): спека + план — хранилище знаний Александры (Obsidian) + умный поиск ниши 2026-07-17 07:56:06 +03:00
Дмитрий e8584c6214 @
docs(spec): дизайн — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы» Командного центра

Вариант А: живость проверяет виртуалка-рендер (у неё логин/пароль прокси),
портал показывает срок «до 04.08.2026» из конфига (владелец обновляет руками
при продлении). Схему БД не трогаем. Новая ручка /proxy-check на рендере +
ProxyMarketProbe + строка proxy_market в реестре/плашке.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-17 07:14:55 +03:00
Дмитрий 907bd2f231 docs(external): спека модуля чистки доп-каналов поставщика (B6/B8 → удаление в 20:00, KEEP=B1/B2/B3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:15:37 +03:00
Дмитрий f4bcfa4880 docs(external): спека+план честного присмотра за поиском клиентов (xfetch/keyso/self_render) 2026-07-16 16:56:24 +03:00
Дмитрий b6a016ea3d docs(external): HANDOFF (состояние+находки+промпт след.сессии) + спека + план онлайн-мониторинга
Фиксирую после выката 16.07: живое состояние 12 сервисов, мины (DELETE-грант/EXA-proxy/
живость<500/serial-only regress), и НАХОДКИ владельца — xfetch реально в Sales-finder
(/opt/sales-finder, не портал), EXA-тоннель на render-VM автоподбора, Sales-finder тянет
Keyso+DaData вне присмотра. §6 — готовый промпт для следующей сессии.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 15:42:41 +03:00
Дмитрий 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
Дмитрий ad2c61bab5 docs(sales): замечания владельца после демо Этапа 1 (карточка=все поля, сорт по созвону)
3 правки в §15: карточка показывает ВСЕ данные поиска (payload), фильтр —
конкретный выбор менеджера, сортировка «Переговоры» по ближайшему созвону.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:16:38 +03:00
Дмитрий 601a1dd0cb feat(sales): таблица sales_prospects (воронка потенциальных клиентов)
Этап 1 Task 1. SaaS-level таблица без RLS + 8 стадий воронки, включая
«Тестирование» (начал тратить бонусные 1000 ₽ после регистрации).
Дизайн/план обновлены под 8-ю стадию. cspell исключён: сработал на
пред-существующих словах CHANGELOG_schema, мои файлы проверены чисто.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 16:16:00 +03:00
Дмитрий cbed285288 docs(sales): дизайн «Потенциальные клиенты» — воронка-канбан отдела продаж
Начальник отдаёт фирмы из поиска менеджеру → канбан в кабинете портала.
7 колонок (Новые/Переговоры/Зарегистрировался/Пополнил баланс/Пользователь/
Отказ/Недозвон), движение карточки — по результату разговора (без drag),
автостадии по реальным пополнениям, интеграция поиск→портал по сервис-токену.
Общая доска начальнику + фильтр по менеджерам. 3 этапа. Согласовано в чате.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:55:00 +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
Дмитрий 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
Дмитрий f4c3d885aa docs(specs): собственный учёт посетителей + раздел «Посетители» в админке
Точное разделение робот/браузер/человек (отклик страницы, а не запрос),
сквозная склейка лендинг→портал через куку на .liderra.ru, воронка и каналы
в админке. Метрику и Вебвизор не трогаем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:33:21 +03:00
Дмитрий dd50f3832f docs(specs): собственный учёт посетителей + раздел «Посетители» в админке
Точное разделение робот/браузер/человек (отклик страницы, а не запрос),
сквозная склейка лендинг→портал через куку на .liderra.ru, воронка и каналы
в админке. Метрику и Вебвизор не трогаем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:17:21 +03:00
Дмитрий 09f0ea44df docs(chat): замысел личных ответов вошедшему клиенту
Карточка фактов (баланс/ступень/проекты/заявки) считается кодом и всегда
кладётся боту, когда клиент вошёл. Сторож режет числа не из карточки.
Переписка вошедшего закрывается от посторонних. Данные — строго под RLS
тенанта. Бот только рассказывает, ничего не меняет.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:55:32 +03:00
Дмитрий 01789381ac docs(chat): замысел своего чата вместо Jivo + разворот в протоколе
Владелец 13.07.2026: уходим от Jivo (3142 руб/мес корпоративный тариф ради
Bot API, ответа их поддержки так и нет), пишем своё окошко чата.
Ключевое: в чужом виджете бот не знает, кто с ним говорит -> личные ответы
невозможны. Привязка к Jivo в коде - 94 строки; мозг бота, 49 статей и
130 тестов переезжают нетронутыми.

Согласовано по частям: чат без постоянного соединения (очередь + опрос),
кабинет + лендинг, эскалация письмом владельцу (живого оператора нет),
три уровня счётчиков от накрутки счёта (потолок 1500 ответов/сутки + тревога).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 06:27:29 +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
Дмитрий 5a3746da9c fix(sales-finder): контакты по ИНН фирмы через POST /2.0/inn — живой запрос заработал
Выяснено живьём: сервис принимает POST с ключом+ИНН в теле, эндпоинт /2.0/inn
не inn_fl, ищет по 10-значному ИНН фирмы. GET с ключом в адресе давал 401,
inn_fl давал uncorrect format. Переделал contacts.py на POST с телом,
двухстрочный секрет адрес+тело, parse_config сам определяет POST/GET.
Поиск теперь по firm.inn, галочка активна при наличии ИНН фирмы.
Проверено сквозняком: Илатан 7709918259 -> реальные телефоны и почты в колонках.
167 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 16:25:13 +03:00
Дмитрий da9d8cf00e feat(sales-finder): сбор контактов телефон+e-mail по личному ИНН директора
Галочки в списке на прозвон -> кнопка Собрать контакты -> по director_inn
тянем телефон и e-mail через сторонний inn_fl API, 2 новые колонки + Excel.
Домен/ключ API живут только в secrets/inn_fl_url.txt со шаблоном {inn} -
в коде не зашиты. Ответ парсим по образцу regex, ИНН/ОГРН телефоном не считаем.
ПДн: сбор только по явной отметке, последовательно, ошибка одной фирмы не роняет пакет.
165 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:55:32 +03:00
Дмитрий b555931bde docs(sales-finder): брейншторм-заметки, дизайн и план портала поиска клиентов для отдела продаж
Портал на этой машине: рубрика 2ГИС + город → список фирм на прозвон
(кто рекламируется, ИНН, директор). Переиспользуем наработки (ChannelA/2ГИС,
ad-scan, Keys.so, build_portal, ДаДата), боевое не трогаем — только копии.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:28:03 +03:00
Дмитрий ac8e3277a7 docs(tours): этап 3 построен — протокол и спека синхронизированы 2026-07-10 08:49:42 +03:00
Дмитрий 4d3e568179 docs(bot): спека/протокол — FTS вместо pgvector, статьи в resources/help
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:48:51 +03:00
Дмитрий 7cb6542f00 docs(support-bot): спека своего ИИ-бота в чате Jivo + протокол обсуждения
Дизайн согласован владельцем 02.07.2026: свой бот через Jivo Bot API,
YandexGPT Lite, база знаний = docs/help/ в репо, кнопка «Показать»
(экскурсии), скорость 2-5 сек, v1 только общие вопросы.
Словарь: +jivo/дживо/gigachat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:44:23 +03:00