# Техническое задание — ProfCalc

**Версия документа:** 1.0 · реконструировано из кодовой базы (reverse-engineered)
**Дата:** 2026-06-24
**Платформа:** PLUR Solutions · Алматы
**Продукт:** SaaS-платформа для расчёта оконных, дверных и витражных конструкций

---

## 1. Назначение и общее описание

ProfCalc — облачная платформа расчёта светопрозрачных конструкций (окна, двери, витражи/фасады) из ПВХ, алюминия, дерева и дерево-алюминия. Продукт решает три задачи:

1. **Расчёт стоимости** конструкции с инженерной точностью (профиль, фурнитура, стекло, армирование, уплотнители, расходники, монтаж) по трём уровням цен.
2. **Инженерный раскрой** профиля по хлыстам с деловыми остатками, картой реза для цеха и советами по экономии материала.
3. **Документооборот**: коммерческие предложения (КП), заявки-накладные, спецификации стекла и фурнитуры в PDF, CRM-книга клиентов, заявки/лиды, уведомления.

Продукт состоит из трёх поверхностей:

| Поверхность | URL | Аудитория | Технология |
|---|---|---|---|
| **Telegram Mini App** | `/miniapp/` | оконщики, прорабы, цеха, розничные клиенты | vanilla-JS SPA |
| **Веб-суперадминка** | `/admin/` | команда PLUR Solutions | vanilla-JS SPA (desktop-only, 1280px) |
| **Telegram-бот** | [@profcalckz_bot](https://t.me/profcalckz_bot) | все пользователи | long-polling процесс |

---

## 2. Архитектура и технологический стек

### 2.1 Стек
- **Runtime:** Node.js (ESM-модули).
- **Веб-сервер:** Express 4.
- **БД:** SQLite через `better-sqlite3` (синхронный драйвер), файл `data/profcalc.db`, режим WAL.
- **PDF:** PDFKit + системные шрифты DejaVu (кириллица + символ ₸).
- **Бот:** `node-telegram-bot-api` (long-polling), отдельный процесс.
- **Фронтенд:** чистый vanilla JS без фреймворков и сборщика; рендеринг через DOM-фабрику `h()`; SVG-отрисовка окон.

### 2.2 Структура процессов
- **Веб-процесс** (`server.js`, `npm start`): API + статика admin/miniapp/shared + лендинг + SPA-fallback. Порт `PORT` (деф. 3000), хост `HOST` (деф. 0.0.0.0).
- **Бот-процесс** (`bot.js`, `npm run bot`): отдельный, требует `BOT_TOKEN`, `MINIAPP_URL` (HTTPS), опц. `ADMIN_TG_IDS`.

### 2.3 Серверные модули
| Файл | Назначение |
|---|---|
| `server.js` | точка входа: статика + API + no-cache заголовки + SPA-fallback |
| `server/db.js` | схема SQLite (30+ таблиц), сид-данные, миграции, хелперы |
| `server/calc.js` | ценовой и инженерный движок расчёта (1500+ строк) |
| `server/shapes.js` | геометрия 13 нестандартных форм контура |
| `server/routes.js` | REST API (Express-роутер `/api`) |
| `server/pdf.js` | генерация всех PDF-документов |
| `server/advisor.js` | советник по экономии раскроя |
| `server/auth-admin.js` | авторизация админки (scrypt + сессии в SQLite) |
| `server/telegram-auth.js` | верификация Telegram initData (HMAC-SHA256) |

### 2.4 Фронтенд-модули
| Файл | Назначение |
|---|---|
| `public/index.html` | лендинг с переходами на admin/miniapp |
| `public/miniapp/miniapp.js` | Mini App SPA (3549 строк, 19 экранов) |
| `public/admin/admin.js` | админка SPA (8 разделов + 19 справочников) |
| `public/shared/window-svg.js` | отрисовка окон: `WindowSchema`, `WindowSection`, `WindowIsometric`, `MiniOpeningGlyph`, `WINDOW_TEMPLATES` |
| `public/shared/api.js` | обёртка fetch + авторизация (`api`, `authUrl`, форматтеры) |

### 2.5 Кэширование статики
Все HTML/JS/CSS/SVG отдаются с `Cache-Control: no-cache, no-store, must-revalidate` — Telegram WebApp агрессивно кэширует, поэтому форсируется ревалидация на каждом запросе.

---

## 3. Роли и модель доступа

### 3.1 Типы пользователей
| Роль | Уровень цен | Описание |
|---|---|---|
| **Гость (guest)** | retail | не авторизован |
| **Розничный клиент (client)** | retail | конечный покупатель, идентификация по Telegram; создаётся автоматически при первом обращении |
| **Оконщик (okonshchik)** | dealer | B2B-аккаунт, дилерские цены + персональные скидки |
| **Прораб (prorab)** | dealer | B2B-аккаунт, отдельная политика |
| **Цех / завод (tsekh)** | dealer/base | B2B-аккаунт, прямой доступ к каталогам |
| **Суперадмин** | — | полный доступ через `/admin/` (cookie-сессия) |

> Уровни цен `base / dealer / retail` хранятся в каждом артикуле прайс-листа. Привязка роль → уровень реализуется в роутах: монтажник всегда получает `dealer`, гость/клиент — `retail`.

### 3.2 Механизмы аутентификации
- **Mini App:** автоматически отправляет `X-Telegram-Init-Data` (валидация HMAC-SHA256 по схеме Telegram WebApp; отклонение просроченного >24ч initData).
- **PDF/навигации:** канал `?tgauth=<initData>` в query (top-level навигации браузера не могут ставить заголовки). Сервер редактирует `tgauth` в логах.
- **Dev-имперсонация:** `?as=<installer_id>` — имитация оконщика; **только при `NODE_ENV !== 'production'`**.
- **Админка:** отдельная cookie-сессия (`pcadmin`, HttpOnly, SameSite=Lax, TTL 12ч, Secure в проде); scrypt-хэш пароля + соль в SQLite.

### 3.3 Правила владения (ownership)
- `ownerKey(req)` = `i:<installerId>` для монтажника, иначе `tg:<telegramId>`.
- На всех `:id`-роутах (projects, calculations, kp, crm, orders, notifications) проверяется владелец; чужой/анонимный → **404** (не 403, чтобы не раскрывать существование id); валидная админ-сессия → байпас.
- `requireAdmin` на всех мутирующих CRUD, `/analytics`, `/log`, `/discounts`, `/installers`.

---

## 4. Функциональные требования: Telegram Mini App

SPA с hash-роутингом (`#/route`), 19 экранов, нижняя навигация из 5 вкладок (Главная / Проекты / Каталог / Документы / Профиль), управление кнопками Telegram WebApp (BackButton/MainButton/HapticFeedback).

### 4.1 Онбординг и регистрация
**Экран `onboarding`** — выбор одной из 4 ролей карточками:
- 🏠 Розничный клиент → `register-client`
- 🪟 Оконщик / Профильщик → `register` (role=okonshchik)
- 👷 Прораб → `register` (role=prorab)
- 🏭 Цех / завод → `register` (role=tsekh)

**Экран `register-client`** — поля Имя (из Telegram), Город (деф. Алматы), Телефон. Кнопка «Начать пользоваться» → POST `/me/register-client`.

**Экран `register`** — сегментированный переключатель роли + поля Название, Город, БИН, Телефон. Кнопка «Зарегистрироваться» → POST `/me/register-installer` (требует Telegram initData).

**Гейт роутера:** гость/незарегистрированный при любом маршруте, кроме онбординга → редирект на `#/onboarding`.

### 4.2 Главная (`home`)
- Приветствие с ролью.
- Кнопка «Новый замер / расчёт» → создаёт чистый проект.
- Для монтажника: карточки статистики (Расчётов, Статус верификации).
- «Последние замеры» — 5 проектов с мини-превью окна, клиентом, суммой.
- Ссылки: «Все →» (проекты), «Помощь».

### 4.3 Проекты (`projects`, `project`)
**Список `projects`** — кнопка «+ Новый замер» + список всех проектов с превью.

**Обзор замера `project`** состоит из карточек:
- **Клиент** — имя/телефон/адрес; редактирование через bottom-sheet.
- **Заказ-накладная** (Logikal-метаданные) — Объект, Отв. лицо, № заказа (авто), Склад, Сборка (фикс/за м²), Каталог. Кнопка «Скачать заявку-накладную PDF».
- **Отчёты** (только для сохранённого проекта): Общий КП, Заявка-накладная, Спецификация стеклопакетов, Спецификация фурнитуры, КП по каждой позиции отдельно — все открываются через `authUrl` + `tg.openLink`.
- **Раскрой и экономия** — метрики (хлыстов, длина м, отходы нетто/брутто %), вердикт (отлично/нормально/много отходов), сегрегация по цвету, советы по уменьшению размеров.
- **Позиции** — список окон/дверей с превью, кнопки Изменить/Копия/Удалить; «+ Окно», «+ Дверь» (picker дверных шаблонов).
- **Что считаем** (scope) — пресеты (Всё / Только профиль / Только стекло / Только фурнитура / Без доп.) + 7 переключаемых категорий.
- **Итоги** — кнопка «Рассчитать проект» (POST `/projects/calc`), блок Итого/подытог/скидка/НДС, разбивка по разделам; кнопки «Сохранить» и «Сформировать КП»; кнопка «🔍 AR».

### 4.4 Конструктор позиции (`edit-item`)
Центральный экран. Содержит:
- Имя позиции + количество (1–99).
- **Режим конструкции:** «📋 Из шаблона» / «✏️ Произвольный» (пошаговый: рама → импост → открывание → стекло).
- **Шаблоны:** первые 6 из ~26 `WINDOW_TEMPLATES` + picker «Все 18 →» с фильтром (Все/Окна/Двери/Смешанные).
- **Пошаговый конструктор:**
  - «↻ Начать с чистой рамы», «① Размер рамы».
  - Импост узкий/широкий (влияет на свет секций и расход).
  - Форма наружного контура — 12 форм (прямоугольник, арочное, полукруг, треугольник, трапеция, готика, круг, овал, пятиугольник, шестиугольник, четверть круга, полигон).
  - «✏️ Векторный редактор» → `shape-editor`.
- **Поверхность рисования** — поля Ширина (300–8000) × Высота (300–4000); метка «проём»/«изделие». Переключатель вида: **Анфас / Профиль (разрез) / 3D-изометрия**.
- **Действия над секциями (рекурсивное деление):** Разделить ↕ / ↔, Удалить; тап по секции → bottom-sheet выбора открывания; n-арное и вложенное split-дерево.
- **Список секций** — рекурсивный обход листьев с глифами.
- Выбор: **профильная система**, **стеклопакет**, **атрибуты стекла** (мультивыбор: закалённое/триплекс/тонировка и т.д.), **цвет профиля** (с наценкой %), **фурнитура** (фильтр по window/door/sliding), **ручка** (фильтр стиля для дверей + цвет ручки).
- Для дверей: **Тип двери** (door_types), **Дверной комплект** (12 категорий компонентов + пресеты: Балконная/Входная/Двойная штульп./Раздвижная).
- **Спец. профили:** Разворотный профиль, Адаптер рамы.
- **Дополнительно (extras):** Подоконник, Отлив, Москитная сетка, Монтаж — с выбором моделей через сгруппированный picker; стоимость монтажа (фикс/за м²).
- Кнопки «📷 Примерить через камеру» (AR) и «Готово».

**Bottom-sheets конструктора:** размер рамы (с живым превью площади/периметра/стеклопакетов и переключателем проём/изделие), параметры секции (открывание + размеры + шпрос), picker дверей, picker шаблонов.

### 4.5 Векторный редактор формы (`shape-editor`)
Полноценный SVG-редактор контура: 12 форм, перетаскиваемые точки-ручки (mouse+touch), snap 10мм, размерные линии, числовые поля параметров, для полигона — добавить/убрать вершину, расчёт периметра и площади, показ glass_factor/bend_fee.

### 4.6 AR-примерка (`ar`)
Камера (`getUserMedia`, environment/user) + перетаскиваемый/масштабируемый/вращаемый SVG-оверлей окна. Контролы: табы позиций, палитра цветов (живая смена), ползунок прозрачности стекла, флип камеры, затвор → снимок (композит видео+SVG на canvas) → сохранение (Web Share API / download).

### 4.7 Коммерческое предложение (`kp`)
Создание КП (POST `/kp`), карточки № КП/Заказчик/Итого, список позиций. Кнопки «Скачать PDF» (через `authUrl`) и «Поделиться» (`tg.shareMessage` / копирование).

### 4.8 Каталог (`catalog`, `manufacturer`)
Производители (с рейтингом, переход в карточку), Профильные системы, Стеклопакеты, Шаблоны окон. Карточка производителя — его системы.

### 4.9 Документы (`documents`, `order`)
- Заявки от клиентов (со статусами).
- Мои КП (история, скачивание PDF).
- Мои проекты.
- Карточка заявки `order` — детали + превью + для монтажника-владельца обновление статуса (new → contacted → measuring → production → installation → done / cancelled).

### 4.10 Профиль (`profile`)
Профиль пользователя; для монтажника — статистика + **«Моя наценка»** (markupPct 0–200%, пресеты, применяется молча ко всем расчётам). Меню: Уведомления, Все замеры, Заявки и КП, Клиенты (CRM), Каталог, Сменить роль, Поддержка, Помощь.

### 4.11 CRM-клиенты (`clients`)
Только для монтажника: список клиентов, добавление/редактирование/удаление, «Новый замер» с предзаполненным клиентом.

### 4.12 Уведомления (`notifications`)
Список с иконками по типу (order.new/update/confirm, kp.created, discount.changed, system), «Прочитать всё», переход по ссылке уведомления, бейдж непрочитанных в шапке.

### 4.13 Помощь и поддержка (`help`, `support`)
FAQ + форма обращения в поддержку (POST `/support`).

---

## 5. Функциональные требования: Веб-суперадминка

Desktop-only SPA (`width=1280`), cookie-сессия, hash-роутинг. Навигация в 3 группы: **Обзор / Каталог / Партнёры**.

### 5.1 Вход и сессия
Экран логина (логин/пароль → POST `/admin/login`); глобальный перехват 401 → «Сессия истекла». User-card с кнопкой выхода. Смена пароля (мин. 6 символов).

### 5.2 Обзор
- **Дашборд** — 4 KPI (Расчётов всего, Конверсия, Средний чек, Активных оконщиков), карточки производителей, последние события журнала, география запросов (бары).
- **Аналитика** — те же KPI, график «Расчёты по месяцам», donut «Доля профильных систем», география, топ оконщиков. *(Кнопки «За год»/«Экспорт» — заглушки без обработчиков.)*
- **Журнал событий** — таблица (Время/Актор/Действие/Детали), `GET /log?limit=200`.

### 5.3 Каталог
- **Производители** — CRUD: id, название, регион, рейтинг, статус (active/paused/blocked), привязка систем (чекбоксы).
- **Цены (артикулы)** — фильтр по системе (чипы), inline-редактирование 3 цен (base/dealer/retail), колонка наценки Δ, CRUD, **массовое изменение цен** (bulk-bump: vendorPrefix + pct%).
- **Каталоги и параметры** — generic-CRUD движок (`CATALOGS` + `editRow`/`renderCatTable`) на **19 справочников** с клиентским поиском по таблице:
  1. Цвета (RAL) — surcharge_pct
  2. Фурнитурные комплекты — kind, price_per_sash
  3. Ручки — style, length_mm, sided, color
  4. Подоконники
  5. Отливы
  6. Москитные сетки
  7. Дверной комплект (14 категорий компонентов, qty_per_door)
  8. Профильные части (kind: frame/sash/mullion/mullion_wide/bead/shtulp/turn/adapter/door_sash/threshold; price_per_m, stock_length_mm)
  9. Типы открывания (needs_sash, complexity, hinges, locking_points, hw_factor)
  10. Параметры раскроя (system_id + 14 параметров)
  11. Уплотнители
  12. Уголки/сухари/соединители
  13. Формы окон (glass_factor, bend_fee, params_schema)
  14. Атрибуты стеклопакетов (multiplier, surcharge_per_m2, per_pane)
  15. Типы конструкций (фасады)
  16. Фасадные профили
  17. Типы дверей (reinforcement_factor, required_components, default_opening)
- **★ Все материалы** — сводный поиск по 11+ справочникам (`GET /materials?search=`, дебаунс 300мс).

### 5.4 Партнёры
- **Оконщики** — CRUD: id, название, город, БИН, телефон, верификация.
- **Скидочная матрица** — Оконщик × Производитель, цветовая градация, **потолок 25%**, экспорт CSV.

---

## 6. Функциональные требования: Telegram-бот

Команды:
- `/start` — приветствие + кнопка открытия Mini App (web_app, требует HTTPS).
- `/app` — повторная отправка кнопки.
- `/me` — профиль оконщика (если зарегистрирован).
- `/stats` — статистика платформы (для `ADMIN_TG_IDS`).
- `/help` — справка.

Дополнительно: устанавливает persistent menu button на Mini App; принимает `web_app_data` от `tg.sendData()` (тип `kp.shared`); логирует события.

---

## 7. Расчётный движок (ядро)

### 7.1 Модель раскладки (layout)
Каноническая модель: `{ width, height, rows: [{ ratio, sections: [{ ratio, opening, split?, muntins? }] }] }`.
- Секция — либо **лист** `{opening}`, либо **контейнер** `{split:{dir:'cols'|'rows', children:[...]}}` (рекурсивное split-дерево).
- Legacy-формат `{width, height, sections:[code,...]}` конвертируется в один ряд.
- Коды открывания: FIX, ПЛ, ПП, ОТК, ПОЛ, ПОП, ФР (окна); ДВЕРЬ-ПЛ/ПП/FIX/ШТЛ/ШТП/МАЯТ; РАЗД-Л/П/ЦП.

### 7.2 Размерная цепочка (инженерная, `itemGeometry`)
Все размеры в мм, центрлайн-модель. Источник констант — таблица `cutting_params` per-система (или `DEFAULT_CUTTING_PARAMS`).

1. **Проём → Рама:** при `dimType='opening'` вычитается монтажный зазор: `frameW = W − 2·montage_gap`; при `dimType='frame'` — без изменений.
2. **Свет рамы:** `frameLight = frame − 2·Wf` (Wf — ширина лица рамы).
3. **Импост:** длина по оси = `свет_по_оси + (2·Wf − impost_subtract)`. Пример: проём 1100, рама 70 → импост ~960мм.
4. **Потери секции:** граница с рамой → Wf; граница с импостом → Wi/2.
5. **Створка:** `overlap = naplav − falzluft`; `sashOuter = cellLight + 2·overlap`; `sashLight = sashOuter − 2·Wsp`.
6. **Стекло:** глухое `cellLight − 2·glazing_zazor`; створка `sashLight − 2·glazing_zazor`.
7. **Длина реза (заготовка):** `finished + weld` (weld = 2·weld_allowance для рамы/створки ПВХ; 0 для импоста/штапика/алюминия/дерева). Углы 45° (рама/створка), 90° (импост/штапик).
8. **Армирование:** только при `needs_reinforcement`; `len = finished − 2·reinf_setback`; пропуск коротких белых деталей.

### 7.3 Параметры раскроя (`cutting_params`, дефолты)
| Параметр | Деф. | Назначение |
|---|---|---|
| stock_length_mm / _bead / _steel | 6000 | длины хлыстов |
| kerf_mm | 4 | ширина реза |
| end_trim_mm | 5 | обрезка торца хлыста |
| weld_allowance_mm | 3 (ПВХ) / 0 | припуск сварки на торец |
| montage_gap_mm | 20 | монтажный зазор проём→рама |
| naplav_mm | 20 | нахлёст створки |
| falzluft_mm | 12 (ПВХ) / 8 | фальцлюфт |
| glazing_zazor_mm | 5 | зазор остекления |
| impost_subtract_mm | 140 (≈2×лицо рамы) | вычет длины импоста |
| reinf_setback_mm | 15 | отступ армирования |
| reinf_min_part_mm | 700 | мин. деталь для армирования |
| reuse_threshold_mm | 300 | порог делового остатка |

### 7.4 Раскрой по хлыстам (`packStockBars`)
Алгоритм **Best-Fit-Decreasing**. Сегрегация по ключу `profileCode|color|material` (разные цвета/материалы не делят хлыст; сталь — отдельная группа). Учёт пропила и обрезки торца. Деловые остатки (≥ порога) vs чистый отход. Метрики: КПД, отход брутто/нетто %, сегрегация по цвету.

### 7.5 Ценовой расчёт (`calcWindow`)
Считает стоимость по упрощённой метровой геометрии. Строки сметы по 7 категориям: **profile, hardware, glazing, reinforcement, sealing, consumables, extras**. Ключевые элементы:
- Профиль (рама/створка/импост/штапик), гнутые участки ×1.5 + bend_fee.
- Стекло — лот на каждый уникальный размер, множители формы и атрибутов.
- Фурнитура — оконная (×hw_factor), дверная (×1.5), раздвижная (×1.8), ручки, дверной комплект по `required_components`.
- Армирование/термомост/лак (по материалу).
- Уплотнители, шпросы, кронштейны/сухари.
- Расходники — 1.5% от суммы.
- Extras — подоконник/отлив/москитка/монтаж.
- **Множители уровня цен:** base 1.00 / dealer 1.06 / retail 1.18.
- **Цветовая наценка** (surcharge_pct) к профилю.
- **Скидка** (discounts, кэп 25%) + **наценка монтажника** (markupPct, кэп 0–200%, молча).
- НДС 12% (декларируется как включённый).

### 7.6 Прочие расчёты
- `calcProject` — агрегат по нескольким позициям (×qty).
- `calcFacade` — фасады/витражи (стойки/ригели, остекление, анкеровка, кровля).
- `compareManufacturers` — сравнение производителей (сортировка по итоговой цене).
- `buildCutList` / `buildGlassList` — карта реза и список стекла (инженерная модель).
- `buildBom` — спецификация в 4 секции (Профили / Спец. длины / Аксессуары / Уплотнители).

> ⚠️ **Известное расхождение:** `calcWindow` (цена) и `itemGeometry`/`buildCutList` (инженерия) используют независимые геометрические пути; метражи могут не совпадать при разных дефолтах профилей.

### 7.7 Советник (`cuttingAdvice`)
Анализирует раскрой и предлагает уменьшить размер детали (≤10% длины или ≤250мм), чтобы в хлыст вошла +1 деталь. Возвращает экономию хлыстов/метров, позиции, вердикт (good/ok/bad по отходу нетто %).

---

## 8. PDF-документы

Все на A4, шрифты DejaVu (кириллица + ₸), фирменный «тёплый» бренд-стиль (акцент `#b56b3a`).

| Документ | Содержание |
|---|---|
| **Коммерческое предложение (КП)** | шапка с №, блок исполнитель/заказчик, спецификация позиций со схемами окон и размерами, итоги (подытог/скидка/НДС/ИТОГО), условия (срок 14 дней, гарантия 5 лет/фурнитура 2 года) + доп. страницы: карта реза, раскрой по хлыстам, BOM |
| **Заявка-накладная (invoice)** | формат с фото, цена со скидкой 15%, сборка, НДС 12%, авто-№ |
| **Спецификация стеклопакетов** | для стекольного цеха, группировка по размеру+формуле |
| **Спецификация фурнитуры** | агрегация hardware-строк |
| **КП по одной позиции** | клон проекта до одной позиции |

**Отрисовка схем окон в PDF:** `drawSchemaWithDims` (детальный чертёж в стиле Logikal с размерными линиями, рекурсивным split-деревом, внутренними размерами стеклопакетов) + `drawWindowSchema` (упрощённая иконка). Защита от вырожденного layout.

---

## 9. Модель данных (БД)

SQLite, 30+ таблиц, режим WAL. FK логические (физических ограничений нет). Ключевые таблицы:

**Справочники/каталоги:** `profile_systems` (41 система: ПВХ/алюминий/дерево/дерево-ал.), `glazing` (6 стеклопакетов), `manufacturers` (4), `colors` (8 RAL), `hardware_kits` (21), `handles`, `sills`, `ebbs`, `meshes`, `profile_parts`, `seals`, `brackets`, `door_hardware`, `door_types` (14), `opening_types` (17), `cutting_params`, `shape_types` (13), `glass_attributes` (11), `construction_types` (6), `facade_profiles` (18), `articles` (прайс, 3 уровня).

**Пользователи/доступ:** `installers` (role: okonshchik/prorab/tsekh), `clients`, `discounts` (матрица, кэп 25%), `admin_users`, `admin_sessions`.

**Документооборот:** `calculations`, `projects`, `kp_documents`, `orders` (7 статусов), `crm_clients`, `favorites`, `notifications`, `log_events`.

**Хелперы:** `getCuttingParams(systemId)` (мерж с дефолтами), `getOpeningType(code)` (с кэшем), `logEvent()`, `addColumnIfMissing()`, `DEFAULT_CUTTING_PARAMS`.

**Миграции:** стратегия «create-if-not-exists + аддитивные ALTER» (без версионирования), идемпотентные сиды (`isEmpty`), пересоздание таблицы для `kp_documents`, генеративный сид `cutting_params` по материалу.

---

## 10. REST API (сводка)

| Раздел | Эндпоинты |
|---|---|
| **Auth админки** | POST `/admin/login`, `/admin/logout`, GET `/admin/me`, POST `/admin/change-password` |
| **Текущий пользователь** | GET/PUT `/me`, POST `/me/register-client`, `/me/register-installer` |
| **Каталоги (public GET, admin-мутации)** | `/profile-systems`, `/glazing`, `/manufacturers`, `/articles` (+`/bulk-bump`), `/opening-types`, `/window-templates`, `/materials` + generic-CRUD на 15 справочников |
| **Расчёты** | POST `/calc`, `/facade/calc`, `/compare`, `/projects/calc` |
| **История/проекты** | CRUD `/calculations`, `/projects` |
| **КП** | POST `/kp`, GET `/kp/:id`, `/kp/:id.pdf`, `/my/kps` |
| **Раскрой/отчёты** | `/projects/:id/cutlist`, `/stock-bars`, `/glass`, `/advice`, `/bom`, POST `/advice` |
| **PDF проекта** | `/projects/:id/invoice.pdf`, `/items/:idx/kp.pdf`, `/glass-spec.pdf`, `/hardware-spec.pdf`, `/reports` |
| **CRM/заказы/уведомления** | CRUD `/crm/clients`, `/orders`, `/notifications`, `/favorites` |
| **Аналитика** | GET `/analytics`, `/log` (admin) |
| **Скидки/оконщики** | GET `/discounts`, PUT `/discounts/:i/:m`, CRUD `/installers` (admin) |
| **Поддержка** | POST `/support` |

---

## 11. Нефункциональные требования

- **Локализация:** интерфейс и документы на русском; валюта — тенге (₸); регион — Казахстан (БИН).
- **Тестирование:** 69 unit/integration-тестов (`npm test`, `NODE_ENV=test`): движок расчёта (35), API (31), Telegram-auth (3).
- **Производительность:** синхронный SQLite (better-sqlite3); PDF-генерация в `try/catch` (сбой доп. страниц не ломает КП).
- **Надёжность:** защита от вырожденного layout в рендере и PDF; `art()` возвращает 0-заглушку при отсутствии артикула; fresh-DB boot.
- **Совместимость:** Mini App — мобильный Telegram WebApp; админка — desktop 1280px.

---

## 12. Безопасность

**Реализовано:**
- Валидация Telegram initData (HMAC-SHA256, отклонение >24ч).
- Проверка владельца на всех `:id`-роутах (404 для чужих).
- `requireAdmin` на мутациях/аналитике/журнале/скидках/оконщиках.
- `?as=` имперсонация только вне production.
- scrypt + соль, timing-safe сравнение паролей админки; HttpOnly cookie, Secure в проде.
- Редактирование `tgauth` в логах.

**Технический долг / риски (для устранения):**
1. **Legacy BOT_TOKEN** жёстко зашит как fallback (`routes.js`, `bot.js`) — в проде лишь warning. Знающий токен может подделать initData. → Сделать fail-fast в проде.
2. **Дефолтный пароль админки** `BruceWayne2026!` зашит как fallback — в проде warning, не блокирует. → Fail-fast при незаданном `ADMIN_PASSWORD`.
3. **Номера КП/invoice** через `COUNT(*)+1` — неатомарны, уязвимы к гонкам/удалениям. → Отдельная sequence-таблица.
4. **`telegram-auth.js`** сравнивает хеши через `!==` (не constant-time). → `timingSafeEqual`.
5. **GET `/calculations` и `/orders`** без идентичности возвращают первые 100 (нет жёсткой изоляции на listing). → Требовать owner.
6. **Пути к шрифтам DejaVu** жёстко под Linux без фолбэка на встроенный шрифт. → Бандлить шрифты.
7. FK-ограничения не объявлены (целостность не контролируется на уровне БД).

---

## 13. Известные ограничения и Roadmap

**Заглушки/незавершённое:**
- Кнопки «За год»/«Экспорт» в аналитике — без обработчиков.
- Дельты KPI и выручка топ-оконщиков — hardcoded/эвристичны.
- Кэш `getOpeningType` не инвалидируется (изменения типов открывания требуют рестарта).

**Запланировано (из проектных решений):**
- Общий склад деловых остатков **между проектами** (сейчас — в пределах одного проекта).
- Data-driven фурнитура дверей по `opening_types`.
- Калибровка `impost_subtract` под реальные системы.
- Публичный share-token для «Поделиться КП» (получатель без auth не откроет PDF).
- Расхождение двух геометрических путей (`calcWindow` vs `itemGeometry`) — унификация.

---

## Приложение А. Глоссарий

| Термин | Значение |
|---|---|
| Импост | вертикальный/горизонтальный профиль, делящий раму на секции |
| Наплав (притвор) | нахлёст створки на раму |
| Фальцлюфт | зазор между створкой и рамой |
| Деловой остаток | обрезок хлыста ≥ порога, пригодный к повторному использованию |
| Хлыст | мерная заготовка профиля (деф. 6000мм) |
| Свет | внутренний просвет рамы/створки/секции |
| FIX | глухое (неоткрывающееся) остекление |
| Штапик (bead) | профиль, удерживающий стеклопакет |
| Штульп | профиль для двустворчатых дверей без импоста |
| BOM | спецификация материалов (Bill of Materials) |
| КП | коммерческое предложение |
</content>
</invoke>
