П/ВИН

v0-german: генератор документов для воронки продаж

·8 мин чтения
v0-german: генератор документов для воронки продаж

Представь себе такую воронку: потенциальный клиент приходит через лид-магниты, проходит трёхдневный прогрев, потом попадает на диагностику с бизнес-консультантом, и только после этого — на предложение войти в программу за несколько сотен тысяч рублей. Каждый шаг этой воронки требует персонализированных документов: подробного отчёта о диагностике, коммерческого предложения под конкретный тариф, дорожной карты на несколько месяцев вперёд. Раньше всё это делалось вручную — часами. Мы это автоматизировали.

Проект v0-german — это внутренний инструмент для команды Германа Юна, бизнес-консультанта, который работает с предпринимателями над систематизацией их компаний. Проект живёт на germanyun.online и уже включал в себя CJM-модуль (Customer Journey Map) для визуализации воронок, транскрибацию аудио через Whisper и интеграцию с Supabase. Новый модуль — Generator — стал логичным продолжением: взять транскрипцию диагностического звонка и автоматически сгенерировать три PDF-документа, готовых к отправке клиенту.

Зачем это вообще нужно

Диагностика с клиентом — это разговор на 1-2 часа. После него консультант должен подготовить:

  1. Отчёт «Точка А» — подробное описание текущей ситуации в бизнесе клиента: отдел продаж, найм, дебиторка, управление, ключевые проблемы и их корневые причины.
  2. Коммерческое предложение — персонализированный оффер с выбранным тарифом (три варианта: 250К, 750К и 1М рублей), программой и условиями.
  3. Дорожная карта — помесячный план работы под конкретный запрос клиента.

Без автоматизации это занимает несколько часов работы. С генератором — несколько минут: загрузил аудио или вставил текст транскрипции, заполнил пару полей, нажал кнопку — и три PDF готовы к отправке.

Архитектура: пошаговый визард

Мы приняли решение не делать одну большую форму, а разбить процесс на четыре чётких шага. Это снижает когнитивную нагрузку на менеджера и позволяет валидировать данные на каждом этапе.

Шаг 1 — Данные клиента. Имя, описание бизнеса, локация, дата встречи, дата следующего касания с Германом.

Шаг 2 — Транскрипция. Два режима: загрузка аудиофайла (обрабатывается через Whisper API) или прямая вставка текста транскрипции. Переиспользовали логику из существующего /api/otchet роута.

Шаг 3 — Выбор тарифа. Три карточки с подробным описанием каждого тарифа, что включено, цена. Менеджер выбирает то, что обсуждалось на диагностике.

Шаг 4 — Генерация документов. Три карточки документов с кнопками «Сгенерировать» и «Скачать PDF». Плюс кнопки «Сгенерировать все» и «Скачать все PDF» для удобства.

Структура файлов получилась такая:

app/generator/
  page.tsx              — Страница-визард (клиентский компонент)
  layout.tsx            — Layout с password-gate

app/api/generator/
  transcribe/route.ts   — Whisper транскрибация
  report/route.ts       — Генерация отчёта через GPT-4o
  kp/route.ts           — Генерация КП
  roadmap/route.ts      — Генерация дорожной карты
  pdf/route.ts          — Рендеринг PDF через @react-pdf/renderer

components/generator/
  client-form.tsx       — Шаг 1: данные клиента
  transcription-step.tsx — Шаг 2: транскрипция
  tariff-select.tsx     — Шаг 3: выбор тарифа
  documents-step.tsx    — Шаг 4: генерация и скачивание

Выбор стека для PDF

Один из ключевых технических вопросов — как генерировать красивые PDF. У нас было три варианта:

@react-pdf/renderer — настоящие векторные PDF с кастомными шрифтами, работает в API routes Next.js, лёгкий (~5MB). Минус — свой синтаксис, не Tailwind, нужно переписывать layout.

