# Покрытие экранов и состояний InfCRM Mobile

Этот документ сверяет UX-галерею с планом v0.73. Он отвечает на два разных вопроса:

1. Нужен ли состоянию отдельный макет, потому что меняются layout, решение пользователя или recovery action.
2. Достаточно ли варианта существующего компонента с другим локализованным текстом и server state.

Галерея содержит отдельный экран только в первом случае. Это сохраняет полноту, но не создаёт десятки визуально одинаковых страниц для каждого `Problem Details` code.

## 1. Авторизация и обязательный onboarding

| Сценарий | Экран | Покрытие |
|---|---:|---|
| Android fresh install, только Google | 01 | Отдельный platform layout |
| iOS fresh install, только Apple | 55 | Отдельный platform layout |
| Первое принятие Terms/Privacy | 56 | После provider login и до телефона |
| Просмотр Terms или Privacy | 57 | Локализованный document viewer с возвратом к согласию |
| Новая обязательная версия terms/privacy | 49 | Re-consent до club data |
| Условие 18+ | 56/57/49 | Входит в обязательные versioned Terms; отдельного экрана нет |
| Первый ввод телефона | 02 | Полный международный номер, автоматический флаг по +коду, без country picker и OTP |
| Возвращающийся пользователь | — | Guard пропускает уже выполненные шаги; нового layout нет |
| Истёкшая/revoked session | 38 | Повторный provider login |
| Версия/build ниже platform minimum | 50 | Forced update до login без back/dismiss; отдельная store-ссылка Android/iOS |
| Bootstrap недоступен при первом запуске | 58 | Независимый retry без ложного forced update и club context |

Порядок guard: login/session → legal acceptance → phone → каталог или fixed connection. Terms содержат возрастной пункт 18+.

## 2. Клуб, филиал и подключение карточки

| Сценарий | Экран | Покрытие |
|---|---:|---|
| Каталог сети/клуба | 03 | Universal build |
| Поиск клуба ничего не нашёл | 30 | Empty list сохраняет search и предлагает изменить запрос |
| Выбор одного из нескольких филиалов | 04 | Branch context до linking |
| Единственный филиал | — | Автоматический переход; отдельный экран был бы ложной остановкой |
| Fixed-connection build | — | Каталог 03 пропускается, используется тот же linking flow |
| Выбор proof или гостевого профиля | 05 | «Я ещё не клиент» отсутствует при effective `guestProfileCreation=false`; прямой `/guest` также закрыт |
| Ручной invitation code | 06 | Общий flow с QR claim; текущий profile phone показан read-only и не входит в request |
| Камера разрешена | 41 | Scanner без microphone permission |
| Камера denied/permanently denied | 42 | Settings и manual-code fallback |
| Legacy proof | 07 | Read-only profile phone, dynamic semantic label и authoritative currency; request содержит только proof data |
| Создание гостя | 08 | `PendingApproval`; ФИО и дата рождения вводятся, телефон read-only и подставляется BFF, а не mobile form |
| Успешное подключение | 09 | Краткая сводка результата |
| Phone mismatch/missing | 43 | Invitation не потребляется, номер CRM не раскрывается |
| Expired/used/revoked invitation | 43, вариант | Общий текст и запрос нового кода |
| Invitation другой connection в fixed build | 43, вариант | Нейтральная несовместимость без переключения закреплённой сборки |
| 10 ошибочных legacy attempts | 35 | `Retry-After` и invitation recovery |
| Другой клиент уже подключён в филиале | 29 | Явный replacement вместо второй identity |
| Лимит replacements | 29, вариант | Тот же layout без primary replacement action и с датой retry |
| Лимит создания гостей 5/7d | 08, вариант | Форма заменяется recoverable limit message; новый layout не нужен |
| Добавление другого филиала | 27–28 | Существующие links не удаляются |
| Unlink одной карточки | 52 | Последствия и scoped cache wipe |
| Context invariant нарушен | 39 | Fail closed и refresh/support |
| CRM-телефон ранее связанного клиента изменился | 39, вариант | Отозван только этот link; остальные клубы остаются доступны |

Экран 43 — семейство одного layout. Причина, текст и recovery action приходят из локальной таблицы по стабильному error code; BFF не присылает готовые пользовательские строки.

## 3. Телефон, язык, сессии и аккаунт

