GortsUp Academy: своя LMS вместо 48 Excel-таблиц. Посещаемость, долги, отвал и оценки в одной системе
GortsUp Academy, академия профессий для взрослых, Армения
Исследование, архитектура, разработка и внедрение LMS
Академия вела учёт в связке из примерно 48 Excel-таблиц: посещаемость отмечали на следующий день, должникам звонили на третьем неоплаченном счёте, бэкапов не было. За неполные три месяца мы провели погружение, построили LMS на 27 рабочих экранов и 24 роли, связали её с книгами бухгалтерии, amoCRM, телефонией, Slack и Telegram. Отметка в день занятия выросла с 5-12% в июле до 94-100% с конца июля.
Карточка группы: доходимость и зона риска
Сетка посещаемости Д/Н
Кто не пришёл и кто должен
Карточка студента
Чек-лист онбординга, 14 шагов
Не распределённые: назначение группы
Навигатор: шесть показателей и полнота данных
Таблица групп: светофор, оплата, отток
Прозвон: автоочередь с приоритетом
Лестница долга по дням от даты платежа
Договоры: сверка LMS с книгой бухгалтерии
Календарь аудиторий из расписания
5% → 100%
занятий отмечено в день занятия: июль против августа и сентября
27
рабочих экранов под 24 роли вместо ≈48 Excel-таблиц
11 недель
от первого коммита 10 июля до 24 сентября
432
смерженных PR, каждый через тесты и AI-ревью
1 656
проверок логики на TypeScript и ещё 116 SQL-тестов на каждый PR
20 388
действий сотрудников в журнале аудита
?
Задача
Академии нужна была одна система вместо копирования между десятками листов. Цифры расходились: доходимость в разных таблицах считалась с разным знаменателем, у 40% студентов не был прикреплён договор, отказы и паузы никто не отслеживал как задачу. Бухгалтерия при этом должна была и дальше работать в своих Google-книгах. Руководство поставило условие: четыре показателя (доходимость, оплаты, оценки, отвал) система считает сама и показывает не проценты, а людей, с которыми нужно работать сегодня.
Решение
Неделю мы провели в интервью и разборе таблиц. Потом собрали пилот на одной группе и расширяли его по одному показателю: посещаемость, долги, отвал, оценки. База на Supabase забирает книги бухгалтерии каждые 10-30 минут и сверяет их построчно. Права лежат в базе, поэтому каждый сотрудник видит только свои экраны. Метрики считаются на лету. Каждый PR проходил тесты, ревью другой моделью и живую проверку на проде, а каждую функцию мы неделю наблюдали, прежде чем называть внедрённой.
Технологический стек
Слой
Технология
Версия
Задача
Frontend
Next.js + React
16.2 / 19.2
App Router, серверные компоненты, 27 экранов
Язык
TypeScript strict
5
Типы на всю бизнес-логику
Стили
Tailwind CSS
4
Светлая и тёмная тема
База
Supabase Postgres
Postgres 16
310 миграций, 247 функций, RLS на каждой таблице
Логика в базе
PL/pgSQL, SECURITY DEFINER
Права, деньги, расписание считаются в одном месте
Фоновые задачи
Supabase Edge Functions + pg_cron
Deno
15 функций: синки, отчёты, алерты
Вход
Supabase Auth
Почта и Google, доступ только сотрудникам академии
Интеграции
Google Sheets, amoCRM, Viva PBX, Slack, Telegram Bot API
Книги бухгалтерии, сделки, звонки, отчёты, алерты
Чат
Python + Pyrogram
MTProto
Переписка со студентами от аккаунта академии
Тесты
Vitest + SQL-тесты
1 656 / 116
Логика, права, деньги, дрейф схемы
CI
GitHub Actions
AI-ревью, changelog, db-guard, тесты на каждый PR
Хостинг
Vercel
Прод из main и превью на каждый PR
Как мы это построили
Все экраны ниже сняты с рабочей системы. Имена, телефоны, номера договоров, почты и первые буквы в аватарах мы закрасили, а суммы пересчитали скрытым коэффициентом. Реальных данных студентов на картинках нет.
1. Исходная точка: 48 таблиц и звонок на третьем долге
GortsUp Academy учит взрослых профессиям в нескольких городах Армении: бухгалтерия, маркетинг, продажи, аналитика, дизайн, HR, программирование. До нас академия жила в связке примерно из 48 Excel-таблиц, у части из них больше сотни листов. Данные между «Планом запусков», «Реестром курсов», «Календарём» и «Учётом посещаемости» переносили руками через копирование и вставку.
Посещаемость отмечали на следующий день, по памяти. Должникам звонили, когда у человека копилось два-три неоплаченных счёта, а на этом этапе студенту проще уйти, чем платить. У 40% студентов в старой системе не был прикреплён договор. Бэкапов не делал никто. Однажды ключ к Google API без лимита списал 2000 долларов за сутки.
≈48
Excel-таблиц в связке до LMS
40%
студентов без привязанного договора
0
бэкапов данных
2-3 счёта
долга, когда начинали звонить
«Человек ходит и платит, всё отлично. Как только перестаёт ходить или платить, что-то не то, реагировать уже на первой просрочке». CEO академии, 9 июля
2. Неделя погружения до первой строчки кода
Первую неделю мы не писали код. Мы поговорили с людьми, которые каждый день работают в таблицах, разобрали сами таблицы и посмотрели, как ту же задачу решают готовые LMS. Каждое интервью записывали и расшифровывали, каждую вводную заносили в журнал дословно. К концу проекта в этом журнале набралось больше 400 записей.
12+
интервью по ролям: контроль качества, методология, лекторы, продажи, запуски, бухгалтерия
8
письменных анкет после разговоров
139
скриншотов пяти LMS разобрали на паттерны
250+
источников в ресёрче мировых практик учёта посещаемости
1
Интервью и анкеты
Контроль качества, отдел заботы, методолог, координатор лекторов, руководитель запусков, продажи, маркетинг, бухгалтерия. Отдельно две большие встречи с CEO и сооснователем.
2
Разбор таблиц
Мы нарисовали схему связей между листами и нашли, где одна и та же цифра считается по-разному. Доходимость в разных листах имела разный знаменатель.
3
Референсы
HolliHop, Vimbox, OneTap, Jibble и AccuClass. Из 112 паттернов интерфейса взяли 23, ещё 37 адаптировали.
4
Реестр фич
38 пунктов из ресёрча превратились в 89 после интервью. По MoSCoW в первую очередь попали 25.
3. Четыре показателя вместо списка хотелок
На встрече 9 июля руководство академии назвало три цифры, ради которых строится система: доходимость, оплаты и оценки. 16 июля к ним добавили четвёртую, отвал. Сооснователь сформулировал правило работы: брать один показатель и доводить всю цепочку до рабочего состояния, прежде чем браться за следующий. Каждую задачу проекта мы потом оценивали по тому, какой из четырёх показателей она двигает.
1
Доходимость
Отметка на занятии, а не на следующий день. Знаменатель берёт только состоявшиеся занятия. Руководство смотрит на список не пришедших, а не на процент пришедших.
2
Оплаты
Кто не заплатил, когда звонить, звонил ли менеджер и о чём договорились. Реакция на первой просрочке.
3
Оценки
Оценка курса и лектора сразу после занятия, по QR-коду на армянском, без регистрации.
4
Отвал
Четыре среза оттока и ранний сигнал: серия пропусков или просроченный платёж поднимают студента в работу.
«Для меня важно не то, что пришло 80%, а что 20% не пришло». CEO академии, 10 июля
4. Архитектура: таблицы остаются, LMS забирает из них правду
Бухгалтерия продолжает вести договоры и график платежей в своих Google-книгах, это её рабочий инструмент. Мы не стали ломать процесс. Синхронизации каждые 10-30 минут забирают книги в базу, сверяют их построчно и останавливаются, если лист пришёл неполным. Всё остальное LMS считает сама и раздаёт людям в интерфейс, Slack и Telegram.
Источники
Книга договоров
Google Sheets бухгалтерии: договоры, оплаты, списания
График платежей
Google Sheets, лист счетов
amoCRM
Сделки и карточки клиентов
АТС Viva
Звонки менеджеров через REST API
Telegram
Переписка и привязка аккаунтов
Синхронизация
15 edge-функций
Синки, отчёты, алерты по расписанию
Сторожа синков
Лист пришёл неполным, замена отменяется
Чат-воркер
Python и Pyrogram, аккаунт академии в Telegram
База
Supabase Postgres
94 таблицы, 247 SQL-функций, 17 вьюх
Права в базе
Роль видит только свои разделы, RLS на каждой таблице
Журнал действий
Кто, что и когда поменял
Продукт
Веб-приложение
Next.js 16, 27 рабочих экранов
24 роли
У каждой свой набор разделов
Публичные страницы
Оценка занятия и доходимость для лектора по ссылке
Каналы
Slack
Сводки полноты данных, отчёт дня, отчёт сборов
Telegram
Алерты «кому звонить», автопост в чаты групп
Google Sheets
Обратная запись отметок в старый журнал
Решение
Почему
LMS в отдельном проекте базы, а не схемой рядом с маркетингом
В основной базе живут лиды и 200 полей продаж. Продукту они не нужны, а сломать синк CRM легко
Бэкапы и лимиты на все API с первого дня
Бэкапов не было, а ключ без лимита уже стоил академии 2000 долларов
Паспортные данные не храним
Армения движется к требованиям уровня GDPR, лишние ПДн сейчас дешевле не собирать
Метрики только на лету, никаких сохранённых процентов
Поправили отметку задним числом, цифра пересчиталась сама
Веб-приложение вместо нативного
Нативное имеет смысл после 500-1000 активных пользователей
Биометрию для контроля явки не используем
Риск штрафа за ПДн выше пользы, контроль строим на расписании и QR
5. Проводник по системе: 27 экранов
Меню не зашито в код. Разделы и права лежат в базе, и каждый сотрудник видит только свой набор экранов. Ниже все рабочие разделы, сгруппированные по показателям. Там, где стоит ролик, мы разобрали экран: курсор идёт по элементам, а рамки показывают, что делает каждый блок.
Кто пришёл на занятие, кто нет и кто за это отвечает. Отметку ставят в день занятия, проценты система считает сама.
Группы
/
Кто работает: админ точки, модераторы, отдел заботы, руководство
Стартовый экран: все группы академии по направлениям и жизненному циклу. Админ точки видит плашки «сегодня отметить», «прошлые без отметок» и «долгов нет» по своим группам.
Фильтр по 13 направлениям
Активные, завершённые и архив
Создание группы вместе с расписанием
Разделы по одному: как сделано, изюминка, сложности, решения
Ниже каждый раздел отдельно. Данные студентов на снимках закрашены.
Доходимость
Доходимость
Группы
/
Кто работает: админ точки, модераторы, отдел заботы, руководство
Как сделано
Список всех групп академии строится из групп и их модулей, фильтруется по 13 направлениям и жизненному циклу: активные, завершённые, архив. Для админа точки над списком висят плашки «сегодня отметить», «прошлые без отметок» и «долгов нет» по его группам.
Изюминка
Форма создания группы сразу строит расписание. Состав группы она не создаёт никогда: студент попадает в группу только из книги договоров, иначе в LMS снова появились бы «ничьи» студенты.
Что было сложно
Число занятий при создании группы сначала бралось по умолчанию, 24 плюс одно. У настоящего курса бухгалтерии их 49, и прогноз освобождения аудитории уезжал на полгода раньше. Теперь при генерации расписания потока число занятий обязательно вводят руками, а форма группы прямо подписывает, сколько занятий создаст.
Решения и споры
CEO, 6 июля: единица учёта академии это группа, не поток и не подгруппа.
Сортировка групп по направлению пришла 22 июля, вторым PR проекта, ещё до ролевой модели разделов.
Доходимость
Карточка группы
/groups/[id]
Кто работает: все, у кого есть доступ к группам
Как сделано
Плитки «студентов», «прошло модулей», «отметок» и доходимость группы против цели академии считаются при каждом открытии страницы из отметок и статусов. Ни одного сохранённого процента в базе нет.
Изюминка
Доходимость показывается двумя цифрами: без учёта отвала и со всеми. Руководство видит и дисциплину тех, кто учится, и цену тех, кто ушёл.
Что было сложно
Статусы «пауза» и «отвал» спорили с живыми отметками: у 13 студентов ярлык говорил «ушёл», а сетка показывала «пришёл». Сначала ввели правило «факт сильнее ярлыка»: живая отметка возвращала студента в учёбу. С 26 августа его сузили: отметка возвращает только из отвала до старта и только если в книге нет списания.
Решения и споры
CEO, 10 июля: знаменатель доходимости берёт только состоявшиеся занятия.
Сооснователь, 24 июля: пауза считалась отвалом с возвратом. 14 августа он упразднил паузу как класс, 26 августа запрет встал в базу.
Сооснователь и CEO, 4 августа: отвал до старта курса не портит доходимость группы.
Доходимость
Сетка посещаемости
/groups/[id]
Кто работает: админ точки, модераторы
Как сделано
Матрица Д/Н по всем модулям группы. Отметку ставит одна серверная функция с проверкой прав и окна занятия. Пока академия работала параллельно в Excel, каждая отметка уходила обратно в старый Google-журнал.
Изюминка
У каждой отметки есть время постановки. По нему видно, отметили занятие вживую или залили пачкой задним числом. Флаг «прогноз» такого различия не давал.
Что было сложно
Окно отметки. Слишком узкое злило админов («уже закрыто, когда заходим»), слишком широкое превращало учёт обратно в «заполню завтра». Дедлайн отметки поставили через несколько часов после начала занятия, а саму отметку можно поставить до конца дня. На встрече это оценили: «удобнее, чем было».
Решения и споры
CEO, 9 июля: доходимость в лайве единственный источник правды, заполнение на следующий день не работает.
28 июля лектор публично оспорил цифру группы. Разбор по времени отметок показал 161 отметку, залитую из Excel за секунды, и дал протокол таких споров.
Доходимость и оплаты
Не пришли и должники группы
/groups/[id]
Кто работает: админ точки, отдел заботы
Как сделано
Под сеткой список не пришедших на последнее занятие: причина, телефон и комментарий в одной строке. Ниже должники группы с днями просрочки и гипотеза потерь от отвала для студентов в зоне риска.
Изюминка
Тех, с кем уже работает кейс-менеджер, система убирает из обзвона. Двум людям не нужно звонить одному студенту с разными вопросами.
Что было сложно
Потенциальный убыток легко принять за факт. Блок прямо подписан как гипотеза и показывает правило расчёта: серия пропусков или доходимость ниже порога.
Решения и споры
Сооснователь, 18 июля: просрочка наступает в день платежа, одно определение на всю систему.
Доходимость
Отчёт дня
/day-report
Кто работает: админ точки
Как сделано
Один отчёт за день по всем группам админа. Заполненность система считает из сетки. После сдачи отчёт уходит в Slack, а в Telegram-каналы групп до 7 сентября уходил пост на армянском: занятие проведено, пришло столько-то, оцените модуль.
Изюминка
Каждые 15 минут функция проверяет динамический дедлайн и шлёт админу алерт в Telegram, если отчёт не сдан.
Что было сложно
В сентябре автопост студентам пришлось аварийно остановить одной миграцией. Старые дыры в отчётах мы не стали наказывать задним числом: объявили амнистию, и спрос начался с 20 августа.
Решения и споры
CEO, 10 июля: оплата админа и лектора привязана к сдаче доходимости как отчёта, а не к проценту заполненности.
Пост в каналы групп печатал только агрегат, без фамилий: канал публичный.
Доходимость
Расписание
/schedule
Кто работает: методисты, координаторы лекторов, руководители запусков
Как сделано
Все занятия с правкой лектора, аудитории, даты и статуса видеозаписи. Каждая операция над расписанием живёт отдельной функцией в базе: добавить, удалить, перенести со сдвигом хвоста, отменить, перенумеровать по датам.
Изюминка
Перед переносом модуля экран показывает предпросмотр: какие занятия сдвинутся и на какие даты. Один мульти-фильтр работает на четырёх экранах сразу.
Что было сложно
Перенос одного занятия оставлял дыру в середине курса, а не в хвосте. Отмена занятия тянет за собой нумерацию, зарплату лектора и знаменатель доходимости, поэтому каждую операцию закрыли SQL-тестом.
Решения и споры
CEO, 10 июля: дата модуля считается от старта потока, перенос только вперёд, у съёмки свой крайний срок.
Руководитель запусков дал ТЗ на единый фильтр расписания, мы собрали его одним компонентом.
Доходимость
События недели
/events
Кто работает: команда контента, лекторы на чтение
Как сделано
Недельная сетка занятий. Отсюда управляют расписанием и одной кнопкой копируют текст расписания в чаты групп.
Изюминка
Пятый смерженный PR проекта, в первые сутки PR-процесса: доска контент-производства по просьбе CEO. К августу она выросла в главную точку управления расписанием.
Что было сложно
Доска научилась различать четыре типа модуля: занятие, экзамен, пересдачу и выпускной. От типа зависит, когда студенту звонят с опросом.
Решения и споры
CEO, 31 августа: три дополнительных типа модуля, экзамен, пересдача и выпускной.
Занятость аудиторий по дням и слотам. Календарь ничего не хранит и строится из расписания, поэтому не расходится с ним.
Изюминка
Аудиторию ставят кликом в любом месте интерфейса. Аудитория привязана к занятию, а не к группе, и конфликты расписания видны сразу.
Что было сложно
CEO попросил задавать реальную дату окончания группы, иначе распределение аудиторий «может оказаться ложным». Разбор показал главную причину: у двух групп расписание сгенерировали на 25 занятий вместо 49, и календарь обещал освободить аудиторию осенью вместо весны.
Решения и споры
Ручную дату окончания завели, но только вторым мнением рядом с расчётной. Сооснователь 26 августа запретил подменять ею расчёт: чинить нужно длину курса.
Реестр курсов с чек-листом готовности запуска и панелью потоков. Рядом экран стартов: что вот-вот запускается и набралось ли людей.
Изюминка
Курсы перестали заводить SQL-скриптами. Коды курсов перевели на трёхбуквенные: A·15 стал ACC·15.
Что было сложно
Дату окончания курса каждая функция считала своей копией одной формулы, у правила не было имени. Правило «окончание равно последнему модулю» закрепили в базе одной функцией, и этот PR дважды упирался в лимит в пять кругов AI-ревью.
Решения и споры
Дату старта потока меняют в LMS. Сразу всплыли 18 потоков без расписания, которые раньше никто не видел.
Отвал
Запуски
/launches
Кто работает: менеджеры запусков
Как сделано
Список тех, у кого заканчивается пауза: связаться и вернуть человека в поток. Строки без даты возврата стоят сверху, их нужно разобрать первыми.
Изюминка
До отмены паузы запись без даты возврата в базу не проходила. Страж стоял в самой базе, и миграция при выкатке проверяла, что он ловит нарушение.
Что было сложно
Правило нарушили 12 реальных студентов ещё до стража: без даты напоминание о возврате не сработало бы никогда. В августе паузу упразднили совсем, и экран теперь разбирает старые паузы.
Решения и споры
Напоминание о конце паузы уходит двумя каналами: автору статуса и команде запусков.
Доходимость
Лекторы
/lecturers
Кто работает: координаторы лекторов, методисты
Как сделано
CRM лекторов из шести вкладок: база со ставками по курсам, расчёт зарплаты по факту проведённых занятий, дашборд, отпуска с заменами, обратная связь и заявки от кандидатов.
Изюминка
Зарплата считается из расписания автоматически. Любая корректировка требует причину, и её видно в журнале.
Что было сложно
Роль лектора только читает расписание, а обратную связь лектор сдаёт в отдельной форме. На той же базе мы собрали отдельный сайт подбора лекторов с воронкой кандидатов.
Решения и споры
Координатор лекторов в интервью назвал зарплаты лекторов одной из главных болей. Расчёт зарплаты мы закрыли первым делом, 27 июля.
Оплаты
Оплаты
Сборы
/payments
Кто работает: менеджеры по оплатам, отдел заботы, бухгалтерия на чтение
Очередь должников по стадиям и возрасту долга и отчёт сбора оплат.
Изюминка
Единая лента коммуникаций. Звонки, сообщения, договорённости и заявления по должнику лежат в одной ленте, а не в шести разных списках.
Что было сложно
Счета без привязки к студенту однажды склеились в одного «должника», и отчёт сборов вырос вдвое при том же числе людей. Сейчас непривязанный и списанный долг идут отдельными строками, а к отчёту приложен чеклист доверия к цифрам.
Решения и споры
Сооснователь, 12 августа: любая коммуникация отражается лентой событий, как в CRM, одна точка входа.
Сооснователь, 25 августа: провёл одну границу, какие должники уходят к продажам, а какие остаются сборам.
Оплаты
Скоро платёж
/payments/soon
Кто работает: руководство, модераторы
Как сделано
Узкий срез: только те, у кого платёж на подходе, с карточкой CRM, телефоном и формой комментария.
Изюминка
Отдельный маршрут, а не вкладка: срез открывается одной ссылкой и не тянет за собой всю очередь сборов.
Что было сложно
Раздел запустили для админа точки, а через два дня право у этой роли сняли. Экран остался у руководства и модераторов.
Решения и споры
Напомнить до просрочки дешевле, чем звонить после: тот самый принцип «реагировать на первой просрочке».
Оплаты
Лестница долга
/debt-ladder
Кто работает: менеджер по долгам, отдел заботы, бухгалтерия
Как сделано
Лента дел на сегодня по ступеням от даты платежа: напоминание до срока, касание в день оплаты, звонок с первой просрочки. У каждой ступени свой канал. Касание, заявка и вопрос бухгалтерии живут прямо в строке.
Изюминка
Раздел за девять дней собрала команда заказчика в нашем конвейере. Она взяла готовые ленту коммуникаций, согласования и роли и прошла те же тесты и ревью.
Что было сложно
Один из дней между ступенями сначала не попал ни в одну, и клиент на сутки пропадал с экрана. Ступень сделали паузой, а не пропуском.
Решения и споры
Деньги, пришедшие, но ещё не разнесённые бухгалтерией, видны менеджеру сразу, над лентой.
Оплаты и отвал
Прозвон
/calls
Кто работает: отдел заботы, менеджеры
Как сделано
Автоматическая очередь: кто не пришёл на последнее занятие и кто должен. Звонок фиксируется тремя слоями: канал, дозвонились ли и чем закончился разговор.
Изюминка
Запись разговора из АТС слушается прямо в строке очереди. К контакту можно приложить файл: скрин оплаты или документ.
Что было сложно
Списанный долг продолжал звать менеджеров на звонок. Мы убрали его из очереди, и прозвон перестал тратить время на тех, кого бухгалтерия уже закрыла.
Решения и споры
Сооснователь, 6 августа: новые статусы и исходы звонка согласуются только с ним. «Это система, а не личные заметки менеджера».
Построчная сверка LMS с книгой договоров бухгалтерии: девять цифр «в LMS, в книге, расхождение». Клик по цифре фильтрует список. Отдельная страница сравнивает списанный долг.
Изюминка
Деньги двух книг мы сверили построчно: 1 560 договоров из 1 560 совпали по оплатам, долгу и списаниям.
Что было сложно
Месяц LMS сверялась с производной книгой, которая сама копирует первоисточник. У 144 договоров из 2 001 код потока потерял букву курса, ещё у 117 был пуст, и матчинг молча терял 261 договор.
Решения и споры
Сооснователь, 7 августа: источник правды по деньгам и отказам это книга договоров, а не её копия.
Сооснователь, 7 августа: отказ определяет заполненная сумма списания, ручному статусу доверия меньше.
Оплаты
Согласования
/approvals
Кто работает: отдел заботы, бухгалтерия, директор
Как сделано
Заявления на отказ и перенос срока платежа идут цепочкой подписей. У каждой роли ровно одно действие, порядок проверяет база. Счётчик в меню показывает то, что ждёт именно тебя.
Изюминка
Раздел построили по плану из пронумерованных этапов, от нулевого до четвёртого, и все этапы влили за четыре дня.
Что было сложно
Раньше заморозка студента одной транзакцией ставила статус и сдвигала график платежей. Технически удобно, по договору недопустимо. Сцепку разобрали на заявление и подписи.
Решения и споры
Сооснователь, 6 августа: дату платежа нельзя менять одним вызовом без согласования CEO или руководителя, это прописано в договоре.
Отвал и путь клиента
Отвал
Онбординг
/onboarding
Кто работает: менеджер онбординга
Как сделано
Новые клиенты, которые ещё не были ни на одном занятии: подтвердить группу, добавить в канал, собрать фото, пройти анкету. Каждая отметка спрашивает подтверждение, фото лежит в приватном хранилище.
Изюминка
Без первой оплаты группу назначить нельзя. Система не пускает в поток того, кто ещё не стал клиентом по деньгам.
Что было сложно
Через неделю после запуска очередь выросла до 169 человек, а отметок чек-листа было ноль. Мы записали это как «не внедрено» и пошли выяснять процесс, а не дописывать код.
Решения и споры
Клиента можно снять с онбординга с исходом «не требуется» или «отказ от курса», вместо вечной строки в очереди.
Отвал
Чек-лист онбординга
/onboarding-checklist
Кто работает: менеджер онбординга
Как сделано
Весь путь клиента от договора до первого занятия в 14 шагов со ссылками и напоминаниями. Ниже порога готовности клиент попадает в красную зону.
Изюминка
Первые два шага, подтверждение курса и потока, открывают выбор группы. Пока они не закрыты, группу выбрать не из чего.
Что было сложно
Контур собрала команда заказчика на основе нашего онбординга. Порог красной зоны вынесли из кода в настройку.
Решения и споры
На каждый шаг одно напоминание и срок на ответ, потом система отмечает, что срок вышел.
Отвал
Не распределённые
/unassigned
Кто работает: продажи, модераторы
Как сделано
Договоры из книги без группы. Три очереди: обязаны быть в группе, без первой оплаты, долг списан. Поток из amoCRM подставляет вероятную группу.
Изюминка
Раздел начинался как заплатка «куда деть договор без группы» и стал рабочим местом отдела продаж.
Что было сложно
Назначение группы сверяется с книгой. Если поток в книге и в LMS расходится, экран не молчит, а показывает, почему группу подобрать не вышло.
Решения и споры
Роль «Продажи» видит только рабочие разделы, и SQL-тест проверяет, что денег, групп и карточек студентов у неё нет.
Все четыре
Карточка студента
/students/[id]
Кто работает: отдел заботы, руководство, бухгалтерия на чтение
Как сделано
Одна точка вместо охоты по листам: статус, доходимость, реквизиты, договоры и сканы, платежи, звонки, карточка amoCRM и лента коммуникаций. Открывается страницей и выезжающей панелью с любого экрана.
Изюминка
Кнопка отказа в карточке отсутствует. Отказ фиксирует книга договоров, LMS только зеркалит его, а вручную доступен только возврат в «учится».
Что было сложно
Ключ студента. Сначала им был ID сделки из CRM, потом замер прода показал, что номер договора заполнен у всех и без дублей. Ключ пересмотрели, но первичный ключ таблицы не трогали: на нём держатся отметки, оценки и звонки.
Решения и споры
CEO, 8 июля: телефон, почта и ФИО ненадёжны, опираемся на ID сделки.
6 августа, по данным прода: номер договора стал ключом сверки, ID сделки остался второй проверкой.
Отвал
Критические
/critical
Кто работает: отдел заботы, директор
Как сделано
Рабочая доска риска: серия пропусков или просроченный платёж. Корзины «сегодня», «неделя», «старше» и лента коммуникаций прямо в строке.
Изюминка
Одно имя и одна формула. До этого «критичными» называли две разные метрики с разными числами.
Что было сложно
В первый боевой день клик по карточке ронял страницу: обработчик клика оказался внутри серверного компонента. Тесты и типы были зелёными. С тех пор после каждого релиза экрана мы кликаем его руками на проде.
Решения и споры
Сооснователь, волна фидбэка 18 июля: одна формула и одно имя для «критичных» во всей системе.
Отвал
Case Manager и трекер кейсов
/cases · /case-tracker
Кто работает: кейс-менеджер, руководство
Как сделано
Кейсы тех, кто сказал «отказ» или «пауза»: встреча, ответственный, исход. Трекер кейсов отдела заботы открывается прямо из меню LMS.
Изюминка
Отказ в звонке больше не отваливает студента автоматически. Он открывает кейс, и только из прозвона за три недели таких кейсов набралась сотня.
Что было сложно
Статус «отказ» в LMS не ставит никто. Его решает книга договоров, а кейс живёт параллельно, пока человека пытаются вернуть.
Решения и споры
Отказ и расторжение договора несут юридические и финансовые последствия, поэтому проходят только через бухгалтерию.
Главный экран руководства: показатели, тепловые карты по группам, оценкам, сборам и оттоку, тренды, полнота данных и выезжающие панели «глубже» по каждой цифре.
Изюминка
Клик по любой цифре раскрывает список, из которого она посчитана. Руководитель проверяет цифру сам, без разработчика.
Что было сложно
После заливки всех групп навигатор молча считал метрики по 18% отметок: база отдаёт максимум тысячу строк и не сообщает об обрезке. Все растущие таблицы теперь читает один постраничный хелпер.
Решения и споры
Сооснователь прислал 41 пункт фидбэка за один вечер, 18 июля, а 20 июля попросил отвечать ему по номерам пунктов.
«Не может быть ни у какой из наших групп 100% сборов»: такая цифра это сигнал искать ошибку, а не праздновать.
Оценки
Опрос и оценки
/survey · /rate
Кто работает: отдел заботы, студенты без входа
Как сделано
Студент оценивает курс и лектора по QR на армянском, без регистрации. Отдел заботы ведёт очередь опроса по звонку, окно опроса система считает из занятий.
Изюминка
Страница оценки знает только занятие. Про студента, телефон и платежи она не знает ничего, поэтому её не страшно открыть кому угодно.
Что было сложно
Честная часть: опрос по звонку так и не заработал, за всё время ноль отработанных звонков, а оценки по QR к сентябрю почти перестали приходить. Коду не хватает ответственного и регламента, и мы так и написали заказчику.
Решения и споры
Руководство, 9 июля: оценка по кнопке в конце лекции, а не рассылка Telegram-ботом.
Журнал показывает, кто, что и когда менял: отметки, статусы, модули, звонки, студентов, группы. В настройках пороги, границы сверки с книгой, справочник курсов и привязка Telegram для алертов. Значения порогов задаёт руководство, поэтому в кейсе мы их не показываем.
Изюминка
Журнал действий был первой фичей архитектуры, 8 июля. Повод: админ случайно поменял номер телефона, и данные было уже не найти. Сейчас в журнале больше 20 тысяч записей.
Что было сложно
Статистику активности сотрудников для бонусов собрали за одно утро тремя PR: расчёт в базе, правила чтения, экран.
Решения и споры
Сооснователь: лимиты и пороги задаёт руководство в админке, в коде их быть не должно.
Связь
Чат
/chat
Кто работает: руководство
Как сделано
Переписка со студентами в Telegram от аккаунта академии с пометкой, кто из сотрудников написал. Воркер на Python держит сессию аккаунта и забирает исходящие из очереди атомарно.
Изюминка
Первым всегда пишет студент. Так аккаунт не получит бан за спам, а сотрудник не напишет человеку без повода.
Что было сложно
Раздел построен и ждёт запуска. Мы не записываем его в результаты, пока в нём нет живых переписок.
Решения и споры
Внутренние заметки сотрудников видны в диалоге, но студенту не уходят.
Доходимость
Доходимость для лектора по ссылке
/reach/[token]
Кто работает: лекторы, без входа
Как сделано
Лектор получает ссылку на своё занятие и видит агрегат и список не пришедших. Ссылка адресует занятие отдельным случайным кодом, не тем, что стоит в QR формы оценки.
Изюминка
Рабочих экранов у лектора нет: его роль только читает расписание, учётка открывает форму обратной связи, а доходимость занятия приходит по ссылке без входа.
Что было сложно
Страница не показывает телефоны, договоры и деньги. Всё, что ей можно, решает одна узкая функция базы.
Решения и споры
CEO, 27 июля: доходимость занятия отдаём лектору отдельной ссылкой, как форму оценки.
Телефония Viva: звонки менеджеров внутри LMS
Менеджеры академии звонят через облачную АТС Viva. С 24 июля записи звонков шли в LMS через мост со сторонней базой. В августе мы завели телефонию целиком: звонки, агенты и записи разговоров попадают в карточку студента, в очередь прозвона и в отчёты.
1
Фундамент, 21 августа
Справочник агентов АТС и единое представление звонков по сотрудникам. Один источник правды по звонкам для всех экранов.
2
Теневой режим
Прямой забор звонков из REST API АТС работал рядом со старым мостом через стороннюю базу. Мы сравнивали два потока, прежде чем чему-то верить.
3
Переключение, 28 августа
Старый мост сняли с расписания, прямой API стал единственным источником звонков. Переключение прошло в два PR.
4
Записи в строке
Запись разговора слушается прямо в очереди прозвона и в карточке студента.
46,6 тыс.
звонков АТС в базе LMS на 24 сентября, история с мая 2025
3 127
из них привязаны к карточкам студентов
2
PR на переключение со старого моста на прямой API
Менеджер по долгам писал в чат итог дня руками: 59 звонков. Бот LMS в тот же день показал 31. Замер по сырым записям АТС дал 57. Телефония не врала: отчёт сознательно считает только исходящие, а синк не успел довезти хвост дня.
Главный урок интеграции: старый мост отчитывался «успешно», когда данных уже не было. Живость синка мы теперь проверяем по времени последнего звонка в базе, а не по статусу задачи.
6. Что изменилось в цифрах
Главная цифра проекта про посещаемость. В июле отметки за прошедшие занятия переносили в систему из Excel задним числом, в тот же день ставили 5-12% занятий. С недели 27 июля отметку ставят в день занятия почти по всем группам, и этот уровень держится два месяца подряд.
Доля занятий, отмеченных в день занятия, по неделям
06.07
11,8%
13.07
5%
20.07
40,9%
27.07
97,4%
03.08
100%
10.08
100%
17.08
94,3%
24.08
100%
31.08
100%
07.09
100%
14.09
100%
21.09
100%
Считаем по состоявшимся занятиям, у которых есть хотя бы одна отметка. Покрытие отметками держится на 92,9-100% занятий каждую неделю. Замер по базе 24.09.2026.
9 879
отметок посещаемости по 1 051 состоявшемуся занятию
3 127
звонков из АТС привязаны к карточкам студентов
745
контактов с клиентами записано в единую ленту
177
кейсов «отказ или пауза» открыто вместо тихого отвала
20 388
действий сотрудников в журнале аудита
2 151
договоров сверяются с книгой бухгалтерии автоматически
Не всё взлетело. Опрос по звонкам так и не заработал, за всё время ноль отработанных звонков, оценки по QR в сентябре почти перестали приходить. Каждая функция у нас проходила 5-7 дней наблюдения, и такие вещи мы показывали заказчику как «не внедрено», а не записывали в успехи.
7. Инженерные решения, которыми мы гордимся
Код LMS принадлежит заказчику, поэтому ниже не фрагменты репозитория, а схемы решений, написанные заново для кейса.
Права на экраны живут в базе
Одна функция отвечает на вопрос «можно ли этому человеку в этот раздел» и для меню, и для политик безопасности базы. Экран не может показать то, что база не разрешит записать. Выдать права новому сотруднику значит поправить строку в таблице, релиз не нужен.
схема
// Схема решения, не код проекта
can(раздел, уровень):
есть ли у роли текущего пользователя право на раздел,
где право = «запись» или право = уровень
// одно и то же правило читают меню и политики безопасности базы
Режим «смотрю как»: руководитель видит экран глазами сотрудника
Переключатель в шапке подменяет роль только для отрисовки интерфейса. Любая запись всё равно проходит под настоящей ролью, поэтому в режиме просмотра нельзя случайно испортить чужие данные, а журнал действий не путает, кто нажал кнопку.
Замена графика платежей: всё или ничего
Раньше синк удалял старый график и вставлял новый двумя запросами. Одна битая дата в листе ломала вставку уже после удаления, и экран показывал «должников нет». Теперь пустой набор строк считается аварией, а удаление и вставка идут одной транзакцией.
схема
// Схема решения, не код проекта
заменить_график(студенты, новые_строки):
если новых строк нет -> авария, старый график не трогаем
в одной транзакции:
удалить старый график этих студентов
вставить новый
// ошибка на любом шаге откатывает всё, «должников нет» не появится
Миграция сама проверяет свой запрет
Правило «у паузы должна быть дата возврата» стоит в базе, а не в форме: форму уже однажды обошла заливка из Excel. Сама миграция при выкатке пробует записать неправильную строку и падает, если страж её пропустил. Позже паузу упразднили совсем, а приём с самопроверкой миграции остался.
схема
// Схема решения, не код проекта
при выкатке миграции:
попробовать записать заведомо запрещённую строку
прошла -> остановить выкатку: «страж не работает»
база отказала -> всё верно, выкатка идёт дальше
Доходимость для лектора по ссылке, без логина и без лишних данных
В рабочие экраны лектор не входит, а цифру по своему занятию видеть должен. Мы не стали открывать таблицы наружу. Ссылка адресует занятие отдельным случайным кодом, а узкая функция отдаёт только агрегат и список не пришедших, без телефонов, договоров и денег.
схема
// Схема решения, не код проекта
доходимость_по_ссылке(код_ссылки):
только прошедшее занятие действующей группы
отдаёт: группу, номер и дату занятия,
сколько активных, сколько пришло, процент,
имена не пришедших
не отдаёт: телефоны, договоры, деньги
Тест на расхождение базы и кода
Однажды колонку добавили руками через админку базы, и четыре месяца перевод студента между группами не мог отработать ни в одном тестовом прогоне. Теперь каждый PR сверяет снимок боевой схемы с миграциями в обе стороны и падает со списком расхождений.
Чат-аккаунт академии, который никогда не пишет первым
Для переписки со студентами нужен живой аккаунт академии, а не бот. Воркер на Python забирает исходящие из очереди атомарно, соблюдает паузы между сообщениями и сам диалог не начинает. Так мы убрали и риск бана за спам, и риск написать студенту без повода.
8. Как мы строили: один разработчик и конвейер AI-агентов
С 13 июля продукт вёл один инженер FaraMirAI. Параллельно работали несколько сессий AI-агентов: каждая брала задачу через заявку в базе, чтобы две сессии не делали одно и то же, а номера миграций не конфликтовали. Ни один PR не попадал в main без внешнего ревью другой моделью и зелёных тестов.
Гейт
Что проверяет
codex-review
Независимое ревью PR другой моделью по правилам проекта. Автор не может одобрить свою правку
changelog
Правка кода без записи в CHANGELOG не вливается
vitest
1 656 проверок бизнес-логики на TypeScript
sql
Миграции применяются с нуля на Postgres 16, затем 116 SQL-тестов: права, деньги, расписание, дрейф схемы
db-guard
Необратимые изменения базы вливаются только после живого ответа владельца
Vercel Preview
Превью каждого PR по ссылке до слияния
1
Самопроверка до PR
Отдельный агент с чистым контекстом пытается доказать, что изменения не готовы. Автор видит замысел, а ревьюер видит только код.
2
Живой клик на проде
Задача считается сделанной, когда её видно на проде. Однажды зелёные тесты пропустили ошибку, которая уронила экран на 10 минут, и с тех пор после каждого релиза экрана мы открываем его руками.
3
Наблюдение 5-7 дней
Функцию считаем внедрённой, когда ей пользуются. Через неделю смотрим на счётчики и честно пишем «не внедрено», если строк ноль.
4
Модель решений заказчика
Мы собрали прецеденты и правила, по которым сооснователь принимает отчёты, и прогоняли через них каждый черновик до отправки. Отдельный реестр ответов не давал нам переспрашивать то, на что он уже ответил.
1 457
коммитов в main
432
смерженных PR, из них 329 наши
+237 906
строк в смерженных PR
310
миграций базы
15
edge-функций
76
PR за самую плотную неделю, 3-9 августа
В сентябре к разработке в том же конвейере подключилась команда заказчика: 68 PR с их стороны прошли те же гейты и то же ревью. Так появились лестница долга и чек-лист онбординга на 14 шагов. Для нас это лучший результат процесса: систему можно передать людям, и она не развалится.
9. Истории с прода
1
Лектор против цифры
Лектор публично оспорил сводку: у него 90%, у системы 50%. Первая версия «список лектора неполный» оказалась ошибкой. Проверка по времени каждой отметки показала, что 161 отметку на 14 студентов залили одним пакетом из старого Excel за секунды. Журнал лектора стал первым внешним источником правды по явке, а мы написали протокол таких споров: сначала журнал лектора, потом SQL.
2
Сверяли не с тем источником
Месяц LMS сверялась с Google-книгой, которая сама копировала данные из другой, настоящей книги договоров. Деньги совпадали 1 560 из 1 560, но статус договора в копии давала сломанная формула, а причину отказа копия не несла вовсе. Мы переключили синк на первоисточник, а признаком отказа стала сумма списания, а не ручной статус.
3
Дашборд считал по 18% данных
Сервер базы отдаёт максимум 1 000 строк на запрос и не сообщает об обрезке. После заливки всех групп навигатор молча считал метрики по первой тысяче отметок из 5 558. Теперь растущие таблицы читает только один постраничный хелпер.
4
Курс короче, чем в жизни
При создании группы число занятий бралось по умолчанию, 24 плюс одно. У настоящего курса бухгалтерии их 49. Расчёт «когда освободится аудитория» называл октябрь вместо марта. Мы вынесли длину курса в форму и берём её из выбранного курса.
5
Право выдано, но не работает
Сотруднику дважды выдавали доступ к составу групп, и оба раза он не работал. В системе оказалось четыре двери: кнопка, серверная функция, запрет на запись и запрет на чтение, который не ругается, а тихо отдаёт пустой список. Мы починили все четыре сразу и завели проверку прав по каталогу разделов.
6
Девять находок ревьюера
AI-ревьюер девять раз подряд вернул PR с автопостом посещаемости, и все девять замечаний оказались настоящими. Девятое нашло, что в одной миграции «отозвать права» и «выдать право» отменили друг друга, и служебную функцию мог вызвать любой сотрудник.
10. Решения, которые принял заказчик
Дата
Кто
Решение
09.07
Сооснователь
Строим сверху вниз от трёх цифр. Один показатель за раз
09.07
Сооснователь
Отдельный проект базы для LMS, бэкапы и лимиты с первого дня
10.07
CEO
Знаменатель доходимости: только состоявшиеся занятия
10.07
CEO
Показываем не пришедших, а не процент пришедших
10.07
CEO
У сотрудников нет прав менять структуру и формулы
10.07
CEO
Оплата лектора и админа привязана к сдаче доходимости
16.07
Разработка и сооснователь
Четвёртый показатель: отвал
18.07
Сооснователь
Просрочка наступает в день платежа, одно определение на всю систему
06.08
Сооснователь
Новые статусы и причины отказа только через его согласование
06.08
Сооснователь
Дату платежа нельзя менять одной кнопкой без согласования руководителя
06.08
Разработка и сооснователь
Ключ студента: номер договора, ID сделки остаётся второй сверкой
07.08
Сооснователь
Отказ определяет сумма списания в книге договоров, а не ручной статус
11. Итог
За неполные три месяца академия получила систему, в которой посещаемость отмечают в день занятия, долг видно с первого дня просрочки, у каждого отказника есть кейс и ответственный, а бухгалтерия продолжает работать в своих книгах. Мы оставили после себя не только код: 310 миграций с тестами, документацию по каждой подсистеме, журнал всех решений заказчика и конвейер, в котором команда клиента уже выпускает свои изменения.
Хронология проекта
01
3-9 июля
Погружение: интервью, анкеты, разбор таблиц, ресёрч, четыре показателя
02
10 июля
Первый коммит: пилот учёта посещаемости на одной группе
03
11-21 июля
Журнал действий, график платежей, прозвон, лекторы. 156 коммитов до первого PR
04
22 июля
Конвейер: PR, AI-ревью и обязательный changelog
05
24 июля
«Сборы» и «Отчёт дня» выходят к реальным сотрудникам, SQL-тесты встают в CI
06
27 июля
Отметка в день занятия выходит на 97% и больше не падает ниже 94%
07
3-9 августа
76 PR за неделю: расписание, раздел «Договоры», согласования
08
7 августа
Синк переключён на первоисточник книги договоров
09
10-16 августа
Онбординг, критические, Case Manager, английский интерфейс для бухгалтерии
10
28 августа
Звонки идут напрямую из API телефонии, старый мост отключён
11
9-13 сентября
Защита базы db-guard, пять сервисов переехали в один репозиторий
12
16-24 сентября
Команда заказчика выпускает лестницу долга и чек-лист онбординга через наш конвейер
Что мы сделали
Погружение: 12+ интервью, 8 анкет, разбор таблиц, ресёрч 250+ источников и 139 экранов пяти LMS
Четыре показателя (доходимость, оплаты, оценки, отвал) как основа всех экранов и приоритетов
Сетка посещаемости Д/Н с отметкой в день занятия и зоной риска по серии пропусков
Отчёт дня для админов точек с автопостом в Slack
Навигатор для руководства: тепловые карты, тренды, полнота данных, клик по цифре раскрывает список
Долги: сборы, «Скоро платёж», прозвон и единая лента контактов по каждому должнику
Сверка с книгой договоров: 9 цифр расхождений, списанный долг, возвраты
Согласования: отказ и перенос срока платежа проходят цепочку подписей, а не одну кнопку
Отвал: критические студенты, Case Manager для отказников и паузы, трекер кейсов
Онбординг от договора до первого занятия: группа, канал, фото, анкета
Расписание, события недели, календарь аудиторий, курсы и старты потоков
CRM лекторов: база, расчёт зарплаты по факту занятий, отпуска, обратная связь
Оценка занятия по QR без регистрации и доходимость для лектора по ссылке
24 роли с правами в базе и режим «смотрю как» для руководителя
Синки Google Sheets, amoCRM, АТС Viva, Slack и Telegram со сторожами от неполных данных
Конвейер качества: 1 656 проверок TypeScript, 116 SQL-тестов, AI-ревью каждого PR, защита базы