# 13 — VİTRİN ÖZELLİKLERİ (Botble'da olmayanlar)

Beş özellik. Hepsi Botble'da **yok**, hepsi "kim satıyor, ne zaman geliyor"
tezini güçlendiriyor, hiçbiri eklenti dosyasına dokunmuyor.

| # | Özellik | Botble'da | Ne kazandırıyor |
|---|---|---|---|
| 1 | Şehir bazlı teslim tahmini + kesim sayacı | yok | Dönüşüm |
| 2 | Fiyat geçmişi + dürüst indirim | yok | Güven, yasal hazırlık |
| 3 | Stok ve fiyat alarmı | yok | Kaybedilen talep |
| 4 | Satıcı bazlı sepet + eşik ilerlemesi | kısmen | Sepet ortalaması |
| 5 | Birleşik paket takibi | yok | Destek yükü |

Migration: `2026_07_24_110000_create_storefront_features.php` — 4 tablo.

---

## 1. Şehir bazlı teslim tahmini

`App\Magostore\Storefront\DeliveryEstimator`

Botble kargo **ücreti** hesaplıyor, teslim **süresi** tahmini yapmıyor.
M¼şterinin en çok sorduğu soruya cevap veremiyor.

Hesap: `bugün + satıcı termini + bölge transit + (kesim aşıldıysa 1)` — hepsi
**iş günü**, resmî tatiller dahil (Faz 3c'deki `BusinessDayCalendar`).

Beş bölge seed'li: İstanbul (1 gün), Marmara (2), Ege/İç Anadolu (2),
Akdeniz/Karadeniz (3), Doğu/Güneydoğu (4). Kargo entegrasyonu gelince gerçek
transit sürelerle güncellenecek.

### Kesim saatinin etkisi — test sonuçları

24 Temmuz Cuma, 2 iş günü termin varsayımıyla:

| Bölge | Kesim | Kesimden önce | Kesimden sonra |
|---|---|---|---|
| İstanbul | 16:00 | 29 Tem Çar | 30 Tem Per |
| Marmara | 16:00 | 30 Tem Per | 31 Tem Cum |
| Akdeniz | 15:00 | 31 Tem Cum | 3 Ağu Pzt |
| Doğu | 14:00 | 3 Ağu Pzt | 4 Ağu Sal |

Sayaç bu yüzden değerli: müşteri "4 saat 22 dakika içinde sipariş verirsen"
görüyor ve karar hızlanıyor.

Etiket dili bilinçli: "Yarın kapında" > gün adı > tarih. Belirsiz "2-4 iş
günü" ifadesi kullanılmıyor — kesin tarih daha çok güven veriyor.

Bilinmeyen şehirde **en yavaş** bölge varsayılıyor. Fazla iyimser tahmin,
geciken kargodan daha çok şikâyet üretir.

---

## 2. Fiyat geçmişi ve dürüst indirim

`App\Magostore\Storefront\PriceInsight`

### Sorun

"%38 indirim" etiketi enflasyonist ortamda anlamını yitirdi. Satıcı fiyatı
1.000'den 1.800'e çıkarıp %44 indirim yazabiliyor, kimse fark etmiyor.

AB'de bu zaten yasak (Omnibus Direktifi: indirim ilan ederken önceki 30 günün
en düşük fiyatı gösterilmek zorunda). Türkiye aynı yöne gidiyor. Baştan doğru
kurmak sonradan mecbur kalmaktan iyi.

### Testte bir hata yakaladım ve ölçütü değiştirdim

İlk yazdığım mantık **dürüst indirimi yanıltıcı sanıyordu.** Sebep: en düşük
fiyatı hesaplarken bugünün fiyatını da dahil ediyordum. Gerçek bir indirimde
bugünün fiyatı zaten en düşük olduğu için "düşüşe göre indirim" sıfır çıkıyor
ve iddia edilen indirimle arasındaki fark yanıltıcı görünüyordu.

Düzeltme iki parçalı:

**a)** Referans dönem **bugün hariç** — mevcut fiyat yürürlüğe girmeden
önceki fiyatlar.

**b)** Ölçüt yüzde karşılaştırması değil, doğrudan asıl soru:
**bu liste fiyatı son 30 günde gerçekten uygulandı mı?**

