# MVP Spec 07 — Lead Submission (Buyer)

## Паспорт документа

- Статус документа: working spec
- Актуально на: 28 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: перед реализацией, при изменении domain rules или при смене source-of-truth по контракту
- Область применения: детальные feature-спеки для product backlog и реализации MVP/v1 направлений
- Связанные документы:
  - [Product roadmap и delivery checklist](../product-roadmap.md)
  - [Roadmap](../../project/roadmap.md)
  - [Карта API-маршрутов](../../architecture/api-routes.md)

> Version: MVP · Priority: P0 · Phase: B (Demand)
> Status: Draft v1

---

## 1. Контекст и цель

Lead Submission — ключевая точка монетизации платформы. Каждый лид — это заявка потенциального ученика (или его родителя) на пробный урок или запись на курс. Именно лиды являются основным продуктом, который Qadam продаёт продавцам.

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

**Ключевые продуктовые решения:**
- Гость (незарегистрированный пользователь) МОЖЕТ отправить лид — регистрация не требуется
- Если байер залогинен: имя и телефон предзаполнены из профиля
- Два типа лида: `trial` (пробный урок) и `buy` (запись на курс)
- После отправки: подтверждение с контактами продавца или «мы вам перезвоним»
- Лид привязывается к конкретному айтему И продавцу
- Каждый лид = 1 CPL-списание с баланса продавца (биллинг → Spec 16)
- Продавец получает уведомление через Telegram (→ Spec 10)

**Что не входит в этот модуль:**
- Статусная модель лида со стороны продавца (CRM) → Spec 09
- Telegram-уведомление продавцу → Spec 10
- Биллинг (CPL-списания) → Spec 16
- Публичная карточка айтема `/item/[slug]` → Spec 06
- Отзывы и рейтинги → Spec 11

---

## 2. Роли пользователей

| Роль | Действие в этом модуле |
|------|----------------------|
| **Гость** | Открывает LeadModal, заполняет имя/телефон вручную, отправляет лид |
| **Buyer (залогиненный)** | Открывает LeadModal с предзаполненными полями, подтверждает или редактирует, отправляет лид |
| **Seller / Seller Staff** | Не могут отправлять лиды на собственные айтемы (блокируется) |
| **Admin** | Видит все лиды в /admin, может менять статусы |

---

## 3. Use Cases

---

### UC-01: Гость отправляет лид (Happy Path)

**Актор:** Гость (незарегистрированный пользователь)
**Предусловие:** Айтем опубликован (moderation_status = active, item_isvisible = true)
**Триггер:** Гость хочет записаться на курс или попробовать пробный урок

**Полный поток:**

```
[Точка входа]
→ Пользователь находится на странице /item/[slug]
→ Видит кнопку "Записаться" (CTA, всегда видна на странице айтема)
→ Нажимает "Записаться"
→ Открывается LeadModal (модальное окно поверх страницы)

───────────────────────────────────────────────────────
LEAD MODAL — Структура и поля
───────────────────────────────────────────────────────
→ Заголовок: "Записаться на курс" (название айтема под заголовком, max 60 символов, обрезается с ...)
→ Переключатель типа заявки (обязательный, radio):
    ⦿ Пробный урок    ○ Записаться (полный курс)
  По умолчанию: Пробный урок (trial)
→ Поля:
    - Ваше имя *  (placeholder: "Иван Иванов")
    - Номер телефона * (placeholder: "+998 XX XXX-XX-XX", маска при вводе)
    - Email (необязательно, placeholder: "для подтверждения записи")
    - Комментарий (необязательно, placeholder: "Возраст ребёнка, пожелания по времени...")
      textarea, max 500 символов, счётчик символов
→ Внизу: кнопка "Отправить заявку"
→ Дисклеймер под кнопкой: "Нажимая кнопку, вы соглашаетесь с обработкой персональных данных"

───────────────────────────────────────────────────────
ОТПРАВКА
───────────────────────────────────────────────────────
→ Пользователь заполняет имя и телефон
→ Нажимает "Отправить заявку"
→ Кнопка переходит в состояние loading: spinner + "Отправляем..."
→ Система:
    1. Создаёт Lead {
         item_id: текущий айтем,
         seller_id: продавец айтема,
         buyer_account_id: null (гость),
         lead_name, lead_phone, lead_email, lead_comment,
         lead_type: trial | buy,
         lead_source: 'item_page',
         lead_status: 'new'
       }
    2. Запускает фоновую задачу: уведомление продавца в Telegram (→ Spec 10)
    3. Запускает фоновую задачу: CPL-списание с продавца (→ Spec 16)
→ Modal переключается в состояние SUCCESS

───────────────────────────────────────────────────────
ЭКРАН УСПЕХА (внутри того же модала)
───────────────────────────────────────────────────────
→ Иконка ✓ (зелёная)
→ Заголовок: "Заявка отправлена!"
→ Текст: "Менеджер {org_name} свяжется с вами в течение рабочего дня."
→ Если у продавца есть phone: показываем "Или позвоните сами: {contact_phone}"
→ Кнопка: "Закрыть"
→ При закрытии: модал закрывается, пользователь остаётся на странице /item/[slug]
```

