# Офлайн-карта проверки черновика CRM-цепочки

Начните с `scenario.txt`: там правила учебной цепочки и её границы. Затем откройте `inputs.json` и `check-map-blank.csv`. Для каждого события запишите действие, причину и номер условного сообщения; сравните со столбцами `expected_*`. Карту автоматического прогона заполняет `walkthrough.py r2 (offline)` в поле `checked_by`; расхождения перечисляются в `mismatch`. При ручном проходе укажите своё имя/обозначение. События внутри одного случая проходят по порядку, а каждый новый случай начинает с пустого журнала.

Это упражнение можно пройти без Python, аккаунта и подключения CRM. Столбцы `expected_*` — редакционное ожидание, а не вывод сервиса. Колонка `status` заполняется `pass`, только когда совпали действие, причина и номер сообщения; при отличии — `fail` с описанием расхождения.

## Автоматический повтор

Из распакованной папки, Python 3.9 или новее без установки зависимостей:

```sh
python3 walkthrough.py --out result
```

Выходные файлы: `actual.csv`, заполненная `check-map.csv`, сводка `summary.json`. Код 0 — все события совпали с заранее сохранёнными ожиданиями. Это согласованность нашей учебной модели и ожиданий, не тест RetailCRM. Код 1 — расхождение; ошибка входов — исключение, а не успешная пустая карта.

Данные: только вымышленные ID. Времена включают `+03:00`; ожидание проигрывается по отметкам времени, поэтому запуск не длится сутки. Никаких адресов, телефонов, токенов, API и отправки нет. Идентификаторы урока — не названия реальных полей CRM.

## Как читать результаты

| Действие | Значение |
|---|---|
| `accept` | Учебный вход принят, сообщений пока нет |
| `wait` | Срок ещё не наступил, намерение не создано |
| `skip` | Это повтор входа или шага, второе намерение не создаётся |
| `stop` | Нет продолжения этой цепочки; сообщение запрещено |
| `would_send` | Записано условное намерение, ничего не отправлено |

`M1` — напоминание без скидки; `M2` — предложение с ещё действующим учебным промокодом. После запрета последующий вызов шага может получить `run_closed`: это следствие предыдущей остановки, а не новая проверка оплаты или согласия.

## Четыре обязательные ветви

| Случай | Что изменяется во время цепочки | Ожидание |
|---|---|---|
| `paid` | Заказ оплачен после входа, до первого сообщения | Ни одного намерения сообщения, второй шаг тоже закрыт |
| `no-consent` | При входе нет согласия | Вход отклонён, сообщений нет |
| `repeat` | Повторный вход при ожидании и после завершения; повтор первого шага | Только M1 и M2 исходной цепочки, без дублей |
| `expired` | Срок промокода истекает между первым и вторым шагом | Только M1 без скидки, M2 запрещено |

Дополнительно сохранены разрешённая ветвь (чтобы не принять модель «всё запрещено»), отзыв согласия, точная граница срока, неизвестное согласие, раннее событие (в том числе с временно неподтверждённым согласием), задержка первого напоминания с переносом второго, пропущенный первый шаг и неизвестный срок промокода.

## Что не доказывает эта карта

- Что AI-помощник RetailCRM сгенерировал этот черновик: мы им не пользовались.
- Что в CRM доступны именно эти поля, условия, сроки и ключи повторов.
- Что реальные данные свежие, отправка законна, сообщения доставлены или выросли продажи.
- Что есть защита от конкурентной отправки, сбоев, возобновления процесса, повторов доставки и слишком частых контактов по разным корзинам.
- Что четыре отрицательные ветви заменяют проверку всего рабочего сценария.


Ошибочный момент события at (в том числе без часового пояса) прерывает запуск. Неизвестный срок промокода останавливает только соответствующую цепочку.

На Python 3.9 используйте времена с явным смещением +HH:MM, как во входах; суффикс Z на этой версии не поддержан.
