# Prompt p05 — Pompiste Mobile (mobile)
*Contextes requis : ctx-global.md + ctx-mobile-expo.md + ctx-laravel-bff.md*

---

## Profil & Accès
- **Rôle** : POM (Pompiste)
- **Nombre d'utilisateurs** : ~92 (4 par station × 23 stations)
- **Accès** : mobile uniquement — sa pompe assignée, sa station uniquement
- **Platform** : React Native + Expo SDK 54
- **Session** : durée du shift uniquement (auto-logout à la fin du shift)

---

## Fichiers à créer

```
app/(mobile)/pompiste/scan-shift.tsx
app/(mobile)/pompiste/vente.tsx
app/(mobile)/pompiste/recap-shift.tsx
app/(mobile)/pompiste/fin-shift.tsx
components/pompiste/QrScanner.tsx
components/pompiste/VenteForm.tsx
components/pompiste/PaiementSelector.tsx
components/pompiste/OfflineQueue.tsx
components/pompiste/ShiftHeader.tsx
hooks/usePompisteShift.ts
hooks/useOfflineQueue.ts
stores/venteStore.ts
```

---

## Spec UX condensée

### Écran 1 — Démarrage shift (scan QR)
```
┌─────────────────────────┐
│    FLOWUP — Démarrage   │
│         shift           │
│                         │
│  ┌───────────────────┐  │
│  │                   │  │
│  │   [CAMÉRA QR]     │  │
│  │   Visez le QR     │  │
│  │   de votre cabine │  │
│  │                   │  │
│  └───────────────────┘  │
│                         │
│  ── ou saisie manuelle ─│
│  [__________________]   │
│  [   COMMENCER SHIFT  ] │
└─────────────────────────┘
```

### Écran 2 — Saisie vente
```
┌─────────────────────────┐
│ Pompe 3 · Kaloum   [🔴2]│
│ Shift 08h00 · 47 ventes │
├─────────────────────────┤
│ PRODUIT                 │
│ ┌────────┐ ┌────────┐   │
│ │ Gasoil │ │  SP95  │   │
│ └────────┘ └────────┘   │
│ ┌────────┐ ┌────────┐   │
│ │  SP98  │ │Pétrole │   │
│ └────────┘ └────────┘   │
│                         │
│ VOLUME (litres)         │
│ ┌───────────────────┐   │
│ │       45,00       │   │
│ └───────────────────┘   │
│ [5L] [10L] [20L] [50L] │
│                         │
│ PAIEMENT                │
│ ○ Espèces  ○ Mobile Money│
│ ○ Crédit client         │
│                         │
│ Montant : 270 000 GNF   │
│                         │
│ [    ENREGISTRER VENTE ]│
│ ──────────────────────  │
│ 📶 Hors ligne · Queue:3 │
└─────────────────────────┘
```

### Écran 3 — Récap shift
```
┌─────────────────────────┐
│ RÉCAP SHIFT — Pompe 3   │
│ 08h00 → 16h00           │
├─────────────────────────┤
│ Ventes           : 47   │
│ CA Total : 5 640 000 GNF│
│ Espèces  : 4 200 000 GNF│
│ Mobile M.: 1 200 000 GNF│
│ Crédit   :   240 000 GNF│
├─────────────────────────┤
│ Gasoil    : 1 250 L     │
│ SP95      :   840 L     │
├─────────────────────────┤
│ [  REMETTRE LA CAISSE ] │
└─────────────────────────┘
```

### Écran 4 — Fin shift + remise caisse
```
┌─────────────────────────┐
│ REMISE CAISSE           │
│ Pompe 3 · 13 mai 2026   │
├─────────────────────────┤
│ Espèces remises         │
│ ┌───────────────────┐   │
│ │   4 200 000       │   │
│ └───────────────────┘   │
│ ≈ Attendu : 4 200 000   │
│ Écart : 0 GNF ✓         │
│                         │
│ Signature :             │
│ ┌───────────────────┐   │
│ │  [Pad signature]  │   │
│ └───────────────────┘   │
│ [     CLÔTURER SHIFT  ] │
└─────────────────────────┘
```

---

## Endpoints BFF