---

### UC-01 — Альтернативные потоки и обработка ошибок

#### Ошибки валидации полей

**1a. Поле "Имя" пустое при нажатии "Отправить":**
```
UI-реакция:
→ Поле имени: красная обводка + красный значок ⚠ справа
→ Под полем: "Введите ваше имя"
→ Кнопка остаётся в нормальном состоянии (не loading)
→ Фокус переходит на поле имени
```

**1b. Поле "Телефон" пустое или неверный формат:**
```
UI-реакция:
→ Поле телефона: красная обводка + ⚠
→ Под полем (если пустое): "Введите номер телефона"
→ Под полем (если неверный формат): "Введите номер в формате +998 XX XXX-XX-XX"
→ Маска при вводе автоматически форматирует: +998 (__)___-__-__
```

**1c. Email введён, но неверный формат (on-blur валидация):**
```
UI-реакция:
→ Поле email: красная обводка + ⚠
→ Под полем: "Введите корректный email, например: name@mail.ru"
→ Кнопка "Отправить заявку" остаётся заблокированной до исправления
```

**1d. Комментарий превысил 500 символов:**
```
UI-реакция:
→ Счётчик под textarea: красный "507/500"
→ Поле textarea: красная обводка
→ Под полем: "Комментарий не должен превышать 500 символов"
→ Кнопка "Отправить заявку" недоступна
```

#### Серверные ошибки

**1e. Технический сбой (сеть/сервер недоступен):**
```
UI-реакция:
→ Кнопка выходит из состояния loading
→ Toast (красный) под модалом: "Не удалось отправить заявку. Проверьте интернет-соединение и попробуйте снова."
→ Все поля формы остаются заполненными — данные не сбрасываются
→ Кнопка снова активна
→ Если ошибка повторяется 2+ раз: добавляется "Написать напрямую: {contact_phone продавца}"
```

**1f. Сервер вернул 500 (неожиданная ошибка):**
```
UI-реакция:
→ Toast: "Что-то пошло не так. Попробуйте ещё раз или напишите нам: support@qadam.uz"
→ В консоль (dev): оригинальное сообщение ошибки
→ Данные формы не сбрасываются
```

---

### UC-02: Залогиненный байер отправляет лид (поля предзаполнены)

**Актор:** Buyer (авторизован, account_type = BUYER)
**Предусловие:** Байер авторизован через cookie, айтем опубликован
**Триггер:** Байер нажимает "Записаться" на странице айтема

**Полный поток:**

```
[Точка входа]
→ Байер находится на /item/[slug], авторизован (httpOnly cookie присутствует)
→ Нажимает "Записаться"
→ Открывается LeadModal

───────────────────────────────────────────────────────
ПРЕДЗАПОЛНЕНИЕ ПОЛЕЙ
───────────────────────────────────────────────────────
→ Система читает профиль байера (GET /api/v1/me/profile)
→ Поле "Ваше имя": предзаполнено "{first_name} {last_name}" из профиля Buyer
→ Поле "Номер телефона": предзаполнено из Account.phone
→ Поле "Email": предзаполнено из Account.email (если есть)
→ Рядом с предзаполненными полями: серый текст "из вашего профиля" (мелкий)
→ Все предзаполненные поля редактируемы — байер может изменить для этой конкретной заявки

───────────────────────────────────────────────────────
ВЫБОР ТИПА И ОТПРАВКА
───────────────────────────────────────────────────────
→ Байер выбирает тип заявки (trial / buy)
→ При необходимости редактирует или добавляет комментарий
→ Нажимает "Отправить заявку"
→ Система создаёт Lead с buyer_account_id = {текущий account_id}
→ Экран успеха — аналогично UC-01

───────────────────────────────────────────────────────
ОТЛИЧИЕ ОТ ГОСТЯ
───────────────────────────────────────────────────────
→ Лид сохраняется с buyer_account_id (не null)
→ Лид отображается в /me/leads байера
→ На странице успеха: дополнительная ссылка "Посмотреть мои заявки" (→ /me/leads)
```

