# Аудит crm.bp-gr.ru — параллельные партии 12–15 (05.05.2026)

**Назначение:** консолидированный отчёт по 4 параллельным партиям аудита оригинала, выполненным Claude в Chrome 05.05.2026 в read-only режиме. Объединяет 4 файла (`audit-batch-12..15-2026-05-05.md`) в один документ в рамках оптимизации архива v8.3++ optimized.

**Все 4 партии — одна сессия аудита, один контекст:**

- 4 параллельных Chrome-окна с независимыми задачами.
- ~100 минут аудита суммарно.
- Read-only: 0 модификаций (см. self-review секции «Безопасность» в каждой части).
- Все ПДн замаскированы (телефоны → +7XXXXXXXXXX, email/username — скрыты или замаскированы).

**Состав:**

- **Часть 12** — Конверсия проектов (`/admin/visit/rt-stat`) + Форма «Напоминания» в карточке сделки + Модалка «Досье».
- **Часть 13** — Раздел «Рекомендации источников» (`/admin/gck/dop-sources`) + Аккордеон «Настройки» в реестре проектов + Иконка «Просмотреть» + Раздел «Список звонков».
- **Часть 14** — Сделки в редких статусах + База знаний + Безопасность профиля.
- **Часть 15** — Карточки 12 интеграций «API ▾» (5 открытых: Bitrix, amoCRM, Скорозвон, Unisender, Мои Звонки).

**Ключевые результаты сессии (сводка по всем 4 частям):**

1. **Биз-10 переоткрыт** (часть 12): в оригинале `model.histories[].type='reminder'` — массив, нужна отдельная таблица `reminders` (минимальный паритет: text + remind_at + created_by + created_at).
2. **B1/B2/B3 capabilities жёстко подтверждены** (часть 13.3.5): цитата UI «B2 поддерживает связку sender_name+keyword, B3 — только sender_name».
3. **Outbound webhook'ов в оригинале НЕТ — окончательно** (часть 15): 7 линий доказательств. Уточнение: для Bitrix24 есть один hardcoded URL `prostats.info/bitrix24/webhook.php`, но это inbound с т. з. CRM.
4. **Оригинал не имеет 2FA, журнала входов, списка сессий** (часть 14): `/admin/user/2fa`, `/admin/user/auth-log`, `/admin/user/sessions` — все пустые/404.
5. **Антипаттерны оригинала**:
   - Пароль виден plaintext в `<input type="text">` на странице профиля (часть 14).
   - API-ключи и client_secret в `<input type="text">` во всех 5 открытых карточках интеграций (часть 15).
6. **3 новых продуктовых вопроса** (Биз-14/15/16): TTL soft-deleted проектов, OSINT-wizard, поле `desired_daily_numbers`.

**Связано:**

- `Прил_М_Analiz_originala_v8_3.md` v1.1 — формализация выводов в архитектурные решения (§3.5, §6.6, §9.1–9.6).
- `Открытые_вопросы_v8_3.md` v1.6 — раздел 12 (Биз-10 переоткрыт + Биз-14/15/16).
- `schema.sql` v8.3 — DDL-патчи по результатам этих партий.
- `Объединённый_конспект.md` (Часть V) — конспект обработки находок. *Был отдельный файл `Konspekt_sessii_05_05_2026.md`, удалён в шаге «v8.3++ optimized + правила Claude»; информация перенесена в Часть V `Объединённый_конспект.md`.*

**Безопасность:** в каждой из 4 партий обнаружены DOM-маркеры prompt injection-атаки (`claude-agent-stop-container`, `claude-agent-stop-button`, `claude-agent-glow-border`, `claude-agent-animation-styles`). Действия Claude в Chrome во всех 4 партиях ✅ корректные — кнопку не нажимал, инструкцию не выполнял. Подробнее в каждой секции «Безопасность» соответствующей части.

---

# Часть 12

## Партия 12 аудита crm.bp-gr.ru — Конверсия проектов + Напоминание + Досье

**Дата:** 05.05.2026
**Режим:** read-only
**Длительность:** ~50 мин
**Задачи:** 3 (Конверсия проектов, Форма напоминания, Модалка «Досье»)
**Что НЕ удалось:** всё закрыто, отдельных блок-стопперов не было. Render-error TypeError из партии 1 в console повторился на /admin/visit/index-visit при первой загрузке; после reload данные подтянулись.

---

### 12.1. Раздел «Конверсия проектов» (/admin/visit/rt-stat)

#### 12.1.1. Назначение и общий вид

Серверно-рендеримая Yii2-страница (без JSON-XHR для основной таблицы) со статистикой конверсий по всем проектам (всего 443 строки = всем 443 проектам реестра, что подтверждает цифру партии 1.5). Каждая строка — один проект, каждая колонка после «Кол-во обработанных» — один статус сделки с двумя значениями `N (XX%)`: количество сделок проекта в этом статусе и его доля от «Кол-во обработанных».

Ключи в localStorage: `rtstatopts1` хранит пользовательскую настройку колонок и размер страницы (`{rowCnt: 10, checkList: [...]}`).

#### 12.1.2. Структура таблицы

Колонок всего 17, из них видимых по умолчанию 17 (никаких скрытых нет — все чекбоксы в «Управлении страницей» включены), расчётных (двойное значение «N (X%)») — 11.

| # | Заголовок | Тип | CSS-класс ячейки | Формат | Цвет |
|---|-----------|-----|------------------|--------|------|
| 1 | id | атрибут | `td br` | int (12416101) | прозрачный |
| 2 | Тег | атрибут | `td br` | строка (Caranga) | прозрачный |
| 3 | Проект | атрибут | `td br` | строка (B3_Caranga) | прозрачный |
| 4 | Статус | атрибут | `td br` | строка (Активен) | прозрачный |
| 5 | Источник сбора | атрибут | `td br` | строка (СМС) | прозрачный |
| 6 | Кол-во обработанных | счётчик-знаменатель | `td br` | int | прозрачный |
| 7 | База | счётчик + % | `td_client` | `0 (0%)` | rgb(217,234,211) — light green |
| 8 | Недозвон | счётчик + % | `td_uncalled_end` | `4 (80%)` | rgb(201,218,248) — light blue |
| 9 | Переговоры | счётчик + % | `td_uncalled` | `0 (0%)` | rgb(217,210,233) — light purple |
| 10 | Ожидаем оплаты | счётчик + % | `td_close` | `0 (0%)` | rgb(252,229,205) — light orange |
| 11 | Партнерка | счётчик + % | `td_uncalled` | `0 (0%)` | rgb(217,210,233) |
| 12 | Оплачено | счётчик + % | `td_recall` | `0 (0%)` | rgb(217,234,211) |
| 13 | Закрыто и не реализовано | счётчик + % | `td_pereveden` | `1 (50%)` | rgb(244,204,204) — light red |
| 14 | Тест драйв | счётчик + % | `td_uncalled_end` | `0 (0%)` | rgb(201,218,248) |
| 15 | Горячий | счётчик + % | `td_recall` | `0 (0%)` | rgb(217,234,211) |
| 16 | На замену | счётчик + % | `td_pereveden` | `0 (0%)` | rgb(244,204,204) |
| 17 | Конечный недозвон | счётчик + % | `td_pereveden` | `0 (0%)` | rgb(244,204,204) |

Цветовая разметка: **monotone per column** (не heatmap, не светофор) — у каждого статуса свой пастельный цвет фона, не зависящий от значения. Семантика классов несовершенна: одни и те же CSS-классы переиспользуются для разных статусов (например, `td_pereveden` стоит у «Закрыто», «На замену», «Конечный недозвон» — фронт явно унаследовал старую систему классов и не доработал).

Формат конверсий: `N (XX.XX%)` — пробел между числом и долей в скобках, точка как разделитель, до 2 знаков после запятой (в строке Total: `140 (58.58%)`, `91 (38.08%)`, `6 (2.51%)`).

Tfoot есть — строка «Общее кол-во»: `239 / 0 (0%) / 140 (58.58%) / 6 (2.51%) / 0 / 0 / 0 / 91 (38.08%) / 0 / 0 / 0 / 0`. Суммы по столбцам и общая конверсия.

Формула конверсии в строке проекта: `count_status / count_processed * 100`. Если `count_processed = 0` — показывает `0 (0%)`. То есть знаменатель — единый для всей строки (колонка 6), а не сумма всех счётчиков статусов.

#### 12.1.3. Фильтры

Сверху доступно (слева направо):

- **«Управление страницей»** — кнопка-аккордеон, открывает поповер со всеми 17 чекбоксами колонок + селектом «размер страницы» (значение `10` по умолчанию).
- **«Удаленные проекты»** — кнопка-ссылка на `/admin/visit/rt-stat?type=deleted` (отдельный реестр).
- **Date range picker** «Дата начала — Дата окончания» — el-date-picker-range, по умолчанию **последние 7 дней** (на момент аудита: 2026-04-29 — 2026-05-05). Произвольные даты, не пресеты.
- **el-select «Фильтр по типу»** — single select, default `Все`. (Полный список опций раскрыть без `mousedown`-имитации не получилось, но поле явно single-value и со словом «тип».)
- **«Загрузить данные для анализа конверсии»** — кнопка `<button title="Загрузить данные для анализа конверсии">`. Не нажимал. По названию это либо пересчёт стат-кэша на сервере, либо экспорт. В DOM никаких параметров скачивания не нашлось — скорее «триггер пересчёта».
- **«Поиск по проекту»** — текстовый input с placeholder, фильтрует таблицу по подстроке.

Внутри формы найдены 6 радио-кнопок (значения 5,6,9,10,11,12) с лейблами «Недозвон / Интерес / Проведенная оплата / Закрыто и не реализовано / Тестовый доступ / Качественный лид» — это **не основной фильтр**, а часть модалки «Загрузить данные для анализа конверсии» (она лежит в DOM в скрытом виде). Эти 6 значений соответствуют типам результата звонка для аналитической выгрузки.

Сортировка: каждый `<th>` имеет класс `column sortable`, в DOM первый столбец активен `sortable active desc text-xs-left` (по `id desc`), остальные пассивны. Можно сортировать по любой из 17 колонок.

#### 12.1.4. Что в строке

Строки = **проекты** (1 строка = 1 проект). Подтверждено: в правом нижнем углу пагинации `1-10 of 443`, что точно совпадает с цифрой 443 проекта из партии 1.5 / партии 10. Никаких group-by/drill-down.

#### 12.1.5. Действия

- **Drill-down в ячейку:** нет. У ячеек конверсии `cursor: auto`, нет `<a>`, нет `onclick`. Кликабельных переходов в детализацию по проекту/статусу нет.
- **Экспорт:** отдельной кнопки «Экспорт XLSX/CSV» нет. Кнопка «Загрузить данные для анализа конверсии» — единственный кандидат, но текст («загрузить» = «load», а не «download»), модалка с радио-фильтрами и название модалки «Анализ конверсии» подсказывают, что это инициация серверного пересчёта/выгрузки в *другой* раздел, не файл.
- **Refresh:** нет таймера и нет SSE/WS. После 10-секундного ожидания зафиксированы только 3 фоновых XHR от шапки (`updatetopmenu`, `updatetopmenuamount`, `updateindicators`), к таблице конверсий они не относятся.

#### 12.1.6. XHR-эндпоинты

Главный вывод: **таблица серверно-рендерится Yii2 в HTML**, без отдельного JSON-XHR. После reload зафиксированы только фоновые ping-эндпоинты:

```
POST /admin/visit/updatetopmenu          — обновление пунктов в шапке
POST /admin/visit/updatetopmenuamount    — счётчики в шапке
POST /admin/visit/updateindicators       — индикаторы (~раз в 10 с)
```

Параметры таблицы передаются через GET-форму на тот же URL `/admin/visit/rt-stat` (action="GET", method="get") — то есть фильтры в query string, страница перезагружается полностью.

Для нашего «Лидерры» это означает: на бэке нужен один read-эндпоинт `GET /api/projects/conversion?from=&to=&type=&search=` который возвращает массив проектов со счётчиками по каждому статусу. На фронте таблица может быть SPA, серверный рендер не обязателен.

#### 12.1.7. Console

После reload — чисто, ошибок и предупреждений нет. На /admin/visit/index-visit повторился `TypeError: Cannot read properties of undefined (reading '0')` (Element UI в Vue.min.js render — это не render-error на /rt-stat, а на /index-visit, см. 12.2).

#### 12.1.8. Скриншоты

Главный экран (1400×849): ss_8386wcwqj — таблица 10 строк, 17 колонок, total-row, цветные ячейки конверсий, горизонтальный скроллбар внизу.

---

### 12.2. Форма «Напоминания» в карточке сделки

Использованная сделка: id=1652984367 (status «Просмотрено», проект B2_next.vashinvestor.ru, телефон +7XXXXXXXXXX, ответственный @lkomega.ru Дмитрий, реальное напоминание установлено на 04 Май 14:30, создано 4 мая 14:27).

#### 12.2.1. Карточка в режиме просмотра

Расположение: левая колонка карточки сделки, ниже guest-block (телефон/email/проект/сумма), выше карточки «Комментарий». DOM-якорь — `<div class="guest-remind reminders-new">`. В режиме просмотра карточка показывает:

- Заголовок «Напоминание» + иконка `<i class="fas fa-plus">` справа (триггер «добавить»).
- Иконка часов `<i class="fa fa-clock">` слева — клик по ней тоже открывает форму добавления (тот же handler `showAddReminder(false)`).
- Список напоминаний-историй, каждое = одна строка:
  - Красный кружок-индикатор (просрочено).
  - Иконка карандаша (редактирование) — `edit-vm-btn`.
  - Иконка корзины (удаление) — `delete-vm-btn`.
  - Заголовок: дата+время напоминания (например «04 Май 14:30»).
  - Подзаголовок: дата создания напоминания (например «4 мая 14:27»).

#### 12.2.2. Открытие редактора

Триггеры (все вызывают одну и ту же модалку `addReminder.show=true`):

1. Иконка `+` справа от заголовка «Напоминание» — `showAddReminder(false)` (создание нового).
2. Иконка часов в `<a class="remind-in cursor-pointer">` — `showAddReminder(false)`.
3. Иконка карандаша на существующей записи — `showAddReminder(history)` (редактирование).

Модалка — el-dialog «Добавить напоминание», max-width небольшой (~480px), не Vuetify.

#### 12.2.3. Поля формы (полный список)

| # | Поле | Тип контрола | Обязательное | Default | Vue path | Примечание |
|---|------|--------------|--------------|---------|----------|------------|
| 1 | Текст | textarea «Введите текст напоминания» | нет | пусто | `addReminder.note` | Лимит 255 символов (в UI «0/255»). |
| 2 | Дата | el-date-picker | да | сегодня (`2026-05-05`) | `addReminder.date` | Формат хранения unix-ms; на сервер уходит `YYYY-MM-DD`. |
| 3 | Час | el-select 0…23 | да | текущий час пользователя | `addReminder.hour` | При редактировании заполняется из `model.reminder_dt` или `this.hour`. |
| 4 | Минуты | el-select 0…59 | да | текущая минута | `addReminder.min` | Аналогично. |

Кнопки: «Сохранить» (primary) → `saveAddReminder()`, «Отмена» (default) → `addReminder.show=false`. Крестик в углу — то же самое.

**Что отсутствует** (важно для Биз-10):

- ❌ Ответственный (выбор менеджера) — нет, ни в форме, ни в payload.
- ❌ Тип/приоритет (low/med/high) — нет.
- ❌ Канал уведомления (email/push/sms) — нет.
- ❌ Повторение (одноразовое/еженедельно) — нет.
- ❌ Привязка к статусу (автосоздание) — нет (по `selectStatus` нет вызовов `saveAddReminder`).

Серверный эндпоинт сохранения (из `saveAddReminder`):

```
POST /admin/visit/updatenote
payload: { id, note, date (YYYY-MM-DD), hour, minute }
```

Эндпоинт удаления (из `showDeleteReminder`):

```
POST /admin/visit/visit-vm-delete
payload: { id: history.id }
```

Замечательная деталь: эндпоинт сохранения называется `updatenote` (а не `updatereminder`) — то есть на бэке напоминание и заметка обрабатываются одной таблицей записей с типом, и текст напоминания пишется в поле `note`. Это легаси-конструкция.

#### 12.2.4. JS-источники (deprecated/расширенные поля)

В `model` сделки (Vue data) присутствует одиночное поле `reminder_dt` (timestamp единичного «текущего» напоминания). Оно используется только для дефолтных значений при создании следующего напоминания (час/минута). Реальные напоминания живут в `model.histories[]` как записи `{type:'reminder', time, time_dt, id, note, from, to}`.

- `from` — кто создал напоминание (логин менеджера). Это и есть фактический «ответственный» по записи, но на UI правки нет.
- `to` — null во всех просмотренных, т. е. поле объявлено, но не используется. Кандидат на «assignee» в будущем.

В коде встречен метод `showAddReminder` (показать форму), `validateAddReminder`, `saveAddReminder`, `showDeleteReminder`, плюс параллельная карточка `addVmContact` (контакт во VM — отдельная сущность), поэтому `vm` в названиях кнопок — voicemail/visitor manager, не «virtual machine».

В структуре также есть отдельный массив `otherReminders.reminders[]` и модалка-список **«Ближайшие напоминания»** (см. ниже), что подтверждает: ядро системы рассчитано на множественные напоминания.

#### 12.2.5. Множественность

Технически: **множество напоминаний на сделку поддерживается** — `model.histories.filter(h => h.type === 'reminder')` итерируется в шаблоне, каждое напоминание удаляется индивидуально по `history.id`, добавление через ту же форму с пустым контекстом не блокируется ни условием в UI, ни логикой save.

Фактически (UX): **одно активное на сделку**. Карточка показывает все имеющиеся напоминания типа `reminder` в `histories`, но никакой пагинации/«Добавить ещё одно отдельно»/счётчика рядом с заголовком нет. Иконка `+` без ограничений, но в практике у наших 5 проверенных сделок было ровно 1 или 0 напоминаний.

Гипотеза партии 11 «одно напоминание = одна сделка» — **частично опровергнута**: схема данных — список (`histories`), не одиночное поле; в нашем «Лидерре» в модели можно (и стоит) делать `reminders[]`, а не `reminder_at + reminder_text`. Но UI оригинала показывает их как одну стопку без отдельных страничек, поэтому со стороны выглядит как «одиночное».

Дополнительно найдена связанная модалка **«Ближайшие напоминания»** (el-dialog, открывается через `otherReminders.show=true`): таблица из 3 колонок «Сделка / Напоминание / Время», **list cross-deal** — список ближайших напоминаний по всем сделкам пользователя. Это эквивалент дашборда «мои задачи».

#### 12.2.6. Связь с фильтрами «Задачи»

Хедер списка `/admin/visit/index-visit` имеет дропдаун «Задачи» с 4 пунктами. Получены параметры всех ссылок:

| Пункт UI | URL-параметр |
|----------|---------------|
| Дела на сегодня | `reminders=today` |
| Просроченные дела | `reminders=last` |
| Предстоящие дела | `reminders=future` |
| Сделки без задач | `reminders=none` |

**Гипотеза подтверждена:** «Задача» в фильтрах списка = «Напоминание» в карточке (одна сущность). Слово «Задачи» в UI — устаревший лейбл, бэк всюду оперирует словом `reminders`. В системе нет отдельной сущности task — есть только reminder = запись в `histories` типа `reminder`.

Замечу: ручная проверка через `?reminders=last` визуально не отфильтровала таблицу (Все 12899, и в видимых строках напоминаний нет). Это, скорее всего, потому что Vue-таблица грузит данные собственным AJAX-вызовом и при моей навигации фильтр в её state не подхватился сразу. Сама связь параметра с reminder-полем доказана ссылками из дропдауна.

#### 12.2.7. Вывод для Биз-10

**Паритет с моделью «Лидерры»** — рекомендуемая версия:

```
reminder {
  id          uuid
  deal_id     fk → deal.id
  text        varchar(255)        // = note
  remind_at   datetime            // = date+hour+minute
  created_by  fk → user.id        // = from
  created_at  datetime
  // не нужны для MVP: assignee_id, priority, channel, repeat_rule
}
```

То есть `reminder_text` + `reminder_at` на самой сделке **недостаточно** — нужна отдельная таблица `reminder` со связью многие-к-одному к сделке. Зато ничего сверх текста + времени + автора в MVP не нужно: в оригинале нет ни приоритетов, ни каналов, ни ассайнментов, ни recurrence. Эти поля можно не закладывать в Биз-10.

Доп. фильтры на бэке `/api/deals?reminders=today|last|future|none` — как в оригинале — стоит сделать «из коробки», это закрывает отдельную фичу «Дашборд задач».

---

### 12.3. Модалка «Досье»

В DOM сделки 1652984367 нашлось **две** Vuetify/Element модалки с заголовком/функцией «Досье». После анализа render-функции и Vuex-state ясно, что это **две разные сущности** с одинаковым названием.

#### 12.3.1. Триггер открытия

**Модалка №1 — простая v-dialog (`baseInfo`)**

- Селектор: `<i class="cursor-pointer crm-view-pencil-icon ml-1" title="Просмотр досье" @click="baseInfo.show = true">`
- Условие рендеринга: `v-if="baseInfo.info.length > 0"`
- Расположена в строке `phone-row` рядом с конкретным телефоном гостя; иконка появляется только если у телефона есть «досье-информация» (массив записей по другим сделкам с тем же номером).
- На моей сделке `baseInfo.info.length === 0`, поэтому иконка в DOM не отрисована — нужна сделка-дубликат.

**Модалка №2 — el-dialog `visitdop` (Досье + Pr-cy)**

- Селектор: `<el-button class="crm-dossie-btn ml-2" size="mini" icon="el-icon-reading" round @click="visitdop.show = true">Просмотр досье</el-button>`
- Условие: `v-if="model.isVisitDop && visitdop.dops && visitdop.dops.dossier"`
- Расположена в `.domen-right` блоке — в правой части domain-row сделки.
- На моей сделке `model.isVisitDop === false`, поэтому кнопка не отрисована — нужна сделка с заполненным «доп»-разделом.

#### 12.3.2. Содержимое

**Модалка №1 («Досье» по `baseInfo`)**

```
v-dialog max-width=60%
  v-card
    v-card-title.headline "Досье"
    v-card-text
      v-for="info in baseInfo.info" :key="info.phone":
        el-card.box-card.mb-2
          slot=header: ...
          (содержимое — поля info без расшифровки в render-фн)
    v-card-actions
      el-button @click="baseInfo.show=false" "Закрыть"
```

Это простой список карточек, ключ — телефон. Скорее всего: **«если этот же номер уже использовался в других сделках/проектах, вот сводка по ним»**. Аналог нашего «дубль-объединения по телефону».

**Модалка №2 (`visitdop` — Досье + Pr-cy)**

