Розробка квіз-воронки на 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка квіз-воронки на 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка квіз-воронки на 1С-Бітрікс

Квіз-воронка — наступний рівень після звичайного квізу. Запитання показуються не лінійно: відповідь на одне запитання визначає, яке запитання поставити наступним. Різні шляхи — різні результати й різні пропозиції в кінці. Це дозволяє точково кваліфікувати лідів: хтось отримає пропозицію на початковий продукт, хтось — на преміум, хтось — перенаправляється на іншу сторінку. Технічно основна складність — реалізувати логіку розгалуження.

Логіка розгалуження: структура даних

Кожне запитання має переходи (transitions): при виборі варіанту X — наступне запитання Y. Якщо переходу немає — переходити за замовчуванням до наступного за порядком або до фінального кроку.

HL-блок запитань з розгалуженням:

class FunnelQuestionTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_hl_quiz_funnel_questions'; }

    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new IntegerField('FUNNEL_ID'),
            new StringField('SLUG'),              // Унікальний ідентифікатор запитання всередині воронки
            new IntegerField('SORT'),
            new StringField('TEXT'),
            new StringField('TYPE'),              // single | multiple | scale | input
            new TextField('OPTIONS_JSON'),        // [{id, text, next_slug?, result_id?}]
            new StringField('DEFAULT_NEXT_SLUG'), // Наступне запитання за замовчуванням
            new BooleanField('IS_FINAL', ['values' => [false, true]]),
        ];
    }
}

Структура OPTIONS_JSON для запитання з розгалуженням:

[
  {"id": "opt_a", "text": "Фізична особа", "next_slug": "q_budget_personal"},
  {"id": "opt_b", "text": "Компанія (до 50 осіб)", "next_slug": "q_budget_smb"},
  {"id": "opt_c", "text": "Велика компанія", "next_slug": "q_budget_enterprise"}
]

Граф переходів

Воронка — орієнтований граф. На бекенді граф будується при завантаженні воронки:

class FunnelGraph
{
    private array $questions = []; // slug => question
    private string $startSlug;

    public function __construct(int $funnelId)
    {
        $rows = FunnelQuestionTable::getList([
            'filter' => ['FUNNEL_ID' => $funnelId],
            'order'  => ['SORT' => 'ASC'],
        ])->fetchAll();

        foreach ($rows as $row) {
            $row['OPTIONS'] = json_decode($row['OPTIONS_JSON'], true) ?? [];
            $this->questions[$row['SLUG']] = $row;
        }

        // Стартове запитання — перше за SORT
        $this->startSlug = array_key_first($this->questions);
    }

    public function getNextSlug(string $currentSlug, string $selectedOptionId): ?string
    {
        $question = $this->questions[$currentSlug] ?? null;
        if (!$question) {
            return null;
        }

        // Шукаємо перехід за обраною опцією
        foreach ($question['OPTIONS'] as $option) {
            if ($option['id'] === $selectedOptionId && !empty($option['next_slug'])) {
                return $option['next_slug'];
            }
        }

        // Перехід за замовчуванням
        return $question['DEFAULT_NEXT_SLUG'] ?: null;
    }

    public function isFinal(string $slug): bool
    {
        return (bool)($this->questions[$slug]['IS_FINAL'] ?? false);
    }

    public function toClientJson(): array
    {
        // Відправити клієнту лише необхідне — без серверної логіки
        $result = [];
        foreach ($this->questions as $slug => $q) {
            $result[$slug] = [
                'text'    => $q['TEXT'],
                'type'    => $q['TYPE'],
                'options' => $q['OPTIONS'],
                'is_final' => $q['IS_FINAL'],
            ];
        }
        return ['start' => $this->startSlug, 'questions' => $result];
    }
}

Клієнтська логіка навігації

class QuizFunnel {
    constructor(graphData) {
        this.questions    = graphData.questions;
        this.currentSlug  = graphData.start;
        this.history      = []; // Стек для кнопки "Назад"
        this.answers      = {}; // slug => [optionIds]
    }