```php
$misleading = $claimed !== null
    && $highestPrior !== null
    && $days >= 7
    && $listPrice > $highestPrior * 1.02;
```

Bu hem daha basit hem daha savunulabilir. Test sonuçları:

| Senaryo | Fiyat | Liste | Pencere | Yanıltıcı |
|---|---|---|---|---|
| Gerçek indirim 1590→1249 | 1249 | 1590 | 1449–1590 | ✗ doğru |
| 30 gün 1000, liste 1800 | 1000 | 1800 | 1000–1000 | ✓ yakalandı |
| Max 1100 ama liste 1200 | 899 | 1200 | 999–1100 | ✓ yakalandı |
| Liste gerçekten uygulanmış | 899 | 1100 | 999–1100 | ✗ doğru |
| 3 günlük veri | 500 | 700 | — | ✗ iddia yok |

**7 günden az veri varsa hiçbir şey söylemiyoruz.** Yanlış güven vermek, hiç
bilgi vermemekten kötü.

M¼şteri mesajları: "Son 30 günün en düşük fiyatı" · "Son 30 günün en
düşüğüne göre %13,8 daha ucuz" · "Son 30 günde en düşük 1.449,00 ₺ oldu"

`misleading` işaretini müşteriye göstermiyoruz — **admin paneline** düşüyor.
Satıcıyla konuşulacak bir konu, müşteriye "bu satıcı sizi kandırıyor"
demek değil.

---

## 3. Stok ve fiyat alarmı

`App\Magostore\Storefront\ProductAlertService`

Botble'da favori var, **bildirim yok.** Stokta olmayan ürüne bakan müşteri
kaybedilmiş talep.

### İki tasarım kararı

**Üye olmayan da alarm kurabiliyor.** Sadece e-posta yeterli. Üyelik
zorunluluğu dönüşümün yarısını kaybettiriyor — ve alarm zaten bir üyelik
kazanma aracı: ürün gelince gelen e-posta müşteriyi geri getiriyor.

**%3 eşiği.** Hedef fiyat verilmemişse, kurulduğu andaki fiyattan en az %3
düşüş şart. Test:

| Kurulduğunda | Yeni fiyat | Düşüş | Sonuç |
|---|---|---|---|
| 1000 | 999 | %0,1 | sessiz |
| 1000 | 975 | %2,5 | sessiz |
| 1000 | 970 | %3,0 | **bildir** |
| 1000 | 900 | %10 | **bildir** |

Kuruş oynamalarında bildirim göndermek spam'e dönüşür ve müşteri e-postayı
tamamen kapatır. Bir kez kaybedilen izin geri gelmiyor.

### Yan fayda: satıcıya talep sinyali

`demandFor($productId)` bir üründe kaç kişinin stok beklediğini veriyor.
Satıcı panelinde göstermek: "bu ürün için 34 kişi stok bekliyor" — satıcıyı
stok girmeye teşvik eden en güçlü cümle.

---

## 4. Satıcı bazlı sepet ve eşik ilerlemesi

`App\Magostore\Storefront\CartGrouper`

Botble sepeti satıcıya göre gösteriyor ama üç şeyi yapmıyor: eşiğe ne kadar
kaldığını göstermiyor, fiyat değişikliğini uyarmıyor, paket bazlı teslim
tarihi vermiyor.

### Eşik ilerlemesi

Genel bir "300 ₺ üzeri kargo bedava" banner'ı bilgi vermiyor. **"Bu satıcıdan
42 ₺ daha eklersen kargo bedava"** müşterinin o anki durumunu söylüyor ve
doğrudan sepet ortalamasını yükseltiyor.

Test edilen sepet:

| Satıcı | Ara toplam | Kargo | Eksik | Çubuk |
|---|---|---|---|---|
| Nar Kozmetik | 258 ₺ | 49 ₺ | **42 ₺** | 86% |
| Kandil Ev Tekstili | 3.429 ₺ | bedava | — | 100% |
| Ege Tekstil | 689 ₺ | bedava | — | 100% |

**Sıralama bilinçli:** eşiğe en yakın grup en üstte. Müşteri en kolay
kazanacağı fırsatı ilk görüyor.

### Fiyat değişikliği uyarısı

