# Бот напоминаний о записи — шпаргалка

Медсанчасть 168. Бот в MAX и Telegram: при появлении новой записи в МИС «Медиалог»
пациенту уходит напоминание с 4 кнопками; нажатия меняют статус в МИС или
подключают оператора Bitrix24.

Сервер: `151.248.122.183`, каталог `/var/www/spa-bitrix.ru/msch/bot/`

---

## 1. Что делает

**Два сообщения на каждую запись** (крон, дедуп по `PLANNING_ID`):

| Когда | Сообщение | Кнопки |
|---|---|---|
| сразу при появлении брони | «Вы записаны на приём» | перенести · отменить · оператор |
| за 24 часа до приёма | «Напоминаем о записи» | **подтвердить** · перенести · отменить · оператор |

Если запись на процедуру с подготовкой (колоноскопия/ФКС, ФГДС, бронхоскопия, дуоденальное зондирование, УЗИ брюшной полости, рентген кишечника-пассаж) — **следом отдельным сообщением приходит памятка подготовки**.

**Действия кнопок:**

| Кнопка | Действие |
|---|---|
| Подтвердить запись | Медиалог `updateCall` → STATUS2 = подтверждена |
| Перенести запись | **Сделка в Б24**: воронка 82, стадия C82:NEW, ответственный 78 |
| Отменить запись | **Сделка в Б24** (та же воронка). В МИС бот не отменяет — заявку обрабатывает КЦ |
| Соединение с оператором | Сделка в Б24 (причина «Консультация») |

После нажатия кнопки убираются — повторно нажать нельзя.

**Привязка пациента.** MAX/TG не дают писать по номеру телефона — только тем, кто
открыл бота. Поэтому пациент один раз жмёт «Поделиться контактом», бот находит его
в Медиалоге по телефону и запоминает связку. Накопленные напоминания уходят сразу
в момент привязки.

---

## 2. Архитектура

Транспорт вынесен за интерфейс `Messenger` — бизнес-логика общая для MAX и Telegram.

```
ReminderBot ──> Messenger ──┬── MaxMessenger      (MaxApi)
                            └── TelegramMessenger (TelegramApi)
            ──> MedialogApi     (api_v2.php)
            ──> ReminderQueue   (очередь, дедуп, статусы)
            ──> PatientStore    (платформа+chat ↔ пациент)
            ──> OperatorHandoff (Bitrix24, линия своя для каждого мессенджера)
```

| Файл | Назначение |
|---|---|
| `config.php` | токены, адрес МИС, клиника/филиалы, Б24, параметры рассылки |
| `bootstrap.php` | сборка зависимостей, фабрика `makeBot('max'\|'tg')` |
| `queue_run.php` | **крон**: найти записи → очередь → отправить |
| `send_reminder.php` | ручная отправка + диагностика МИС |
| `cli.php` | управление ботами (`max:*`, `tg:*`) |
| `webhook.php` / `webhook_tg.php` | приём событий (прод) |
| `src/AppointmentMapper.php` | **вся завязка на поля МИС** (маппинг + фильтр) |
| `src/Preparations.php` | тексты подготовки к процедурам (матч по названию услуги) |
| `src/ReminderQueue.php` | очередь: дедуп, `pending/sent`, повторы |
| `ADD_TO_api_v2.txt` | SQL-метод, добавленный в `api_v2.php` Медиалога |

Файлы данных: `patients.json` (привязки), `queue.json` (очередь), `bot.log`,
`cron.log`, `webhook_error.log`.

---

## 3. Команды

