# UX-переходы InfCRM Mobile

## 1. Первый запуск и подключение карточки

```mermaid
flowchart TD
    A["01/55 · Вход"] --> L["56 · Принятие документов"]
    L --> V["57 · Просмотр документа"]
    V --> L
    L --> B["02 · Телефон"]
    B --> C["03 · Поиск клуба"]
    C --> D["04 · Выбор филиала"]
    D --> E["05 · Способ подключения"]
    E -->|"Код или QR"| F["06 · Приглашение"]
    E -->|"Данные клиента"| G["07 · Проверка по данным"]
    E -->|"Ещё не клиент"| H["08 · Гостевой профиль"]
    F --> I["09 · Успешное подключение"]
    G --> I
    H --> J["33 · Ожидание подтверждения"]
    I --> K["10 · Главная"]
    J --> K
```

Пользователь сначала создаёт мобильную identity, затем отдельно открывает и принимает актуальные legal-документы. Условие 18+ находится внутри обязательной versioned редакции Terms; отдельного возрастного экрана и age-specific состояния BFF нет. При изменении обязательной версии возвращающийся пользователь проходит экран 49 до загрузки данных клуба.

После legal flow пользователь вводит полный международный телефон вместе с `+кодом` страны. Флаг определяется автоматически по распознанному коду; отдельного country picker нет. Номер сохраняется в мобильном профиле и подставляется в дальнейшие способы подключения; повторно вводить его для каждого филиала не нужно. В v1 linking не читает CRM-дату рождения: условие 18+ остаётся только частью принятых Terms.

Во всех invitation/QR/legacy-proof/guest/replacement экранах этот телефон может только отображаться read-only. Inline edit отсутствует, а mobile request не содержит `phone`: BFF подставляет authenticated `Users.PhoneNormalized` и `PhoneVersion`. Чтобы изменить номер, пользователь выходит из подключения и проходит отдельный flow профиля с предупреждением об отвязке карточек.

На экране 03 выбирается организация или сеть, на 04 — конкретный филиал. Это разделение важно: один API connection может обслуживать сеть, но разные `ClubId` могут иметь разных клиентов и непереносимые абонементы.

Строка сети на экране 03 показывает optional compact-логотип через semantic BFF URL. Для клуба без логотипа или при ошибке загрузки используется initials fallback; клуб не исчезает из результатов и список не запрашивает remote configuration отдельно для каждой строки.

После выбора клуба global `club-cover` используется в hero главной, а `client-card-artwork` — только на цифровой карте. Изображение конкретного вида абонемента не берётся из branding: оно приходит в membership DTO и повторяется в home preview, списке и деталях этого продукта. Если какого-либо asset нет, соответствующий компонент использует градиент или компактный icon-layout без пустой картинки.

Экран 05 предлагает только effective возможности выбранного клуба. Карточка «Я ещё не клиент» вообще не рендерится и route `/guest` недоступен, если глобальная настройка `guestProfileCreation=false`. Флаг действует на все филиалы connection и запрещает только создание новых гостей: ранее созданные гостевые профили и записи не исчезают. Метки полей локализуются приложением по стабильным semantic keys (`clientNumber`, `barcode`, `phone`), а не приходят с сервера готовой строкой.

Настроенный card-value provider выбирает значение только для мобильной карты и mobile legacy linking. Он не меняет существующий поиск клиента или отмечание посещения в CRM: администратор продолжает пользоваться текущими полями и правилами независимо от mobile-настройки.

## 2. Повторное подключение другого филиала

```mermaid
flowchart LR
    A["10 · Главная"] --> B["27 · Выбор клуба/филиала"]
    B --> C{"Филиал уже подключён?"}
    C -->|"Да"| D["Переключить context"]
    C -->|"Нет"| E["05 · Подключить карточку"]
    E --> F["06 или 07"]
    F --> G["09 · Готово"]
    G --> D
```

В одном филиале пользователь не добавляет несколько `RemoteClientId`. Если найден другой человек, это не новая карточка рядом со старой, а контролируемый flow замены 29 с предупреждением о последствиях. Карточки разных филиалов одной сети допустимы и переключаются через 27.

## 3. Ежедневный сценарий