| Сценарий | Экран | Покрытие |
|---|---:|---|
| Профиль | 31 | Глобальный account отдельно от клуба |
| Помощь и контакты | 59 | Branch phone/global fallback, адрес, structured working hours и системная карта; без отдельного публичного support InfCRM |
| О приложении | 60 | Версия/build и ссылки на актуальные документы |
| Logout текущего устройства | 61 | Явное подтверждение и очистка account-scoped local data |
| Выбор `ru`/`en`/`uk` | 45 | `uk`, не `ua`; английский fallback |
| Смена телефона | 46 | Impact preview, fresh auth и unlink всех links |
| Лимит смены телефона 3/30d | 46, вариант | Поля read-only, показан следующий доступный срок |
| Тот же нормализованный номер | 46, вариант | No-op без unlink и расходования лимита |
| Управление server sessions | 54 | Отзыв одной или всех остальных sessions |
| Создание заявки на удаление | 32 | Границы BFF/CRM deletion |
| Статус и отмена заявки | 47 | `canCancel` до обработки |
| Заявка уже обрабатывается/завершена | 47, вариант | Cancel action скрыт, отображается текущий server status |

## 4. Абонементы и клиентская карта

| Сценарий | Экран | Покрытие |
|---|---:|---|
| Несколько абонементов и филиалов | 11 | Группировка по текущему и другим филиалам |
| Artwork вида абонемента | 10–12 | Один product asset используется в home/list/details; без него layout становится компактным без пустой рамки |
| Детали абонемента | 12 | Artwork, срок, остаток, филиалы и услуги |
| Разрывы и заморозки внутри сводного срока | 67 | Лениво загружаемый календарь года; `available`/`frozen`, отсутствующий день означает `unavailable` |
| Одноразовый абонемент без календарного срока | 12, вариант | Показывается «Без срока действия», действие перехода к календарю отсутствует |
| Ошибка или offline при загрузке другого года | 67, вариант | Тот же layout сохраняет уже загруженный год и показывает retry для запрошенного года |
| Нет активных абонементов | 51 | Подключённый клиент без commerce-обещаний |
| Общая client-level карта | 13 | Один provider value для нескольких абонементов |
| Client-card artwork | 13–15 | Только фон вокруг контрастной белой barcode/QR-зоны; fallback — branded gradient |
| Несколько разных membership values | 14 | Явный selector |
| Одинаковый membership value | 15 | Одна карта со списком абонементов |
| Provider не настроен | 16 | `not_configured`, без silent fallback |
| Provider value небезопасен/неоднозначен | 48 | `unavailable`, без раскрытия внутренних данных |
| Offline card разрешена | 34 | Только сохранённый static value с timestamp |
| Offline card запрещена/динамическая | 34, вариант | Card action disabled с требованием сети |

## 5. Расписание и booking lifecycle

| Сценарий | Экран | Покрытие |
|---|---:|---|
| Список опубликованных занятий | 17 | Дата, фильтры, места и group waitlist |
| Пустая дата/фильтр | 37 | Следующий доступный день и reset filters |
| Детали занятия | 18 | Правила и eligibility до mutation |
| Запись с подходящим абонементом | 19 | Явный client/membership context |
| Запись без абонемента | 20 | Только когда policy разрешает |
| Успешная подтверждённая запись | 21 | Server-confirmed result |
| Добавление групповой записи в календарь | 21/23 | Системная форма события открывается только по нажатию |
| Последнее место заняли конкурентно | 53 | `409`, refresh и waitlist recovery |
| Waitlist approval гостевой заявки | 44 | Не маскируется под confirmed, не резервирует место и отменяется как выход из очереди |
| Guest booking отклонена клубом | 44, вариант | Нет cancel action; причина/контакт клуба и переход в историю |
| Требующая approval заявка отменена клиентом | 22/44, вариант | Удаляется из active waitlist, approve после отмены невозможен |
| Список своих записей | 22 | Confirmed и waitlist; approval pending использует chip из 44 |
| Детали и обычная отмена | 23 | Deadline и последствия |
| Поздняя mobile-отмена | 24 | Связь с клубом, admin bypass сохраняется |
| Вступление в group waitlist | 25 | Предварительная позиция без обещания автоматического продвижения/push |
| Активный group waitlist | 62 | Authoritative position, статус и выход из очереди |
| Подтверждение выхода из group waitlist | 63 | Явная потеря позиции до destructive action |
| Выбор тренера и слота | 26 | Только при включённой capability; trainer-role UI сюда не относится |
| Подтверждение записи к тренеру | 64 | Тренер, услуга, слот, membership и отдельный deadline |
| Успешная запись к тренеру | 65 | Server-confirmed result и системный календарь |
| Детали/отмена записи к тренеру | 66 | Общий booking list, но отдельный service type и policy |