---

### UC-02 — Альтернативные потоки

**2a. Профиль байера загружается с задержкой (медленная сеть):**
```
UI-реакция:
→ Поля имя/телефон показывают skeleton-loader (серые плейсхолдеры)
→ Кнопка "Отправить заявку" disabled пока данные загружаются
→ Если загрузка > 3 секунд: поля становятся пустыми и редактируемыми
→ Toast (жёлтый): "Не удалось загрузить данные профиля. Заполните вручную."
```

**2b. Байер пытается отправить лид на айтем своего же аккаунта (edge case):**
```
Поведение:
→ Это теоретически невозможно (у байера account_type = BUYER, айтемы создаёт SELLER)
→ Если один человек зарегистрирован и как байер и как продавец (multi-account):
    — Лид НЕ блокируется (продавец может тестировать свой флоу)
    — В системе создаётся лид с пометкой is_self_test: true (не учитывается в биллинге)
    — Продавец не получает уведомление в Telegram для self-test лидов
    → Внутри LeadModal: баннер (жёлтый) "Это ваш собственный курс. Заявка будет помечена как тест."
```

---

### UC-03: Байер повторно оставляет заявку на тот же айтем (дубликат)

**Актор:** Buyer (залогиненный) или Гость (с тем же телефоном)
**Предусловие:** Лид с таким buyer_account_id + item_id или phone + item_id уже существует
**Триггер:** Нажал "Записаться" повторно на том же айтеме

**Полный поток:**

```
[Точка входа]
→ Байер или гость открывает /item/[slug], нажимает "Записаться"
→ Открывается LeadModal

───────────────────────────────────────────────────────
ПРОВЕРКА ДУБЛИКАТА (для залогиненного байера)
───────────────────────────────────────────────────────
→ При открытии модала: GET /api/leads/check?item_id={item_id}
    — если существует лид с lead_status IN (new, contacted, enrolled):
      → Показываем баннер (синий/информационный) в верхней части модала:
        "Вы уже оставляли заявку на этот курс. Статус: {human_readable_status}.
         Отправить ещё одну заявку?"
      → Кнопка "Отправить ещё одну" (продолжает нормальный флоу)
      → Ссылка "Посмотреть мою заявку" → /me/leads
    — если нет активных лидов: нормальный флоу без баннера

───────────────────────────────────────────────────────
ОТПРАВКА ПОВТОРНОГО ЛИДА
───────────────────────────────────────────────────────
→ Байер нажимает "Отправить ещё одну"
→ Создаётся новый лид (повторный лид разрешён)
→ В Lead.lead_comment автоматически добавляется пометка: "[повторная заявка]"
→ CPL НЕ списывается за повторный лид на тот же айтем в течение 30 дней (→ Spec 16)
→ Экран успеха — стандартный
```

**3a. Гость повторно оставляет заявку с тем же телефоном:**
```
Поведение (серверная проверка при POST /api/leads):
→ Сервер проверяет: EXISTS(lead WHERE lead_phone = X AND item_id = Y AND created_at > now() - 24h)
→ Если дубликат обнаружен в течение 24 часов:
    → 200 (не ошибка!) с флагом { duplicate: true }
    → Фронтенд показывает экран успеха с текстом:
      "Заявка уже была отправлена. Менеджер {org_name} свяжется с вами в ближайшее время."
→ Дубликат не создаётся в БД, CPL не списывается
```

---

### UC-04: Айтем стал недоступен во время заполнения модала (race condition)

**Актор:** Любой пользователь (гость или байер)
**Предусловие:** Пользователь открыл LeadModal, затем айтем был снят с публикации/заблокирован
**Триггер:** Нажимает "Отправить заявку" — сервер возвращает ошибку

**Полный поток:**

