Commit Graph

1568 Commits

Author SHA1 Message Date
Дмитрий 7d3ed2f6eb feat(реклама): Часть 6 — списание по факту показов + клиентский отчёт по показам
CampaignImpressionCharger: списание с рекламного кошелька за фактически
показанные показы (показано×120/1000, дельта от уже списанного, идемпотентно
по external_key yandex-imp:{id}:{billable}), режет по оплаченному, статус
completed при достижении оплаченного; bcmath, 6 boundary-тестов. Клиентский
отчёт CampaignReportDialog переведён с недельного бюджета на показы
(оплачено/показано/частота/потрачено); статусы queued/completed в списке и
отчёте. Маржа/yandex_cost клиенту не видны. Бэкенд 105/105, фронт 104/104.

Отложено до Части 4 (нужен Директ): джоб, тянущий фактические показы из отчёта
Директа и зовущий CampaignImpressionCharger; campaigns.suspend при стопе;
переделка админ-маржи с наценки-% на реальный yandex_cost_rub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 16:07:19 +03:00
Дмитрий de9f0d7d8e feat(реклама): Часть 5b — мастер «за показы», отправка-заявка, скрытие маржи
Мастер CampaignWizard переделан с клик-модели на показы: окно дней → частота
+ живая смета (120 ₽/1000 показов, без маржи) → одна картинка + галерея превью
+ утверждение баннеров → проверка и «Отправить заявку». Бэкенд store/update
принимают частоту/бюджет показов (клик-поля убраны), новый submit → статус
queued («готова к запуску», реальный запуск в Директ — Часть 4). Маржа
yandex_cost_rub скрыта от клиента ($hidden, тест). CampaignList: метка queued
и показы вместо недельного бюджета. Тесты: бэкенд 99/99, фронт 102/102.

Директ: заявка на ПОЛНЫЙ доступ к API подана 26.07 (upgrade, статус «новая»);
картинка-креатив через API невозможна (creatives.add только видео) — гибрид,
баннер заливается в кабинет вручную, CreativeId вставляется в портал.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 15:45:53 +03:00
Дмитрий 449c6c5e27 docs(реклама): промт для перезапуска сессии — восстановление контекста + продолжение с Ч.5b
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 12:30:07 +03:00
Дмитрий 1faf6ea76c feat(реклама): Часть 5a — фронт API-слой показов + HANDOFF (Директ err58)
Часть 5a из 6. Функции api/advertising.ts под endpoint'ы показов для мастера (Ч.5b).

- AudienceSize +estimate-поля; fetchAudienceSize(id,days,frequency) шлёт frequency
  для сметы. Баннеры: uploadBannerSource/fetchBanners/approveBanners/bannerPreviewUrl.
- HANDOFF: Часть 4 ЗАБЛОКИРОВАНА Яндексом (err58 — проверено живым API-вызовом; доступ
  к API не одобрен, нужна заявка+Госуслуги) + вторая стена (баннер-креатив только руками).

Тесты: vitest 6/6 новых + 19/19 регресс.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 12:23:19 +03:00
Дмитрий ed1be2edfe docs(реклама): HANDOFF стройки «за показы» — 4 части готовы, план 3b-2/4/5/6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 11:36:57 +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
Дмитрий db2bf0113c docs(телефония): снимок состояния Dasha-ботов и Mango + разбор ремонта исходящей
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Хендофф по голосовым «Лена»: маршруты Asterisk (вход→приёмщик, обзвон через
obzvonbot+endpoint obzvon-lena), состояние Dasha (2 агента, лимит 1 разговор),
Mango (8 номеров, этикетка «Омега», МАВ), открытые задачи. Плюс перенесён с
Рабочего стола разбор ремонта исходящей связи Mango (403→180).
Полные телефоны/секреты не кладём (ПДн) — только состояние и команды.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 23:07:52 +03:00
Дмитрий ddabe61558 Merge commit 'a90d60a3' into sms-multi-operator
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
# Conflicts:
#	app/app/Jobs/SendSmsCampaignJob.php
2026-07-23 14:34:52 +03:00
Дмитрий 80c9ca7e67 feat(витрина B1,B2,B4,B5): летопись эпизодов прогрева + значки в воронке; уборка мёртвых скоупов
Кусок B «витрина прогрева» (спека 2026-07-21 §6), решение владельца — полная летопись:
- B1: таблица sales_ad_audience_warming_episodes + модель + WarmingEpisodeRecorder
  (open/close/record идемпотентно). Схема v8.85, бэкфилл из firm_channels(warming)
  и боевых СМС, guard по источнику. rls-reviewer OK 8/8.
