# Приложение М — Анализ оригинала crm.bp-gr.ru и архитектурные следствия для Лидерры (v8.3)
**Дата:** 05.05.2026 (расширено по итогам партий 12–15).
**Версия:** 1.1 (расширено по итогам параллельного аудита партий 12–15 от 05.05.2026).
**Адресат:** заказчик + CTO + backend + frontend + продакт.
**Источники данных:**
- `crm-bp-gr-audit-2026-05-04.md` — партии 1–11 (исходный аудит, не входит в архив v8.3++ optimized; хранится в предыдущей итерации).
- `audit-batch-12-2026-05-05.md` — Конверсия проектов + Напоминание + Досье.
- `audit-batch-13-2026-05-05.md` — Рекомендации источников + Настройки реестра + Просмотреть + Список звонков.
- `audit-batch-14-2026-05-05.md` — Редкие статусы + База знаний + Безопасность профиля.
- `audit-batch-15-2026-05-05.md` — Карточки внешних интеграций «API ▾».
> **⚠ Состояние на 05.05.2026 (v8.3++ optimized):** 4 файла `audit-batch-12..15-2026-05-05.md` объединены в единый документ **`Аудит_partii_12_15_originala_v8_3.md`** (части 12, 13, 14, 15). Содержимое полностью сохранено без изменений. Все ссылки в данном Прил. М вида «партия 12.2.4», «партия 15.3» и т. п. читать с учётом нового размещения: соответствующая «Часть N, раздел X.Y» в `Аудит_partii_12_15_originala_v8_3.md`.
**Назначение:** перевести наблюдения аудита в формальные архитектурные решения для платформы Лидерра. Закрыть пробелы между нашей моделью v8.3 и реальной структурой оригинала. Поднять перед заказчиком новые продуктовые вопросы, возникшие из аудита.
**История изменений:**
- v1.0 (04.05.2026) — исходный анализ по 11 партиям, 4 новых вопроса (Биз-10/11/12/13).
- v1.1 (05.05.2026) — добавлены результаты партий 12–15 (раздел 9), 3 новых вопроса (Биз-14/15/16), уточнения в §3 (новая модель `reminders`, расширение `suppliers` capabilities, новое поле `tenants.desired_daily_numbers` — §3.5), уточнение в §2.5 (Биз-10 переоткрыт), окончательный вердикт по outbound webhooks (§2.3).
---
## 0. TL;DR
Аудит crm.bp-gr.ru, проведённый в 15 партий за 04.05–05.05.2026 (11 исходных + 4 параллельные), подтвердил архитектурные решения Лидерры, обнаружил расхождения и выявил новые продуктовые вопросы.
**Главное расхождение №1:** в оригинале существует архитектурный слой **«Поставщик» (B1/B2/B3)** с **разными capabilities** для каждого (B1 — звонки/сайты, B2 — sms+sender_name+keyword, B3 — sms+только sender_name). У нас этого слоя не было — нужна таблица `suppliers` с полями `channel`, `supports_sender_name`, `supports_keyword` + связь `project_suppliers`.
**Главное расхождение №2:** в оригинале **дневной лимит проекта автоматически режется при нехватке баланса** (`effective_daily_limit = min(project.daily_limit, balance / lead_cost)`) — это бизнес-правило, без которого продуктовый паритет с оригиналом не достигается. У нас сейчас лимиты статичные.
**Главное расхождение №3 (новое в v1.1):** в оригинале множественные напоминания на сделку реализованы через массив `histories[].type='reminder'`, не через одиночное поле. У нас в schema v8.2 — поля `deals.reminder_text` + `deals.reminder_at` (одиночное). Нужна **отдельная таблица `reminders`** с FK на `deals` (поля минимального паритета: `text + remind_at + created_by + created_at`).
**Главное подтверждение:** в оригинале **исходящих webhook'ов нет** — окончательно, 7 независимых линий доказательств (партии 10 + 15). Уточнение: для Bitrix24 есть один hardcoded URL `prostats.info/bitrix24/webhook.php`, но это inbound-канал (Bitrix → prostats → CRM), не outbound CRM. Наш Уровень 1 (outbound webhook на MVP, OPEN-И-2) — это конкурентное преимущество.
**Безопасность оригинала (новое в v1.1):**
- В DOM каждой страницы — **подтверждённая prompt injection-атака** с явным таргетингом на агентов-Claude (партия 10, повторно подтверждено в 12/13/14/15). Источник не установлен. Действия Claude в Chrome корректные во всех 4 партиях.
- На странице профиля **пароль пользователя виден в HTML-форме plaintext** (`` — партия 14). У нас выделенная защищённая форма «старый/новый/подтверждение».
- В карточках интеграций **API-ключи и client_secret хранятся как ``** (Скорозвон, Unisender, Мои Звонки — партия 15). У нас должны быть `type="password"` + кнопка «показать».
- В оригинале **отсутствуют 2FA, журнал входов, список сессий, история паролей** (партия 14). У нас всё это есть в schema v8.2 (`auth_log`, `user_sessions`, `user_recovery_codes`) — это **сильное конкурентное преимущество**.
**3 новых продуктовых вопроса (Биз-14/15/16):** TTL удаления проектов, wizard рекомендаций источников, таргет `desired_daily_numbers`. Все с дефолтами, не блокеры. См. §9.5.
---
## 1. Контекст аудита
### 1.1. Что было сделано
Аудит провёл оператор Claude в Chrome в **read-only режиме** (без сохранений, удалений, изменений настроек). Аккаунт владельца с ролью админа. ПДн клиентов замаскированы.
**Аудит проведён в 2 захода — всего 15 партий:**
#### Заход 1 — 04.05.2026 (11 партий)
| № | Раздел | Что исследовано | Заметка |
|---|---|---|---|
| 1 | Каркас приложения | Меню, шапка, дашборд, реестр сделок, реестр проектов | Render error в первой версии 1.4 — переписан в 11 |
| 2 | Сделка и статусы | Карточка сделки, 14 статусов | Переписан в партии 2-bis (11) |
| 3 | API / Интеграции | Ingress endpoint, документация | — |
| 4 | Воронки и статусы | Персонализация воронок, kanban | — |
| 5 | Биллинг и тарифы | История транзакций, баланс | **Заглушка «Технические работы»** — не доступно |
| 6 | Профиль, безопасность, пользователи | Профиль, RBAC, magic-link | — |
| 7 | Аналитика и звонки | Отчёты, конверсия проектов, звонки | — |
| 8 | База знаний и задачи | Внешний ресурс / нет в меню | — |
| 9 | Технический срез | Стек, console, network | Vue 2 + Vuetify 1.5 + Element UI + Yii2 |
| **10** | **Карточка проекта (повторный заход)** | **3 типа карточек + форма создания + история действий за 5 месяцев** | **Раскрыта сущность «Поставщик»** |
| **11** | **Карточка сделки (повторный заход)** | **5 реальных сделок в 5 разных статусах** | **Подтверждена свободная state-machine** |
#### Заход 2 — 05.05.2026 (4 партии)
| № | Раздел | Что исследовано | Заметка |
|---|---|---|---|
| **12** | **Конверсия проектов + Напоминание + Досье** | Структура отчёта 17 колонок, форма напоминания (4 поля), 2 разные модалки «Досье» | **Биз-10 переоткрыт** — модель `histories[]` с массивом напоминаний |
| **13** | **Рекомендации источников + Настройки реестра + Просмотреть + Список звонков** | Wizard OSINT-разведки, аккордеон настроек, иконка «Просмотреть», агрегация звонков | **B1/B2/B3 жёстко подтверждены** — цитата из UI о capabilities; новое поле `desired_daily_numbers` |
| **14** | **Редкие статусы + KB + Безопасность профиля** | Косвенное подтверждение единой карточки во всех 14 статусах, внешняя KB HelpDeskEddy, /admin/user/account | **Ключевое:** в оригинале нет 2FA, нет auth_log, нет sessions — **наше преимущество**. Анти-паттерн: пароль plaintext в HTML. |
| **15** | **Карточки внешних интеграций «API ▾»** | 12 пунктов меню, 5 открытых карточек (Bitrix, amoCRM, Скорозвон, Unisender, Мои Звонки), JS-поиск 31 keyword, network-мониторинг | **Финальный вердикт:** outbound webhook'ов нет (7 линий доказательств). Только hardcoded receiver `prostats.info/bitrix24/webhook.php` (inbound). Анти-паттерн: API-key в `` |
### 1.2. Что НЕ исследовано (важно учитывать)
**После 15 партий пробелов осталось значительно меньше.** Закрыты партиями 12–15: форма «Напоминания» (партия 12), модалка «Досье» (партия 12), внешние интеграции «API ▾» (партия 15), безопасность профиля (партия 14). Косвенно подтверждены — единая структура карточки во всех 14 статусах (партия 14, через DOM фильтра).
**Остающиеся пробелы:**
1. **Биллинг полностью** — `/admin/site/pay-history` и `/admin/site/balance` отдают «Технические работы». Структуру транзакций, тарифной сетки, формы пополнения восстановить невозможно. **Следствие:** все наши решения по биллингу (§20–21) — не «реверс-инжиниринг», а наша собственная модель с оглядкой на видимый виджет «4 счётчика в шапке».
2. **Эмпирическая проверка карточки сделки в 9 редких статусах** (Новый, Оплачено, Горячий, На замену, Партнёрка, Ожидаем оплаты, Конечный недозвон, Тест драйв, База) — на текущем аккаунте 0 сделок во всех 14 статусах. Косвенное подтверждение единой структуры через DOM фильтра + партию 11 для 5 «частых» статусов. Окончательная эмпирическая проверка требует аккаунта с непустыми редкими статусами.
3. **Tab «Pr-cy» в модалке `visitdop`** — частично заблокирован render-fn, требует сделки с заполненным доп-разделом (`model.isVisitDop=true`). На MVP не реализуется (см. §9.1.4), поэтому пробел не критичен.
4. **Голосовой Робот (3 sub-страницы), Google Sheets, Chat GPT** — карточки не открыты в партии 15 как «низкоприоритетные для гипотезы outbound webhook». HTML серверным fetch проверен (0 webhook-keyword), что исключает основную гипотезу.
5. **Реальный TTL удалённых проектов** — нижняя граница ≥3 месяца (наблюдение 96 дней), верхняя не определяется без админ-доступа. Требует решения Биз-14.
### 1.3. Технические условия аудита
- **Outbound webhook поиск:** проверены 24 keyword'а (webhook, postback, колбэк, s2s, server-to-server, endpoint, ingress, вебхук, пиксел, signature, hmac, bearer, retry, timeout, payload, тестовая отправка и др.) — **ноль совпадений** в карточке проекта, форме создания, истории за 5 месяцев и 27 уникальных дат.
- **Real-time:** WebSocket / SSE — 0 каналов на всех исследованных страницах.
- **XHR:** 6 эндпоинтов в партии 10, все внутренние GET. Списки грузятся POST + CSRF.
- **Render error:** `TypeError: Cannot read properties of undefined (reading '0')` в Element UI повторяется 4× за каждую навигацию. Сломал отрисовку таблицы сделок в первой сессии — отсюда ошибочный вывод «0 сделок». **На самом деле в системе 12 876 сделок** (выборка `visit=rt`).
---
## 2. Главные архитектурные открытия
### 2.1. 🔴 Поставщики B1/B2/B3 — отдельная сущность над проектом
**Что было неясно до партии 10.** Префиксы `B1_`, `B2_`, `B3_` фигурировали в slug'ах статусов и тегах проектов с самого начала аудита, но смысл был непонятен. Гипотеза до аудита: «это просто префиксы названий».
**Что раскрыла партия 10.** B1/B2/B3 — это **три внутренних поставщика данных (источников номеров)** в оригинале, между которыми проект может выбирать:
- **B1** — основной поставщик для типов «Сайты» и «Звонки».
- **B2** — поддерживает «СМС» через связку «Наименование отправителя» + «Ключевое слово».
- **B3** — поддерживает «СМС» только по наименованию отправителя.
**Где видно.** В **форме создания нового проекта** B1/B2/B3 — это **три чекбокса (все checked по умолчанию)**. В **карточке существующего проекта** — readonly-индикаторы (см. партию 10.3, секция 3 структуры карточки).
**Что это означает для модели данных.**
Реальная цепочка не «Проект → Лид», а **«Поставщик → Проект → Лид»**, где у каждого поставщика свои правила приёма (тип данных, обязательные поля, формат payload).
**Чего у нас сейчас нет (schema.sql v8.1):**
- Нет таблицы `suppliers`.
- В `supplier_lead_costs` поле `supplier_code VARCHAR(50)` со значением по умолчанию `'crm_bp_gr'` — то есть **один абстрактный поставщик** для всей платформы.
- В `system_settings` константа `supplier_default_cost_rub` — одна цена для всех лидов.
- В `projects` нет связи с поставщиком вообще.
**Что нужно добавить (детально — §3.1).** Новая таблица `suppliers` (B1/B2/B3 как seed, расширяемо), связь m2m `project_suppliers` с per-supplier настройками, миграция `supplier_lead_costs.supplier_code` → `supplier_id BIGINT REFERENCES suppliers(id)`.
**Замечание.** Заказчик в реселлерской модели (Ю-2) покупает у crm.bp-gr.ru как у одного контрагента — то есть для нас **поставщик первого уровня = `crm_bp_gr`**, а B1/B2/B3 — это **«суб-поставщики» в рамках crm.bp-gr.ru**. Это важно для DDL: `suppliers.parent_id` или отдельный уровень `supplier_channels`. Решение в §3.1.
### 2.2. 🔴 Динамическая регулировка лимитов по балансу
**Что зафиксировано в партии 10.6.** В истории действий проекта (`/admin/visit/rt-clogs-load?id=`) обнаружена системная запись:
> Проект был запущен с лимитом N **по причине нехватки баланса**. По факту M
Это означает, что в оригинале **есть джоба планирования**, которая ежедневно (или при пополнении/списании) пересчитывает реальный лимит проекта по формуле:
```
effective_daily_limit = min(project.daily_limit, available_balance / lead_cost)
```
**Что у нас сейчас.** В schema.sql:
- В `projects` нет полей `daily_limit` или `effective_daily_limit`.
- Нет джобы планирования лимитов.
- Бизнес-логика автокоррекции не описана в narrative.
**Что нужно добавить (детально — §3.2).** Поля `projects.daily_limit_target` (что хочет клиент) и `projects.effective_daily_limit_today` (что реально на сегодня — кэш). Сервис `EffectiveLimitCalculator`, вызываемый при пополнении баланса, при создании проекта, и cron-ом в 00:00 МСК. Логирование автокоррекции в `activity_log` с типом `auto_limit_adjustment`.
### 2.3. 🟢 Подтверждено: исходящих webhook'ов в оригинале НЕТ (окончательно)
**Что было предположено до партии 10.** «Возможно, оригинал умеет отправлять лиды наружу через webhook — проверить надо отдельно».
**Что подтвердила партия 10 (заход 1).** Сильное «нет», по 6 независимым линиям доказательств. Оставался единственный пробел — карточки в шапке «API ▾».
**Что закрыла партия 15 (заход 2).** **Окончательное подтверждение** — карточки 5 интеграций (Bitrix24, amoCRM, Скорозвон, Unisender, Мои Звонки) из 12 пунктов меню детально открыты, плюс серверный HTML-fetch ещё 4 страниц. Ни в одной нет редактируемого поля «Webhook URL»/«Callback URL»/«Notify URL». Поиск по 31 webhook-keyword в 71 КБ кастомного JS — **0 совпадений**. Network-мониторинг — **0 внешних XHR**, все вызовы провайдер-API идут серверной стороной.
**Полный список доказательств (7 линий):**
1. 3 типа карточек проекта (Сайты / Звонки / СМС) — нет полей webhook (партия 10).
2. Форма создания нового проекта — нет полей webhook (партия 10).
3. История действий проекта за 5 месяцев и 27 уникальных дат изменений — нет логов webhook-параметров (партия 10).
4. DevTools Network — 6 XHR за сессию, все внутренние GET (партия 10).
5. WebSocket / SSE — 0 каналов (партия 9).
6. Поиск по 24 webhook-keywords в HTML — ноль совпадений (партия 10).
7. **5 открытых карточек интеграций + поиск 31 keyword в JS + network-мониторинг — 0 (партия 15).**
**Уточнение по партии 15.** Найден **один hardcoded URL** `prostats.info/bitrix24/webhook.php?id={{formData.id}}` в Vue-template карточки Bitrix24 — это **read-only текст** для копирования пользователем в админку Bitrix24. **Архитектурно это inbound-канал** для CRM: Bitrix24 постит туда события, prostats.info принимает и передаёт в crm.bp-gr.ru через серверную связь. То есть **с точки зрения CRM это endpoint для приёма, не для отправки** — что не противоречит выводу «исходящих webhook'ов нет».
**Природа интеграций оригинала.** В партии 15 структурно изучены все 5 открытых карточек:
- **CRM (Bitrix24, amoCRM):** outbound API-clients с токеном/secret. CRM пушит лиды в Bitrix/amoCRM по REST API.
- **Телефония (Скорозвон, Мои Звонки, Mango):** outbound API-clients с тогглами «Передавать визиты» / «Передавать сделки». Ключевые поля — `client_id+client_secret+api_key` (OAuth2 client credentials или basic auth). URL получателя НЕ настраивается — захардкожен в backend.
- **Email (Unisender):** outbound API-client с токеном.
**То есть архитектурный паттерн оригинала** — провайдер-специфичные backend-интеграции с переключателями ON/OFF, но **без** generic «push в любой webhook URL по выбору пользователя».
**Что это означает для нас.** Решение **OPEN-И-2 «Уровень 1 (outbound webhook) на MVP + amoCRM-коннектор в спринте 14–15»** — это **двойное конкурентное преимущество**:
1. **Generic webhook subsystem** (URL+secret+events+retry+test) — то, чего у оригинала **категорически нет**. У нас должно быть в продуктовой документации с явным указанием.
2. **amoCRM-коннектор** — паритет с одной из 12 интеграций оригинала, на которой клиенты завязаны. По полям (Субдомен, API-ключ, маппинг полей контакта/сделки/задачи) — структура из партии 15.2.2 — может быть прямым ориентиром.
### 2.4. 🔴 Карточка сделки всегда editable, нет state-machine переходов
**Что зафиксировано в партии 11.** Из 5 проверенных сделок в 5 разных статусах (Проработан, Недозвон, Просмотрено, Закрыто и не реализовано, Переговоры):
- Все поля `guest-block` editable, включая статус **«Закрыто и не реализовано»**.
- Селектор статуса в любой сделке показывает **все 14 значений** — никаких ограничений «из A нельзя в B».
- Структура карточки полностью идентична для всех 5 статусов — нет условных полей или условных действий.
- Возврат из терминальных статусов (Закрыто, Конечный недозвон) разрешён.
**Что у нас в narrative v8.3.** В §8 (Воронка продаж и статусы) явных запретов на переходы нет, но и явного разрешения «из любого в любой» — тоже. Это пробел: разработчик может реализовать ограничения «по интуиции», что разойдётся с оригиналом.
**Что нужно добавить.** Явная фиксация в §8: «переходы статусов сделки — свободные, без машины состояний; возврат из терминальных статусов разрешён». В Прил. В (State machines) — отдельный пункт «состояний deal'ов state-machine НЕТ — паритет с оригиналом».
### 2.5. 🔴 «Напоминания» — массив на сделку, минимальный паритет — отдельная таблица (Биз-10 уточнён)
**Что зафиксировано в партии 11.6.** В карточке сделки **нет ни секции задач, ни вкладки, ни кнопки «Создать задачу»**, ни полей ответственного/срока/типа/приоритета. При этом в фильтрах списка сделок (партия 1.4) задачи фигурируют («Дела на сегодня», «Просроченные», «Предстоящие», «Сделки без задач»).
**Гипотеза партии 11:** карточка «Напоминание» в левой колонке — это и есть «задача». Модель «1 напоминание = 1 deal», а не «массив задач».
**Что выяснила партия 12 (детальный аудит формы напоминания) — гипотеза переоткрыта:**
В оригинале реальная модель — **массив напоминаний** на сделку:
- В Vue-data сделки `model.histories[]` — массив всех событий, где **каждое напоминание = отдельная запись `{type:'reminder', time, time_dt, id, note, from, to}`**.
- В коде `model.histories.filter(h => h.type === 'reminder')` итерируется в шаблоне.
- Каждое напоминание удаляется индивидуально по `history.id`.
- Иконка `+` без ограничения «нельзя добавить второе».
- Эндпоинт `POST /admin/visit/visit-vm-delete` принимает индивидуальный id записи.
То есть **технически множество поддержано полностью**. Эмпирически в 5 проверенных сделках было ровно 1 или 0 напоминаний — UX-паттерн «обычно одно», но это не архитектурное ограничение.
**Поля минимального паритета (из формы партии 12.2.3):**
| Поле | Vue path | Тип | Обязательное | Что соответствует |
|---|---|---|---|---|
| Текст | `addReminder.note` | textarea, лимит 255 | нет | `text` в нашей модели |
| Дата | `addReminder.date` | el-date-picker | да | компонент `remind_at` |
| Час | `addReminder.hour` | el-select 0..23 | да | компонент `remind_at` |
| Минуты | `addReminder.min` | el-select 0..59 | да | компонент `remind_at` |
| `from` (автор) | `history.from` | login строка | автоматически | `created_by` в нашей модели |
| `to` (исполнитель) | `history.to` | null во всех проверенных | — | задел на `assignee_id`, не используется |
**Чего в форме оригинала НЕТ** (важно для решения о паритете):
- ❌ Ответственный (выбор менеджера) — поле `to` объявлено, но в UI редактирования не выведено.
- ❌ Тип/приоритет (low/med/high) — нет.
- ❌ Канал уведомления (email/push/sms) — нет.
- ❌ Повторение (одноразовое/еженедельно) — нет.
- ❌ Привязка к статусу (автосоздание при смене статуса) — нет.
**Что у нас сейчас в schema.sql v8.2.** В `deals` есть поля `reminder_text TEXT` и `reminder_at TIMESTAMPTZ`, индекс `idx_deals_reminder` для cron уведомлений. **Это не паритет — это упрощение.** Нужно мигрировать на отдельную таблицу `reminders`.
**Дашборд задач — бесплатный паритет.** В оригинале фильтры списка сделок принимают параметры:
- `?reminders=today` — «Дела на сегодня»
- `?reminders=last` — «Просроченные дела»
- `?reminders=future` — «Предстоящие дела»
- `?reminders=none` — «Сделки без задач»
Это значит, бэкенд оригинала умеет фильтровать `deals` по агрегатам `reminders`. У нас на нашем `GET /api/deals?reminders=today|last|future|none` это закрывается простым WHERE EXISTS subquery — отдельной сущности «дашборд задач» делать не нужно.
**Решение для Биз-10 (уточнено):**
- ✅ **Реализуем минимальный паритет** — отдельная таблица `reminders` с полями: `id, deal_id, tenant_id (для RLS), text VARCHAR(255), remind_at TIMESTAMPTZ, created_by FK→users, assignee_id FK→users NULL (задел, не используется в UI), completed_at TIMESTAMPTZ NULL, is_sent BOOLEAN, sent_at TIMESTAMPTZ NULL`. Без приоритетов / каналов / recurrence — это можно отложить в Post-MVP.
- ✅ **Удаляем поля `deals.reminder_text` и `deals.reminder_at`** — они теперь в новой таблице.
- ✅ **Дашборд задач** через существующий `/api/deals?reminders=...` — паритет через 1 query parameter, без отдельного экрана.
- 🟡 **Биз-10 переоткрыт:** заказчик подтверждает «массив напоминаний без приоритетов/каналов» (паритет с оригиналом, рекомендация Claude) или хочет «массив с приоритетами/каналами/исполнителями» (улучшение). Дефолт — паритет.
См. §3.X (новый раздел) — DDL для таблицы `reminders`.
### 2.6. 🟡 В карточке сделки нет файлов / вложений
**Что зафиксировано в партии 11.6.** Ни ``, ни секции «Документы», ни превью изображений ни в одной из 5 сделок.
**Объяснение из аудита:** «Лидген-CRM не работает с документооборотом — лид это телефон + статус + история, не сложный pipeline».
**Что у нас в narrative.** Раздела «вложения к сделке» в §7–11 нет.
**Открытый вопрос для нас.** Делаем ли мы паритет (вложений нет) или улучшение (вложения есть как Post-MVP)? Низкоприоритетное решение, можно оставить на Post-MVP по умолчанию.
### 2.7. 🟡 Телефония — внешняя интеграция, не наш модуль
**Что зафиксировано в партии 7.3 и 11.5.** Записи звонков хранятся **не на инфраструктуре crm.bp-gr.ru**, а на внешнем CDN провайдера телефонии **«Скорозвон»** (`oki.needcallbuy.ru/skrzvn/audio/YYYY/MM/DD/{recordId}.mp3`). CRM хранит только URL и метаданные (направление, оператор, timestamp, длительность через косвенный счётчик «Минуты»).
**Кнопок:**
- ✅ Встроенный браузерный плеер `