M¼şteri sepete attı, üç gün sonra döndü, fiyat değişti. Botble sessizce yeni
fiyatı gösteriyor. `mg_cart_price_snapshots` tablosu eklendiği andaki fiyatı
tutuyor ve fark varsa uyarı üretiyor.

M¼şteri kendi fark ederse güven kaybı olur, biz söylersek olmaz.

---

## 5. Birleşik paket takibi

`App\Magostore\Storefront\PackageTracker`

Sipariş bölme mimarimizin doğal sonucu: tek ödeme, N satıcı, N paket. Botble
her mağaza için ayrı sipariş üretiyor ama müşteriye **birleşik** bir ekran
sunmuyor — müşteri üç ayrı sipariş görüyor ve "ben tek sipariş verdim" diye
destek talebi açıyor.

Bu servis tek sipariş / N paket görünümü üretiyor. Her paketin kendi durum
adımları ve kendi tahmini teslim tarihi var.

### Botble'ın onlarca durumu 5 adıma indiriliyor

```
Onaylandı → Hazırlanıyor → Kargoda → Dağıtımda → Teslim edildi
```

M¼şteri "transfer merkezinde" görmek istemiyor, nerede olduğunu anlamak
istiyor. Botble'ın iç durumları korunuyor, müşteriye gösterilen eşleniyor.

Tasarımı `tasarim/admin-ve-musteri-paneli.html` içinde, müşteri paneli
bölümünde — üç paket, üç ayrı zaman çizelgesi.

---

## Kurulum

```bash
cp -r app/Magostore/Storefront ~/magostore/app/Magostore/
cp database/migrations/2026_07_24_110000_* ~/magostore/database/migrations/
php artisan migrate
```

### Bağlanacak yerler (observer, eklenti dosyası değil)

| Olay | Ne yapılacak |
|---|---|
| Ürün kaydedildi | `PriceInsight::record()` — fiyat geçmişi |
| Stok 0'dan pozitife geçti | `ProductAlertService::pendingStockAlerts()` → kuyruk |
| Fiyat düştü | `pendingPriceAlerts()` → kuyruk |
| Sepete eklendi | `CartGrouper::snapshotPrice()` |
| Sipariş tamamlandı | `clearSnapshots()` |

Cron:
```
0 4 * * *   php artisan magostore:expire-alerts
```

### Arayüz tarafı

CSS sınıfları `tema/public/css/magostore.css` içinde hazır:
`.mg-ship`, `.mg-thresh`, `.mg-cart-group`, `.mg-track`, `.mg-pkg`, `.mg-steps`

Blade partial'ları bu beş servisi çağıracak şekilde yazılacak — sıradaki iş.

---

## Sırada ne var

Bu paket **motorları** kuruyor. Kalan iş arayüze bağlamak:

1. Ürün sayfasına teslim sayacı + fiyat geçmişi grafiği + alarm butonu
2. Sepet sayfasına gruplu görünüm + eşik çubuğu + fiyat uyarısı
3. Müşteri paneline paket takip ekranı
4. Alarm bildirimi e-posta şablonları + kuyruk işi
5. Admin paneline "yanıltıcı indirim" raporu

Beşinci madde küçük ama değerli: `misleading` işaretli ürünlerin listesi,
satıcıyla konuşmak için somut veri veriyor.

---

# EK — ARAYÜZ BAĞLANTISI TAMAMLANDI

Beş motor artık arayüze bağlı. Eklenti dosyasına dokunulmadı; bağlantı Botble
hook'ları ve Eloquent olayları üzerinden.

## Eklenen dosyalar

| Dosya | Görevi |
|---|---|
| `app/Providers/MagostoreStorefrontServiceProvider.php` | Olay dinleyicileri |
| `app/Console/Commands/DispatchProductAlerts.php` | Alarm e-postası gönderimi |
| `app/Http/Controllers/Magostore/AlertController.php` | Abonelik ve iptal |
| `routes/magostore.php` | 4 uç |
| `tema/views/magostore/delivery-countdown.blade.php` | Teslim sayacı |
| `tema/views/magostore/price-insight.blade.php` | Fiyat içgörüsü |
| `tema/views/magostore/stock-alert.blade.php` | Alarm formu |
| `tema/views/magostore/cart-groups.blade.php` | Gruplu sepet + eşik |
| `tema/views/magostore/package-tracker.blade.php` | Paket takibi |
| `tema/views/magostore/mail/product-alert.blade.php` | Bildirim e-postası |