```
[Сценарий]
→ Пользователь открыл страницу /item/[slug], айтем был виден
→ Открыл LeadModal, заполняет поля
→ В это время: продавец снял айтем с публикации (item_isvisible = false)
  ИЛИ: модератор отклонил айтем (moderation_status = rejected)
  ИЛИ: аккаунт продавца заблокирован
→ Пользователь нажимает "Отправить заявку"
→ Сервер отклоняет с 410 ITEM_UNAVAILABLE

UI-реакция:
→ Модал не закрывается
→ Над кнопкой появляется блок ошибки (красный):
    ⚠ "Этот курс больше не доступен для записи."
    "Возможно, набор завершён или курс временно приостановлен."
→ Кнопка "Отправить заявку" заменяется кнопкой "Смотреть похожие курсы"
    → нажатие закрывает модал и переводит на /catalog с предустановленным фильтром
      по subject_id айтема
→ Данные введённые пользователем НЕ используются — лид не создаётся
```

---

## 4. Бизнес-правила и валидации

### Таблица валидаций полей

| Поле | Правило | Ошибка пользователю |
|------|---------|-------------------|
| lead_name | 2–100 символов, обязательное | "Введите ваше имя" / "Имя: от 2 до 100 символов" |
| lead_phone | +998 + 9 цифр | "Введите номер в формате +998 XX XXX-XX-XX" |
| lead_email | RFC 5322, необязательное | "Введите корректный email, например: name@mail.ru" |
| lead_comment | max 500 символов, необязательное | "Комментарий не должен превышать 500 символов" |
| lead_type | trial / buy, обязательное (выбирается радиокнопкой) | — (UI не позволяет отправить без выбора) |

### Бизнес-правила

1. **Гость может отправить лид:** `buyer_account_id` nullable — это не ошибка
2. **Дубликат от залогиненного байера:** проверяется при открытии модала; повторная отправка разрешена, но с предупреждением
3. **Дубликат от гостя:** проверяется по `lead_phone + item_id` за последние 24 часа — дубликат не создаётся
4. **CPL за повторный лид:** не списывается если уже есть лид с тем же `buyer_account_id + item_id` за последние 30 дней
5. **Self-test лид:** если `buyer_account_id` принадлежит тому же аккаунту что и продавец — помечается `is_self_test = true`, CPL не списывается
6. **lead_source:** фиксируется автоматически; значение `item_page` для всех лидов из LeadModal на странице айтема
7. **Айтем должен быть доступен:** `item_isvisible = true AND moderation_status = active AND seller.account_status = active`
8. **Rate limiting:** не более 5 лидов с одного IP за 10 минут (защита от спама)
9. **Telegram-уведомление:** отправляется асинхронно — не блокирует ответ API
10. **Статус нового лида:** всегда `new` при создании; переходы статусов управляются продавцом через CRM (→ Spec 09)

---

## 5. Модель данных

> Используем существующие сущности Account, Item, Seller. Lead — новая сущность.

### Lead

| Атрибут | Тип | Описание |
|---------|-----|---------|
| lead_id | UUID | PK |
| item_id | UUID FK | → Item |
| seller_id | UUID FK | → Seller (денормализация для быстрых запросов) |
| buyer_account_id | UUID FK? | → Account, nullable (гость) |
| lead_name | string | 2–100 символов |
| lead_phone | string | +998XXXXXXXXX |
| lead_email | string? | Необязательный |
| lead_comment | string? | max 500 символов |
| lead_type | LeadType | trial / buy |
| lead_source | string | item_page / catalog / direct / api |
| lead_status | LeadStatus | new → contacted → enrolled → attended / no_show → purchased / not_purchased |
| special_offer_id | UUID FK? | → SpecialOffer, если лид создан по акции (→ Spec 02) |
| is_self_test | boolean | default: false; true если байер = продавец |
| is_duplicate_guest | boolean | default: false; служебный флаг |
| created_at | DateTime | |
| updated_at | DateTime | |

### LeadStatus (enum)

| Статус | Кто устанавливает | Что видит байер в /me/leads |
|--------|------------------|----------------------------|
| new | Система (при создании) | "Новая" |
| contacted | Продавец (CRM) | "Продавец связался" |
| enrolled | Продавец | "Вы записаны" |
| attended | Продавец | "Посещено" |
| no_show | Продавец | "Не пришли" |
| purchased | Продавец | "Куплено" |
| not_purchased | Продавец | "Не купили" |

---

## 6. Технические контракты

### 6.1 Prisma Schema