- B2: «Греть»/«Убрать» на площадке открывают/закрывают эпизоды канала.
- B4: warmingByProspect считает значки из летописи (live/count вместо массива каналов),
  тип WarmingBadgeState в sales.ts.
- B5: единый компонент WarmingBadges.vue (идёт/грели раньше/×N) в канбане;
  осиротевший WarmingChannelIcons удалён.
Уборка: убраны мёртвые скоупы forYandex/Vk/Mts + их импорт + тест (боевых вызовов нет).
cspell: +5 пре-существующих слов CHANGELOG в словарь (apk/cvtjpq/hgq/sar/sca).

Проверено: Sales 427/427, composer stan 0, pint/prettier чисто, весь Vue-набор зелёный.
B3 (СМС→эпизод) — отдельным коммитом (СМС-блок правит и параллельная сессия).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:54:09 +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
Дмитрий c88e81e803 docs(смс): план реализации — маршрутизация СМС по операторам (МТС + СМС-центр)
9 задач TDD: справочник операторов, каналы МТС(Exolve)/СМС-центр(smsc.ru),
реестр каналов в конфиге + сборка роутера, нормализация оператора,
имя отправителя под канал, резерв выключателем (off). Схему БД не трогаем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:36:56 +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
Дмитрий 48b88737e7 feat(прогрев ф2 этап D): backend СМС-канала — свой список sms-firms + счётчик «Отправлено N» (+ план этапа D)
Фаза 2 Этап D, Task D1. Новый эндпоинт GET /api/sales/ad-audience/sms-firms
(только head) отдаёт фирмы со строкой firm_channels(channel='sms'). Хелпер
smsSentCounts считает по каждой фирме число боевых СМС (sales_sms_messages
status='sent') по её номерам; fake_sent (песочница) не считается. firmRow получил
поле sms_sent_count (0 если не слали), проброшено и в firms/allFirms. СМС строкой
всегда loaded, сроками не греется — только чтение sales_sms_messages для счётчика,
тарификация/провайдер/запись кампаний не тронуты. Bump baseline (actingAs-ложняк).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 18:28:43 +03:00
Дмитрий 6775f58003 feat(прогрев ф2 этап C): показ по каналам — firms/бейджи/числа из firm_channels (+ план этапа C)
Фаза 2 Этап C, Task C1. Вкладка канала показывает ТОЛЬКО свой состав: firms({platform})
фильтрует по наличию строки firm_channels(channel) (loaded или warming). Бейджи
warming_channels строятся из строк firm_channels (yandex/vk/mts по строкам, sms по
факту рассылки), а не из ch_*. Числа вкладки (in_ads/expiring_week/waiting_sync)
считаются по фирмам со строкой firm_channels(channel, status='warming') — loaded в
рекламе не участвует. Это чинит поломку после этапа B (заезд не пишет ch_* ⇒ старые
ch_*-подсчёты давали 0/пусто у новых фирм). channelColumn удалён (сирота).
allFirms/toggle/mtsFile/движок/заливки не тронуты. ch_* не удаляем (откат).
Тесты мигрированы. Bump baseline (actingAs-ложняки).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:38:15 +03:00
Дмитрий ce48d911d6 feat(прогрев ф2 этап B): заезд = только загрузка во все 4 канала (loaded), без автостарта
Фаза 2 Этап B, Task B1 (+ план этапа B). AdAudienceIntake::ingest больше не
запускает прогрев: грузит фирму во все 4 канала (yandex/vk/mts/sms) строками
firm_channels status='loaded' (firstOrCreate — не понижает уже греющийся канал).
ch_* на заезде не пишутся (источник членства — строки firm_channels, resolveChannels
удалён). Повторный заезд обновляет только снимок-поля и НЕ сбрасывает
warmup_started_at/ready_at/stopped_at/stop_reason (заезд ≠ рестарт прогрева).
Фирма без warming-строк ни в одну заливку не идёт ⇒ автостарта нет по построению.
Запуск прогрева — кнопкой «Греть» на портале (этап C). Тесты intake мигрированы
на новую семантику. Bump baseline (+6 postJson-ложняков).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:39:45 +03:00
Дмитрий 199af4f906 docs(прогрев): план Фазы 2 этап A — ядро (firm_channels, 9 задач TDD) 2026-07-22 13:58:26 +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
Дмитрий 072a646230 docs(прогрев): план Фазы 1 — раздельные сроки по площадкам (11 задач, TDD)
Миграция сроков на площадки + бэкфилл, модель durations(), хелпер decideForPlatform
(чистое ядро), контроллер per-platform, recalc сводка, sync Яндекс/ВК по своим срокам
(+ВК на строку площадки), mtsFile, firmRow per-platform, фронт-текст, регресс. Выкат —
отдельно по спеке §5. Реализация — следующей сессией.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 08:32:18 +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
Дмитрий fbe4701399 feat(прогрев): firmRow отдаёт нишу, дату запуска, менеджера и состояние прогрева
Task 1 плана «кусок B». firmRow() в SalesAdAudienceController (firms()/allFirms())
теперь отдаёт rubric, warmup_started_at (ISO8601), manager_id/manager_name (через
prospect.sales_user_id) и агрегаты warming_active/warming_channels (yandex/vk/mts/sms).
Заглушка smsWarmedFirmIds() — Task 3 наполнит связью с СМС-рассылкой.