    selectOption(optionId) {
        const question = this.questions[this.currentSlug];
        this.answers[this.currentSlug] = [optionId];

        // Визначити наступний крок
        let nextSlug = null;
        for (const opt of question.options) {
            if (opt.id === optionId && opt.next_slug) {
                nextSlug = opt.next_slug;
                break;
            }
        }

        if (!nextSlug && question.is_final) {
            this.showContactForm();
            return;
        }

        if (nextSlug && this.questions[nextSlug]) {
            this.history.push(this.currentSlug);
            this.currentSlug = nextSlug;
            this.renderQuestion(nextSlug);
        } else {
            this.showContactForm();
        }
    }

    goBack() {
        if (this.history.length === 0) return;
        this.currentSlug = this.history.pop();
        delete this.answers[this.currentSlug];
        this.renderQuestion(this.currentSlug);
    }

    renderQuestion(slug) {
        const q   = this.questions[slug];
        const el  = document.getElementById('quiz-question');
        el.querySelector('.quiz-text').textContent = q.text;

        const optionsEl = el.querySelector('.quiz-options');
        optionsEl.innerHTML = q.options.map(opt =>
            `<button class="quiz-option" data-option-id="${opt.id}">${opt.text}</button>`
        ).join('');

        optionsEl.querySelectorAll('.quiz-option').forEach(btn => {
            btn.addEventListener('click', () => this.selectOption(btn.dataset.optionId));
        });
    }

    showContactForm() {
        document.getElementById('quiz-questions').style.display = 'none';
        document.getElementById('quiz-contact-form').style.display = 'block';
    }
}

// Ініціалізація
const funnel = new QuizFunnel(window.FUNNEL_DATA); // Дані з PHP
funnel.renderQuestion(funnel.currentSlug);

Результати й пропозиції

Наприкінці воронки користувач отримує не просто «дякуємо», а персоналізований результат. Результат визначається за шляхом: які відповіді дав користувач.

HL-блок результатів воронки:

class FunnelResultTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_hl_quiz_funnel_results'; }

    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new IntegerField('FUNNEL_ID'),
            new StringField('TITLE'),
            new TextField('DESCRIPTION'),
            new StringField('CTA_TEXT'),
            new StringField('CTA_URL'),
            new TextField('CONDITIONS_JSON'), // Правила: [{question_slug, option_ids}]
        ];
    }
}

Сервіс підбору результату за відповідями:

class ResultMatcher
{
    public function match(int $funnelId, array $answers): ?array
    {
        $results = FunnelResultTable::getList([
            'filter' => ['FUNNEL_ID' => $funnelId],
        ])->fetchAll();

        foreach ($results as $result) {
            $conditions = json_decode($result['CONDITIONS_JSON'], true) ?? [];
            if ($this->checkConditions($conditions, $answers)) {
                return $result;
            }
        }

        return null; // Немає збігу — показати результат за замовчуванням
    }

    private function checkConditions(array $conditions, array $answers): bool
    {
        foreach ($conditions as $condition) {
            $slug      = $condition['question_slug'];
            $required  = $condition['option_ids'];
            $given     = $answers[$slug] ?? [];

            if (empty(array_intersect($required, $given))) {
                return false;
            }
        }
        return true;
    }
}

Лід з контекстом воронки

У Битрикс24 створюємо лід і додаємо повний шлях користувача:

$matchedResult = (new ResultMatcher())->match($funnelId, $answers);
$resultTitle   = $matchedResult['TITLE'] ?? 'Не визначено';

$lead = new \CCrmLead(false);
$lead->Add([
    'TITLE'              => 'Воронка: ' . $funnelName . ' → ' . $resultTitle . ' — ' . $name,
    'NAME'               => $name,
    'PHONE'              => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']],
    'SOURCE_ID'          => 'WEB',
    'COMMENTS'           => "Результат: {$resultTitle}\nШлях: " . implode(' → ', array_keys($answers)),
    'UF_CRM_QUIZ_PATH'   => json_encode($answers, JSON_UNESCAPED_UNICODE),
]);

Терміни розробки

Варіант Склад Термін
Лінійна воронка Без розгалужень, фінальний результат 4–6 днів
Воронка з розгалуженням Граф переходів, кілька результатів 8–12 днів
З конструктором Управління воронкою через UI без розробника 15–22 дні