CSS: `tema/public/css/magostore.css` sonuna 8 bileşen bloğu eklendi (202 kural).

---

## Üç tasarım kararı

### 1. Alarm e-postası ürün kaydında GÖNDERİLMİYOR

Sağlayıcı yalnızca `pending → triggered` işaretliyor; gönderimi ayrı komut
yapıyor.

Sebebi: Excel ile 500 ürün aktarımı yapıldığında binlerce e-posta anında gitmeye
çalışır. SMTP boğulur, yarısı kaybolur, gönderilenlerin bir kısmı spam
klasörüne düşer. Ve bir kez spam işaretlenen alan adı geri gelmiyor.

Komut hız sınırlı ve **yeniden denenebilir** — başarısız gönderimde
`notified_at` boş kalıyor, sonraki turda tekrar deniyor.

### 2. Ürün kaydetme akışı asla bozulmuyor

Sağlayıcıdaki her kanca `try/catch` içinde ve tablo varlığı kontrol ediliyor:

```php
if (! Schema::hasTable('mg_price_history')) return;
```

Migration çalışmadıysa özellik sessizce kapalı kalıyor, ürün kaydetme çalışıyor.
Bir vitrin özelliği yüzünden satıcının ürün girememesi kabul edilemez.

### 3. Alarm formunda hız sınırı var

Alarm üyelik istemiyor — bu dönüşüm için doğru ama spam'e açık. IP başına
dakikada 5 alarm sınırı koydum (`RateLimiter`). Sınır aşılırsa 429 dönüyor.

---

## Kurulum

```bash
# kod
cp app/Providers/MagostoreStorefrontServiceProvider.php  ~/magostore/app/Providers/
cp app/Console/Commands/DispatchProductAlerts.php        ~/magostore/app/Console/Commands/
cp -r app/Http/Controllers/Magostore                     ~/magostore/app/Http/Controllers/
cp routes/magostore.php                                  ~/magostore/routes/

# tema
cp -r tema/views/magostore  ~/magostore/platform/themes/farmart/views/
cp tema/public/css/magostore.css ~/magostore/platform/themes/farmart/public/css/
```

`bootstrap/providers.php` içine:
```php
App\Providers\MagostoreStorefrontServiceProvider::class,
```

`routes/web.php` sonuna:
```php
require __DIR__ . '/magostore.php';
```

```bash
php artisan migrate
php artisan cms:theme:assets:publish
php artisan optimize:clear
```

### Şablonlara yerleştirme

**Ürün detay** — `partials/ecommerce/product-cart-form.blade.php` veya
`views/ecommerce/product.blade.php`, fiyatın altına:

```blade
@include(Theme::getThemeNamespace('views.magostore.price-insight'), ['product' => $product])
@include(Theme::getThemeNamespace('views.magostore.delivery-countdown'), ['product' => $product])
@include(Theme::getThemeNamespace('views.magostore.stock-alert'), ['product' => $product])
```

**Sepet** — `views/ecommerce/cart.blade.php`, mevcut tablo yerine:

```blade
@include(Theme::getThemeNamespace('views.magostore.cart-groups'))
```

**Müşteri paneli** — sipariş detayında:

```blade
@include(Theme::getThemeNamespace('views.magostore.package-tracker'), [
    'groupCode' => $order->code,
])
```

### Cron

```
0 4 * * *      php artisan magostore:dispatch-alerts --limit=500
*/15 * * * *   php artisan magostore:dispatch-alerts --limit=100
```

İlk çalıştırmada kuru dene:
```bash
php artisan magostore:dispatch-alerts --dry-run
```

---

## Kalan tek iş

**Admin paneline "yanıltıcı indirim" raporu.** `mg_price_history` verisi hazır,
`PriceInsight::forProduct()` `misleading` işaretini üretiyor. Eksik olan
Filament/Botble tablosu.

Küçük iş ama değerli: liste fiyatını şişirip indirim iddia eden satıcıların
listesi, o satıcıyla konuşmak için somut veri veriyor. Şu an bu bilgi
hesaplanıyor ama hiçbir yerde görünmüyor.