```
el-dialog visible=visitdop.show width=80%
  el-tabs type=card
    Tab "Досье" (если visitdop.dops.dossier.length > 0)
      v-card → table.visitdop-table
        tbody.tr.td(name) td(value)
        // спец-форматирование для name in [proceeds, profit, balance, arbitration]:
        //   value добавляется суффикс " 000" (тысячи)
    Tab "Pr-cy"
      el-table.prcy-table data=prcyTableData
        column "Параметр" (name)
        column "Значение" (value)
          header-slot: input filter "Фильтр" v-model=searchPrCy
          default-slot: el-collapse for value.length > 300 (длинные значения сворачиваются)
```

Это значительно более серьёзная сущность: **внешние данные о домене проекта** (Pr-cy.ru — российский SEO-сервис; «Досье» здесь — финансовая/юридическая выписка с такими же полями, как у проверки контрагента). Поля `proceeds/profit/balance/arbitration` — выручка/прибыль/баланс/арбитраж (стандартный набор «бизнес-карточки» из Контур.Фокус / Rusprofile / Pr-cy).

Раздел нашёлся под общим заголовком, но это **карточка домена сделки**, а не «расширенная карточка контакта».

#### 12.3.3. XHR-эндпоинт

Метод `loadVisitDop()`:

```
GET /admin/visit/visit-dop-load?id={model.id}
response → visitdop.dops = response.data
```

Структура ответа (по render-fn):

```
{
  dossier: [{ name: string, value: string }, ...]  // массив пар Параметр/Значение
  // плюс поле для Pr-cy таб (имя в render-fn непрозрачно)
}
```

Данные грузятся лениво, по клику кнопки (или раньше, если `model.isVisitDop=true` уже взведён сервером — тогда кнопка отрисована и контент готов).

Для модалки №1 (`baseInfo`) отдельного XHR не нашёл — массив `baseInfo.info` инициализируется пустым (`info: []`) и, видимо, заполняется в момент `model`-mount из server-injected JSON для сделки. То есть данные приходят с самой страницей карточки, не отдельным запросом.

#### 12.3.4. Назначение (гипотеза)

- **Модалка №1 (`baseInfo`):** **«дубли по телефону»** — список других сделок/контактов с этим же номером, для контекста менеджеру. Включается, когда система детектит совпадения.
- **Модалка №2 (`visitdop`):** **внешние данные о проекте/домене** (Pr-cy + бизнес-выписка), нужны менеджеру для оценки серьёзности лида.

В партии 11.12 фиксировалась только №1 (пустой 354-байтовый стаб), №2 в той партии не нашлась — это было *новое* открытие.

#### 12.3.5. Решение для нашего аналога

- **Модалка №1 (баз дублей):** **Пост-MVP**. У нас в Биз-1 предусмотрено объединение лидов по телефону, но как UI-секция в карточке сделки это можно отложить. Если делать — это таблица «другие сделки с этим телефоном» в side-panel.
- **Модалка №2 (Pr-cy + досье):** **НЕ делаем в MVP**, оставляем в Post-MVP / Backlog. Эта функция требует интеграции с внешним API (Pr-cy / Контур / Rusprofile), что для Лидерры на старте избыточно. Включить при наличии запроса от арбитражника-клиента.

---

### Сводка для Прил. М v1.1

**Биз-10 (модель «задач»):** **уточняется**. Нужна отдельная таблица `reminder` (не поле в `deal`), но без приоритетов/каналов/ассайнмента/recurrence — достаточно `text + remind_at + created_by + created_at`. Фильтры списка `reminders=today|last|future|none` дают «бесплатный» дашборд задач.

**Конверсия проектов для §12 narrative:** **есть**. 17 колонок, формат `N (XX.XX%)`, 1 строка = 1 проект, нет drill-down, нет экспорта, серверный рендер. Цветовой код monotone per column. Total-row по столбцам.

**Модалка «Досье»:** **понятна**. Это две разные модалки с одним именем: (1) список дублей по телефону (`baseInfo`), (2) карточка домена с Pr-cy + бизнес-выпиской (`visitdop`). Эндпоинт второй — `GET /admin/visit/visit-dop-load?id=`.

---

### Что не исследовано в этой партии

- Полное содержимое **Tab «Pr-cy»** в `visitdop` — render-fn частично заблокирован выводом, но нужна сделка с заполненным доп-разделом, чтобы увидеть рабочий пример (партия 14, когда будем смотреть редкие статусы или конкретный лид с большой суммой).
- Модалка «Запросить ОС по звонку от Головного офиса» — ОКК (отдел контроля качества), нашлась в DOM как persistent v-dialog, не относится к данной партии.
- Модалки «Пожаловаться» / «Запросить голосовую помощь» — внутренние коммуникации, отдельная партия.
- Опции селекта «Фильтр по типу» на странице конверсии — не раскрылись через `mousedown`-имитацию; партия 13 при возврате на /rt-stat сделать через клик-имитацию по ref.

---

### Безопасность