```bash
cd /var/www/spa-bitrix.ru/msch/bot

# --- управление ботами ---
php cli.php max:me | max:subscribe | max:unsubscribe | max:subs | max:poll
php cli.php tg:me  | tg:setwebhook | tg:webhookinfo  | tg:delwebhook | tg:poll

# --- рассылка ---
php queue_run.php                     # прогон: sync + dispatch (это на кроне)
cat queue.json                        # что в очереди и с каким статусом

# --- диагностика МИС ---
php send_reminder.php bydate 2026-07-13 2026-07-20   # записи по дате приёма
php send_reminder.php dump <planning_id>             # сырая запись
php send_reminder.php findphone 79991234567          # поиск пациента по телефону
php send_reminder.php searchraw "89991234567"        # сырой searchPacients
php send_reminder.php appts <patients_id>            # активные записи пациента

# --- ручная отправка (тест без МИС) ---
php send_reminder.php manual max <chat_id> 500 "21.05.2026" "11:30" "гинеколога" "Иванов И. И."   # напоминание
php send_reminder.php manualbooking max <chat_id> 500 "21.05.2026" "11:30" "Приём лимфолога"      # «вы записаны»
php send_reminder.php <planning_id>                  # боевая по записи
```

---

## 4. Эксплуатация

**Поллер MAX (демон):**
```bash
systemctl status|stop|restart msch-bot-poll
journalctl -u msch-bot-poll -f
```

**Крон рассылки:**
```cron
* * * * * php /var/www/spa-bitrix.ru/msch/bot/queue_run.php >> /var/www/spa-bitrix.ru/msch/bot/cron.log 2>&1
```

**Два режима приёма событий — взаимоисключающие.** Либо long polling
(`max:poll`, демон), либо webhook (`max:subscribe`). Если поллинг пустой —
проверь `php cli.php max:subs`, при наличии подписки сделать `max:unsubscribe`.

**После обновления кода** — перезапустить поллер (`systemctl restart msch-bot-poll`),
иначе в памяти останется старая версия.

**Настройки рассылки** (`config.php`):
- `sync_days_ahead` — на сколько дней вперёд смотреть записи (30)
- `remind_before_hours` — за сколько часов до приёма слать напоминание (24)
- `remind_created_within_hours` — «свежесть» брони для сообщения «вы записаны»;
  при кроне раз в минуту ставить 1–2 (сейчас 48). На напоминание не влияет:
  в очередь попадают ВСЕ будущие записи, иначе старая бронь осталась бы без напоминания

---

## 5. Ключевые решения (почему так)

- **Источник записей — по дате приёма, не `getTodayAppointment`.** Последний
  оказался эфемерным фидом: после синхронизации с Б24 (`updateAppointmentB24`
  проставляет `idb24`) записи из него исчезают → гонка. Поэтому в `api_v2.php`
  добавлен метод `getAppointmentsByDate` (см. `ADD_TO_api_v2.txt`).
- **Поиск пациента устойчив к формату телефона.** В МИС номер лежит как
  `89772628707`; поиск идёт по нескольким вариантам (`7…`, `8…`, 10 цифр, `+7…`)
  со сверкой по последним 10 цифрам.
- **Гарантия доставки.** Статус `sent` ставится только после успешного ответа
  мессенджера; ошибка или непривязанный пациент → остаётся `pending` и повторится.
  `flock` не даёт двум кронам отправить дважды.
- **Формат времени МИС** `HEURE = "1815"` → 18:15.
- **Отмена/Перенос → сделка в Б24, не открытая линия.** По кнопке создаётся
  сделка в воронке КЦ (82, стадия C82:NEW, ответственный 78): контакт ищется по
  телефону (`crm.duplicate.findbycomm`) или создаётся (`crm.contact.add`), сделка —
  `crm.deal.add`, детали записи — в поле «Комментарий». Бот сам запись в МИС не
  отменяет — это делает оператор из сделки. Двухшаговое подтверждение убрано.
- **Два статуса на запись** (`booking_status`, `reminder_status`) — независимы,
  поэтому «вы записаны» и напоминание не мешают друг другу и не дублируются.
- **Фильтр записей:** только `STATUS=0`, не отменённые, дата в будущем.
  `CREATE_DATE_TIME` бывает пустым (например «Прямая запись (Продокторов)») —
  такие брони не теряются.