```mermaid
flowchart TD
    H["10 · Главная"] --> C["13 · Карта клиента"]
    H --> M["11 · Абонементы"]
    M --> MD["12 · Детали абонемента"]
    MD --> MA["67 · Доступность по дням"]
    H --> S["17 · Расписание"]
    H --> B["22 · Мои записи"]
    C --> CS{"Один код?"}
    CS -->|"Несколько разных"| C2["14 · Выбор кода"]
    CS -->|"Одинаковый"| C3["15 · Общая карта"]
    CS -->|"Не настроен"| C4["16 · Нет карты"]
```

Главная не пытается показать всё сразу. Она отвечает на четыре частых вопроса: «как пройти», «что у меня активно», «что дальше по расписанию» и «куда я записан».

Если у карточки несколько абонементов, экран 11 показывает каждый отдельно. На экране карты одинаковые значения штрихкода группируются; разные значения можно явно переключать. Название абонемента и филиал всегда видны рядом с кодом, чтобы пользователь не показывал неверный код на стойке.

Экран 12 показывает понятный сводный диапазон от первой до последней qualifying даты. Если между продажами есть разрыв или действует заморозка, пользователь открывает экран 67 и видит точное состояние каждого дня. Календарь не обещает возможность записаться: остаток посещений, подходящая услуга и правила клуба проверяются отдельно.

## 4. Запись на занятие

```mermaid
flowchart TD
    A["17 · Расписание"] --> B["18 · Детали занятия"]
    B --> C{"Есть места?"}
    C -->|"Да"| D{"Есть применимый абонемент?"}
    C -->|"Нет, group waitlist включён"| W["25 · Лист ожидания"]
    D -->|"Да"| E["19 · Подтверждение"]
    D -->|"Нет"| F["20 · Без абонемента"]
    E --> R{"Результат сервера"}
    R -->|"Confirmed"| G["21 · Успешная запись"]
    R -->|"Waitlisted · approval pending"| P["44 · Требует подтверждения"]
    R -->|"Последнее место заняли"| X["53 · Booking conflict"]
    G --> H["22 · Мои записи"]
    P --> H
    X --> W
    W --> W2["62 · Позиция в очереди"]
    W2 --> W3["63 · Выход из очереди"]
```

Применимость абонемента и право гостя записываться вычисляются сервером. Клиент показывает результат и причины, но не дублирует CRM business rules. Повторное нажатие защищено idempotency key; кнопка блокируется на время запроса.

Если места группового занятия закончились между открытием и подтверждением, пользователь остаётся на detail flow и получает конкретный выбор: вступить в лист ожидания или вернуться в расписание.

После обычного вступления экран 62 показывает authoritative position из CRM. Пользователь может открыть запись из общего списка 22 и выйти из очереди через подтверждение 63. Для `BookingRequiresApproval=true` и policy `WaitlistOnly` экран 44 сначала показывает group waitlist без позиции и резервирования места. После решения клуба заявка либо становится `Confirmed`, либо переходит в обычный одобренный waitlist с экраном 62.

## 5. Запись к тренеру

```mermaid
flowchart LR
    A["17 · Расписание"] --> B["26 · Тренер и слот"]
    B --> C["64 · Подтверждение"]
    C --> D["65 · Успешная запись"]
    D --> E["22 · Мои записи"]
    E --> F["66 · Детали тренировки"]
    F -->|"До deadline"| G["Отмена"]
    F -->|"После deadline"| H["24 · Обратиться в клуб"]
```

Индивидуальная запись использует общую booking-модель и общий список, но сохраняет тип услуги, выбранного тренера и собственный лимит отмены. Waitlist для trainer appointment в v1 отсутствует: показываются только доступные slots, а при `GuestAfterNoShowPolicy=WaitlistOnly` персональная запись гостю недоступна. На экранах 21, 23, 65 и 66 действие «Добавить в календарь» открывает системную форму события только после нажатия пользователя; фоновой синхронизации календаря в v1 нет.

## 6. Отмена записи

```mermaid
flowchart LR
    A["22 · Мои записи"] --> B["23 · Детали записи"]
    B --> C{"До лимита отмены?"}
    C -->|"Да"| D["Подтверждение отмены"]
    D --> E["Запись отменена"]
    C -->|"Нет"| F["24 · Поздняя отмена"]
    F --> G["Обратиться в клуб"]
```

Лимит задаётся администратором клуба в часах. До подтверждения приложение показывает точное локальное время, до которого отмена разрешена. После лимита mobile-клиент не обходит правило: действие блокируется, а пользователю показываются контакты клуба.