```prisma
enum LeadType {
  trial
  buy
}

enum LeadStatus {
  new               // только что создан
  contacted         // продавец связался с байером
  enrolled          // клиент записан на занятие
  attended          // пришёл на пробное занятие
  no_show           // не пришёл на пробное занятие
  purchased         // оплатил / оформил покупку
  not_purchased     // решил не покупать
  rejected          // продавец отклонил лид
}

model Lead {
  lead_id           String      @id @default(uuid())
  item_id           String
  seller_id         String
  buyer_account_id  String?
  lead_name         String      @db.VarChar(100)
  lead_phone        String
  lead_email        String?
  lead_comment      String?     @db.VarChar(500)
  lead_type         LeadType
  lead_source       String      @db.VarChar(50)
  lead_status       LeadStatus  @default(new)
  special_offer_id  String?
  is_self_test      Boolean     @default(false)
  is_duplicate_guest Boolean    @default(false)
  created_at        DateTime    @default(now())
  updated_at        DateTime    @updatedAt

  item          Item     @relation(fields: [item_id], references: [item_id])
  seller        Seller   @relation(fields: [seller_id], references: [seller_id])
  buyer_account Account? @relation(fields: [buyer_account_id], references: [account_id])

  @@index([item_id])
  @@index([seller_id])
  @@index([buyer_account_id])
  @@index([lead_phone, item_id])  // для проверки дубликатов гостей
}
```

### 6.2 TypeScript DTO

```typescript
// ─── Создание лида ────────────────────────────────────────────────────────

export class CreateLeadDto {
  @IsUUID()
  item_id: string

  @IsString() @MinLength(2) @MaxLength(100)
  lead_name: string

  @Matches(/^\+998\d{9}$/, { message: 'Введите номер в формате +998XXXXXXXXX' })
  lead_phone: string

  @IsOptional()
  @IsEmail({}, { message: 'Введите корректный email' })
  lead_email?: string

  @IsOptional() @IsString() @MaxLength(500)
  lead_comment?: string

  @IsEnum(LeadType)
  lead_type: LeadType

  @IsOptional()
  @IsUUID()
  special_offer_id?: string  // id акции если лид создан по акции
}

// ─── Проверка дубликата (для залогиненного байера) ────────────────────────

export interface LeadDuplicateCheckResponse {
  has_active_lead: boolean
  lead_status?: LeadStatus
  lead_id?: string
}

// ─── Ответ после создания лида ────────────────────────────────────────────

export interface CreateLeadResponse {
  lead_id: string
  duplicate: boolean          // true если гость-дубликат (24ч)
  seller_contact_phone: string | null
  seller_org_name: string
  message: string             // human-readable подтверждение
}

// ─── Список лидов байера (GET /api/v1/me/leads) ───────────────────────────

export interface BuyerLeadListItemDto {
  lead_id: string
  lead_type: LeadType
  lead_status: LeadStatus
  lead_status_label: string   // human-readable: "Продавец связался"
  item_id: string
  item_name: string
  item_slug: string
  seller_org_name: string
  created_at: string          // ISO 8601
}
```

### 6.3 API Endpoints

```
────────────────────────────────────────────────────────────────
СОЗДАНИЕ ЛИДА (публичный endpoint, авторизация не требуется)
────────────────────────────────────────────────────────────────

POST /api/leads
Auth: нет (но если cookie присутствует — читаем buyer_account_id)
Body: CreateLeadDto
→ 201: CreateLeadResponse
→ 410: { error: 'ITEM_UNAVAILABLE', message: 'Этот курс больше не доступен для записи.' }
→ 422: { errors: [{ field: string, message: string }] }
→ 429: { error: 'RATE_LIMIT', message: 'Слишком много заявок. Попробуйте через {retry_after} секунд.' }
→ 500: { error: 'INTERNAL_ERROR', message: 'Что-то пошло не так. Попробуйте позже.' }

────────────────────────────────────────────────────────────────
ПРОВЕРКА ДУБЛИКАТА (для залогиненного байера)
────────────────────────────────────────────────────────────────

GET /api/leads/check?item_id={item_id}
Auth: Bearer (buyer) — опциональный
→ 200: LeadDuplicateCheckResponse
→ 401: { error: 'UNAUTHORIZED' }  // если пытаются обойти авторизацию

────────────────────────────────────────────────────────────────
ЛИДЫ БАЙЕРА (личный кабинет)
────────────────────────────────────────────────────────────────

GET /api/v1/me/leads
Auth: Bearer (buyer)
Query params: ?page=1&limit=20&status=new,contacted (опционально)
→ 200: {
    data: BuyerLeadListItemDto[],
    pagination: { page: number, limit: number, total: number, total_pages: number }
  }
→ 401: { error: 'UNAUTHORIZED' }

GET /api/v1/me/leads/:lead_id
Auth: Bearer (buyer)
→ 200: BuyerLeadListItemDto & { lead_comment: string | null }
→ 401: { error: 'UNAUTHORIZED' }
→ 403: { error: 'FORBIDDEN' }  // лид принадлежит другому байеру
→ 404: { error: 'LEAD_NOT_FOUND' }
```