---

## 6. Известные ограничения / что доделать

| Тема | Статус |
|---|---|
| **Telegram** | Код готов и равноценен MAX. Долго был недоступен `api.telegram.org` с сервера; после разблокировки поднять демон `msch-bot-poll-tg` |
| **Webhook (прод)** | Сейчас работает long polling. Для вебхуков нужен **PHP 8.1+ под Apache** — сайт обслуживается PHP 7.x, код падает на синтаксисе. Решение: отдать папку бота php8.3-fpm через `SetHandler` |
| **Bitrix24** | По «Отмена»/«Перенос» создаётся сделка (воронка 82 / C82:NEW / отв. 78), контакт ищется по телефону или создаётся, детали — в комментарий. Нужен только `webhook` портала в `config.php`. Пустой webhook → заявка пишется в `bot.log` |
| **STATUS2 при подтверждении** | Значение в `config.php` → `medialog.status_confirmed`; уточнить точный код у ведущего Медиалог |
| **Отмена в МИС** | Метод `editStatus` реализован в `MedialogApi`, но ботом не вызывается (отмену делает оператор). Включается одной строкой, если потребуется |
| **ФИО врача** | В ответе МИС только `MEDECINS_ID`. В сообщении показываются услуга и филиал; резолв ФИО через `getAllDoctors` можно добавить |
| **Адреса филиалов** | Заполнить `clinic.branches` в `config.php` (Васхнил, Ленина и др.) |
| **WhatsApp** | По ТЗ через Wazzup24 (платно) — не реализован; добавляется как ещё одна реализация `Messenger` |

---

## 7. Траблшутинг

| Симптом | Причина / решение |
|---|---|
| `unexpected ':'` в вебхуке | Apache на PHP 7.x — нужен 8.1+ (см. раздел 6) |
| `SSL certificate problem` на MAX | нет сертификатов Минцифры → установить в систему (`INSTRUCTION.md`, разд. 10) |
| `max:poll` пустой | висит подписка на вебхук → `php cli.php max:unsubscribe` |
| `tg:poll` пустой | висит вебхук → `php cli.php tg:delwebhook` |
| `Operation timed out ... 0 bytes` | было при long polling, исправлено (curl-таймаут > серверного) |
| Пациент «не найден» при верном номере | проверить `php send_reminder.php findphone <номер>`; МИС доступен только из разрешённой сети |
| Запись не попала в рассылку | `php send_reminder.php dump <id>` → проверить `STATUS`(=0), `CANCELLED`, дату и окно `remind_created_within_hours` |
| Вебхук молчит | `php cli.php tg:webhookinfo` → поля `url`, `last_error_message`; ошибки бота — в `webhook_error.log` |

Логи: `bot.log` (события, МИС, handoff), `cron.log` (рассылка),
`webhook_error.log` (фаталы вебхука), `journalctl -u msch-bot-poll` (демон).

---

## 8. Развёртывание с нуля

```bash
cd /var/www/spa-bitrix.ru/msch/bot
tar xzf max-mis-bot.tar.gz            # полный архив (с config.php)
# обновление кода без потери токенов:
tar xzf max-mis-bot-update.tar.gz     # архив без config.php

php -r 'echo function_exists("curl_init")&&function_exists("mb_substr")?"OK\n":"нужны php-curl / php-mbstring\n";'
# заполнить config.php: токены MAX/TG, адрес Медиалога, клинику, Б24
php cli.php max:me                    # проверка токена
php queue_run.php                     # первый прогон
```

Требования: PHP 8.1+ (`curl`, `mbstring`), доступ к Медиалогу из разрешённой
сети/VPN, сертификаты Минцифры для MAX.

Подробнее: `README.md` (архитектура), `INSTRUCTION.md` (пошаговое тестирование),
`ADD_TO_api_v2.txt` (метод для Медиалога).