Puppeteer — pixel-perfect из HTML, Tailwind работает как в браузере. Но ~400MB Chromium в зависимостях, медленный cold start, проблемы в Docker Alpine-контейнерах.

window.print() — самый простой вариант, работает мгновенно, но результат непредсказуем и зависит от браузера пользователя.

Мы выбрали @react-pdf/renderer — он даёт предсказуемый результат в серверном окружении, не раздувает образ Docker и позволяет точно контролировать оформление. Да, пришлось написать отдельные layout-компоненты для PDF, но это разовая работа.

Брендинг взяли с germanyun.online: бордовый #9b2c2c как основной цвет, золотой акцент #c9a959, тёплый белый фон, шрифты Inter + Noto Serif JP. Японская эстетика — строго, благородно, дорого.

AI-промпты: структура важнее красоты

Для генерации отчёта использовали GPT-4o с детальным системным промптом. Ключевой принцип — жёсткая структура вывода, которую можно парсить и рендерить в PDF:

// Структура промпта для отчёта «Точка А»
const REPORT_SYSTEM_PROMPT = `
Ты — эксперт по диагностике бизнесов, обученный в школе Германа Юна.
На основе текста транскрипции составь отчет строго по структуре:
 
## Точка А
### Отдел продаж
[анализ]
### Найм и HR
[анализ]
### Финансы и дебиторка
[анализ]
### Управление и процессы
[анализ]
 
## Ключевые выводы
### Корневые причины
[список]
### Ограничения роста
[список]
### Особенности мышления собственника
[анализ]
`;

Для КП и дорожной карты промпты учитывают выбранный тариф — разный объём программы, разные акценты, разные сроки.

Отдельно пришлось поработать над промптами для слайдов (проект параллельно включает PowerPoint-like презентацию из 15 слайдов). Несколько итераций потребовало слайд 03 — список симптомов и ограничений: изначально AI давал 2-3 пункта, а нам нужно 5-7. Решили явным требованием в промпте: require minimum 5 symptoms and 3 current_constraints from AI. Помогло.

CJM-модуль: аудит и рефакторинг

Параллельно с разработкой генератора мы провели глубокий аудит CJM-модуля — визуального редактора Customer Journey Map на базе React Flow. Этот модуль уже был в продакшене, но работал нестабильно: баги в отображении, потери данных, неправильная логика каскадных расчётов.

Автоматизированный анализ 50+ компонентов и 15 API-роутов выявил около 40 проблем разной степени критичности. Самые серьёзные:

SQL Injection — UUID'ы вставлялись через string interpolation в запросах типа .not("id", "in", ...). В Supabase это безопасно благодаря PostgREST, но паттерн всё равно плохой и потенциально опасный при изменении ORM.

Потеря данных при сохранении — поле weight для рёбер графа не передавалось в API при сохранении, из-за чего веса связей между этапами воронки сбрасывались.

Были и UX-проблемы: несинхронизированное состояние между вкладками «Конструктор», «Таблица» и «Дашборд», race conditions при быстром переключении, некорректный рендеринг на мобильных.

Решение: поэтапный рефакторинг с планом из 11 задач. К моменту написания этой статьи 9 из 11 задач завершены.

Password Gate: защита внутренних страниц

И генератор, и CJM — внутренние инструменты, не для публичного доступа. Поэтому обе страницы закрыты простым password gate: при первом заходе показывается форма с полем пароля, при успехе — токен сохраняется в localStorage, и следующие заходы не требуют повторного ввода.

Паттерн реализован через общий компонент PasswordGate с параметрами title, endpoint и storageKey. Для генератора добавили отдельный env var и соответствующий API endpoint — /api/generator/auth.

// layout.tsx для /generator
import { PasswordGate } from '@/components/password-gate';
 
export default function GeneratorLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <PasswordGate
      title="Generator — Команда Германа Юна"
      endpoint="/api/generator/auth"
      storageKey="generator_auth"
    >
      {children}
    </PasswordGate>
  );
}

Просто, надёжно, не перегружено. Для внутреннего инструмента этого достаточно.