Тест AdAudienceWarmingFieldsTest — TDD (failing → green), плюс regression-прогон
72 тестов AdAudience* и composer stan (0 ошибок, phpstan-baseline.neon дополнен
записью под Pest actingAs() в новом тестовом файле — по конвенции остальных тестов).
2026-07-21 15:53:10 +03:00
Дмитрий 3197e24bbb docs(прогрев): план реализации — 4 страницы, ниша/менеджер/пагинация/дата/карточки
10 задач по TDD: бэкенд (firmRow +ниша/дата/менеджер/состояние/каналы, маршрут
назначения для СМС, sms-канал, блок warming в проспектах), фронт (composable
useWarmingFirms + 4 ячейки, v-data-table на 4 экранах, фильтры, select-all,
карточки воронки), полный регресс.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:41:49 +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
Дмитрий 14a6b801d7 docs(deploy): runbook выката «три площадки прогрева» (кусок A)
Предполёт GO, пошаговый план: миграция+бэкенд (redeploy.sh) → фронт
(deploy-build.sh, маркер обновлён), smoke-проверки, откат. Ждёт «эскейп».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 12:22:50 +03:00
Дмитрий 1078566795 docs(прогрев): план куска A — фундамент площадок + три страницы
9 задач через TDD: миграция таблицы настроек площадок + бэкфилл из state,
модель SalesAdAudiencePlatform, джоб Яндекса на строку площадки,
контроллер по {platform}, маршруты /warming/{platform}, фронт (API +
общая обёртка трёх страниц + меню), уборка старого экрана + схема v8.81,
регресс «поведение не изменилось» + предполёт.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:15:19 +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
Дмитрий 3fd0d81e09 @
docs(александра): передача по стройке — лёгкий оператор и точило

Приняли архитектуру по замыслу владельца: наставник не даёт подсказку
на каждый ход, а точит промпт оператора в фоне. Замер: лёгкий промпт
2.60с против 3.14с, наставник успевает лишь в 35% ходов — итого 0.77с.