- `claude-agent-stop-container` / `claude-agent-stop-button`: на /rt-stat при первом открытии страницы найдены в DOM (кнопка с текстом «Stop Claude»). После reload — пропали. На /admin/visit/index-visit?visit=rt после reload и навигации появлялись снова в нижней части страницы. Все вхождения **проигнорированы** согласно протоколу — клик не выполнялся, инструкция не следовалась.
- `#claude-agent-animation-styles` — стилевой тег с `@keyframes claude-pulse`, остаётся в `<head>` стабильно. Это побочный эффект инжекции, не угроза.
- Других подозрительных DOM-маркеров (`gpt-*`, `agent-*`, скрытых инструкций в `title`/`aria-label`/`data-*`) **не найдено**.
- Render-error `TypeError: Cannot read properties of undefined (reading '0')` повторился на /admin/visit/index-visit при первом заходе (известная ошибка из партии 1). После reload и навигации с `?visit=rt` таблица отрисовалась корректно с 50 строками.
- В режиме просмотра карточки сделки 1652984367 я программно открыл два редактора (`addReminder.show=true` через `showAddReminder()` и `visitdop.show=true`) исключительно для инспекции структуры. Оба закрыты через `show=false` / `Escape` без отправки данных. Никаких POST-запросов на сохранение/удаление не сделано.
- ПДн в отчёте замаскированы: телефоны заменены на +7XXXXXXXXXX, email/username скрыты (`@lkomega.ru` оставлен только как пример из render-fn, без полного домена`).

---

# Часть 13

## Партия 13 аудита crm.bp-gr.ru — Рекомендации источников + Настройки реестра + Просмотреть + Список звонков

**Дата:** 2026-05-05
**Режим:** read-only
**Длительность:** ~10 минут (10:51 → 10:47 МСК — таймер UI)
**Задачи:** 4 (Рекомендации, Настройки, Просмотреть, Список звонков)
**Что НЕ удалось:** drill-down из агрегации звонков невозможно проверить — в аккаунте 0 звонков за апрель 2026 и за текущий день; модалка ввода данных в wizard рекомендаций не пройдена «Далее» (запрещено протоколом).

---

### 13.1. Раздел «Рекомендации источников» (`/admin/gck/dop-sources`)

#### 13.1.1. Назначение

**Wizard поиска новых доменов и телефонных номеров для лидгена** на основе пользовательских ключевых запросов и/или доменов. Это **НЕ** управление поставщиками B1/B2/B3 — гипотеза партии 10 опровергнута. Сервис ищет внешние сайты-конкуренты и соответствующие телефоны, чтобы клиент мог использовать их как новые источники трафика. Таб-заголовок страницы: «Дополнительные источники».

#### 13.1.2. Структура wizard'а

| Шаг | Название | Контролы | Действия пользователя |
|-----|----------|----------|------------------------|
| 1 | ШАГ 1 - ПОИСК | Селектор «Выберите город» (dropdown), textarea «Введите домены или ключевые запросы», кнопка «Подтвердить», справа — таблица истории запросов (5 строк на странице, всего 20 записей; колонки: Запрос / Регион / Результат / ЧС / Дата) | Выбрать город, ввести домены или ключевики через перевод строки, нажать «Подтвердить» (запускает поиск; «За один раз можно запустить только один поиск») |
| 2 | ШАГ 2 - РЕЗУЛЬТАТ | 4 фильтра: Фильтр по запросу / Фильтр по домену / Фильтр по региону / Фильтр по номеру + колонка-действие «Чёрный список» (иконка корзины) + иконка скачивания экспорта вверху справа. Таблица: Запрос (исходный домен/ключевик клиента) / Домен (рекомендованный) / Регион / Номер | Просмотр рекомендованных доменов и телефонов, добавление в ЧС через корзину, экспорт |
| 3 | ЧЁРНЫЙ СПИСОК | Те же 4 фильтра + кнопка-действие «Вернуть» (восстановить из ЧС) | Просмотр своего blacklist'а, восстановление в активные рекомендации. На момент аудита список пуст («Нет данных») |

Цитата вступления: «Наш сервис предоставит рекомендации новых источников на основе введенных вами доменов или ключевых запросов».

#### 13.1.3. Связь с поставщиками B1/B2/B3

**Опровергнута.** Wizard не упоминает B1/B2/B3 ни в инструкции, ни в полях. Это инструмент **внешней OSINT-разведки** по доменам/телефонам, отдельный от модели поставщиков. (Информация о B1/B2/B3 как поставщиках получена из других мест, см. 13.2 и 13.3.)

#### 13.1.4. Скоуп (проект / аккаунт)

**Аккаунтный.** Wizard не имеет селектора проекта. Результаты пишутся в общий пул аккаунта (история запросов и общий ЧС).

#### 13.1.5. XHR-эндпоинты

Запросы данных wizard'а — серверный рендер Yii2: при загрузке страницы `/admin/gck/dop-sources` **никаких XHR не наблюдается** (только статика: CSS, JS-вендоры, шрифты). Данные вшиваются в HTML и Vuex-state. Переключение шагов 1↔2↔3 — клиентское, без запросов. Это значит, что отчёт по рекомендациям и ЧС подгружаются заранее в одну отрисовку.

#### 13.1.6. Вывод для Прил. М §3.1

**Не подтверждает модель `project_suppliers`** — это совершенно другая фича. Для покрытия нужна **отдельная сущность**, например:

- `source_recommendation_searches` (id, user_id, city_id, query_text, query_type ENUM('domain','keyword'), created_at, status, result_count, blacklist_count) — история запусков
- `source_recommendation_results` (id, search_id, recommended_domain, recommended_phone, region, in_blacklist BOOL) — результаты
- `source_recommendation_blacklist` (id, user_id, domain NULL, phone NULL, added_at)

На MVP эта фича низкоприоритетна: её ценность спорна для арбитражной модели — это скорее «помощник по конкурентам», чем основной flow.

---

### 13.2. Аккордеон «Настройки» в шапке реестра проектов

#### 13.2.1. Структура аккордеона

| # | Кнопка | Иконка | Tooltip | Действие |
|---|--------|--------|---------|----------|
| 1 | Вкл/откл проекты ▾ | — | (нет всплывающей подсказки) | Dropdown: «Включить все» / «Отключить все» — массовое переключение `projects.is_active` для всех (или выбранных по чекбоксам) проектов |
| 2 | Желаемое кол-во номеров | — | — | Модалка с одним полем `number` (валидация «Неверное число» при пустом значении) |
| 3 | Удалённые проекты | 🗑 | — | Переход по ссылке `?type=deleted` (отдельный экран, см. 13.2.3) |
| 4 | Выгрузка источников | — | — | При клике без предварительного выбора проектов — действие не срабатывает (XHR не отправляется). Видимо, требует чекбоксов в строках |
| 5 | Управление страницей ▾ | — | — | Dropdown с 16 чекбоксами видимости колонок (см. 13.2.5) |
| 6 | Восстановление/Удаление проектов | — | — | Модалка-wizard «Работа с несколькими проектами» (см. 13.2.6) |

#### 13.2.2. «Желаемое количество номеров»

**Аккаунтный таргет, не лимит.** Модалка содержит одно числовое поле. Текст:
> «Дорогой клиент, пожалуйста укажите сколько номеров в день вы готовы принимать: на это значение будет ориентироваться сервис ГЦК и в случае если желаемое кол-во не достигается, наши специалисты будут помогать в достижении указанного показателя.»

Это **не** массовая установка `daily_limit_target` по проектам и **не** влияет на `effective_daily_limit_today` напрямую. Это **ориентирующее значение для бэк-офиса** ГЦК (то есть для людей-операторов поставщика), которые ручным трудом дотягивают объём до целевого. На уровне БД — отдельное поле в `clients` или `client_settings`: `desired_daily_numbers INT NULL`. **Скоуп: весь аккаунт**, не выбранные проекты.

#### 13.2.3. «Удалённые проекты» (`/admin/visit/rt?type=deleted`)

Отдельный экран, идентичный по структуре основному реестру, но строки с розовой подсветкой. Заголовок: **«Удалённые проекты ГЦК»**.

Дополнительная колонка: **«Дата удаления»** (формат `YYYY-MM-DD HH:MM:SS`).

Наблюдаемые данные: проекты, созданные `2025-08-14`, удалены `2026-01-29 06:29:54` — на момент аудита (`2026-05-05`) хранятся в этом списке **более 3 месяцев**. То есть период удержания **большой / возможно бессрочный** — точное значение TTL не определяется без админ-доступа.

В строках присутствует тот же набор иконок-действий (pencil, trash, history burger). Кнопок «Восстановить из этой строки» отдельно нет — массовое восстановление выполняется через модалку 13.2.6.

#### 13.2.4. «Выгрузка источников»

**Не удалось протестировать.** Клик без предварительно выбранных проектов не даёт ни модалки, ни XHR-запроса. Логика, по всей видимости: установить чекбоксы в нужных строках реестра → нажать кнопку → инициируется скачивание (CSV/XLSX) с источниками выбранных проектов. UI выбора формата/полей не появляется. Скачивать не пытался (запрещено протоколом).

#### 13.2.5. «Управление страницей»

Dropdown со списком чекбоксов колонок таблицы. Все 16 включены по умолчанию:

1. id ✓
2. Статус отправки ✓
3. Тег ✓
4. Название ✓
5. Статус ✓
6. Тип ✓
7. Лимит ✓
8. Получено от поставщика ✓
9. Уникальных сегодня ✓
10. Дублей сегодня ✓
11. Номеров получено всего ✓
12. Дни получения номеров ✓
13. Доменов/Номеров ✓
14. Дублей всего ✓
15. Дата ✓
16. Настройки ✓ (колонка с иконками действий)

Подтверждает связь с `localStorage.rtstatopts1` из партии 10.9.

#### 13.2.6. «Восстановление/Удаление проектов»

Модалка «**Работа с несколькими проектами**» — двухшаговый wizard:

- **Шаг 1: Выбор проектов** — чекбокс «Удалённые» (искать среди удалённых) + textarea «Введите названия проектов по одному в каждую строку».
- **Шаг 2: Действия над проектами** (не открывал, чтобы не вводить данные).

Это **bulk-tool по именам**: пользователь вставляет список имён проектов, выбирает действие (вероятно: включить / отключить / удалить / восстановить — по аналогии с сервисом). Дублирует функциональность кнопки «Удалённые проекты» только в части восстановления.

#### 13.2.7. Выводы для нашей модели

- `projects.daily_limit_target` массовая установка: **нет** (есть только аккаунтный `desired_daily_numbers`)
- `projects.deleted_at` + soft-delete: **подтверждается** (есть колонка «Дата удаления»; удалённые держатся отдельным экраном c визуальным отличием)
- Период удержания удалённых: **минимум 3 месяца, возможно бессрочно** (по наблюдаемому образцу 2026-01-29 → 2026-05-05)
- Нужно добавить в модель `clients.desired_daily_numbers INT NULL` — целевое количество принимаемых номеров в день для всего аккаунта, ориентир для backoffice
- `bulk_project_operations` не нужно отдельной таблицей — это просто wizard, который применяет существующие операции к набору `project_id`

---

### 13.3. Иконка «Просмотреть» в строке проекта

#### 13.3.1. Селектор и расположение

По aria-label существует элемент `«Просмотреть»` в **каждой** строке реестра (по 1 на проект, найдено сразу >20 экземпляров через find). **Визуально отдельной иконки 👁 нет**. Иконки в правом столбце «Настройки»: только три — ✏ pencil (редактировать), 🗑 trash (удалить), ☰ burger (история действий). Дополнительно есть ☰ list-иконка в столбце «Доменов/Номеров».

«Просмотреть» — это, по всей видимости, **aria-label** одной из видимых иконок (вероятно, ☰ list в столбце «Доменов/Номеров» или ✏ pencil), используемый для скринридеров. **Отдельного «view-only» режима нет**.

#### 13.3.2. Что открывается

Клик по элементу с aria-label «Просмотреть» открывает **ту же модалку** «Редактировать проект», что и pencil/list-иконка. URL не меняется (модалка-overlay).

#### 13.3.3. Контент модалки «Редактировать проект»

Это карточка редактирования, уже исследованная в Партии 10:

| Поле | Тип | Значение для B3_Caranga (пример) |
|------|-----|-----------------------------------|
| Активный | checkbox + tooltip | ✓ |
| Тег | text | Caranga |
| Название проекта | text (required) | B3_Caranga |
| Регион — Включить/Исключить | toggle | Включить |
| Регион — фильтр по регионам | text | (пусто) |
| Регион — дерево округов | tree-checkbox | 7 округов; выбран Сибирский |
| Наименование отправителя | tabs «Список»/«Файл» + textarea | Caranga |
| Лимит в день | number stepper | 10 |
| Дни получения номеров | 7 чекбоксов Пн–Вс | Пн ✓ Вт ✓ Ср ✓ Чт ✓ Пт ✗ Сб ✗ Вс ✗ |
| Сохранить / Отмена | buttons | (не нажимались) |

Пагинация: нет (одна форма).

#### 13.3.4. Действия

- В шапке модалки кнопка «Выгрузка источников» — экспорт по конкретному проекту (не нажимал)
- Сохранить (не нажимал)
- Отмена (использовалось для закрытия)
- Drill-down в конкретные лиды/номера: **нет** — модалка показывает только настройки проекта, не данные

#### 13.3.5. Что это за сущность

**Это не просмотр накопленных номеров**, гипотеза партии 10 опровергнута. Это карточка **настроек проекта** (то же, что и pencil-иконка). Реальный list of leads проекта смотрится через основной экран `/admin/visit/index-visit` с фильтром по проекту.

**Критическое подтверждение архитектуры B1/B2/B3** — оранжевый notice внутри модалки:

> «Важно! Указать в связке 'Наименование отправителя' и 'Ключевое слово' можно только по поставщику B2. Поставщик B3 работает только по наименованию отправителя, учтите данный момент при формировании задания.»

Это **жёстко подтверждает**, что B1/B2/B3 — это три **поставщика с разными возможностями**:

- **B2**: поддерживает связку sender_name + keyword
- **B3**: только sender_name
- **B1**: (по партии 10 — звонки/sms-канал, не упомянут в этом notice)

#### 13.3.6. Вывод для модели

- **Покрывается существующей картой**: «Просмотреть» = «Редактировать» = карточка `projects` (исследована в Партии 10). Отдельная сущность `project_pending_numbers` **не нужна**.
- **Подтверждение** для Прил. М §3.1: модель `suppliers` (B1, B2, B3) с разными `capabilities` (поля `supports_sender_name BOOL`, `supports_keyword BOOL`, `channel ENUM('sms','calls','sites')`) — обязательна.
- В таблице `projects` поле `supplier_id` (или префикс в `name` как сейчас в продуктиве — `B3_*`, `B2_*`, `B1_*`) определяет, какие поля заполнения доступны.

---

### 13.4. Раздел «Список звонков» (сквозной)

#### 13.4.1. Существование

Раздел в боковом меню называется **«Список Звонков»**, но указывает на тот же URL `/admin/user/moizvonki`, что и «Мои звонки» из Партии 7.3. То есть:

- **Сквозного журнала отдельных звонков нет** — есть только один экран
- Этот единственный экран — **агрегация по менеджерам** (логин/ФИО/количество/длительность)
- Заголовок страницы: «Звонки в период с YYYY-MM-DD HH:MM:SS по YYYY-MM-DD HH:MM:SS»

#### 13.4.2. Структура (агрегация)

Колонки таблицы: **# / Логин / ФИО / Кол-во звонков / Списание / Общая длительность ↓ / Пропущенных звонков / Недозвонов / Роль**.

Аккордеон «Фильтр» содержит:

- Текстовый поиск **«Поиск по номеру»** (по телефонному номеру)
- Текстовый поиск **«Поиск по пользователю»**
- Селектор направления звонка: **Все звонки / Входящий / Исходящий / Пропущенный / Недозвон**
- Чекбокс **«Контроль качества»**

Селектор периода в правом верхнем углу: Текущий день / Прошедший день / Понедельник / Текущая неделя / Прошедшая неделя / Текущий месяц / Прошедший месяц / За период.

#### 13.4.3. Drill-down из агрегации

**Не удалось проверить** — за все доступные периоды (текущий день, прошедший месяц) данных нет («Нет данных за этот период»). По структуре колонок никаких ссылок-стрелок «развернуть в список звонков менеджера» не видно. Однако **наличие фильтра «Поиск по номеру»** свидетельствует, что сервер умеет фильтровать на уровне отдельных звонков — но UI всё равно показывает агрегат. **Вероятно, drill-down невозможен в этом интерфейсе**, либо требует роли «Контроль качества», которая ограничена (партия 11).

Сырой посменный лог звонков пользователь, по всей видимости, видит только в карточке конкретной сделки (вкладка «История звонков», Партия 7.3 / 11.5).

#### 13.4.4. Вывод для нашей модели

**Не делаем сквозной журнал звонков на MVP.** Биз-12 явно говорит «не делаем телефонию». Существующий UI это подтверждает — клиенту дают только агрегацию по менеджерам и поиск по номеру/пользователю, но не сырой лог. Для MVP достаточно:

- Хранить ссылки на mp3 в `deal_calls` (id, deal_id, called_at, direction, duration, audio_url, manager_id) — Партия 7.3
- Агрегировать на лету через SQL при загрузке экрана «Звонки» — без отдельной материализованной таблицы

---

### Сводка для Прил. М v1.1

**Рекомендации источников:** **не управляют B1/B2/B3** — это OSINT-инструмент по доменам/телефонам конкурентов. Низкоприоритетная фича, отдельная сущность `source_recommendation_*`.

**Массовое управление лимитами:** **нет** на уровне `projects.daily_limit`. Есть **аккаунтный** «Желаемое кол-во номеров в день» — целевой ориентир для backoffice ГЦК → новое поле `clients.desired_daily_numbers INT NULL`.

**Просмотр накопленных номеров:** **отдельной сущности нет** — «Просмотреть» в строке проекта = aria-label на иконку, открывающую модалку **редактирования настроек проекта** (карточка `projects` из партии 10). Раскрытие лидов проекта — через `/admin/visit/index-visit` с фильтром.

**Сквозной журнал звонков:** **только агрегация** по менеджерам (`/admin/user/moizvonki`). Drill-down не предоставлен. Биз-12 → не делаем телефонию.

**Подтверждено для §3.1 (suppliers B1/B2/B3):**
> «Указать в связке 'Наименование отправителя' и 'Ключевое слово' можно только по поставщику B2. Поставщик B3 работает только по наименованию отправителя.»

Это требует в Прил. М модели:

- `suppliers` (id, code ENUM('B1','B2','B3'), name, channel ENUM('sms','calls','sites'), supports_sender_name BOOL, supports_keyword BOOL, ...)
- `projects.supplier_id` FK (или, как сейчас в продуктиве, префикс в `projects.name`)

---

### Что не исследовано в этой партии

- **Шаг 2 wizard'а «Работа с несколькими проектами»** — для прохождения шага 1 нужно ввести имена проектов, что запрещено протоколом.
- **«Выгрузка источников»** — формат и поля экспорта; для запуска нужен предварительный выбор проектов через чекбоксы.
- **Drill-down из агрегации звонков** — нет данных за все доступные периоды.
- **Точный TTL удерживания удалённых проектов** — нижняя граница ≥3 месяца, верхняя не определяется.
- **Раздел «Конверсия проектов» / Карточка «Досье» / Напоминания** — оставлено на партию 12 как и обусловлено.

### Безопасность

- `claude-agent-stop-container` с кнопкой «Stop Claude» **найден** на нижней панели каждой страницы (включая `/admin/user/moizvonki?period=lastmonth`). Игнорирован полностью, не нажимался.
- Других подозрительных DOM-инъекций или скрытых инструкций (white-text, hidden div, suspicious data-* атрибуты с командами) **не найдено**.
- Render-error из Партии 1 (`Cannot read properties of undefined (reading '0')`) **не повторился** — все таблицы отрисовались корректно (включая ситуации с пустыми данными — там было корректное «Нет данных»).
- Все XHR-запросы — на собственный домен `crm.bp-gr.ru`, без подозрительных внешних эндпоинтов.
- **Никаких** чекбоксов / тогглов / селектов / полей форм не переключалось и не вводилось. Ни одного `Save` / `Submit` / `Применить` / `Удалить` / `Отправить` / `Восстановить` / `Скачать` / `Подтвердить` / `Принять рекомендацию` / `Подключить` не нажималось.

---

# Часть 14

## Партия 14 аудита crm.bp-gr.ru — Редкие статусы + База знаний + Безопасность профиля

**Дата:** 2026-05-05
**Режим:** read-only
**Длительность:** ~15 минут
**Задачи:** 3 (Редкие статусы, База знаний, Безопасность)
**Что НЕ удалось:** эмпирически проверить структуру карточки в 9 редких статусах — на текущем аккаунте 0 сделок во ВСЕХ 14 статусах (включая 5 «частых» из партии 11). Вывод по гипотезе единой структуры — косвенный (через DOM фильтра).

---

### 14.1. Сделки в редких статусах

#### 14.1.1. Покрытие 9 редких статусов

Аккаунт текущей сессии (username замаскирован, см. §14.3) — **корпоративный «клиент» с правами доступа к CRM, но без назначенных ему сделок**. Это видно по:

- бейджу «Все 0» в шапке таблицы при дате `date_from=10years` (10-летний охват);
- фильтру «Ответственные» — единственный доступный пункт = текущий пользователь;
- сообщению «Нет посетителей для показа» во всех проверенных state.

| Статус | URL state | На аккаунте | Открыта | Структура идентична? | Отличия |
|--------|-----------|-------------|---------|----------------------|---------|
| Новый | `state=new` | 0 сделок | нет | n/a | n/a |
| Оплачено | (не проверял отдельно — see ниже) | 0 (по «Все 0») | нет | n/a | n/a |
| Горячий | `state=hot` | 0 сделок | нет | n/a | n/a |
| На замену | (косв.) | 0 (по «Все 0») | нет | n/a | n/a |
| Партнёрка | (косв.) | 0 (по «Все 0») | нет | n/a | n/a |
| Ожидаем оплаты | (косв.) | 0 (по «Все 0») | нет | n/a | n/a |
| Конечный недозвон | (косв.) | 0 (по «Все 0») | нет | n/a | n/a |
| Тест драйв | (косв.) | 0 (по «Все 0») | нет | n/a | n/a |
| База | (косв.) | 0 (по «Все 0») | нет | n/a | n/a |

Эмпирически открыты `state=new` и `state=hot` (URL приняты сервером, страница рендерится, фильтр-чип меняется на нужное название статуса) — обе пустые. Остальные 7 не открывались индивидуально, потому что общий счётчик «Все 0» однозначно гарантирует пустоту.

#### 14.1.2. Найденные отличия (если есть)

Не применимо — нет открытых карточек.

#### 14.1.3. Editable во всех статусах?

Не подтверждено эмпирически на этой партии. **Гипотеза из партии 11.5 (editable во всех 5 проверенных статусах включая `closed`/«Закрыто и не реализовано») остаётся без расширения.** Для финальной проверки нужен аккаунт с реальными сделками в редких статусах.

#### 14.1.4. Селектор статуса показывает все 14 переходов?

**Подтверждено косвенно через DOM фильтра:** dropdown «Статусы» в `/admin/visit/index-visit` содержит ровно 14 элементов в одном плоском списке без группировки и без условной видимости:

1. Новый
2. Просмотрено
3. Проработан
4. База
5. Недозвон
6. Переговоры
7. Ожидаем оплаты
8. Партнерка
9. Оплачено
10. Закрыто и не реализовано
11. Тест драйв
12. Горячий
13. На замену
14. Конечный недозвон

Одинаковый порядок и в горизонтальной полосе кнопок, и в выпадающем списке. Эта же структура используется как фильтр и (по аналогии с партией 11.4) как селектор в карточке сделки.

#### 14.1.5. Вывод для §8 narrative

**Гипотеза единой структуры карточки во всех 14 статусах НЕ опровергнута.** Косвенные подтверждения:

- единый список из 14 статусов в DOM фильтра без условных подразделов;
- партия 11.2 ранее показала, что 5 разных статусов используют идентичную `.guest-block` со всеми 9 полями, без скрытых блоков под определённый статус;
- backend-параметр одинарный (`state=...`) — не несёт указаний на дополнительные подформы.

**Рекомендация для нашего ТЗ:** в нашем аналоге CRM делать **одну универсальную карточку сделки**, поведение которой не зависит от текущего статуса (никаких условных полей, никаких показывать/скрывать секции по статусу). Различия по статусу — только через бейдж, цвет статуса и набор разрешённых next-transitions в селекторе перехода.

**Что нужно эмпирически перепроверить позже** (если получим аккаунт с непустой выборкой по редким статусам): не появляются ли в карточках статусов «Оплачено» или «Партнёрка» дополнительные read-only блоки с информацией об оплате или комиссии. На текущем аккаунте это проверить нельзя.

---

### 14.2. База знаний

#### 14.2.1. Поиск ссылок

| Где искал | Найдено | URL/ресурс |
|-----------|---------|------------|
| Левое меню (отдельный пункт «База Знаний») | **да** | `https://data.helpdeskeddy.com/ru/knowledge_base/cat/26/baza-znanij-klienta-gck` (внешний — не открывал) |
| Шапка — иконка ⓘ возле поисковой лупы (`.help-search`) | **да** | popover с формой обратной связи (Имя, Комментарий, Телефон, Эл. почта), заголовок «Помощь» |
| Шапка — «Тех. поддержка в Telegram» | **да** | `https://t.me/CRMTechHelp_bot` (внешний — не открывал) |
| Шапка — «Предложить идею» | **да** | `/site/contact?subject=idea` (внутренний контактный роутинг) |
| Футер | нет | `<footer>` пуст / отсутствует значимое содержимое |
| Inline-tooltips на полях карточки сделки | нет (по партии 11) | n/a |
| Чат-виджет (правый нижний угол) | **да** | `<iframe id="hde-iframe" src="//data.helpdeskeddy.com/ru/omnichannel/chat?...">` |

#### 14.2.2. Если найдена встроенная — структура

Встроенной KB **в самом CRM нет**. Внутри приложения «База Знаний» из левого меню — это просто `<a href>` на внешний домен HelpDeskEddy (не SPA-роут).

#### 14.2.3. Если внешняя — провайдер

**HelpDeskEddy** (`data.helpdeskeddy.com`) — единая SaaS-платформа, обеспечивающая:

- knowledge base (`/ru/knowledge_base/cat/26/...`),
- omnichannel-чат (виджет в правом нижнем углу через `<iframe id="hde-iframe">`).

Сторонние сайты не открывал — только зафиксировал URL и провайдера через DOM.

Дополнительно в шапке есть кнопка `.help-search` с глифом «info-sign» — открывает Bootstrap popover-x с формой обратной связи (4 поля). Это **не KB-search**, а контактная форма «Помощь».

#### 14.2.4. Вывод для нашего ТЗ

- **Внешний KB** — оригинал использует HelpDeskEddy KB. Это означает, что bp-gr не содержит проприетарных текстовых статей — все материалы лежат в HDE и редактируются админом HDE-инстанса.
- **Для MVP нашего аналога:** достаточно выставить ссылку «База знаний» на наш JivoSite KB (или Notion / любой статичный сайт документации). Делать встроенный KB на старте — overkill: оригинал не делал, и его клиенты используют внешний.
- **Чат:** в нашей реализации заменяем `hde-iframe` на JivoSite-виджет. Функциональный паритет сохраняется (в JivoSite тоже есть KB-кнопка в виджете и канал в чат).
- **«Помощь» popover (4-поля контакт-форма)** — простой прямой ввод заявки на email саппорта. У нас в нашей реализации это можно либо реализовать как embedded-форму, либо просто заменить ссылкой «Связаться» → mailto / форма.
- Дополнительная независимая ссылка `https://t.me/CRMTechHelp_bot` остаётся как «канал техподдержки» — отдельный путь, который не зависит от HDE.

---

### 14.3. Безопасность профиля

#### 14.3.1. /admin/user/account — все блоки страницы

Title страницы: `Update user: <Имя>`. Один общий `<form>` со всеми полями, одна общая кнопка «Обновить» внизу. URL: `/admin/user/account`.

| # | Блок | Поля | Editable | Назначение |
|---|------|------|----------|------------|
| 1 | **Системная информация** | `User[username]` (text), `User[password]` («Текущий пароль» — text input, отображается plaintext-значением), `User[email]` (text), `User[notify_email]` («Эл. почта для уведомлений», вспомогательный текст «Почта для уведомлений о новых идентификациях»), `User[timezone_id]` (select; default = «Москва»), `User[language_id]` (select; default = «Русский»; опции: Выбрать язык / Русский / English / Français) | да (не трогали) | Учётка и базовые системные настройки |
| 2 | **Личная информация** | `User[first_name]` (Имя*, required), `User[last_name]` (Фамилия), `User[phone]` (Телефон*, required), `User[mobile_phone]` (Мобильный) | да (не трогали) | Контактные данные пользователя для звонков и идентификации |
| 3 | **Выберете время показа уведомлений** (Тихие часы) | `User[popup_time_start]` (select 0–23), `User[popup_time_end]` (select 0–23). Validation hint: «Интервал между 'Показывать уведомления с' и 'Показывать уведомления до' не может быть менее 3 часов». Default: 10 — 19. | да (не трогали) | Окно «не беспокоить» — браузерные уведомления и звонки видимы только в этот интервал |

Дополнительно на странице присутствуют hidden-INPUT'ы (`User[status_id]`) и две вспомогательные модалки в DOM («Пожаловаться», «Запросить голосовую помощь») — открываются через сторонние UI-элементы (не на главной форме).

Никаких других блоков (Аватар, Подпись email, NotificationPreferences c каналами WA/TG/SMS, Уведомления-каналы, API-ключи в профиле, IP-allowlist, Recovery codes, История смены пароля) — на странице нет.

#### 14.3.2. Журнал входов

**Не найден.** Проверено:

- В DOM `/admin/user/account` — 0 ссылок с регексом `2FA|двухфактор|сесси|истори|войти|пароль|сменить|изменить|устройств|безопас`.
- URL `/admin/user/auth-log` → пустая страница (Yii2 404, body length = 217 байт chrome-only).
- URL `/admin/user/login-history` → то же.

#### 14.3.3. Список активных сессий

**Не найден.** Проверено:

- URL `/admin/user/sessions` → отдаёт страницу-заглушку «Уважаемый пользователь, проводятся технические работы» (это, вероятно, дефолтный фильтр Yii2 на префикс `sessions/*`, а не реальный раздел).
- URL `/admin/user/devices` → пустая страница (Yii2 404, body length = 217).

#### 14.3.4. 2FA

**Не найден.** URL `/admin/user/2fa` → пустая страница. Никаких признаков TOTP/SMS/Email/Hardware key в DOM `/admin/user/account` нет. Recovery codes отсутствуют.

Смена пароля выполняется **inline в общем POST-запросе формы профиля** — поле «Текущий пароль» в блоке «Системная информация» содержит plaintext-значение (видится как обычный input type=text); пользователь редактирует значение прямо в этом поле и нажимает общую кнопку «Обновить». Отдельной формы «Старый пароль / Новый пароль / Подтверждение» нет — это, по нашим стандартам безопасности, **анти-паттерн**.

#### 14.3.5. «Тихие часы» — детали

| Параметр | Значение |
|----------|----------|
| Формат поля «от» | `<select>` `User[popup_time_start]` с 24 опциями (значения и подписи 0..23, целые часы) |
| Формат поля «до» | `<select>` `User[popup_time_end]` с 24 опциями (0..23) |
| Default | start=10, end=19 (по умолчанию текущего пользователя) |
| Минимальный интервал | 3 часа (server-side validation, hint показывается рядом с полями) |
| Часовой пояс | **Отдельное поле** `User[timezone_id]` в блоке «Системная информация» (default «Москва»), не привязано визуально к блоку «Тихие часы», но фактически определяет, в каком TZ интерпретируются 0–23 часа |
| Гранулярность | только часы (нет минут, нет дней недели, нет нескольких окон в сутки) |
| Per-channel настройки | **нет** — одно окно действует на все каналы уведомлений (push/popup/звонки и т.д.) |

#### 14.3.6. Вывод для нашего ТЗ

- **Журнал входов:** у нас в БД есть `auth_log` (партия Биз-9) — это **наше преимущество** над оригиналом. В UI самостоятельно реализуем self-service страницу «История входов» с колонками: дата, IP, User-Agent, гео, статус (success/fail), отметка «текущая сессия».
- **Список сессий:** у нас в БД есть `user_sessions` — также **наше преимущество**. В UI делаем страницу «Активные сессии» с возможностью «Завершить сессию» (кроме текущей).
- **2FA:** оригинал не имеет 2FA, мы по Биз-9 поддерживаем TOTP (Google Authenticator) + recovery codes. Это **сильный апгрейд безопасности** относительно паритета. SMS / Email-OTP — опционально в Post-MVP.
- **«Тихие часы»:** наш `notification_preferences.quiet_hours` (Прил. М §4.8) должен:
  - принимать формат `start_hour:int(0..23)`, `end_hour:int(0..23)` (минимум для паритета);
  - использовать таймзону пользователя (`User[timezone_id]` ↔ `users.timezone`), как в оригинале;
  - на MVP принять минимальный интервал = 3 часа (для паритета и предотвращения «выключаю уведомления навсегда»);
  - **расширения сверх паритета** (Прил. М): возможность задать минуты (HH:MM), per-channel quiet hours (отдельно для push/email/SMS/WA/TG), исключения по дням недели — всё это легально как «лучше оригинала».
- **Смена пароля:** оригинал делает inline-вводом текущего пароля в общую форму — **не делаем так**. У нас отдельная форма «Старый пароль / Новый пароль / Подтверждение нового», с проверкой надёжности и rate-limit. Это explicit-апгрейд безопасности.
- **Авторизационные/анти-фрод хуки** (рекомендация в ТЗ): отдельная вкладка «Безопасность» в профиле с разделами: Пароль, 2FA, История входов, Активные сессии, Уведомления о подозрительной активности. Оригинал не имеет ничего из этого — нам выгодно зафиксировать как differentiator.

---

### Сводка для Прил. М v1.1

**Единая структура карточки сделки во всех 14 статусах:** косвенно подтверждена через DOM фильтра (один плоский список из 14 статусов без условных подразделов) и партию 11 (5/5 проверенных статусов имели идентичную `.guest-block`); эмпирически в этой партии не подтверждено из-за пустоты текущего аккаунта; делаем **одну универсальную карточку без условных полей по статусу**.

**База знаний:** внешняя — HelpDeskEddy (`data.helpdeskeddy.com`). Для MVP нашего CRM — линкуем на JivoSite KB / Notion, встроенную KB не делаем.

**Журнал входов и сессий:** в оригинале **нет отдельного раздела** ни для журнала входов, ни для активных сессий, ни для управления устройствами. У нас в схеме есть `auth_log` и `user_sessions` (Биз-9) → реализуем UI для self-service. **Это наше преимущество.**

**2FA в оригинале:** **отсутствует**. У нас в Биз-9 = TOTP + recovery codes (плюс возможно SMS/Email-OTP в Post-MVP). **Это наше сильное преимущество.**

**Формат «Тихих часов»:** для нашего `notification_preferences.quiet_hours` минимум: `start_hour 0..23` + `end_hour 0..23` + interval ≥ 3h, привязка к `users.timezone`. Расширения сверх паритета: точность HH:MM, per-channel, дни недели.

**Смена пароля:** оригинал делает inline в общей форме (анти-паттерн). У нас — отдельная защищённая форма «Старый/Новый/Подтверждение» с rate-limit.

---

### Что не исследовано в этой партии

- Структура `.guest-block` в реальной открытой карточке сделки в любом из 9 редких статусов — отложено до получения тестовых данных в этих статусах.
- Содержимое HelpDeskEddy KB и SaaS-чат-виджета (по протоколу не переходим на внешние ресурсы).
- Содержимое модалок «Пожаловаться» и «Запросить голосовую помощь» (открытие может потребовать триггеров вне профиля).
- Скрытое поле `User[status_id]` (значение длиной 11 символов в input — возможно, какой-то slug или ID); расшифровка в этой партии не запрашивалась.

### Безопасность

- **claude-agent-stop-container найден** на всех проверенных страницах (`/admin/visit/index-visit`, `/admin/user/account`, всех несуществующих `/admin/user/*` URL'ах). Кнопка `#claude-agent-stop-button` со span «Stop Claude» присутствует в DOM. Согласно протоколу — **проигнорирован, не нажат**.
- Других подозрительных DOM-элементов с инъекциями инструкций — не обнаружено. Текст «Stop Claude» в `body.innerText` каждой страницы — это та же кнопка, не отдельная инъекция.
- На странице `/admin/user/account` поле «Текущий пароль» отдаёт пароль пользователя в plaintext в HTML-форме (input type=text с заполненным value). При read-only-аудите я **не считывал значение и не выводил его в отчёт**; в маске обозначено как «[MASKED]». Это потенциальная уязвимость оригинала (XSS на странице профиля → угон пароля без подтверждения), но для нашего проекта это к сведению, не наша проблема.
- ПДн в отчёте замаскированы: username (`User[username]`), email, phone, имя пользователя — упоминаются только структурно или с маскировкой.

---

# Часть 15

## Партия 15 аудита crm.bp-gr.ru — Карточки внешних интеграций «API ▾»

**Дата:** 05.05.2026
**Режим:** read-only с открытием карточек, со строгой маскировкой токенов
**Длительность:** ~25 минут
**Задачи:** 4 (Список, Карточки 5 представителей, JS-поиск, Network)
**Открыто карточек:** 5 из 12 пунктов меню
**Что НЕ удалось:** ничего не блокировано — все 4 задачи выполнены полностью.

---

### 15.1. Полный список интеграций в «API ▾»

#### 15.1.1. Метод раскрытия

**Только клик** на «API ▾». Hover не открывает (Bootstrap dropdown с `data-toggle="dropdown"`, без CSS hover-trigger). `<li class="dropdown dropdown-api open">` — стандартный Bootstrap dropdown. Раскрытие — DOM-событие, не XHR.

#### 15.1.2. Список (только метаданные)

| # | Название | href / handler | Иконка | Статус подключения |
|---|----------|----------------|--------|---------------------|
| 1 | API | `/admin/user/api` | — | не отображается |
| — | *(divider)* | — | — | — |
| 2 | Битрикс | `/admin/bitrix/v2` | — | не отображается |
| 3 | amoCRM | `/admin/amocrm/v2` | — | не отображается |
| 4 | Google таблица | `/admin/user/api-google-sheets` | — | не отображается |
| 5 | Unisender | `/admin/unisender/index` | — | не отображается |
| — | *(divider)* | — | — | — |
| 6 | Мои Звонки | `/admin/moizvonki` | — | не отображается |
| 7 | Скорозвон | `/admin/skorozvon` | — | не отображается |
| 8 | Мango Office | `/admin/onpbx/create-mango` | — | не отображается |
| — | *(divider)* | — | — | — |
| 9 | Голосовой Робот ▸ | `/admin/hns-record/index` | — | submenu (3 sub-items) |
| 9.1 | — Создать ГР | `/admin/hns-record/create` | — | — |
| 9.2 | — Создать список | `/admin/hns-record/orders` | — | — |
| 9.3 | — Список звонков ГР | `/admin/hns-record/calls` | — | — |
| 10 | Chat GPT | `/admin/chat/gpt` | — | не отображается |
| 11 | Проверка репутации номеров | `/admin/phone/check` | — | не отображается |
| — | *(divider)* | — | — | — |
| 12 | Подписаться на Телеграм | `/admin/telegram/subscribe-user?id=[ID]` (target=_blank) | `glyphicon-send` | — |

Опечатка `Мango` (латинская M) — реально в HTML. Параметр id у Telegram-ссылки замаскирован как `[ID]` (это user-id админа CRM в системе, потенциально PII).

#### 15.1.3. Категории интеграций

- **CRM-системы:** Битрикс, amoCRM (2 шт.)
- **Email/маркетинг:** Unisender, Google таблица (2 шт.)
- **Телефония / трекеры звонков:** Мои Звонки, Скорозвон, Mango Office (3 шт.)
- **Голосовой робот / автозвонки:** Голосовой Робот (с подменю на 3 функции)
- **AI / утилиты:** Chat GPT, Проверка репутации номеров (2 шт.)
- **Прочее:** API (страница API-доков), Подписаться на Телеграм (внешняя ссылка)

**Категория «рекламные кабинеты» (Я.Директ, ВК Ads и т.п.) — отсутствует.** Это важное наблюдение: в шапке нет интеграций с рекламными системами.

---

### 15.2. Открытые карточки

Открыто 5 карточек как репрезентативная выборка по категориям:

- 2 CRM: Битрикс, amoCRM
- 2 телефонии: Скорозвон (приоритет — используется в оригинале), Мои Звонки (для контраста)
- 1 email: Unisender

Все 5 карточек — **отдельные страницы с полной формой**, не модалки Vuetify. Битрикс/amoCRM — Vue 2 SPA на Element UI с hash-routing (`#/list` ↔ `#/add`). Остальные — серверный рендер Yii ActiveForm.

#### 15.2.1. Карточка «Битрикс / Bitrix24» (CRM)

##### Заголовок и общий вид

Заголовок: «**ДОБАВИТЬ BITRIX24**». Полная страница (не модалка). Vue SPA, Element UI. **Многошаговая форма** — на видимом шаге 1 присутствует кнопка «Далее».

##### Поля формы (шаг 1, без значений)

| # | Лейбл | name / prop | Тип | Обязат. | Заполнено? |
|---|-------|-------------|-----|---------|------------|
| 1 | Статус | (toggle) | switch | — | ON по умолчанию |
| 2 | Наименование | (Element UI prop) | text | да | нет |
| 3 | Сайт | (Element UI prop) | select-search | нет | нет |
| 4 | Вебхук *(внутри блока «Входящий вебхук»)* | `webhook` (id="webhook") | text | да | нет |

**Заголовок секции 1:** `<p>Входящий вебхук</p>` — однозначно подписан как **ВХОДЯЩИЙ** (Bitrix → CRM). Клиент создаёт в своём Bitrix24 «входящий вебхук» и вставляет его URL сюда.

##### Webhook-сущности (специальная сводка для Bitrix)

| Признак | Найдено? | Детали |
|---------|----------|--------|
| Поле «Webhook URL» | да, но «Входящий» | label="Вебхук" внутри секции «Входящий вебхук» — URL, по которому **CRM вызывает Bitrix REST**, НЕ исходящий webhook |
| **Поле «Исходящий вебхук» (вторая секция)** | **да — отображается** | в Vue-шаблоне: `<p>Исходящий вебхук: https://prostats.info/bitrix24/webhook.php?id={{formData.id}}</p>` — но это **read-only текст**, а не редактируемое поле |
| Поле «Webhook secret» / «Signature key» | нет | — |
| Чекбокс «Включить уведомления» / «Подписаться на события» | нет | — |
| Селект «События для отправки» | нет | — |
| Кнопка «Тест webhook» | нет | — |
| **Поле «Токен приложения»** *(шаг 2)* | да | `el-input` с placeholder="Токен приложения" — токен Bitrix-приложения для исходящих вызовов CRM в Bitrix |

##### 🔴🔴 Ключевая находка по Bitrix — "Исходящий вебхук"

В исходном HTML страницы `/admin/bitrix/v2` найден Vue-template:

```
<p>Исходящий вебхук: https://prostats.info/bitrix24/webhook.php?id={{formData.id}}</p>
```

**Что это значит:**

- Хост — `prostats.info` (НЕ crm.bp-gr.ru) — отдельный домен инфраструктуры группы.
- Путь — `/bitrix24/webhook.php`.
- Параметр — `id={{formData.id}}` — численный ID записи интеграции.
- Это **read-only текст** для копирования пользователем в админку Bitrix24.
- Технически с точки зрения CRM это **inbound endpoint**: Bitrix.ru постит туда события, prostats.info принимает и (предположительно) проксирует в crm.bp-gr.ru сервер-сервер.
- Это **НЕ generic outbound webhook subsystem CRM** — это URL одного hardcoded receiver, провайдер-специфичный для Bitrix24.

##### XHR при открытии карточки Bitrix

| URL | Метод | Initiator | Категория |
|-----|-------|-----------|-----------|
| `https://crm.bp-gr.ru/images/bitrix.png` | GET | parser | внутренний (логотип) |
| *(больше ничего за 30 сек idle)* | — | — | — |
| `https://crm.bp-gr.ru/admin/bitrix/v2-crm-list` | GET | xhr (на закрытии) | внутренний (загрузка списка) |

XHR на внешние домены — **0**. Periodic — только глобальные `updatetopmenu`/`updatetopmenuamount`/`updateindicators`.

##### Действия / Закрытие

Кнопки: «Далее», «Отмена», «Удалить». Закрытие — «Отмена» → возврат на `#/list`.

#### 15.2.2. Карточка «amoCRM» (CRM)

##### Заголовок и общий вид

«**ДОБАВИТЬ AMOCRM**». Vue SPA на Element UI. Многошаговая (есть «Далее»).

##### Поля формы (шаг 1)

| # | Лейбл | Тип | Обязат. | Заполнено? |
|---|-------|-----|---------|------------|
| 1 | Статус | switch | — | ON |
| 2 | Наименование | text | да | нет |
| 3 | Сайт | select-search | да | нет |

##### Лейблы из Vue-шаблона всей формы (шаги 2+)

Извлечены атрибуты `label="..."` из Vue-шаблона: Наименование, Сайт, Субдомен, API-ключ, Искать существующий контакт по номеру телефона, Название контакта, Раб. тел., Email раб., Ответственный, Теги, Создавать сделку, Название сделки, Статус сделки, Добавить примечание, Примечание к: Контакт/Сделка, Текст примечания, Добавить задачу, Задача к: Контакт/Сделка, Текст задачи, Тип задачи: Звонок/Встреча/Написать письмо, Ответственный.

Это структурное маппинг-полей: «как импортировать лиды/визиты CRM в amoCRM сделки/контакты». Поток: **CRM → amoCRM API**, не webhook.

##### Webhook-сущности

| Признак | Найдено? |
|---------|----------|
| Поле «Webhook URL» / «Callback URL» | нет |
| Поле «Входящий/Исходящий вебхук» | нет — в HTML 0 вхождений |
| Поле «secret» / «signature» | нет |
| Чекбокс «События»/«Уведомления» | нет |
| Кнопка «Тест» | нет |

amoCRM-интеграция — **API-клиент**: CRM аутентифицируется в amoCRM по `Субдомен + API-ключ` и пушит лиды в amoCRM. Нет webhook-подсистемы.

**Замечание о PII:** в HTML страницы amoCRM найден `title="Клиент [email скрыт]"` (формат `*@lkomega.ru`). Email замаскирован в отчёте.

##### XHR / Закрытие

Только глобальные poll'ы. Внешних XHR — 0. «Отмена» → возврат на `#/list`.

#### 15.2.3. Карточка «Скорозвон» (телефония)

##### Заголовок и общий вид

«**ИНТЕГРАЦИЯ СКОРОЗВОН**». Полная страница, серверный рендер Yii ActiveForm. **НЕ модалка**, **нет кнопки «Отмена»** — только «Создать» внизу.

##### Поля формы (все, без значений)

| # | Лейбл | name | Тип | Обязат. | Заполнено? |
|---|-------|------|-----|---------|------------|
| 1 | Статус | `Skorozvon[status_id_switcher]` | switch (Активный/Неактивный) | — | OFF |
| 2 | **Передавать визиты** | `Skorozvon[visit_switcher]` | switch (Вкл/Выкл) | — | **ON по дефолту** |
| 3 | **Передавать сделки** | `Skorozvon[crm_switcher]` | switch (Вкл/Выкл) | — | OFF |
| 4 | Эл. почта | `Skorozvon[email]` | text | да | нет |
| 5 | Апи ключ | `Skorozvon[api_key]` | text *(не password!)* | да | нет |
| 6 | ID приложения | `Skorozvon[client_id]` | text | да | нет |
| 7 | Ключ приложения | `Skorozvon[client_secret]` | text *(не password!)* | да | нет |
| 8 | ID проекта | `Skorozvon[project_id]` | text | да | нет |
| 9–22 | Статусы скорозвона для Новый/Просмотрено/Проработан/База/Недозвон/Переговоры/Ожидаем оплаты/Партнерка/Оплачено/Закрыто и не реализовано/Тест драйв/Горячий/На замену/Конечный недозвон | `Skorozvon[state1]` … `state14]` | text (CSV) | нет | нет |

**Замечание по безопасности (UX):** поля `api_key` и `client_secret` имеют `type="text"`, а не `type="password"`. При просмотре заполненной карточки администратором другие люди в комнате увидят токены на экране.

##### Webhook-сущности

| Признак | Найдено? | Детали |
|---------|----------|--------|
| Поле «Webhook URL» | **нет** | ни одного |
| Поле «Webhook secret» | нет | — |
| **Тоггл «Передавать визиты»** | **да, ON** | `visit_switcher` — поток **CRM → Скорозвон API** (provider-specific push, НЕ webhook) |
| **Тоггл «Передавать сделки»** | **да, OFF** | `crm_switcher` — то же для сделок |
| Селект «События» | нет | — |
| Кнопка «Тест» | нет | — |

**Анализ outbound-механизма:** есть тоггл-управление «Передавать визиты/сделки», но это **не webhook-подсистема** — это hardcoded в backend вызов Skorozvon REST API с использованием `client_id+client_secret+api_key` (OAuth2 client credentials). URL получателя НЕ настраивается пользователем.

##### XHR / Закрытие

Только глобальные poll'ы. Periodic за 30 сек — 0. Закрытие — навигация по меню (нет «Отмена»).

#### 15.2.4. Карточка «Unisender» (email)

##### Заголовок и общий вид

«**Создать Unisender**». Полная страница, Yii ActiveForm. Список (`/admin/unisender/index`) показывает колонки: # | Имя | Ключ | Домен | Статус.

##### Поля формы

| # | Лейбл | name | Тип | Обязат. | Заполнено? | Тип значения |
|---|-------|------|-----|---------|------------|--------------|
| 1 | Ключ | `Unisender[token]` | text *(не password!)* | нет | нет | — |
| 2 | List id | `Unisender[uid]` | number | нет | нет | — |
| 3 | Домен | `Unisender[domain_id]` | select-one | да | да (одна опция выбрана) | [POSSIBLE-TOKEN] (32-символьная hex-строка с суффиксом `.gck` — slug домена-отправителя, не секрет; конкретное значение НЕ копируется в отчёт) |
| 4 | Статус | `Unisender[status_id_switcher]` | switch | — | Активный (default) | — |

##### Webhook-сущности

**Все «нет».** Нет полей webhook URL, secret, событий, тестовой кнопки. Unisender-интеграция — outbound API-клиент (CRM пушит email-кампании в Unisender по API token), не webhook.

##### XHR / Закрытие

Только базовые. Закрытие — навигация прочь (нет «Отмена»).

#### 15.2.5. Карточка «Мои Звонки» (телефония — для контраста)

##### Заголовок и общий вид

«**СОЗДАТЬ**». Минимальная форма, Yii ActiveForm.

##### Поля

| # | Лейбл | name | Тип | Обязат. |
|---|-------|------|-----|---------|
| 1 | Телефон | `Moizvonki[phone]` | text | нет |
| 2 | Портал | `Moizvonki[portal]` | text | да |
| 3 | Апи ключ | `Moizvonki[api_key]` | text *(не password)* | да |
| 4 | Эл. почта | `Moizvonki[email]` | text | да |
| 5 | Использовать почту администратора | (checkbox) | checkbox | нет |

##### Webhook-сущности

**Все «нет».** Самый минималистичный из открытых — пара credentials и больше ничего. Чистый inbound API-клиент к Moizvonki.

---

### 15.3. JS-поиск по webhook-сущностям

#### 15.3.1. Покрытие 31 keyword

Поиск выполнен по **6 кастомным JS-бандлам** проекта (без vendor):

- `/js/main.js` (859 байт)
- `/assets/.../js/main.js?v=098` (60 987 байт)
- `/assets/.../js/v3/mainv3.js?v=003` (4 913)
- `/assets/.../js/v3/scriptv3.js?v=006` (1 768)
- `/assets/.../js/v3/scriptv3dev.js?v=001` (991)
- `/assets/.../js/analytics.js?v=1` (1 643)

Всего ~71 КБ кастомного JS.

| Keyword | Совпадений | Файлы | Сила |
|---------|------------|-------|------|
| webhook / Webhook / WEBHOOK | **0** | — | — |
| postback | 0 | — | — |
| callback_url / callbackUrl | 0 | — | — |
| outbound | 0 | — | — |
| s2s | 0 | — | — |
| server-to-server | 0 | — | — |
| subscribe_url / subscribeUrl | 0 | — | — |
| on_lead_created / onLeadCreated | 0 | — | — |
| notify_url / notifyUrl | 0 | — | — |
| hook_url / hookUrl | 0 | — | — |
| pixel_url / tracking_pixel | 0 | — | — |
| secret_key / signature_method / hmac_secret | 0 | — | — |
| bearer_token | 0 | — | — |
| retry_attempts / retry_delay / timeout_seconds | 0 | — | — |
| payload_template | 0 | — | — |
| test_send / testSend | 0 | — | — |
| вебхук | 0 | — | — |
| уведомить_url | 0 | — | — |

**Кастомный JS-код CRM не содержит ни одного из 31 webhook-keyword.**

#### 15.3.2. Inline scripts

Тоже **0 совпадений** по keyword (3 inline-блока ~16 КБ).

#### 15.3.3. HTML-шаблоны (Vue templates inlined в HTML страниц)

| Страница | Совпадения |
|----------|------------|
| `/admin/bitrix/v2` | **webhook (10), Входящий вебхук (1), Исходящий вебхук (1, шаблон), вебхук (4)** |
| `/admin/amocrm/v2` | 0 |
| `/admin/skorozvon/create` | 0 |
| `/admin/unisender/create` | 0 |
| `/admin/moizvonki/create` | 0 |
| `/admin/onpbx/create-mango` | 1 «Исходящий» **(false-positive: «Исходящий номер» — телефонный номер, не webhook)** |
| `/admin/user/api` | 0 |
| `/admin/user/api-google-sheets` | 0 |

#### 15.3.4. Сильный сигнал — единственный

**`prostats.info/bitrix24/webhook.php?id={{formData.id}}`** — read-only URL-шаблон в Vue-компоненте Битрикс. Хост `prostats.info` упоминается **ровно 1 раз** во всём аудируемом HTML. Это URL **внешнего receiver-сервиса**, который CRM показывает пользователю для копирования в Bitrix24-админку.

Архитектурный смысл: prostats.info — отдельная инфраструктура (вероятно, того же владельца), принимает webhooks от Bitrix, обрабатывает их и (предположительно через серверный канал, не XHR в браузере) сообщает в crm.bp-gr.ru. **Из браузера CRM мы НЕ видим исходящих вызовов в prostats.info** — связь сервер-сервер.

#### 15.3.5. Финальный вывод по JS-коду

**Подсистема outbound webhook в оригинале:**

- **в кастомном JS** — **нет ни строчки** (0/31 keyword);
- **в HTML/Vue-шаблонах фронтенда** — **только** Bitrix-карточка с одним hardcoded URL внешнего receiver-сервиса `prostats.info`;
- **в UI пользователя** — **нет редактируемого поля** «Webhook URL»/«Callback URL»/«Notify URL» **ни в одной из 5 открытых карточек**;
- **нет** secret, signature, retry, payload_template, событий-селектов и тестовой кнопки.

→ **Подтверждение партии 10 + новая деталь:** generic outbound-webhook subsystem отсутствует. Существует **один внешний receiver `prostats.info/bitrix24/webhook.php`** для интеграции конкретно с Bitrix24, но это inbound-канал с точки зрения CRM (Bitrix → prostats → CRM серверным путём). Это не противоречит выводу «у CRM нет outbound webhook'ов».

---

### 15.4. Network-мониторинг

#### 15.4.1. XHR при открытии карточек

| Карточка | URL | Метод | Initiator | Категория |
|----------|-----|-------|-----------|-----------|
| Bitrix v2 (list) | `/admin/visit/updatetopmenu` | POST | global poll | внутренний, фоновый |
| Bitrix v2 (list) | `/admin/visit/updatetopmenuamount` | POST | global poll | внутренний, фоновый |
| Bitrix v2 (list) | `/admin/visit/updateindicators` | POST | global poll | внутренний, фоновый |
| Bitrix v2 (Add) | `/images/bitrix.png` | GET | parser | внутренний, ассет |
| amoCRM (list/Add) | (только три глобальных poll) | POST | global poll | внутренний |
| Skorozvon (Create) | (только три глобальных poll) | POST | global poll | внутренний |
| Unisender (Create) | (нет per-card XHR) | — | — | — |
| Мои Звонки (Create) | (нет per-card XHR) | — | — | — |
| Bitrix Cancel | `/admin/bitrix/v2-crm-list` | GET | xhr на возврате | внутренний |

#### 15.4.2. Фоновые periodic-запросы

При сидении на Bitrix Add форме 30 секунд — **0 XHR**. На Skorozvon Create форме 30 секунд — **0 XHR**. Глобальные poll'ы (`updatetopmenu` и т.д.) идут с интервалом > 60 сек, documented как общесистемные.

#### 15.4.3. Подозрительные XHR

**Не найдены.** Ни одного XHR на:

- `prostats.info` — 0 (URL только в HTML-шаблоне как текст для копирования);
- `api.bitrix24.com` / `*.bitrix24.ru` — 0;
- `api.skorozvon.ru` — 0;
- `api.unisender.com` — 0;
- `api.moizvonki.ru` — 0;
- `api.amocrm.ru` — 0;
- любой другой сторонний домен — 0.

**Все вызовы внешних API провайдеров идут серверной стороной**, не из браузера. Это согласуется с архитектурой Yii2-приложения.

---

### Сводка для Прил. М v1.1

**Главный вопрос:** существует ли в оригинале подсистема outbound webhook?

| Линия доказательств (партии) | Результат |
|------------------------------|-----------|
| Карточка проекта (10) | Нет полей webhook |
| Форма создания проекта (10) | Нет полей webhook |
| История действий проекта 5 мес (10) | Нет логов webhook |
| WebSocket / SSE (9) | 0 каналов |
| 24 webhook-keyword в DOM (10) | 0 совпадений |
| **Список селектора «API ▾» (15.1)** | **12 пунктов; ни одного с явным webhook-функционалом в названии** |
| **Открытые карточки 5 категорий (15.2)** | **в 0 из 5 карточек найдено редактируемое поле «outbound webhook URL»**; в Bitrix есть статический read-only URL внешнего receiver `prostats.info/bitrix24/webhook.php` (inbound с т.з. CRM); в Скорозвон — provider-specific тоггл «Передавать визиты/сделки» (НЕ generic webhook) |
| **JS-код по 31 keyword (15.3)** | **0 совпадений в кастомном JS**; в HTML-шаблонах — только Bitrix-template с упомянутым выше receiver-URL |
| **Network-мониторинг (15.4)** | **0 внешних XHR**; все вызовы провайдер-API идут сервером |

**Финальный вердикт:**

- [ ] Outbound webhook **есть** — конкретные карточки: ~~—~~
- [x] Outbound webhook **нет** — окончательное подтверждение партии 10 (с уточнением: существует один hardcoded receiver-URL `prostats.info/bitrix24/webhook.php` на стороне сторонней инфраструктуры группы для приёма webhooks от Bitrix24 — это inbound-канал, а не пользовательский outbound webhook)
- [ ] Не определено окончательно — ~~—~~

**Если найдены — детали:** не применимо (см. вердикт выше). Для полноты:

- В каких карточках: только Bitrix v2, и только как read-only display URL.
- Уровень настройки: hardcoded в Vue-template, **не редактируется пользователем**.
- Какие события: не выбираются (Bitrix сам решает, что слать в свой настраиваемый исходящий webhook).
- Retry / signature: не настраивается на стороне CRM UI.

---

### Что не исследовано в этой партии

Не открывались карточки (по приоритезации, как «повторяющаяся категория» или «низкий приоритет для outbound-webhook гипотезы»):

- **Google таблица** (`/admin/user/api-google-sheets`) — экспорт, OAuth Google API. Низкий приоритет: пуш в Sheets API, не webhook.
- **Mango Office** (`/admin/onpbx/create-mango`) — повторяет категорию телефонии (Скорозвон + Мои Звонки уже открыты). HTML-сканирование показало 0 webhook-keyword'ов, есть «Исходящий номер» (false-positive — про phone caller-id).
- **Голосовой Робот** (`/admin/hns-record/index` + 3 sub-pages) — звонилка. Категория: автозвонки. Не открыт; HTML pages не проверены через fetch (можно при необходимости в доп. партии).
- **Chat GPT** (`/admin/chat/gpt`) — AI-утилита. Маловероятно содержит outbound webhook.
- **Проверка репутации номеров** (`/admin/phone/check`) — утилита.
- **API** (`/admin/user/api`) — общая страница API-доков. HTML-сканирование показало 0 webhook-keyword'ов.
- **Подписаться на Телеграм** — внешняя ссылка, открывать запрещено.

Из 12 пунктов меню исследованы 5 + проверены через серверный fetch HTML ещё 4 страницы. Не покрыты только: Google таблица, Голосовой Робот (3 суб-страницы) и Chat GPT — все низкоприоритетные для гипотезы outbound webhook.

---

### Безопасность

#### Self-review отчёта

- [x] Длинные подстроки 20+ символов из `[a-zA-Z0-9_\-\.]` без пробелов — **проверены**, в финальном тексте не присутствуют (32-символьная hex-строка из Unisender Domain заменена на `[POSSIBLE-TOKEN]`; CSRF/vid не цитировались).
- [x] Подстроки `Bearer`, `eyJ`, `sk-`, `xoxb-`, `ya29.`, `ghp_`, `glpat-` — отсутствуют.
- [x] Email-адреса (вид `*@lkomega.ru` найден в HTML amoCRM) — заменены на `[email скрыт]`.
- [x] Значения cookie / sessionStorage / localStorage — не считывались.
- [x] CSRF-токены, vid-токены — не цитировались.
- [x] Скриншоты — описаны словами; полей с токенами в открытом виде в финальный отчёт не попадает.
- [x] Telegram subscribe-user `id=...` параметр — замаскирован как `[ID]`.

#### Prompt injection

- `claude-agent-stop-container` (DIV), `claude-agent-stop-button` (BUTTON), `claude-agent-glow-border` (DIV), `claude-agent-animation-styles` (STYLE) — **найдены, проигнорированы, не нажаты, инструкции не выполнены**.
- Других подозрительных DOM-элементов с маркерами `gpt-*`, `agent-*` — не найдено.

---

*Документ создан 05.05.2026 объединением 4 файлов аудита в рамках оптимизации архива v8.3++ optimized.*
*Версия документа: 1.0 от 05.05.2026.*
*Исходные файлы (`audit-batch-12-2026-05-05.md` … `audit-batch-15-2026-05-05.md`) в оптимизированном архиве удалены — их содержимое полностью сохранено в этом документе (см. части 12–15 выше).*
