# GuaranteeApp — پنل وب (عملیات انبار + صفحهٔ عمومی گارانتی)

> پیش‌نیاز: `00-Overview`، `02-Backend-API` (Endpointها و منطق سرور آنجا تعریف شده‌اند).
> دو صفحهٔ کاملاً جدا: `panel.php` برای کارکنان کارگاه/انبار، `warranty.php` برای مشتری نهایی.

---

## ۱. اصول

- **بدون سیستم کاربر/نقش.** فقط «نوع عملیات» انتخاب می‌شود (بسته‌بندی یا خروج از انبار).
- **موبایل‌محور:** اول روی مرورگر گوشی (دوربین) درست کار کند. دکمه‌های بزرگ (کارمند انبار دستکش دارد/عجله دارد).
- **بدون رفرش صفحه:** بین اسکن‌های پیاپی فقط نوار پیام عوض می‌شود.
- **فناوری:** HTML + CSS + Vanilla JS، بدون فریم‌ورک؛ RTL، تم تیره، فونت خوانا. مسیر فراخوانی API **نسبی** (`api/scan.php`) تا پروژه در هر پوشه/ساب‌دامنه کار کند.
- **HTTPS اجباری** (دوربین مرورگر بدون آن کار نمی‌کند).

---

## ۲. پنل عملیات (`panel.php`)

### ۲.۱ چیدمان
1. بالا: دو دکمهٔ بزرگ «📦 بسته‌بندی» و «📤 خروج از انبار»؛ یکی همیشه فعال، آخرین انتخاب در `localStorage` (داخل try/catch).
2. کادر دوربین زنده برای اسکن (تمام عرض در موبایل) + دکمهٔ «روشن کردن دوربین».
3. **ورود دستی سریال** به‌عنوان جایگزین دوربین (فیلد متن + دکمهٔ ثبت).
4. نوار پیام تک‌خطی زیر کادر (بدون اسکرول).
5. اختیاری: شمارندهٔ سمت کلاینت «این نشست: N مورد».

### ۲.۲ پیام‌ها بعد از هر اسکن

| پاسخ سرور | پیام | رنگ |
|---|---|---|
| `registered` | «سریال {S} ثبت شد ✅» (+ نام مدل) | سبز |
| `registered` با `packagingMissing` | همان پیام + «⚠️ برای این سریال بسته‌بندی ثبت نشده» | زرد |
| `SERIAL_ALREADY_REGISTERED` | «تکراریه — قبلاً ثبت شده بود ⚠️» | زرد |
| `SERIAL_NOT_FOUND` | «این محصول در دیتابیس نیست ❌ (به ادمین اطلاع داده شد)» | قرمز |
| `RATE_LIMITED` / خطای شبکه | «کمی بعد دوباره امتحان کنید» | قرمز |

بعد از هر پیام، اسکنر بدون توقف آماده است؛ تأخیر ~۱٫۵ ثانیه برای جلوگیری از اسکن تکراری همان فریم.

### ۲.۳ اسکنر
- کتابخانهٔ `html5-qrcode` (از CDN مجاز، نسخهٔ pin‌شده) برای QR + Code128/39 + DataMatrix.
- متن اسکن‌شده: اگر URL بود پارامتر `serial` استخراج شود، وگرنه متن خام سریال.
- ثبت با `POST api/scan.php` به‌صورت `fetch` ناهمگام (`02 §۴.۱`).

### ۲.۴ قواعد
- تکراری‌بودن به‌ازای هر نوع عملیات است.
- `warehouse_exit` بدون `packaging` **مسدود نمی‌شود**؛ فقط هشدار زرد.
- Orphan: ثبت می‌شود و اعلان تلگرام فقط در اولین اسکن آن سریال می‌رود.
- **PIN:** پیش‌فرض بدون PIN (لینک نیمه‌مخفی)؛ ساختار آمادهٔ افزودن PIN مشترک در آینده (سؤال باز در `08`).

---

## ۳. صفحهٔ عمومی گارانتی (`warranty.php?serial=...`)

- آدرس همان است که در QR لیبل ۲ رمزگذاری می‌شود: `https://{دامنه}/{مسیر}/warranty.php?serial={SerialNumber}`.
- بدون لاگین؛ صفحه هنگام لود `POST api/warranty/activate.php` می‌زند (`02 §۴.۲`).

| حالت | نمایش |
|---|---|
| اولین فعال‌سازی | پیام سبز بزرگ «✅ گارانتی این محصول فعال شد» |
| تکراری | «این گارانتی قبلاً در تاریخ {activatedAt} فعال شده است» (اطلاع‌رسانی، نه خطا؛ تاریخ خوانا، ترجیحاً شمسی) |
| سریال ناشناخته | پیام خنثی و غیرفنی «این کد شناسایی نشد، لطفاً با پشتیبانی تماس بگیرید» + لاگ پس‌زمینه |
| خطای شبکه/Rate limit | «لطفاً چند لحظه بعد دوباره تلاش کنید» |

- اگر محصول رویداد انباری ندارد، هشدار تلگرام به ادمین می‌رود؛ **مشتری هیچ نشانه‌ای نمی‌بیند.**
- پایان گارانتی نمایش داده نمی‌شود.
- صفحه همیشه عمومی و بدون احراز هویت می‌ماند؛ فقط Rate limit.

---

## ۴. امنیت
HTTPS اجباری؛ Rate limit روی scan/activate؛ escape خروجی HTML؛ `panel.php` فقط با آدرس نیمه‌مخفی (و در آینده PIN اختیاری)؛ هیچ Credential در جاوااسکریپت نیست.

## ۵. تست
با `test.php` (`02 §۸.۴`) و تست واقعی: `panel.php` را روی گوشی باز کن ← «روشن کردن دوربین» ← اجازه ← اسکن QR لیبل ۲؛ و `warranty.php?serial=TEST-WEB-0001` را باز کن.