Точило ВЫБИРАЕТ блоки из 13, а не пишет текст: промпт собирается кодом,
поэтому дрейфа нет. Опасные темы выбираются кодом мгновенно, оттенки —
моделью в фоне, её не ждём никогда.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-20 19:17:01 +03:00
Дмитрий 8e687f99c1 feat: сквозной путь клиента от рекламного клика до Вебвизора
Путь человека рвался пополам: лендинг и кабинет считали разные счётчики,
а вход и регистрация не писались вообще. Почему люди бросают регистрацию,
узнать было нельзя.

Счётчик лендинга 110476275 становится счётчиком полного пути и грузится
на всех страницах кабинета. Прежний счётчик кабинета 110494416 не тронут,
его история цела, на страницах кабинета работают оба.

Что сделано:
- загрузчик Метрики поднимает счётчики по области: public или app
- белый список раскладок: новая раскладка Метрику НЕ получает по умолчанию
- формы входа и регистрации закрыты от записи классом ym-hide-content
- метка utm не теряется при заходе сразу в кабинет минуя лендинг

Найдено при исполнении и закрыто:
- почта выводится в заголовке ДВУХ экранов, то есть вне формы: класс на форме
  её не накрывал, утекла бы в записи открытым текстом. Замаскирована точечно
- Метрика, единожды запустившись, пишет дальше сама и роутером не выключается.
  Админ входит через общий /login и идёт в админку к чужим телефонам.
  Корни админки и портала продаж закрыты ym-hide-content
- ключ metrika в config/services.php был объявлен ДВАЖДЫ: раздвоил его мой
  же merge 42e907c8 от 14.07. PHP молча берёт последний, правка первого блока
  не дала бы ничего и не выругалась. Дубль вычищен, прочие конфиги проверены

В кабинете Метрики включена галочка «включая поддомены»: счётчик принимал
данные только с liderra.ru и молча выбрасывал бы всё из кабинета.

Заодно погашен долг по статанализу: 63 ошибки держали коммит. Все до одной
в тестах, в боевом коде ноль. Природа ложная — анализатор не понимает
устройство Pest и ругается на обычный вызов внутри теста. Их гасят списком
игнора, а список пересобирали 18.07, тогда как тесты добавлялись 19-20.07,
в том числе мои по рекламной аудитории. Список пересобран, стало 0 ошибок.
Долг накопился в том числе потому, что вчера я обошёл эту проверку.

Тесты: 27 новых, все проверены вырезанием защиты. Полный набор 202 файла зелёный.

План: docs/superpowers/plans/2026-07-20-skvoznoy-put-do-vebvizora.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:47:19 +03:00
Дмитрий c503e93c8a docs(александра): передача сессии — ритм разговора и задержки 20.07
Два дня работы: звук (хрип от несовпадения A-law/µ-law, заворот высоких,
канал proxy.market) и механика разговора (сторож эха ел вопросы клиента,
глухота между её же предложениями, поток мозга по предложениям).

Ответ по существу 6.05с → 3.64с, пауз внутри реплики 2 → 0.

В файле: что читать следующей сессии, чем мерить, 8 ловушек
и незакрытый слой, который за владельцем.

Номер боевой линии не пишем — сторож ПДн прав, правило одно для всех
телефонов; номер берётся из журнала моста.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 10:09:38 +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
Дмитрий bd23c20504 docs(sales): план трёх площадок прогрева и модуля МТС
Одно поле channels не растягивается на третью площадку — сочетаний восемь.
Заменяем тремя галочками ch_yandex/ch_vk/ch_mts с переносом значений:
на бою 99 фирм со значением yandex, они получают ch_yandex.

Для МТС — выгрузка файлом: программного доступа к рекламе в Telegram у них
нет, их REST API умеет только SMS (проверено по документации 20.07).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 07:47:37 +03:00
Дмитрий 0f2b8bd51f fix(реклама): про свечение в промпте надо говорить прямо
Владелец сравнил новые картинки с прежними и сразу увидел: «со слитками
поярче было». Так и есть — на первых двух горячее светящееся ядро вышло
САМО СОБОЙ, и выбрал он их именно за это, а в промпте про свет не было
ни слова. Следующая пара без указания получилась матовой и тусклой.

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