Waitlist относится только к group session. `BookingRequiresApproval` и `AfterNoShowPolicy=WaitlistOnly` создают `Waitlisted + approval pending` даже при свободных местах; до решения строка не занимает место и не получает обычную позицию. В trainer flow нет action вступления в очередь, позиции или статуса `Waitlisted`; при forced-waitlist guest policy персональная запись недоступна.

## 6. Общие состояния приложения

| Состояние | Экран | Покрытие |
|---|---:|---|
| Guest profile ждёт подтверждения | 33 | Доступные и будущие функции разделены |
| Offline с разрешённым cache | 34 | Stale timestamp и read-only actions |
| Offline/bootstrap без cache на первом запуске | 58 | Глобальный startup recovery без club chrome |
| Club API недоступен | 36 | Retry, другой клуб и обращение в клуб |
| Клуб заранее выключил mobile integration | — | Connection не попадает в каталог и mobile onboarding; отдельный пользовательский экран не нужен |
| Loading | 40 | Layout-compatible skeleton |
| Empty memberships | 51 | Отдельно от integration error |
| Forced update | 50 | Bootstrap-level blocking state по policy текущей платформы |

## 7. Что намеренно не входит в mobile-галерею v1

- Административные mobile-настройки, approval badge/actions внутри существующего листа ожидания, duplicate-phone warning и guest management проектируются в `admin-panel-ng`, а не как mobile screens. Отдельная вкладка pending bookings не нужна. Mobile-настройки открываются на самостоятельном SuperAdmin route `/admin/mobile-settings`: setup screen/CTA нет, defaults создаёт migration. При `MobileIntegration:Enabled=false` mobile-only surfaces скрыты; исторические external clients/waitlist approval entries остаются управляемыми.
- Ручной provisioning `ClubApiConnections` — maintenance/runbook, не пользовательский UI.
- Публичная web-страница удаления аккаунта — отдельный store-compliance артефакт.
- Notification center, bell, badges, push permissions и push delivery полностью отсутствуют в v1.
- Trainer-role screens, family/dependent profiles, покупка/продление, wallet passes и динамический rotating token относятся к post-v1.

## 8. Состояния, проверяемые без отдельного layout

- QR/deep link при закрытом/открытом приложении и сохранение pending route через login.
- Idempotent retry, duplicate mutation, cache invalidation и last-place transaction race.
- Auto-select одного филиала, fixed build и скрытие выключенных capabilities.
- Deployment gate клуба: выключенный до подключения connection не попадает в поиск. Если ранее доступный клуб перестал отвечать, пользователь получает общий экран 36 без раскрытия причины rollout.
- Неожиданно отсутствующий/повреждённый singleton при включённом gate для mobile выглядит как выключенная connection: partial configuration не показывается, а обычное сохранение заполненной default form в SuperAdmin settings восстанавливает строку.
- `ru`/`en`/`uk`, `uk-UA → uk`, unknown locale → `en`, длинные переводы и missing-key fallback.
- Timezone/DST, разные currency presentation variants и ISO fallback.
- Dynamic Type, screen reader, reduced motion, focus order и контраст.
- User switch, reinstall, unlink/replacement/phone change и account deletion cache wipe; logout layout покрыт экраном 61.

## 9. Результат последней сверки

После добавления экранов 56–67 отдельно покрыты первый legal consent и просмотр документа, bootstrap recovery, помощь/контакты, about/logout, полный waitlist lifecycle, полный client-side trainer booking lifecycle и календарная детализация срока абонемента. Известных v1 mobile-сценариев, которым нужен другой отдельный layout, сейчас не осталось; остаются перечисленные варианты существующих layout, admin/web артефакты и post-v1 возможности.

Эта формулировка не заменяет проверку реальных API contracts: после запуска BFF и экспорта автоматически сгенерированного OpenAPI матрицу нужно повторно сверить с фактическими statuses, `can*` flags и Problem Details codes.