```
POST /api/shifts/start
  Body : { qr_code: "CAB-KAL-003", pompiste_id: INT }
  Réponse : {
    shift_id: INT,
    pompe: "Pompe 3",
    station: "Kaloum",
    debut: "2026-05-13T08:00:00Z",
    produits_disponibles: [{ id: INT, nom: "Gasoil", prix_unitaire: "6000" }]
  }

GET /api/produits?actifs=1&station_id={id}
  Réponse : [{
    id: INT,
    code: "PRD-CAR-001",
    nom: "Gasoil",
    prix_unitaire: "6000",   // VARCHAR (GNF/litre)
    statut: 1
  }]

POST /api/ventes
  Body : {
    shift_id: INT,
    produit_id: INT,
    volume: "45.00",          // VARCHAR (litres)
    montant: "270000",        // VARCHAR (GNF)
    type_paiement: "especes"|"mobile_money"|"credit",
    client_id: INT|null,      // si crédit
    offline_uuid: "uuid-v4"   // pour déduplication
  }
  Réponse : { id: INT, reference: "VNT-2026-0047", statut: 1 }

POST /api/ventes/sync
  Body : { ventes: [{ ...même structure que POST /api/ventes... }] }
  Réponse : { synced: 3, errors: [], ids: [INT, INT, INT] }

POST /api/shifts/end
  Body : {
    shift_id: INT,
    especes_remises: "4200000",   // VARCHAR
    signature_base64: "...",
    ecart: "0"                    // VARCHAR
  }
  Réponse : { id: INT, statut: "CLOTURE", recap: { ... } }

GET /api/shifts/{id}/recap
  Réponse : {
    nb_ventes: 47,
    ca_total: "5640000",      // VARCHAR
    ca_especes: "4200000",    // VARCHAR
    ca_mobile_money: "1200000", // VARCHAR
    ca_credit: "240000",      // VARCHAR
    volumes: [{ produit: "Gasoil", volume: "1250" }]
  }
```

---

## Règles spécifiques

- **RGPOM-01** : Saisie vente offline obligatoire — si `navigator.onLine === false` ou timeout réseau >5s, la vente est stockée dans MMKV (`useOfflineQueue`). Sync automatique dès que le réseau revient. Indicateur visuel permanent (bandeau bas de l'écran) avec compteur de ventes en attente.
- **RGPOM-02** : Chaque vente offline reçoit un `offline_uuid` (UUID v4) pour déduplication côté BFF. Le BFF doit rejeter tout doublon d'`offline_uuid` avec HTTP 409.
- **RGPOM-03** : Montants calculés côté client à partir du volume (string) × prix_unitaire (string) — utiliser `Decimal.js` pour éviter les erreurs de virgule flottante. Résultat = string.
- **RGPOM-04** : Les boutons de volume rapide ([5L] [10L] [20L] [50L]) sont additifs — ils ajoutent au volume actuel. Bouton "Reset" remet à 0.
- **RGPOM-05** : Le bouton "ENREGISTRER VENTE" est `disabled + spinner` pendant le POST (anti-double-soumission). Restauré dans `finally`.
- **RGPOM-06** : Fin de shift = `statut: 3` pour le shift (soft delete = inactif). Le pompiste est automatiquement redirigé vers l'écran de connexion.
- **RGPOM-07** : Le crédit client nécessite un `client_id` valide — un sélecteur de client B2B apparaît (liste des clients autorisés pour cette station).
- **RGPOM-08** : Session limitée à la durée du shift — JWT expire à `shift.fin_prevue + 30min`.

---

## Tâche Claude

Génère le code complet React Native + Expo SDK 54 TypeScript pour l'application Pompiste FlowUp ERP.

**Contraintes techniques absolues :**
- Palette navy `#1A2157`, blue `#1E5EFF`, cyan `#00C8C0` — Inter + JetBrains Mono
- Montants = VARCHAR(255) → `string` dans tous les types TS. Calculs via `Decimal.js`
- IDs = `number` (INT UNSIGNED)
- `expo-camera` pour le scan QR
- MMKV (`react-native-mmkv`) pour la queue offline
- `expo-signature-canvas` pour la remise caisse
- Zustand pour `venteStore` (état local du shift en cours)

**Génère dans cet ordre :**
1. Types TypeScript (`types/pompiste.ts`)
2. `venteStore.ts` (Zustand — shift en cours, vente courante)
3. Hook `useOfflineQueue.ts` (MMKV, sync auto, `offline_uuid`)
4. Hook `usePompisteShift.ts` (état shift, produits)
5. `QrScanner.tsx` (expo-camera, saisie manuelle fallback)
6. `PaiementSelector.tsx` (espèces/Mobile Money/crédit)
7. `VenteForm.tsx` (volume, produit, paiement, calcul montant Decimal.js)
8. `OfflineQueue.tsx` (bandeau bas de l'écran)
9. `ShiftHeader.tsx` (pompe, station, compteur ventes)
10. Écrans : `scan-shift.tsx`, `vente.tsx`, `recap-shift.tsx`, `fin-shift.tsx`