Замер для сверки (средняя яркость / самые яркие точки):
  было матово: 33.5/166 и 43.1/192 при промахе мимо стиля
  прежние одобренные: 26.7/102 и 32.4/137
Числа тут вторичны — решает глаз владельца, но замер помогает поймать
явный провал до показа.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 05:56:43 +03:00
Дмитрий 5b7f1660c1 feat(реклама): формы картинок под ВК — лента 4:5 и Истории 9:16
🪤 С первой попытки модель отдала для 9:16 ШИРОКУЮ картинку, дорисовав
чёрные полосы сверху и снизу до вертикали. В Историях это читается как
брак. Лечится прямым запретом на полосы плюс требованием перестроить
сюжет по вертикали: клики сыплются сверху, золото собирается снизу.

Проверять полосы дёшево: средняя яркость верхней и нижней десятой части
кадра против центра. У letterbox верх ~0, у настоящей композиции все три
близки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 05:50:19 +03:00
Дмитрий 133f3e529d docs(sales): подобраны хвосты по рекламе — ВК в передаче, план отмечен
В плане исправлена последняя проверка: она была написана так, будто
миграция уже на боевом. До выката она обязана падать «column channels
does not exist» — это правильный результат, а не поломка. Отмечены
выполненными 65 шагов.

В передачу дописано состояние ВК (кабинет, охват меньше сотни, открытый
вопрос по API) и три грабли вечера: вырезанная субагентом защита,
негодный тест на защиту, и git commit без явных путей.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:38:21 +03:00
Дмитрий 4f88268678 revert: убрать docs/observer/STATUS.md из прошлого коммита
Файл случайно попал в коммит 7e7c5b6f — был уже застейджен параллельной
сессией (пере-генерируется пост-коммит хуком, коммитить нельзя, см.
границы задачи). Восстанавливает содержимое к версии на aaeab9c6.
2026-07-19 19:53:29 +03:00
Дмитрий 7e7c5b6f29 feat(sales): колонка «Где греем» и кнопки смены площадки
Task 6 плана 2026-07-19-vybor-ploshadki-progreva.md — начальник видит
площадку прогрева каждой фирмы (Яндекс/ВК/оба), отмечает строки галочкой
и жмёт одну из трёх кнопок массовой смены; api-слой зовёт уже готовый
POST /api/sales/ad-audience/channels.
2026-07-19 19:51:52 +03:00
Дмитрий ddfb50cd44 docs(sales): план выбора площадки прогрева (10 задач)
Поле channels у фирмы с бэкфиллом «яндекс» существующим 64, фильтр состава
в яндексовой заливке, приём площадки от «Поиска клиентов», массовая смена
кнопками, колонка «Где греем», джоб заливки в ВК с тремя состояниями.

Тело обращения к API ВК вынесено за рамки плана намеренно: документация
закрыта до получения доступа, гадать нельзя. Джоб, порог 2000 и тесты
пишутся сейчас, меняться будет только тело одного метода.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:18:13 +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
Дмитрий 61f7da3eeb docs(sales): передача по рекламе на кандидатов — состояние на вечер 19.07
Модуль на бою, две боевые поломки найдены и исправлены (права у ночного
пересчёта, отсутствующий confirm сегмента). Сегмент 58034825 создан порталом,
111 из 111 номеров приняты. Пошаговый план на утро + риск порога в 100 номеров.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:55:33 +03:00
Дмитрий f9dea24ac4 fix(sales): сегмент в Яндексе подтверждается, а не остаётся черновиком
Создание сегмента — два шага, а не один: upload_csv_file даёт статус uploaded
(черновик, в списке Аудиторий его нет и Директу он не виден), и только
segment/{id}/confirm сохраняет сегмент. Второй шаг отсутствовал — 19.07 заливка
на бою отчиталась успехом, id записался, а в Аудиториях было пусто.
content_type для телефонов строго 'crm'. Регресс-тест закрывает.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:31:25 +03:00
Дмитрий 2228bae51d fix(sales): ночной пересчёт рекламы читает карточки ролью с правами
На бою джоб бежит вне веб-запроса, то есть на дефолтной роли crm_app_user,
у которой нет прав на sales_prospects — пересчёт падал с permission denied
и не работал вообще. Планировщик теперь передаёт pgsql_supplier, как это
делает SalesProspectsAdvanceJob. Регресс-тест закрывает.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:22:36 +03:00
Дмитрий 0bda0a8bfe test(судья): тест к замерам задержек из боевого журнала
Код ушёл прошлым коммитом без своего теста — досылаю.
Проверяет, что судья вычитывает «до первого звука» и «до ответа
по существу» из журнала БОЯ, а не только со стенда.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 15:26:28 +03:00
Дмитрий b7f1192aeb feat(судья): замеры задержек берутся из боевого журнала, а не только со стенда
Прежде мост считал «до первого звука» и «до ответа по существу» и печатал
их только на стенде — в бою они умирали в памяти, и судья не мог сказать,
сколько человек ждал ответа. Вид строки у стенда и боя один и тот же,
поэтому разбор общий.