Деплой и CI

Проект деплоится через стандартный пайплайн на VPS: git push → автоматический npm run buildpm2 restart. В git-истории видно регулярные auto-sync коммиты каждые 15 минут — это фоновый процесс, который сохраняет промежуточное состояние файлов.

Build перед финальным мёржем прошёл успешно: все роуты на месте, TypeScript без ошибок, @react-pdf/renderer совместим с App Router через серверные компоненты.

Отдельный момент — работа с Supabase: тарифы хранятся в БД и подгружаются динамически. Это позволяет менеджерам обновлять условия тарифов без деплоя кода. Была баг: при передаче tariffId из формы приходил не UUID, а строковое название тарифа. Фикс — добавить предварительную выборку всех тарифов и резолвить по обоим полям.

// До фикса
const tariff = await supabase
  .from('tariffs')
  .select('*')
  .eq('id', tariffId) // падало если tariffId не UUID
  .single();
 
// После фикса
const { data: tariffs } = await supabase
  .from('tariffs')
  .select('*');
 
const tariff = tariffs?.find(
  t => t.id === tariffId || t.name === tariffId
);

Результат

Генератор документов полностью функционален: четырёхшаговый визард, транскрибация аудио через OpenAI Whisper, генерация трёх типов документов через GPT-4o, экспорт в PDF с фирменным брендингом.

Время подготовки документов после диагностики: с нескольких часов до 5-7 минут. Менеджер загружает запись звонка, выбирает тариф, нажимает «Сгенерировать все» — и три персонализированных PDF готовы к отправке клиенту.

CJM-модуль прошёл аудит и рефакторинг: критические баги с потерей данных устранены, логика каскадных расчётов выправлена, UX на трёх вкладках синхронизирован.

Выводы и уроки

Первый важный урок: не нужно изобретать велосипед там, где уже есть рабочий паттерн. Когда появилась идея использовать AFFiNE (мощный open-source workspace) для решения задач проекта, мы трезво оценили ситуацию: AFFiNE — это general-purpose инструмент, он не умеет воронки продаж, каскадные расчёты и генерацию персонализированных PDF. Пришлось бы писать всё заново внутри чужой платформы. Правильное решение — дорабатывать то, что уже есть и работает.

Второй урок: аудит перед фичами. Когда пользователь говорит «инструмент работает с багами и неправильной логикой», первый шаг — не добавлять новые возможности, а найти и зафиксировать все существующие проблемы. Систематический аудит 50+ файлов дал конкретный список из 40 проблем с приоритетами — это несравнимо лучше, чем хаотичное «починить то, что сломалось».

Третий урок: переполнение контекста — нормально, главное — восстановление. В процессе длинных сессий разработки контекстное окно переполнялось, и работу приходилось начинать заново. Решение — сохранять план в файл (PLAN.md), вести KNOWLEDGE.md с описанием архитектуры, делать атомарные коммиты с понятными сообщениями. Когда сессия оборвалась на Task 10 из 11, мы восстановили контекст за пару минут по git log и файлу плана.

Четвёртый урок: промпты нужно тестировать итеративно. Казалось бы, написал промпт — и готово. Но AI генерировал то 2 симптома вместо 5, то неправильный заголовок слайда, то смешивал данные из разных секций. Каждый слайд в презентационном модуле потребовал 2-4 итерации промпта с явными ограничениями (minimum 5 symptoms, use lead_text from financial_risk, not executive_summary). Это нормальная часть разработки AI-фич, не стоит недооценивать этот этап при планировании.

Проект продолжает развиваться: следующие шаги — автоматическая отправка документов клиенту через email или Telegram, версионирование сгенерированных отчётов и аналитика по тарифам.

Технологии в проекте: Next.js 14 App Router, React Flow, @react-pdf/renderer, OpenAI API, Supabase, TypeScript, Tailwind CSS.

Паша Вин
Паша Вин

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.

Связанный проект

CJM Designer