## 7. Гостевой профиль

Гость создаётся для конкретного филиала с ФИО, датой рождения и введённым в mobile profile телефоном. Дата нужна для обязательного существующего поля CRM-карточки, но отдельная age-проверка в v1 не выполняется. В v1 SMS/OTP-подтверждения нет. До подтверждения администратором:

- на главной отображается явный статус;
- расписание доступно для просмотра;
- запись доступна только при `allowGuestBooking = true`;
- запись, требующая решения клуба, сразу создаётся как `Waitlisted + approval pending`, не резервирует место и может быть отменена пользователем как выход из очереди;
- карта прохода не генерируется приложением;
- после подтверждения связь обновляется до обычной клиентской без повторной регистрации.

## 8. Профиль, помощь и выход

```mermaid
flowchart LR
    A["31 · Профиль"] --> B["59 · Контакты клуба"]
    A --> C["60 · О приложении"]
    A --> D["61 · Подтверждение выхода"]
    B --> E["Звонок, письмо или системная карта"]
    C --> F["57 · Terms / Privacy"]
    D --> G["01/55 · Вход"]
```

Все обычные обращения из мобильного приложения направляются в выбранный клуб. На экране 59 показываются данные именно выбранного филиала: его адрес, недельные рабочие часы и отдельный телефон; если branch phone отсутствует, используется общий телефон сети. Email остаётся общим контактом клуба. Если соответствующего значения нет, действие звонка, письма или карты скрывается, а не заменяется выдуманными данными.

Статус «Открыто сейчас»/«Закрыто» и сегодняшние интервалы вычисляются приложением из структурированного недельного графика и общей timezone CRM. Не настроенный график означает unknown и не отображается как «Закрыто». «Открыть в картах» появляется только при полной паре координат и запускает системное приложение карт; «Позвонить» использует canonical E.164. Эти действия не требуют Location или Contacts permission. На экране нет внутренних пояснений о границах поддержки. Если клубу требуется помощь InfCRM, он связывается с InfCRM вне клиентского приложения.

## 9. Ошибки и восстановление

| Ситуация | Экран | Поведение |
|---|---:|---|
| Первый запуск без bootstrap/cache | 58 | Показать глобальный retry без club context и без ложного требования обновиться |
| Нет сети | 34 | Показать сохранённые данные и возраст кэша; запретить серверные действия |
| 10 неуспешных проверок за сутки | 35 | Показать момент следующей попытки, не раскрывать, какое поле совпало |
| CRM клуба недоступна | 36 | Сохранить выбранный context, предложить retry и контакты клуба |
| Нет занятий | 37 | Не тупик: смена даты и сброс фильтров |
| Identity-сессия истекла | 38 | Повторный вход и возврат в безопасную точку сценария |
| Сменился client context | 39 | Не выполнять действие над старой карточкой; попросить обновить context |
| Загрузка | 40 | Skeleton повторяет геометрию главной и не создаёт скачков |
| Камера недоступна | 42 | Открыть системные настройки или продолжить ручным кодом |
| Ошибка invitation/phone/age | 43 | Выбрать recovery по стабильному error code без раскрытия PII |
| Последнее место заняли | 53 | Обновить расписание и предложить waitlist |
| Версия приложения устарела | 50 | Заблокировать protected flow и открыть магазин |

## 10. Deep-link и восстановление маршрута в v1

- QR/invitation link → `/invitation`, затем legal/phone guards и возврат к claim.
- Ссылка на конкретное занятие → `/session/:id` после проверки connection/branch context.
- Внутренний переход из «Моих записей» → `/booking/:id`.
- Требуется повторный вход → `/session-expired`, затем возврат только к ещё актуальному ресурсу.

Каждый deep link сначала восстанавливает `(ConnectionId, ClubId, RemoteClientId)`. Если такой context более недоступен, приложение показывает 39, а не открывает данные другой карточки.

Connection с заранее выключенной mobile capability не показывается новым пользователям в каталоге. Если ранее доступный клуб временно перестал отвечать, приложение использует общий экран 36 и не раскрывает пользователю внутреннюю причину недоступности.

В v1 нет push, notification center и notification entry points. После v1 уведомления смогут ссылаться на уже существующие routes, но это не меняет их v1-контракты.