188 тестов зелёные.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 15:26:00 +03:00
Дмитрий 235c55fd6f fix(finder): реальный телефон директора убран из тестов службы поиска
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Утренняя чистка ПДн искала только в app/ и docs/ и пропустила моя/sales-finder —
папка скрыта от обычного поиска, номер оставался живым в трёх тестах (26 раз).
Заменён на фиктивный 79990000001, все 321 тест зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 15:01:25 +03:00
Дмитрий 31d6df0fcc feat(реклама): промпт и скрипт генерации картинок под Яндекс.Директ
Буклет 2:3 Директ не принимает. Два формата под требования Яндекса
(квадрат 1:1 и широкий 16:9), текста на картинке нет кроме логотипа,
макет телефона и карточка с номером убраны — риск модерации по 152-ФЗ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 12:10:57 +03:00
Дмитрий 9a410a3358 docs(sales): поправки плана рекламы по ходу выполнения (stage_changed_at, оба трейта в тестах)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:53:54 +03:00
Дмитрий 49ee084b7e fix(sales): номер без хозяина не попадает в рекламу
Номера, заведённые до v8.77, остались без firm_id. Ночной пересчёт обходит
фирмы и такой номер не видит, а заливка видела — он крутился бы вечно.
Нашёл rls-reviewer при сквозной проверке.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:51:50 +03:00
Дмитрий c43ed7b283 feat(sales): экран прогрева — список фирм, назначение менеджера, 11 сроков
Task 8 плана v2 (реклама на кандидатов): начальник видит таблицу фирм на
прогреве (человеческие подписи состояний: «Реклама идёт», «Прогрет — ждёт
менеджера» и т.п.), кнопкой «Назначить менеджера» заводит карточку в воронку
через POST /api/sales/ad-audience/firms/{id}/assign, и правит 11 сроков
планировщика понятными фразами («Сколько дней греем до звонка менеджера» и
т.д.) вместо технических ключей.

Список менеджеров переиспользует существующий listSalesManagers() —
отдельного маршрута не заводили. api/sales.ts дополнен типами и функциями
для двух новых бэкенд-ручек Task 7 (firms/assign), которых там ещё не было.

Тест положен в tests/Frontend/ (а не в views/sales/__tests__/, как в плане) —
это единственный путь, который реально подключён в vitest.config.ts; мокает
модуль api/sales.ts, а не global.fetch — экран ходит через axios-обёртку.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:32:00 +03:00
Дмитрий 79d1567e7d feat(sales): список прогрева и назначение менеджера из рекламы
Task 7 плана v2 (реклама на кандидатов): начальник видит список фирм
на прогреве (GET /api/sales/ad-audience/firms) и назначает менеджера
(POST .../assign) — из снимка фирмы рождается карточка «Потенциальные
клиенты» стадии new, реклама дальше следует за стадией карточки.
PATCH /api/sales/ad-audience теперь принимает все 11 сроков планировщика,
GET отдаёт их в durations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:19:46 +03:00