{
  "version": "1.0",
  "title": "Создание продукта с ИИ: от идеи до первых пользователей",
  "nodes": [
    {
      "id": "start",
      "label": "Продукт с ИИ",
      "type": "core",
      "area": "start",
      "x": 0.5,
      "y": 0.5,
      "locked": false,
      "sections": [
        {
          "id": "saas-welcome",
          "kind": "lecture",
          "title": "Ваш маршрут и итоговый проект",
          "desc": "20 практических шагов: проверить проблему, собрать MVP с ИИ, протестировать, опубликовать и оценить интерес.",
          "content": "## Что вы построите\n\nFollow-up — небольшой сервис для самостоятельного специалиста: добавить клиента, записать договорённость и увидеть, кому пора ответить. ИИ помогает создавать приложение; встроенная нейросеть в самом продукте не обязательна. Вы можете заменить сюжет своим, сохранив узкий основной сценарий.\n\n## Что нужно на старте\n\nНужны браузер, редактор и готовность запускать проверки. Полезны основы HTML, JavaScript и Git; пробелы можно закрывать по ссылкам в уроках. Выберите доступный вам ИИ-инструмент. При отсутствии подписки выполняйте шаги вручную или с тестовыми ответами. Не загружайте реальные клиентские данные в учебные промпты.\n\n## Как учиться\n\nИдите по номерам: каждые пять шагов дают очередную часть проекта. Внутри каждого узла — объяснение, разобранный пример, практическое задание с критериями и вопрос на понимание. Пример показывает приём; полноценное решение вы собираете в своём проекте.\n\nПрактику можно подтвердить самостоятельно; подключение ИИ к Loono не требуется. Если внешний инструмент платный, используйте описанный тестовый ответ или ручной сценарий, а сетевую проверку выполните отдельно.\n\n- 1. Проблема и первый сценарий\n- 2. Сборка с помощью ИИ\n- 3. Продукт, которому можно доверять\n- 4. Публикация и проверка интереса\n\nМатериалы подготовлены Loono. Дата редакции: 2026-09-07. Источники указаны рядом с уроками."
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-audience",
      "label": "01. Один пользователь",
      "area": "infra",
      "type": "large",
      "x": 0.28206959172717117,
      "y": 0.426472201515086,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-audience-reading",
          "kind": "lecture",
          "title": "Один пользователь",
          "desc": "Выбрать конкретную ситуацию вместо абстрактной аудитории.",
          "content": "Продукт начинается с повторяющейся трудности. «Сервис для фрилансеров» слишком широко: дизайнер и бухгалтер могут работать совершенно по-разному. Выберите человека в конкретной ситуации — например, дизайнер после созвона забывает вернуться к потенциальному клиенту.\n\nОпишите существующий способ решения: заметки, календарь, таблица. Это ваш первый конкурент. Человеку придётся сменить привычку, поэтому новое решение должно выигрывать в заметном действии. ИИ ускоряет создание интерфейса, но не доказывает, что проблема важна. До выбора фреймворка напишите одно предложение о том, кому и в какой момент станет легче.\n\n## Разбор на примере\n\nГипотеза: «Самостоятельный дизайнер после переговоров за минуту записывает следующий шаг и утром видит, кому ответить». Наблюдаемый результат — договорённость не теряется. «Умная CRM нового поколения» такого результата не задаёт.\n\n## Источники и следующий шаг\n\n- [MDN: изучение веб-разработки](https://developer.mozilla.org/en-US/docs/Learn_web_development)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-audience-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Выберите одну аудиторию и одну повторяющуюся ситуацию. Опишите текущий обходной путь и то, что именно в нём неудобно.",
          "task": "Выберите одну аудиторию и одну повторяющуюся ситуацию. Опишите текущий обходной путь и то, что именно в нём неудобно.",
          "content": "## Задание\n\nВыберите одну аудиторию и одну повторяющуюся ситуацию. Опишите текущий обходной путь и то, что именно в нём неудобно.\n\n## Критерии готовности\n\n- Названа конкретная роль и момент использования.\n- Известно, чем человек пользуется сейчас.\n- Польза описана действием, а не набором функций.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-audience-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Какое описание помогает спроектировать первую версию?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Платформа для всех",
              "feedback": "Неверно. Конкретный контекст задаёт вход, действие и результат продукта."
            },
            {
              "text": "Дизайнер фиксирует следующий контакт после созвона",
              "feedback": "Верно. Конкретный контекст задаёт вход, действие и результат продукта."
            },
            {
              "text": "CRM с максимальным числом функций",
              "feedback": "Неверно. Конкретный контекст задаёт вход, действие и результат продукта."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-interviews",
      "label": "02. Разговор о прошлом",
      "area": "infra",
      "type": "medium",
      "x": 0.2680344761995511,
      "y": 0.3560521074555678,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-interviews-reading",
          "kind": "lecture",
          "title": "Разговор о прошлом",
          "desc": "Отделить вежливый интерес от реальной боли.",
          "content": "На вопрос «пользовались бы вы?» легко получить вежливое согласие. Просите рассказать о последнем реальном случае: когда потерялась договорённость, как человек заметил это, что сделал и чем обошлась ошибка. Не подсказывайте проблему в самом вопросе.\n\nЗаписывайте факты отдельно от интерпретаций. «Дважды за месяц искал письмо по двадцать минут» — наблюдение. «Готов платить за наш продукт» — вывод, которого из этого ещё нет. Небольшое число разговоров помогает уточнить сценарий, но не даёт оценки рынка. Если возможности поговорить нет, явно оставьте гипотезу непроверенной и сократите стоимость разработки.\n\n## Разбор на примере\n\nТри вопроса: «Расскажите о последнем клиенте, которому нужно было написать повторно». «Где был записан следующий шаг?» «Что произошло, если вы забыли?» После ответа попросите показать существующую последовательность на вымышленных данных.\n\n## Источники и следующий шаг\n\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-interviews-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Проведите несколько коротких разговоров с подходящими людьми либо подготовьте сценарий интервью и отметьте его как ещё не проведённый. Не заменяйте интервью ответами ИИ.",
          "task": "Проведите несколько коротких разговоров с подходящими людьми либо подготовьте сценарий интервью и отметьте его как ещё не проведённый. Не заменяйте интервью ответами ИИ.",
          "content": "## Задание\n\nПроведите несколько коротких разговоров с подходящими людьми либо подготовьте сценарий интервью и отметьте его как ещё не проведённый. Не заменяйте интервью ответами ИИ.\n\n## Критерии готовности\n\n- Есть история конкретного случая.\n- Факты отделены от предположений.\n- Согласие попробовать не записано как готовность платить.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-interviews-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что лучше свидетельствует о проблеме?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Похвала идеи",
              "feedback": "Неверно. Реальное поведение надёжнее гипотетического обещания."
            },
            {
              "text": "Сгенерированный ИИ портрет клиента",
              "feedback": "Неверно. Реальное поведение надёжнее гипотетического обещания."
            },
            {
              "text": "Описание недавнего случая и затрат на обходное решение",
              "feedback": "Верно. Реальное поведение надёжнее гипотетического обещания."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-promise",
      "label": "03. Проверяемое обещание",
      "area": "infra",
      "type": "medium",
      "x": 0.276554257145051,
      "y": 0.276554257145051,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-promise-reading",
          "kind": "lecture",
          "title": "Проверяемое обещание",
          "desc": "Сформулировать пользу и условие эксперимента.",
          "content": "Сформулируйте обещание, которое можно проверить после короткого использования. Follow-up обещает показать забытые следующие контакты. Он пока не обещает рост дохода, которого вы не измеряли. Хорошая формулировка позволяет показать результат без длинного обучения.\n\nДо разработки выберите условие следующего решения: сколько приглашённых попробуют основной сценарий и сколько вернутся к нему в подходящий момент. Числа здесь — ваши критерии эксперимента, а не рыночные нормы. Для редкого рабочего действия ежедневное возвращение не подходит. Запишите ограничение времени и усилий, чтобы эксперимент не превратился в бесконечную разработку.\n\n## Разбор на примере\n\nУчебный план: пригласить 8 подходящих знакомых, помочь начать, проверить через неделю, вернулись ли хотя бы 3 к списку следующих контактов. Это пример порога решения, не доказательство спроса и не универсальная конверсия.\n\n## Источники и следующий шаг\n\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-promise-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Напишите одно обещание продукта и условие «продолжаем / меняем сценарий». Укажите, какие наблюдения нужны и когда их соберёте.",
          "task": "Напишите одно обещание продукта и условие «продолжаем / меняем сценарий». Укажите, какие наблюдения нужны и когда их соберёте.",
          "content": "## Задание\n\nНапишите одно обещание продукта и условие «продолжаем / меняем сценарий». Укажите, какие наблюдения нужны и когда их соберёте.\n\n## Критерии готовности\n\n- Обещание относится к текущей функции.\n- Критерий записан до получения результатов.\n- Период проверки соответствует частоте задачи.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-promise-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Почему заранее задают критерий эксперимента?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Чтобы гарантировать успех",
              "feedback": "Неверно. Предварительный критерий помогает честно интерпретировать результат."
            },
            {
              "text": "Чтобы не менять определение успеха после результата",
              "feedback": "Верно. Предварительный критерий помогает честно интерпретировать результат."
            },
            {
              "text": "Чтобы не разговаривать с людьми",
              "feedback": "Неверно. Предварительный критерий помогает честно интерпретировать результат."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-slice",
      "label": "04. Границы MVP",
      "area": "infra",
      "type": "medium",
      "x": 0.3107058848957834,
      "y": 0.19496108774959287,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-slice-reading",
          "kind": "lecture",
          "title": "Границы MVP",
          "desc": "Оставить один рабочий путь до ценности.",
          "content": "Минимальный продукт должен завершать задачу пользователя. Половина формы с красивым дизайном не является рабочим сценарием. Для Follow-up достаточно добавить клиента, записать следующий контакт и увидеть список просроченных действий. Автоматизация почты и командные роли пока не нужны.\n\nРазделите обязательное для сценария и полезное позднее. Сохранение данных обязательно, иначе договорённость исчезнет. Переключение тем оформления не обязательно. Для каждого пункта спросите: без него человек получит обещанный результат? Ограничение функций уменьшает число состояний, которые нужно проверить, и делает запросы к ИИ точнее.\n\n![Цельный MVP сравнивается с готовым мостом, а разрозненные незавершённые функции — с пролётами, не соединяющими берега.](/course-images/saas-mvp-v1.webp \"Мост — один полезный сценарий от начала до конца: добавить договорённость, сохранить её и увидеть, кому пора ответить. Красивые отдельные функции не заменяют этот путь. Минимальный объём не отменяет сохранность данных, доступность и защиту доступа.\")\n\n## Разбор на примере\n\nВходит: имя клиента, следующий шаг, дата, список «сегодня и просрочено», отметка выполненного. Позже: календарные интеграции, биллинг, генерация писем, командный доступ. Граница сохраняется, даже если ИИ предлагает добавить всё сразу.\n\n## Источники и следующий шаг\n\n- [MDN: изучение веб-разработки](https://developer.mozilla.org/en-US/docs/Learn_web_development)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-slice-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Составьте список первой версии и список отложенного. Нарисуйте путь из трёх экранов или состояний без дополнительных функций.",
          "task": "Составьте список первой версии и список отложенного. Нарисуйте путь из трёх экранов или состояний без дополнительных функций.",
          "content": "## Задание\n\nСоставьте список первой версии и список отложенного. Нарисуйте путь из трёх экранов или состояний без дополнительных функций.\n\n## Критерии готовности\n\n- Основной путь заканчивается результатом.\n- Есть сохранение и повторное открытие данных.\n- Каждая обязательная функция объяснена обещанием.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-slice-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что нельзя убрать из MVP Follow-up без потери смысла?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Три цветовые темы",
              "feedback": "Неверно. Без сохранения приложение не решает проблему забытых договорённостей."
            },
            {
              "text": "Командный чат",
              "feedback": "Неверно. Без сохранения приложение не решает проблему забытых договорённостей."
            },
            {
              "text": "Сохранение следующего контакта",
              "feedback": "Верно. Без сохранения приложение не решает проблему забытых договорённостей."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-prototype",
      "label": "05. Прототип до кода",
      "area": "infra",
      "type": "medium",
      "x": 0.37148619569158514,
      "y": 0.11909554727966426,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-prototype-reading",
          "kind": "lecture",
          "title": "Прототип до кода",
          "desc": "Проверить понятность основного действия.",
          "content": "Сначала набросайте пустой список, заполненный список и форму. Пользователь должен понять, с чего начать и что произойдёт после нажатия. Не просите оценить красоту: дайте задачу «добавьте клиента и запланируйте ответ на завтра» и наблюдайте, где человек остановился.\n\nДля прототипа достаточно бумаги или статичного HTML. Реальные данные не нужны. Добавьте состояния загрузки, ошибки и успешного сохранения: они формируют доверие так же, как основной экран. Кнопка «Сохранено» до ответа сервера создаёт ложное впечатление, даже если выглядит аккуратно.\n\n## Разбор на примере\n\nПустой экран: «Следующих контактов пока нет» и кнопка «Добавить клиента». После сохранения — карточка с датой и следующим действием. При ошибке формы введённый текст остаётся, а рядом появляется понятное объяснение.\n\n## Источники и следующий шаг\n\n- [W3C: доступность форм](https://www.w3.org/WAI/tutorials/forms/)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-prototype-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Сделайте прототип трёх состояний. Попросите одного человека пройти сценарий без подсказок либо проведите самостоятельный проход по заранее записанной задаче и отметьте ограничение такого теста.",
          "task": "Сделайте прототип трёх состояний. Попросите одного человека пройти сценарий без подсказок либо проведите самостоятельный проход по заранее записанной задаче и отметьте ограничение такого теста.",
          "content": "## Задание\n\nСделайте прототип трёх состояний. Попросите одного человека пройти сценарий без подсказок либо проведите самостоятельный проход по заранее записанной задаче и отметьте ограничение такого теста.\n\n## Критерии готовности\n\n- Основная кнопка понятна без объяснения.\n- Предусмотрены ошибка и пустое состояние.\n- Отказ сохранения не стирает введённые данные.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-prototype-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Как лучше проверить понятность прототипа?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Спросить, нравится ли цвет",
              "feedback": "Неверно. Прохождение задачи показывает реальные затруднения интерфейса."
            },
            {
              "text": "Дать конкретную задачу и наблюдать",
              "feedback": "Верно. Прохождение задачи показывает реальные затруднения интерфейса."
            },
            {
              "text": "Попросить ИИ поставить оценку",
              "feedback": "Неверно. Прохождение задачи показывает реальные затруднения интерфейса."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-brief",
      "label": "06. Задача для ИИ",
      "area": "data",
      "type": "large",
      "x": 0.573527798484914,
      "y": 0.28206959172717117,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-brief-reading",
          "kind": "lecture",
          "title": "Задача для ИИ",
          "desc": "Дать агенту конкретный проверяемый результат.",
          "content": "ИИ легче помогает, когда знает контекст, границы изменения и критерии готовности. Запрос «сделай CRM» оставляет слишком много решений неявными. Разбейте работу на вертикальные шаги: форма и локальное состояние; затем сохранение; затем доступ пользователей. Каждая задача заканчивается проверкой.\n\nПеред изменением существующего кода попросите инструмент объяснить текущий путь данных и назвать файлы. Укажите, какие соглашения проекта нужно соблюдать. Не отправляйте настоящий .env, клиентскую базу или ключи. Если инструмент не может запустить проверку, он должен явно сообщить это, а не утверждать работоспособность по внешнему виду кода.\n\n## Разбор на примере\n\nПример задания: «Добавь форму клиента: имя, следующий шаг, дата. Сохраняй в существующий API. При 400 покажи ошибку рядом с полем, при 500 сохрани ввод. Готово, когда запись появляется после перезагрузки и двойное нажатие не создаёт дубль. Покажи изменённые файлы и результат проверки».\n\n## Источники и следующий шаг\n\n- [Pro Git](https://git-scm.com/book/en/v2)\n- [MDN: изучение веб-разработки](https://developer.mozilla.org/en-US/docs/Learn_web_development)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-brief-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Напишите три последовательные задачи для ИИ по своему MVP. Для первой подготовьте точные входные данные и ожидаемый результат проверки.",
          "task": "Напишите три последовательные задачи для ИИ по своему MVP. Для первой подготовьте точные входные данные и ожидаемый результат проверки.",
          "content": "## Задание\n\nНапишите три последовательные задачи для ИИ по своему MVP. Для первой подготовьте точные входные данные и ожидаемый результат проверки.\n\n## Критерии готовности\n\n- У задачи одна основная цель.\n- Описаны ошибка и успешный путь.\n- Проверка имеет наблюдаемый результат.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-brief-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что полезнее добавить в запрос к ИИ?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Будь гениальным",
              "feedback": "Неверно. Конкретные критерии уменьшают число произвольных решений."
            },
            {
              "text": "Используй всё самое модное",
              "feedback": "Неверно. Конкретные критерии уменьшают число произвольных решений."
            },
            {
              "text": "Ожидаемое поведение при успешном и неуспешном сохранении",
              "feedback": "Верно. Конкретные критерии уменьшают число произвольных решений."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-stack",
      "label": "07. Стек и окружение",
      "area": "data",
      "type": "medium",
      "x": 0.6439478925444322,
      "y": 0.2680344761995511,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-stack-reading",
          "kind": "lecture",
          "title": "Стек и окружение",
          "desc": "Выбрать инструменты, которые сможете проверить.",
          "content": "Выберите небольшой привычный стек. Для учебного Follow-up подойдёт обычное веб-приложение с сервером и реляционной базой. React или другой интерфейсный инструмент — выбор реализации, а не источник спроса. Чем больше новых сервисов, тем больше регистраций, конфигурации и мест отказа.\n\nЗафиксируйте версии runtime, команды установки, запуска и проверки. ИИ должен работать в этом окружении, а не каждый раз предлагать новый фреймворк. Локальный запуск не означает готовность публикации: ещё предстоят доступ, конфигурация, хранение и наблюдаемость. Для первого прототипа допустимо локальное хранилище, но его ограничения нужно явно показывать.\n\n## Разбор на примере\n\nКарточка стека: «Интерфейс — React; сервер — Node.js; база — PostgreSQL; зависимости фиксируются lockfile; npm run dev запускает локальную разработку; тесты и build выполняются отдельно». Список можно упростить под ваш опыт.\n\n## Источники и следующий шаг\n\n- [MDN: изучение веб-разработки](https://developer.mozilla.org/en-US/docs/Learn_web_development)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-stack-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Запустите пустой проект выбранного стека. Запишите команды и воспроизведите их в чистой копии. Уберите сервисы, не требующиеся основному сценарию.",
          "task": "Запустите пустой проект выбранного стека. Запишите команды и воспроизведите их в чистой копии. Уберите сервисы, не требующиеся основному сценарию.",
          "content": "## Задание\n\nЗапустите пустой проект выбранного стека. Запишите команды и воспроизведите их в чистой копии. Уберите сервисы, не требующиеся основному сценарию.\n\n## Критерии готовности\n\n- Проект запускается по README.\n- Есть lockfile.\n- Каждая внешняя зависимость имеет понятную функцию.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-stack-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Какой стек лучше для первого эксперимента?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Максимально сложный",
              "feedback": "Неверно. Проверяемость и стоимость поддержки важнее числа технологий."
            },
            {
              "text": "Тот, который позволяет быстро собрать и проверить сценарий",
              "feedback": "Верно. Проверяемость и стоимость поддержки важнее числа технологий."
            },
            {
              "text": "Тот, который ИИ предложил последним",
              "feedback": "Неверно. Проверяемость и стоимость поддержки важнее числа технологий."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-git",
      "label": "08. Маленькие изменения",
      "area": "data",
      "type": "medium",
      "x": 0.723445742854949,
      "y": 0.276554257145051,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-git-reading",
          "kind": "lecture",
          "title": "Маленькие изменения",
          "desc": "Сохранять работающие состояния проекта.",
          "content": "После каждого завершённого изменения проверяйте diff и создавайте небольшой коммит. Это позволяет сравнить работающую версию с поломанной. ИИ может незаметно поменять соседний сценарий или удалить проверку; просмотр изменений — часть разработки, а не недоверие к инструменту.\n\nНе смешивайте обновление зависимостей, редизайн и авторизацию в одной задаче. Если что-то сломалось, сначала локализуйте изменение. Возвращение отдельного коммита предпочтительнее удаления всей рабочей папки. Незакоммиченные правки тоже могут быть ценными: перед любым откатом проверьте статус и сохраните нужное. Резервная копия репозитория и Git решают разные задачи.\n\n![Цикл работы с ИИ: небольшая задача, изменение, проверка, сохранение рабочей версии; при ошибке возврат к исправлению.](/course-images/saas-ai-loop-v1.webp \"ИИ предлагает небольшое изменение; человек проверяет diff и запускает сценарии. Ошибка возвращает работу на исправление. Рабочую версию сохраняют после проверок — затем берут следующую задачу. Убедительное объяснение модели не заменяет результат теста.\")\n\n## Разбор на примере\n\nРабочий цикл: git status → небольшая задача → запуск сценария → git diff → коммит. Название «Добавить сохранение следующего контакта» объясняет результат лучше, чем «updates». При ошибке сравните последний рабочий коммит и текущий diff.\n\n## Источники и следующий шаг\n\n- [Pro Git](https://git-scm.com/book/en/v2)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-git-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Сделайте две отдельные правки интерфейса с отдельными коммитами. Покажите diff каждой и объясните, как вернуть только вторую, сохранив первую.",
          "task": "Сделайте две отдельные правки интерфейса с отдельными коммитами. Покажите diff каждой и объясните, как вернуть только вторую, сохранив первую.",
          "content": "## Задание\n\nСделайте две отдельные правки интерфейса с отдельными коммитами. Покажите diff каждой и объясните, как вернуть только вторую, сохранив первую.\n\n## Критерии готовности\n\n- Каждый коммит имеет одну объяснимую цель.\n- Проверены незакоммиченные изменения перед откатом.\n- В истории нет секретов и клиентских данных.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-git-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Почему опасно просить «откати всё» без просмотра статуса?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Git перестанет работать",
              "feedback": "Неверно. Объём отката нужно понимать до действия."
            },
            {
              "text": "Можно потерять нужные незакоммиченные изменения",
              "feedback": "Верно. Объём отката нужно понимать до действия."
            },
            {
              "text": "Коммиты станут длиннее",
              "feedback": "Неверно. Объём отката нужно понимать до действия."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-ui",
      "label": "09. Рабочая форма",
      "area": "data",
      "type": "medium",
      "x": 0.8050389122504071,
      "y": 0.3107058848957833,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-ui-reading",
          "kind": "lecture",
          "title": "Рабочая форма",
          "desc": "Сделать ввод понятным и устойчивым к ошибкам.",
          "content": "Каждое поле имеет видимую подпись, понятный формат и сообщение об ошибке. Placeholder не заменяет label: после ввода он исчезает. Форма должна работать с клавиатуры и на узком экране. Для даты объясните смысл: день следующего контакта, а не момент в произвольном часовом поясе.\n\nВо время сохранения предотвращайте случайный повтор, но не считайте отключение кнопки серверной защитой от дублей. При ошибке сохраняйте ввод и дайте повторить. Успех показывайте после подтверждения записи. Если используете оптимистичное обновление, продумайте возврат состояния при ошибке.\n\n## Разбор на примере\n\nСостояния кнопки: «Сохранить» → «Сохраняем…» → карточка клиента. При 400 поле получает сообщение «Укажите следующий шаг». При сетевом сбое форма остаётся заполненной и показывает «Не удалось сохранить. Повторить».\n\n## Источники и следующий шаг\n\n- [W3C: доступность форм](https://www.w3.org/WAI/tutorials/forms/)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-ui-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Реализуйте форму и проверьте Tab, Enter, пустые поля, длинное имя, узкий экран и сетевую ошибку.",
          "task": "Реализуйте форму и проверьте Tab, Enter, пустые поля, длинное имя, узкий экран и сетевую ошибку.",
          "content": "## Задание\n\nРеализуйте форму и проверьте Tab, Enter, пустые поля, длинное имя, узкий экран и сетевую ошибку.\n\n## Критерии готовности\n\n- Поля связаны с label.\n- Ошибка не стирает ввод.\n- Кнопка доступна и понятна с клавиатуры.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-ui-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Когда безопасно показать «Сохранено»?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Сразу при нажатии",
              "feedback": "Неверно. Интерфейс должен отражать реальный исход операции."
            },
            {
              "text": "После ответа сервера об успешной записи либо с корректной моделью оптимистичного отката",
              "feedback": "Верно. Интерфейс должен отражать реальный исход операции."
            },
            {
              "text": "После запуска анимации",
              "feedback": "Неверно. Интерфейс должен отражать реальный исход операции."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-persist",
      "label": "10. Сохранение данных",
      "area": "data",
      "type": "medium",
      "x": 0.8809044527203358,
      "y": 0.37148619569158514,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-persist-reading",
          "kind": "lecture",
          "title": "Сохранение данных",
          "desc": "Довести сценарий через перезагрузку и перезапуск.",
          "content": "Первый вертикальный срез считается рабочим, когда запись появляется после повторного открытия. Локальный массив исчезает при перезапуске процесса. localStorage живёт в конкретном браузере и не даёт общий аккаунт между устройствами. Серверная база решает другую задачу: постоянное хранение с правилами доступа.\n\nДля Follow-up достаточно таблицы контактов с владельцем, именем, следующим действием, датой и состоянием. Валидацию повторяют на сервере. Если задача привязана к дню, храните дату как дату; превращение её в полночь UTC может сдвинуть отображение. Не добавляйте сложную синхронизацию до проверки обычного сохранения.\n\n## Разбор на примере\n\nКонтакт: id, owner_id, name, next_action, follow_up_date, status. Пользователь передаёт name, next_action и дату. owner_id задаётся сервером. После POST выполните отдельный GET и перезапустите процесс перед повторным чтением.\n\n## Источники и следующий шаг\n\n- [MDN: изучение веб-разработки](https://developer.mozilla.org/en-US/docs/Learn_web_development)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-persist-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Подключите постоянное хранение. Создайте контакт, перезагрузите страницу и перезапустите сервер. Проверьте дату на границе суток.",
          "task": "Подключите постоянное хранение. Создайте контакт, перезагрузите страницу и перезапустите сервер. Проверьте дату на границе суток.",
          "content": "## Задание\n\nПодключите постоянное хранение. Создайте контакт, перезагрузите страницу и перезапустите сервер. Проверьте дату на границе суток.\n\n## Критерии готовности\n\n- Данные сохраняются после обоих перезапусков.\n- Владелец не задаётся произвольным полем формы.\n- Дата следующего контакта не смещается неожиданно.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-persist-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что доказывает сохранение только в localStorage?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Серверная база работает",
              "feedback": "Неверно. Локальное хранилище не заменяет серверный аккаунт и синхронизацию."
            },
            {
              "text": "Данные доступны на любом устройстве",
              "feedback": "Неверно. Локальное хранилище не заменяет серверный аккаунт и синхронизацию."
            },
            {
              "text": "Запись хранится в данном браузере при сохранности его хранилища",
              "feedback": "Верно. Локальное хранилище не заменяет серверный аккаунт и синхронизацию."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-auth",
      "label": "11. Вход и изоляция",
      "area": "auth",
      "type": "large",
      "x": 0.7179304082728288,
      "y": 0.573527798484914,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-auth-reading",
          "kind": "lecture",
          "title": "Вход и изоляция",
          "desc": "Проверить продукт двумя пользователями.",
          "content": "Регистрация не является целью продукта: она помогает безопасно сохранять личные данные. Не заставляйте человека проходить её до понимания пользы, если сценарий допускает демонстрацию. Но демонстрационные данные должны быть вымышленными, а приватные записи — изолированными.\n\nДля входа используйте поддерживаемое решение, не собственную криптографию. На сервере проверяйте владельца при каждой операции. Кнопки интерфейса не являются защитой: запрос можно отправить напрямую. Временный тестовый вход должен отсутствовать в опубликованном окружении. Два тестовых пользователя позволяют обнаружить типовую ошибку «все вошедшие видят все записи».\n\n## Разбор на примере\n\nАня создаёт контакт. Борис открывает его URL и отправляет прямой запрос чтения, изменения и удаления. Во всех случаях доступа нет. Список Бориса также не содержит запись Ани. Выход делает прежнюю сессию недействительной согласно выбранной схеме.\n\n## Источники и следующий шаг\n\n- [OWASP: рекомендации](https://cheatsheetseries.owasp.org/)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-auth-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Подключите вход и проведите проверку двух аккаунтов. Запишите результаты доступа по прямому ID, а не только видимость кнопок.",
          "task": "Подключите вход и проведите проверку двух аккаунтов. Запишите результаты доступа по прямому ID, а не только видимость кнопок.",
          "content": "## Задание\n\nПодключите вход и проведите проверку двух аккаунтов. Запишите результаты доступа по прямому ID, а не только видимость кнопок.\n\n## Критерии готовности\n\n- Чужие данные не читаются и не изменяются.\n- Тестовый вход не активен в опубликованной конфигурации.\n- Выход и истечение сессии обработаны.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-auth-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Почему скрытой кнопки удаления недостаточно?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Её цвет может измениться",
              "feedback": "Неверно. Реальные ограничения применяются на сервере."
            },
            {
              "text": "Запрос можно отправить без интерфейса",
              "feedback": "Верно. Реальные ограничения применяются на сервере."
            },
            {
              "text": "Пользователь может забыть пароль",
              "feedback": "Неверно. Реальные ограничения применяются на сервере."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-debug",
      "label": "12. Разобраться в поломке",
      "area": "auth",
      "type": "medium",
      "x": 0.7319655238004489,
      "y": 0.6439478925444322,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-debug-reading",
          "kind": "lecture",
          "title": "Разобраться в поломке",
          "desc": "Дать ИИ воспроизводимую ошибку.",
          "content": "Когда приложение сломалось, соберите точный сценарий: исходное состояние, действие, ожидаемый результат, фактический результат. Посмотрите запрос в Network и журнал сервера. Не отправляйте ИИ весь журнал без просмотра: там могут быть токены и приватные данные.\n\nПросите сначала объяснить вероятную причину и предложить минимальную проверку гипотезы. Ошибка интерфейса может оказаться неправильным ответом сервера, а ошибка сервера — конфигурацией. Случайное обновление библиотек редко помогает локализовать причину. После исправления повторите исходный сценарий и соседний рабочий путь. Если ошибка не воспроизводится, сохраните наблюдение, а не объявляйте исправление доказанным.\n\n## Разбор на примере\n\nХорошее описание: «POST /contacts возвращает 201, после reload список пуст. В GET /contacts — 200 и []. Сервер перезапускается между вызовами. Ожидание: созданный контакт остаётся». Это направляет проверку к хранилищу и окружению.\n\n## Источники и следующий шаг\n\n- [Playwright: введение](https://playwright.dev/docs/intro)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-debug-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Намеренно внесите небольшую локальную ошибку сохранения, сформулируйте воспроизведение и исправьте её. Сохраните до/после и проверку повторного чтения.",
          "task": "Намеренно внесите небольшую локальную ошибку сохранения, сформулируйте воспроизведение и исправьте её. Сохраните до/после и проверку повторного чтения.",
          "content": "## Задание\n\nНамеренно внесите небольшую локальную ошибку сохранения, сформулируйте воспроизведение и исправьте её. Сохраните до/после и проверку повторного чтения.\n\n## Критерии готовности\n\n- Описан точный запрос и исход.\n- Из журналов убраны секреты.\n- Исправление проверено тем же сценарием.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-debug-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что полезнее передать ИИ при ошибке?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Всё сломалось",
              "feedback": "Неверно. Минимальные точные данные помогают локализовать причину."
            },
            {
              "text": "Весь диск проекта без отбора",
              "feedback": "Неверно. Минимальные точные данные помогают локализовать причину."
            },
            {
              "text": "Шаги воспроизведения и обезличенный ответ проблемного запроса",
              "feedback": "Верно. Минимальные точные данные помогают локализовать причину."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-acceptance",
      "label": "13. Приёмочные сценарии",
      "area": "auth",
      "type": "medium",
      "x": 0.723445742854949,
      "y": 0.723445742854949,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-acceptance-reading",
          "kind": "lecture",
          "title": "Приёмочные сценарии",
          "desc": "Проверить обещание продукта целиком.",
          "content": "Сборка без ошибок доказывает, что код собрался, но не что человек может решить задачу. Подготовьте несколько сквозных сценариев в браузере: новый пользователь создаёт контакт, меняет дату, видит просроченный контакт и завершает действие. Добавьте отказ сети и попытку чужого доступа.\n\nАвтоматизируйте стабильный основной путь, а визуальную понятность проверьте отдельно. Не делайте тест, который лишь подтверждает существование кнопки: нажмите её и проверьте сохранённый результат после перезагрузки. На время теста используйте отдельные данные. Если ИИ пишет тесты вместе с кодом, намеренно нарушьте ключевое правило и убедитесь, что тест способен это обнаружить.\n\n## Разбор на примере\n\nСценарий: заполнить имя и следующий шаг → сохранить → перезагрузить → увидеть ту же запись → сменить дату → перезагрузить → увидеть новую дату. Отдельный сценарий: отказ сервера сохраняет введённый текст.\n\n## Источники и следующий шаг\n\n- [Playwright: введение](https://playwright.dev/docs/intro)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-acceptance-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Запишите пять приёмочных сценариев. Автоматизируйте хотя бы основной путь и ручным проходом проверьте мобильный экран и клавиатуру.",
          "task": "Запишите пять приёмочных сценариев. Автоматизируйте хотя бы основной путь и ручным проходом проверьте мобильный экран и клавиатуру.",
          "content": "## Задание\n\nЗапишите пять приёмочных сценариев. Автоматизируйте хотя бы основной путь и ручным проходом проверьте мобильный экран и клавиатуру.\n\n## Критерии готовности\n\n- Проверка включает перезагрузку.\n- Есть негативный сценарий.\n- Тест действительно падает при нарушении сохранения.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-acceptance-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что лучше подтверждает готовность формы?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Кнопка есть в DOM",
              "feedback": "Неверно. Сквозная проверка подтверждает пользовательский результат."
            },
            {
              "text": "Build завершился",
              "feedback": "Неверно. Сквозная проверка подтверждает пользовательский результат."
            },
            {
              "text": "Запись появилась после сохранения и перезагрузки",
              "feedback": "Верно. Сквозная проверка подтверждает пользовательский результат."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-onboarding",
      "label": "14. Первый полезный результат",
      "area": "auth",
      "type": "medium",
      "x": 0.6892941151042167,
      "y": 0.8050389122504071,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-onboarding-reading",
          "kind": "lecture",
          "title": "Первый полезный результат",
          "desc": "Помочь начать без длинной экскурсии.",
          "content": "Пользователь пришёл решить задачу, а не изучить устройство приложения. Первый экран должен вести к одному действию. Для Follow-up это создание первого следующего контакта. Демонстрационная карточка может объяснить формат, но её нельзя выдавать за реальные данные пользователя.\n\nСократите обязательные поля до нужных сценарию. Телефон, должность, аватар и отрасль не требуются, чтобы записать следующий шаг. После первого успеха покажите, где найти задачу завтра. Не путайте активацию с регистрацией: аккаунт создан, но ценность может быть ещё не получена. Определите событие, которое соответствует обещанию.\n\n## Разбор на примере\n\nПервый экран: «Кому нужно ответить?» Поля: имя, следующий шаг, дата. После сохранения: «Готово. Этот контакт появится в списке на выбранный день». Событие активации — первый сохранённый следующий контакт, а не открытие формы.\n\n## Источники и следующий шаг\n\n- [W3C: доступность форм](https://www.w3.org/WAI/tutorials/forms/)\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-onboarding-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Пройдите приложение как новый пользователь. Уберите одно необязательное препятствие до первой ценности и измерьте число действий в сценарии.",
          "task": "Пройдите приложение как новый пользователь. Уберите одно необязательное препятствие до первой ценности и измерьте число действий в сценарии.",
          "content": "## Задание\n\nПройдите приложение как новый пользователь. Уберите одно необязательное препятствие до первой ценности и измерьте число действий в сценарии.\n\n## Критерии готовности\n\n- Первое действие понятно без тура.\n- Демо-данные явно обозначены.\n- Активация связана с сохранённым результатом.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-onboarding-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что ближе к активации Follow-up?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Открытие лендинга",
              "feedback": "Неверно. Активация должна отражать получение основной пользы."
            },
            {
              "text": "Первый сохранённый следующий контакт",
              "feedback": "Верно. Активация должна отражать получение основной пользы."
            },
            {
              "text": "Выбор цвета аватара",
              "feedback": "Неверно. Активация должна отражать получение основной пользы."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-analytics",
      "label": "15. События без шума",
      "area": "auth",
      "type": "medium",
      "x": 0.6285138043084149,
      "y": 0.8809044527203357,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-analytics-reading",
          "kind": "lecture",
          "title": "События без шума",
          "desc": "Измерить путь от входа до пользы.",
          "content": "Для первого эксперимента достаточно нескольких событий: открытие продукта, начало основного действия, успешное сохранение, повторное полезное действие. Не отправляйте в аналитику имена клиентов, текст договорённостей, ключи и полные тела запросов. Агрегированных событий обычно достаточно для проверки гипотезы.\n\nСобытие успеха отправляется после подтверждённой операции. Повторы и собственные тесты могут искажать числа, поэтому задайте правило исключения технических посещений. Проверяйте инструмент в режиме, соответствующем выбранным настройкам согласия. Отсутствие события не всегда означает отсутствие действия: блокировщик или отказ от аналитики создают неполные данные.\n\n## Разбор на примере\n\nСобытия: contact_form_opened, follow_up_created, follow_up_completed. Разрешённые свойства: версия интерфейса и тип входа. Имя клиента и next_action не передаются. Для повторной отправки используйте идентификатор события, если система поддерживает дедупликацию.\n\n## Источники и следующий шаг\n\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-analytics-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Добавьте три события и проверьте момент отправки. Убедитесь, что неуспешное сохранение не учитывается как активация.",
          "task": "Добавьте три события и проверьте момент отправки. Убедитесь, что неуспешное сохранение не учитывается как активация.",
          "content": "## Задание\n\nДобавьте три события и проверьте момент отправки. Убедитесь, что неуспешное сохранение не учитывается как активация.\n\n## Критерии готовности\n\n- События соответствуют бизнес-результату.\n- В свойствах нет пользовательского содержимого.\n- Известны ограничения полноты данных и собственные тесты.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-analytics-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Когда отправлять follow_up_created?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "При открытии формы",
              "feedback": "Неверно. Иначе метрика считает попытки, а не полученную пользу."
            },
            {
              "text": "При каждом нажатии кнопки",
              "feedback": "Неверно. Иначе метрика считает попытки, а не полученную пользу."
            },
            {
              "text": "После подтверждённого создания записи",
              "feedback": "Верно. Иначе метрика считает попытки, а не полученную пользу."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-release",
      "label": "16. Первый выпуск",
      "area": "deploy",
      "type": "large",
      "x": 0.426472201515086,
      "y": 0.7179304082728288,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-release-reading",
          "kind": "lecture",
          "title": "Первый выпуск",
          "desc": "Опубликовать проверенную версию с возможностью возврата.",
          "content": "Перед публикацией проверьте production-конфигурацию: адрес API, доступ к базе, отсутствие тестового входа, обработку ошибок и HTTPS. Секреты передаются через настройки окружения, а не через frontend bundle. Изучите минимальные требования выбранного хостинга и стоимость, не подключая лишние услуги автоматически.\n\nСохраните версию артефакта и способ возврата. После выпуска повторите реальный сценарий создания и чтения: локальная проверка не обнаружит ошибку внешнего адреса или cookie. Разделяйте работоспособность приложения и сохранность данных. Для эксперимента достаточно небольшого окружения, но должно быть понятно, что произойдёт при перезапуске и как восстановить учебные данные.\n\n## Разбор на примере\n\nПроверка после выпуска: открыть сайт в новой сессии → войти тестовым аккаунтом → создать контакт → перезагрузить → проверить запись → выйти → убедиться в отказе доступа. Удалите только созданные вами тестовые записи по их точным ID.\n\n## Источники и следующий шаг\n\n- [MDN: изучение веб-разработки](https://developer.mozilla.org/en-US/docs/Learn_web_development)\n- [Playwright: введение](https://playwright.dev/docs/intro)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-release-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Подготовьте конфигурацию публикации и проведите репетицию на отдельном окружении. Если публикация требует оплаты, сначала запишите выбранный тариф и ограничения; оплату выполняйте осознанно.",
          "task": "Подготовьте конфигурацию публикации и проведите репетицию на отдельном окружении. Если публикация требует оплаты, сначала запишите выбранный тариф и ограничения; оплату выполняйте осознанно.",
          "content": "## Задание\n\nПодготовьте конфигурацию публикации и проведите репетицию на отдельном окружении. Если публикация требует оплаты, сначала запишите выбранный тариф и ограничения; оплату выполняйте осознанно.\n\n## Критерии готовности\n\n- В клиентском коде нет секретов.\n- Известен рабочий предыдущий артефакт.\n- Проверен сквозной сценарий в опубликованном окружении либо явно отмечено, что проверен только локальный запуск.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-release-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Достаточно ли локального build для подтверждения релиза?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Да",
              "feedback": "Неверно. Конфигурация и сеть могут отличаться от локального окружения."
            },
            {
              "text": "Нет, нужен запуск и проверка внешнего сценария",
              "feedback": "Верно. Конфигурация и сеть могут отличаться от локального окружения."
            },
            {
              "text": "Да, если ИИ сказал, что готово",
              "feedback": "Неверно. Конфигурация и сеть могут отличаться от локального окружения."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-landing",
      "label": "17. Страница с обещанием",
      "area": "deploy",
      "type": "medium",
      "x": 0.3560521074555678,
      "y": 0.7319655238004489,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-landing-reading",
          "kind": "lecture",
          "title": "Страница с обещанием",
          "desc": "Привести человека сразу к понятному действию.",
          "content": "Лендинг объясняет, кому полезен продукт, какую задачу решает и что делать дальше. Заголовок должен соответствовать реальной функции. Покажите короткий пример до/после, одну основную кнопку и ограничения. Не обещайте автоматические письма, если в первой версии есть только список контактов.\n\nДля поиска странице нужны доступный текст, уникальные заголовок и описание, правильный canonical и рабочие ссылки. Индексация не равна трафику, а трафик не равен полезному использованию. Свяжите обещание лендинга с первым экраном: человек, пришедший за списком следующих контактов, не должен сначала разбираться в универсальной CRM.\n\n## Разбор на примере\n\nЗаголовок: «Не теряйте следующий разговор с клиентом». Подзаголовок: «Запишите договорённость и увидьте, кому пора ответить». Кнопка: «Добавить первый контакт». Пример использует вымышленные имена и показывает дату следующего шага.\n\n## Источники и следующий шаг\n\n- [Google: основы SEO](https://developers.google.com/search/docs/fundamentals/seo-starter-guide)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-landing-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Соберите одну страницу продукта. Сверьте каждое обещание с реализованным сценарием и проверьте путь от кнопки до результата.",
          "task": "Соберите одну страницу продукта. Сверьте каждое обещание с реализованным сценарием и проверьте путь от кнопки до результата.",
          "content": "## Задание\n\nСоберите одну страницу продукта. Сверьте каждое обещание с реализованным сценарием и проверьте путь от кнопки до результата.\n\n## Критерии готовности\n\n- Нет обещаний ещё не созданных функций.\n- Основной текст доступен без обязательной регистрации.\n- Кнопка ведёт в соответствующий сценарий.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-landing-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что показывает попадание страницы в индекс?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Продукт точно востребован",
              "feedback": "Неверно. Индексация — технический этап, спрос проверяется дальнейшим поведением."
            },
            {
              "text": "Страница может участвовать в поисковой выдаче",
              "feedback": "Верно. Индексация — технический этап, спрос проверяется дальнейшим поведением."
            },
            {
              "text": "Пользователи готовы платить",
              "feedback": "Неверно. Индексация — технический этап, спрос проверяется дальнейшим поведением."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-first-users",
      "label": "18. Первые пользователи",
      "area": "deploy",
      "type": "medium",
      "x": 0.276554257145051,
      "y": 0.723445742854949,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-first-users-reading",
          "kind": "lecture",
          "title": "Первые пользователи",
          "desc": "Наблюдать использование подходящей аудиторией.",
          "content": "Начните с людей из выбранной ситуации. Массовый случайный трафик может дать много посещений и мало полезных выводов. Приглашение должно честно объяснять раннюю версию, сценарий и ожидаемое время. Попросите человека выполнить настоящую задачу с допустимыми данными, а не просто посмотреть сайт.\n\nОтмечайте, где вы помогли вручную: такое прохождение не равно самостоятельной активации. После использования спросите, что человек сделал бы без сервиса и что мешает вернуться. Не добавляйте каждую пожеланную функцию сразу. Повторяющаяся проблема нескольких подходящих пользователей весит больше единичной идеи о будущем.\n\n## Разбор на примере\n\nПриглашение: «Собрал простой список следующих контактов. Хочу проверить, помогает ли он не забывать договорённости после созвонов. Попробуй записать один следующий шаг; мне важно узнать, где неудобно». Это черновик, отправлять его нужно только выбранным вами людям.\n\n## Источники и следующий шаг\n\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-first-users-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Подготовьте список подходящих участников и приглашение. Проведите несколько проходов; отдельно отметьте самостоятельные и проходы с вашей помощью.",
          "task": "Подготовьте список подходящих участников и приглашение. Проведите несколько проходов; отдельно отметьте самостоятельные и проходы с вашей помощью.",
          "content": "## Задание\n\nПодготовьте список подходящих участников и приглашение. Проведите несколько проходов; отдельно отметьте самостоятельные и проходы с вашей помощью.\n\n## Критерии готовности\n\n- Участники соответствуют исходной аудитории.\n- Есть реальные наблюдения, а не только похвала.\n- Ручная помощь не скрыта в метриках.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-first-users-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Как интерпретировать проход, где вы всё время подсказывали?",
          "correctAnswer": 2,
          "options": [
            {
              "text": "Как полностью самостоятельную активацию",
              "feedback": "Неверно. Помощь меняет условия эксперимента и должна учитываться."
            },
            {
              "text": "Как доказательство спроса",
              "feedback": "Неверно. Помощь меняет условия эксперимента и должна учитываться."
            },
            {
              "text": "Как проход с помощью, полезный для поиска затруднений",
              "feedback": "Верно. Помощь меняет условия эксперимента и должна учитываться."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-economics",
      "label": "19. Цена и расходы",
      "area": "deploy",
      "type": "medium",
      "x": 0.19496108774959292,
      "y": 0.6892941151042167,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-economics-reading",
          "kind": "lecture",
          "title": "Цена и расходы",
          "desc": "Проверить, что полезный сценарий экономически возможен.",
          "content": "Цена продукта и его себестоимость — разные величины. Посчитайте постоянные расходы и переменные на активного пользователя: хранение, почта, внешние API и ИИ, если он действительно есть в продукте. ИИ-инструмент, которым вы разрабатывали приложение, относится к вашим расходам разработки; это не обязательно расход каждого запроса пользователя.\n\nДля ИИ-функции учитывайте всю цепочку вызовов, повторы и лимиты. Не обещайте безлимитный тариф до понимания распределения использования. Обсудите цену с человеком в контексте полученной пользы, а не задавайте абстрактный вопрос «сколько готовы платить». Для начала можно проверить интерес к платной версии без реализации сложного биллинга, ясно обозначив, что оплата ещё не подключена.\n\n## Разбор на примере\n\nУчебная модель: постоянные расходы 1000 условных единиц в месяц; переменные — 20 на активного пользователя; цена — 100. Вклад пользователя до прочих расходов — 80; для покрытия 1000 нужно не меньше 13 таких пользователей. Это пример арифметики, не тариф и не прогноз.\n\n## Источники и следующий шаг\n\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-economics-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Составьте таблицу расходов и три сценария использования: низкий, типичный, высокий. Отдельно отметьте расходы разработки и обслуживания пользователей.",
          "task": "Составьте таблицу расходов и три сценария использования: низкий, типичный, высокий. Отдельно отметьте расходы разработки и обслуживания пользователей.",
          "content": "## Задание\n\nСоставьте таблицу расходов и три сценария использования: низкий, типичный, высокий. Отдельно отметьте расходы разработки и обслуживания пользователей.\n\n## Критерии готовности\n\n- Платные вызовы посчитаны на весь сценарий.\n- Есть предел расходов при активном пользователе.\n- Гипотетический интерес к цене не выдан за оплату.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-economics-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Подписка на ИИ для написания кода автоматически делает каждый запрос пользователя платным?",
          "correctAnswer": 0,
          "options": [
            {
              "text": "Нет, это зависит от архитектуры самого продукта",
              "feedback": "Верно. Разработка с ИИ и встроенный ИИ — разные источники расходов."
            },
            {
              "text": "Да",
              "feedback": "Неверно. Разработка с ИИ и встроенный ИИ — разные источники расходов."
            },
            {
              "text": "Да, если в названии есть ИИ",
              "feedback": "Неверно. Разработка с ИИ и встроенный ИИ — разные источники расходов."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    },
    {
      "id": "saas-v1-decision",
      "label": "20. Решение по эксперименту",
      "area": "deploy",
      "type": "medium",
      "x": 0.11909554727966432,
      "y": 0.6285138043084149,
      "locked": true,
      "sections": [
        {
          "id": "saas-v1-decision-reading",
          "kind": "lecture",
          "title": "Решение по эксперименту",
          "desc": "Выбрать следующий шаг по наблюдениям.",
          "content": "Сопоставьте результат с критерием, записанным до запуска. Отдельно рассмотрите приход, активацию, повторное полезное действие и готовность платить. Если никто не дошёл до формы, причина может быть в привлечении или обещании. Если форму заполняют, но не возвращаются, проверьте ценность и частоту задачи. Маленькая выборка даёт направление для следующей проверки, а не окончательный приговор рынку.\n\nВыберите одно изменение с понятной гипотезой. Не переписывайте весь продукт после двух отзывов. Если проблема не подтверждается и участники предпочитают привычную таблицу, допустимо остановить эксперимент. Сохраните работающие материалы и выводы, чтобы следующая попытка стоила дешевле.\n\n## Разбор на примере\n\nПример: из 8 приглашённых 5 открыли сервис, 3 создали контакт, 2 вернулись через неделю. Двум потребовалась помощь с датой. Следующая гипотеза — упростить выбор даты и повторить проверку; утверждать доказанный спрос по этим числам рано.\n\n## Источники и следующий шаг\n\n- [PostHog: продуктовая аналитика](https://posthog.com/docs/product-analytics)",
          "tags": [
            "разбор",
            "пример"
          ]
        },
        {
          "id": "saas-v1-decision-practice",
          "kind": "code",
          "language": "Markdown",
          "title": "Практика: применить в проекте",
          "desc": "Запишите фактическую воронку, три наблюдения и одно решение: продолжить текущий сценарий, изменить его или остановить. Для выбранного изменения задайте новую проверку.",
          "task": "Запишите фактическую воронку, три наблюдения и одно решение: продолжить текущий сценарий, изменить его или остановить. Для выбранного изменения задайте новую проверку.",
          "content": "## Задание\n\nЗапишите фактическую воронку, три наблюдения и одно решение: продолжить текущий сценарий, изменить его или остановить. Для выбранного изменения задайте новую проверку.\n\n## Критерии готовности\n\n- Цифры имеют понятные знаменатели и период.\n- Технические тесты и помощь отмечены отдельно.\n- Следующий шаг связан с наблюдаемой проблемой.\n\nЗапустите проверки в своём проекте. В ответ запишите результат и приложите небольшой фрагмент решения или вывод команды. Самопроверка не исполняет ваш код и не заменяет проверку в рабочем окружении.",
          "tags": [
            "практика",
            "самопроверка"
          ]
        },
        {
          "id": "saas-v1-decision-quiz",
          "kind": "quiz",
          "title": "Проверка понимания",
          "question": "Что делать с низкой активацией после нескольких посещений?",
          "correctAnswer": 1,
          "options": [
            {
              "text": "Сразу переписать весь продукт",
              "feedback": "Неверно. Малая выборка и разные этапы воронки требуют аккуратной локализации причины."
            },
            {
              "text": "Локализовать место потери и проверить одну гипотезу",
              "feedback": "Верно. Малая выборка и разные этапы воронки требуют аккуратной локализации причины."
            },
            {
              "text": "Объявить рынок отсутствующим",
              "feedback": "Неверно. Малая выборка и разные этапы воронки требуют аккуратной локализации причины."
            }
          ],
          "tags": [
            "тест"
          ]
        }
      ],
      "test": null
    }
  ],
  "connections": [
    {
      "from": "start",
      "to": "saas-v1-audience",
      "kind": "root"
    },
    {
      "from": "saas-v1-audience",
      "to": "saas-v1-interviews",
      "kind": "child"
    },
    {
      "from": "saas-v1-interviews",
      "to": "saas-v1-promise",
      "kind": "child"
    },
    {
      "from": "saas-v1-promise",
      "to": "saas-v1-slice",
      "kind": "child"
    },
    {
      "from": "saas-v1-slice",
      "to": "saas-v1-prototype",
      "kind": "child"
    },
    {
      "from": "saas-v1-prototype",
      "to": "saas-v1-brief",
      "kind": "root"
    },
    {
      "from": "saas-v1-brief",
      "to": "saas-v1-stack",
      "kind": "child"
    },
    {
      "from": "saas-v1-stack",
      "to": "saas-v1-git",
      "kind": "child"
    },
    {
      "from": "saas-v1-git",
      "to": "saas-v1-ui",
      "kind": "child"
    },
    {
      "from": "saas-v1-ui",
      "to": "saas-v1-persist",
      "kind": "child"
    },
    {
      "from": "saas-v1-persist",
      "to": "saas-v1-auth",
      "kind": "root"
    },
    {
      "from": "saas-v1-auth",
      "to": "saas-v1-debug",
      "kind": "child"
    },
    {
      "from": "saas-v1-debug",
      "to": "saas-v1-acceptance",
      "kind": "child"
    },
    {
      "from": "saas-v1-acceptance",
      "to": "saas-v1-onboarding",
      "kind": "child"
    },
    {
      "from": "saas-v1-onboarding",
      "to": "saas-v1-analytics",
      "kind": "child"
    },
    {
      "from": "saas-v1-analytics",
      "to": "saas-v1-release",
      "kind": "root"
    },
    {
      "from": "saas-v1-release",
      "to": "saas-v1-landing",
      "kind": "child"
    },
    {
      "from": "saas-v1-landing",
      "to": "saas-v1-first-users",
      "kind": "child"
    },
    {
      "from": "saas-v1-first-users",
      "to": "saas-v1-economics",
      "kind": "child"
    },
    {
      "from": "saas-v1-economics",
      "to": "saas-v1-decision",
      "kind": "child"
    }
  ],
  "areas": [
    {
      "id": "infra",
      "label": "Проблема и первый сценарий",
      "color": "#6C5CE7",
      "cx": 0.2807968978321703,
      "cy": 0.28079689783217027
    },
    {
      "id": "data",
      "label": "Сборка с помощью ИИ",
      "color": "#00B894",
      "cx": 0.7192031021678298,
      "cy": 0.2807968978321703
    },
    {
      "id": "auth",
      "label": "Продукт, которому можно доверять",
      "color": "#E17055",
      "cx": 0.7192031021678298,
      "cy": 0.7192031021678297
    },
    {
      "id": "deploy",
      "label": "Публикация и проверка интереса",
      "color": "#00CEC9",
      "cx": 0.2807968978321703,
      "cy": 0.7192031021678298
    }
  ],
  "notes": []
}
