Задача 2.0 (разведка) — РЕШЕНА без входа в кабинет: ответ уже был в
находках Фазы 2. Билинг МТС за «показы своей базе» — НАКОПИТЕЛЬНЫЙ
(резерв → списание по факту показов → возврат остатка, экран /payment).
Следствие: списание «по факту» (была задача 2.4) на успехе отправки честно
сделать нельзя — показов ещё нет; перенесено в Этап 3 (опрос завершения).
Этап 2 закрыт составом 2.1 (отмена) + 2.3 (возврат брони) + 2.2.
- 2.2 PHP: RobotResult несёт `actualCostRub` (nullable string) — заготовка,
чтобы позже (Этап 3) прочитать фактическую стоимость из кабинета и списать
её с кошелька клиента. Проброшено в fromRobotJson (робот начнёт класть поле
позже; нет поля → null). Мёртвых фабрик launched()/draftReady() не добавлял.
- 2.2 Node: чистая утилита `parseCost(text)` в cabinet.js — «Стоимость
кампании от 201,6 ₽» → «201.60» (запятая→точка, разделители тысяч включая
неразрывный пробел код 160, дробь до 2 знаков без округления, нет числа →
null). Вынесена отдельной покрытой функцией; к DOM-потоку НЕ подключена
(селектор строки стоимости подтвердим живьём в Этапе 3).
- Разведка билинга и решение по 2.4 зафиксированы в FLOW-FINDINGS.md; план
Этапа 2 обновлён (2.0 решён, 2.4 → Этап 3, Этап 2 закрыт).
TDD, робот замокан, тесты на liderra_testing.
Приёмка: Node 40/40 (npm test), Pest RobotResultTest 5/5 + RobotRunnerTest
(потребитель) зелёный, регрессия ClientTg 120/120, phpstan RobotResult.php 0,
pint чисто, deptrac 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Закрыты 5 находок аудита (безопасность и валидация входа, деньги не задеты):
- #11 ПДн-скрины робота: полноэкранный скриншот кабинета МТС (мог содержать
телефоны базы, 152-ФЗ) больше не снимается и не уходит письмом по умолчанию —
только по явному TG_DEBUG_SHOTS. Чистый хелпер src/shots.js.
- #12 стоп-лист opt-out теперь нормализуется при сравнении (8XXXX / 10-значные
формы вычищаются), сравнение по голому 7XXXXXXXXXX.
- #13 верхние пределы входа: phones max:200000, phones.* max:32, audience_days
max:365; store возвращает dropped_count (сколько номеров не распозналось).
- #14 идемпотентность запуска: Фаза A джоба читает кампанию с lockForUpdate —
два воркера сериализуются на блокировке строки (защита от гонки).
- #2 предстартовый гейт аудитории: в боевом режиме launch НЕ бронирует деньги
под кампанию с <367 / пустой аудиторией (иначе бронь залипала бы). Порог —
config client_tg.auto_batch_threshold (367). В песочнице гейта нет.
TDD, робот в тестах замокан, тесты на liderra_testing (7999… номера).
Приёмка: Node 28/28, Pest ClientTg 105/105, phpstan 0, pint чисто, deptrac 0.
Обновлён CampaignApiTest (тест «денег не хватает → 409» засеян ≥367 контактами,
чтобы дойти до проверки денег после нового гейта).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Клиентский модуль «Реклама в Телеграме по своей базе» (робот-в-браузере, МТС Маркетолог).
Задача 5.2 — обратная связь по отказу:
- миграция client_tg_campaigns.status_reason (varchar 500, nullable; RLS без изменений — колонка на существующей таблице), запись v8.88 в CHANGELOG_schema
- Campaign: status_reason в fillable
- NotificationService::notifyTelegramCampaignRejected — in-app уведомление всем активным
пользователям тенанта без pref-гейта (важное операционное сообщение доходит всегда)
- RunTelegramCampaignJob: при провале робота сохраняет причину в status_reason и шлёт уведомление
(Tenant грузится внутри tenantTx для RLS, notify — после транзакции)
- фронт: telegram.ts (+status_reason), AdvertisingTelegramView показывает причину отказа/ошибки
- тесты: RejectNotifyTest (4 Pest) + 2 Vitest на экран
Закалка робота против частых глюков кабинета МТС:
- browser.js: gotoStable() — терпеливая загрузка с перезагрузкой (кабинет виснет на пустой крутилке)
- session.js: isLoggedIn через gotoStable — больше нет ложного «вход слетел»
- cabinet.js: знакомство пропускается если его нет (только для новичков); оферта по #isOfferAccepted;
осознан шаг /payment (денежная развилка «оплатить/без оплаты» — Сессия 6)
Живая разведка кабинета зафиксирована в FLOW-FINDINGS.md (селекторы, шаг /payment, поле файла модератору).
Проверки: Pest ClientTg 93/93, Vitest экрана 7/7, node-тесты робота 20/20.
Робот в тестах замокан, тесты на liderra_testing. Реальных ПДн нет (номера фейковые 7999…).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Живой прогон runner в режиме draft отработал целиком: визард -> аудитория
(matched=437) -> объявление -> стоимость -> подтверждение -> скрин; кнопку
отправки не жмём, баланс 5010 РУБ не тронут. Юнит-тесты 20/20.
- обход коуч-марка: нативный .click() по DOM-кнопке (domClickButton/continueTo),
т.к. оверлей перехватывает мышиный клик даже с force
- фикс чтения аудитории «Не МТС» (неразрывный пробел, код 160) в readNonMts
+ поллинг завершения асинхронного матчинга
- надёжная категория ОРД (combobox: fill -> pressSequentially -> role=option)
- шаги submitAd->/budget, submitBudget->/confirmation; finalize на /confirmation
(draft=скрин без клика; live=оферта+отправка, за FLOW-CONFIRM)
- fillAd: убраны adTitle/ordCompanyName (полей в кабинете нет, рекламодатель авто)
- task.js/тесты/example: убраны adTitle/ordCompanyName/cpmRub; budgetRub оставлен
как потолок стоимости для боевого запуска
- FLOW-FINDINGS.md: полная карта флоу + две ключевые грабли и их решения
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Дописан standalone-робот (Node ESM + Playwright) полного цикла создания
Telegram-кампании по своей базе телефонов в кабинете МТС Маркетолог:
- cabinet.js: вход в визард, выбор «Своя база клиентов», загрузка базы +
подсчёт «не МТС» с порогом 367, переход «Аудитория→Объявление» (само-
верификация), заполнение объявления + блок ОРД, финал черновик-стоп/боевой
(fail-loud при неизвестном режиме — защита от боевого клика по ошибке).
- runner.js: оркестратор с алярм/отчётом на почту, гарантированное закрытие
профиля браузера, чистка временного файла номеров (152-ФЗ).
- task.js: обязательные поля заголовка и названия ОРД; config: потолок бюджета
падает при нечисловом значении (защита денег), budget: NaN-guard.
- bin/run.js (CLI), bin/keepalive.js (сторож входа с алярмом), bin/login.js
(разовый вход), task.example.json (без ПДн), README.
- Шаг «Стоимость» намеренно не размечен (заглушка бросает Error) — селекторы
снимаются на первом реальном черновике после разового логина владельца.
Ядро: 20/20 юнит-тестов зелёные. Браузерная часть проверяется живым черновиком.
Двухстадийная ревизия каждой задачи + финальный сквозной проход (поймал и
починил пропущенный переход между шагами и NaN-обход потолка бюджета).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Задачи 8–9 плана (код, проверка против кабинета — при логине владельца в
профиль бота):
- browser.js — launchPersistentContext(profileDir), humanPause по темпу
- session.js — isLoggedIn по стабильному пункту меню «Рассылки и звонки»
(селектор подтверждён живьём при разведке)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Задача 1 плана: пройден визард Telegram Ads → «Своя база клиентов» в живом
кабинете в режиме только-чтение. Зафиксированы 7 шагов степпера, селекторы,
требования к файлу номеров (TXT/CSV/XLS/XLSX, формат 79XXXXXXXXX, минимум 367
не-МТС), счётчик «МТС/Не МТС», путь удаления черновика. Черновик 2229823 убран,
баланс 5010₽ не изменился. cspell-words: добавлены CPM/CPF/MTC/ЕРИР/Таргеты/алярм.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>