Ребрендинг БАЗА: новое меню + воронка подбора, интенсив, оплата, карточка менеджеру

Меню Кибершколы БАЗА (4 кнопки): Подобрать тренировку / Летний интенсив CS2 /
Посмотреть цены / Задать вопрос.

Воронка «Подобрать тренировку» (FSM BAZA_*): для кого → возраст → игра → уровень →
формат → групповые / индивидуальные / нужен совет. На каждом шаге — «Назад» и «В
главное меню», ветки: младше 12 и «другая игра» уводят к менеджеру.

Ветка «Летний интенсив CS2»: инфо → возраст → уровень → оплата. Запуск по кнопке и
по кодовым словам (лето/интенсив), основные триггеры и приветствие — из config.

Шаг оплаты: инлайн-кнопка «Перейти к оплате» (ссылки-плейсхолдеры из config.LINKS +
UTM), «Проблема с оплатой» и «Задать вопрос менеджеру» уводят к оператору.

Карточка менеджеру собирается из профиля воронки (пустые поля пропускаются) + метки;
дублируется на почту. Данные не переспрашиваются, после передачи менеджеру бот не
вмешивается (OPERATOR_CHAT). Тесты воронки — pytest (30 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-07-18 18:20:59 +03:00
parent 06847fa87e
commit 60199fb47b
3 changed files with 654 additions and 107 deletions
+74 -17
View File
@@ -5,26 +5,83 @@ def _state(uid):
return bot.db.get_user_data(uid)["state"]
def test_full_lead_flow(app):
def _prof(uid):
return bot.db.get_profile(uid)
def test_baza_group_funnel_to_payment(app):
"""Полный подбор → групповой абонемент → шаг оплаты (BAZA_PAY)."""
u = 70001
assert (app.handle_message(u, "Заполнить заявку 📝"), _state(u))[1] == "FORM_NAME"
app.handle_message(u, "Иван"); assert _state(u) == "FORM_PHONE"
app.handle_message(u, "89990001122"); assert _state(u) == "FORM_QUESTION"
app.handle_message(u, "кс"); assert _state(u) == "FORM_CALLDATE"
app.handle_message(u, "Завтра"); assert _state(u) == "FORM_CALLTIME"
app.handle_message(u, "14:00"); assert _state(u) == "FORM_REVIEW"
app.handle_message(u, "Да, всё верно! ✅"); assert _state(u) == "FORM_CONSENT_1"
app.handle_message(u, "Согласен(а) и подтверждаю ✅"); assert _state(u) == "FORM_CONSENT_2"
app.handle_message(u, "Без рассылки ➡️"); assert _state(u) == "FREE_CHAT"
stats = bot.db.get_lead_stats()
assert stats["total"] == 1 and stats["from_bot"] == 1
app.handle_message(u, "Подобрать тренировку 🎯"); assert _state(u) == "BAZA_FORWHO"
app.handle_message(u, "Для подростка"); assert _state(u) == "BAZA_AGE"
app.handle_message(u, "1415 лет"); assert _state(u) == "BAZA_GAME"
app.handle_message(u, "CS2"); assert _state(u) == "BAZA_LEVEL"
app.handle_message(u, "Играет для себя"); assert _state(u) == "BAZA_FORMAT"
app.handle_message(u, "Групповые тренировки"); assert _state(u) == "BAZA_GROUP"
app.handle_message(u, "Купить 4 тренировки"); assert _state(u) == "BAZA_PAY"
p = _prof(u)
assert p["role"].startswith("Для подростка") and p["game"] == "CS2"
assert p["format"] == "Групповые тренировки" and "4" in p["tariff"]
assert {"role_parent", "game_cs2", "format_group", "clicked_payment", "lead_hot"} <= set(p["metki"])
def test_career_player_flow(app):
u = 70003
app.handle_message(u, "Карьера в ИКП 💼"); assert _state(u) == "VAC_CHOOSE"
app.handle_message(u, "Игроком в состав 🎮"); assert _state(u) == "VAC_P_DISCIPLINE"
app.handle_message(u, "Counter-Strike 2 🔫"); assert _state(u) == "VAC_P_RANK"
def test_baza_individual_and_back(app):
"""Индивидуальная ветка + кнопка «Назад» возвращает на предыдущий шаг."""
u = 70011
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для себя"); assert _state(u) == "BAZA_AGE"
app.handle_message(u, "16 лет и старше"); assert _state(u) == "BAZA_GAME"
app.handle_message(u, "Назад ⬅️"); assert _state(u) == "BAZA_AGE"
app.handle_message(u, "16 лет и старше")
app.handle_message(u, "Dota 2"); assert _state(u) == "BAZA_LEVEL"
app.handle_message(u, "Есть рейтинг или ранг")
app.handle_message(u, "Индивидуальные тренировки"); assert _state(u) == "BAZA_INDIVIDUAL"
app.handle_message(u, "Купить 8 тренировок"); assert _state(u) == "BAZA_PAY"
assert "8" in _prof(u)["tariff"]
def test_baza_under12_goes_to_manager(app):
u = 70012
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для подростка")
app.handle_message(u, "Младше 12 лет"); assert _state(u) == "BAZA_YOUNG"
app.handle_message(u, "Обсудить с менеджером 💬")
assert _state(u) == "OPERATOR_CHAT"
assert "age_under12" in _prof(u)["metki"]
def test_baza_other_game_goes_to_manager(app):
u = 70013
app.handle_message(u, "Подобрать тренировку 🎯")
app.handle_message(u, "Для себя")
app.handle_message(u, "16 лет и старше")
app.handle_message(u, "Другая игра"); assert _state(u) == "BAZA_GAME_OTHER"
app.handle_message(u, "Valorant"); assert _state(u) == "OPERATOR_CHAT"
assert "Valorant" in _prof(u)["game"]
def test_baza_intensive_by_keyword(app):
"""Кодовое слово «лето» запускает интенсив; ветка доходит до оплаты."""
u = 70014
app.handle_message(u, "лето"); assert _state(u) == "BAZA_INT_INFO"
app.handle_message(u, "Хочу участвовать 🔥"); assert _state(u) == "BAZA_INT_AGE"
app.handle_message(u, "1415 лет"); assert _state(u) == "BAZA_INT_LEVEL"
app.handle_message(u, "Играет в команде или турнирах"); assert _state(u) == "BAZA_PAY"
assert "intensive_interest" in _prof(u)["metki"]
def test_baza_question_button_handoff(app):
u = 70015
app.handle_message(u, "Задать вопрос 💬"); assert _state(u) == "OPERATOR_CHAT"
assert "lead_manager" in _prof(u)["metki"]
def test_baza_prices_and_menu(app):
u = 70016
app._sent.clear()
app.handle_message(u, "Посмотреть цены 💰")
assert _state(u) == "FREE_CHAT"
assert any("2 790" in s["msg"] for s in app._sent)
def test_flood_does_not_crash(app):