---

## 7. Edge Cases и обработка ошибок

| Сценарий | Поведение |
|----------|----------|
| Гость отправляет лид с телефоном уже зарегистрированного байера | Лид создаётся с `buyer_account_id = null`. Позже при логине байер не видит эти лиды — они не связываются ретроспективно (MVP ограничение) |
| Айтем принадлежит продавцу с `account_status = blocked` | POST /api/leads → 410 ITEM_UNAVAILABLE (проверяем статус продавца при создании лида) |
| Телефон передан без маски (например: "998901234567") | Backend нормализует: добавляет "+" если нет. Если не соответствует после нормализации — 422 |
| Параллельные запросы создания лида (double submit) | Идемпотентность на фронте: кнопка блокируется после первого нажатия. На бэке: уникальный индекс не предусмотрен (повторная отправка разрешена с задержкой) |
| Продавец не подключил Telegram (telegram_chat_id = null) | Лид создаётся нормально. Уведомление не отправляется. В CRM продавца лид появляется. Toast не показывается байеру — это внутренняя проблема продавца |
| special_offer_id передан, но акция истекла | Backend проверяет expires_at > now(). Если истекла: лид создаётся без special_offer_id, без ошибки (не блокируем отправку) |
| Пользователь закрывает модал в состоянии loading | Запрос не отменяется (fire-and-forget). Если успех придёт — можно игнорировать. Лид создаётся, но confirmtion не показывается |
| Браузер offline при нажатии "Отправить заявку" | fetch() упадёт с TypeError. Обрабатываем как сетевую ошибку (1e) |

---

## 8. TBD / Сознательно опущено

| Тема | Статус | Примечание |
|------|--------|-----------|
| Ретроспективная привязка гостевых лидов к байеру при логине | Исключено из MVP | Слишком сложно для MVP: нужно матчить по телефону при каждом логине. Добавить в v1.0 |
| Lead attribution (UTM, реферальные ссылки) | TBD | lead_source пока принимает статическое значение. Расширить до UTM-параметров в v1.0 |
| Уведомления байеру при смене статуса лида | Исключено из MVP | Байер видит статус в /me/leads. Push/email при смене — v1.0 |
| Лид из каталога (кнопка на карточке) | TBD | lead_source = 'catalog'. Флоу идентичен, но точка входа другая — задокументировать при реализации |
| Капча / анти-спам | TBD | Rate limiting (5 лидов/IP/10мин) — минимальная защита. hCaptcha добавить в v1.0 |
| Удаление лида байером | Исключено из MVP | Байер не может удалять лиды — только архивировать (v1.0) |
| Telegram-уведомление байеру (подтверждение) | Исключено из MVP | Байер получает подтверждение только в UI. Email/Telegram — v1.0 |
| Analytics события (лид отправлен, конверсия) | TBD | SAA-слой описывается отдельно |

---

## 9. Зависимости

| Модуль | Связь |
|--------|-------|
| **Spec 01** (Seller Onboarding) | Lead.seller_id → Seller; проверяем seller.account_status при создании лида |
| **Spec 02** (Item Management) | Lead.item_id → Item; проверяем item_isvisible + moderation_status |
| **Spec 06** (Public Item Page) | Кнопка "Записаться" и LeadModal рендерятся на /item/[slug] |
| **Spec 08** (Buyer Onboarding) | Lead.buyer_account_id → Account; предзаполнение полей из профиля байера |
| **Spec 09** (Seller CRM) | Продавец управляет статусами лидов через /seller/leads |
| **Spec 10** (Notifications) | Telegram-уведомление продавцу при новом лиде |
| **Spec 16** (Billing / CPL) | CPL-списание при создании лида; правила повторного лида |
