# Targo Tech — app technicien (Capacitor)

App mobile native des techniciens TARGO/Gigafibre. **Capture passive** du temps par intervention : appairage **une seule fois** (QR), puis géorepérage natif en **arrière-plan** (app fermée) → arrivées/départs détectés automatiquement → checkpoints au hub. Le tech ne tape rien sur place. UI (liste/carte/Street View/photo/scan) **réutilisée depuis le hub** (`/field`). Scan série/MAC **on-device MLKit**.

- **Statut : APK Android buildé ✅** (debug, sideload) — voir [recette de build](#recette-de-build-android) ci-dessous.
- Design détaillé : [`docs/field-tech-app.md`](../../docs/field-tech-app.md) · build approfondi : [`BUILD-ANDROID.md`](BUILD-ANDROID.md) · CI : [`CI-SETUP.md`](CI-SETUP.md).

---

## Fonctionnement

```
┌─ Bureau (1×) ─────────┐     ┌─ Téléphone du tech ──────────────────────┐
│ Ops → bouton 📱 tech  │ QR  │ app Targo Tech                            │
│ techFieldLink (token) ├────▶│ src/main.js : token → @capacitor/         │
└───────────────────────┘     │   preferences (persistant)               │
                              │       │                                   │
                              │       ▼                                   │
                              │ Transistorsoft BackgroundGeolocation:     │
                              │  addGeofences(jobs du jour, r=200m)       │
                              │  enter/exit ── autoSync ──▶ hub /field/ts │  ← app FERMÉE + reboot
                              │       │                                   │
                              │       ▼ (à l'ouverture)                   │
                              │ location.replace(hub /field?t=token)      │
                              │  → liste/détail/carte/StreetView/photo    │
                              │  → scan série/MAC = MLKit on-device       │
                              └───────────────────────────────────────────┘
```

- **Appairage 1×** — `pairScan()` (MLKit) ou `pairPaste()` lit le lien tech (`techFieldLink`), extrait le token, le persiste. Plus jamais de lien à ouvrir sur place.
- **Géorepérage natif** — `@transistorsoft/capacitor-background-geolocation` enregistre **un geofence par job du jour** (identifier = token signé du job). Les `enter/exit` sont POSTés **nativement** au hub `/field/ts` (déjà déployé) qui mappe l'identifier → checkpoint → dérive `actual_start`/`actual_end`. Survit à l'app fermée et au reboot — c'est tout l'intérêt par rapport à un `watchPosition` JS.
- **UI hébergée** — après appairage, l'app charge `/field?t=token` (servi par le hub `lib/roster.js`) : liste des jobs, carte Mapbox, Google Street View, photo, scan. Aucune UI dupliquée dans l'app.
- **Scan série/MAC** — MLKit on-device en priorité (instantané, hors-ligne) ; replis `BarcodeDetector` web puis proxy IA Gemini (`/field/vision`) pour étiquettes texte seul.

> Backend (tout déjà déployé sur le hub) : `GET /field/tech?t=` (jobs du jour du tech), `GET /field/job?t=`, `POST /field/checkpoint`, `POST /field/ts` (webhook geofence Transistorsoft, auto-auth via identifier signé), `POST /field/photo`, `POST /field/device`, `POST /field/vision`. Tokens = **HMAC signés** (par job / par tech), sans PII, stateless.

---

## Recette de build Android

> Les 4 correctifs Gradle ci-dessous sont **déjà appliqués et commités** dans `android/` (Capacitor ne réécrit pas ces fichiers lors d'un `cap sync`). Ils ne sont à ré-appliquer **que** si on régénère `android/` from scratch (`rm -rf android && npx cap add android`).

### Prérequis (testé sur MacBook Pro M4, sans sudo)
```bash
# JDK 17
brew install openjdk@17
export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home

# Android SDK (cmdline-tools) → ~/Library/Android/sdk
export ANDROID_HOME="$HOME/Library/Android/sdk"
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
```

### Build (APK debug)
```bash
cd apps/field-tech
npm install
npm run build                 # Vite → www/
npx cap sync android          # copie www + plugins (NE touche pas aux fixes Gradle)
cd android && ./gradlew assembleDebug
# → android/app/build/outputs/apk/debug/app-debug.apk
```

### Les 4 correctifs Gradle (pourquoi)
| # | Symptôme | Fichier | Correctif |
|---|----------|---------|-----------|
| 1 | `androidx.work:work-runtime:2.10.0 requires compileSdk 35` | `android/app/build.gradle` | `configurations.all { resolutionStrategy { force 'androidx.work:work-runtime:2.9.1'; force '…-ktx:2.9.1' } }` (on reste en compileSdk 34) |
| 2 | `Manifest merger failed: minSdkVersion 22 < 24 (tslocationmanager)` | `android/variables.gradle` | `minSdkVersion = 24` |
| 3 | `package com.transistorsoft.xms.g.common does not exist` | `android/build.gradle` | dans `allprojects.repositories` : les 2 repos maven locaux des plugins (`…background-geolocation/libs`, `…background-fetch/libs`) **+** `maven { url 'https://developer.huawei.com/repo/' }`. Les AAR `tslocationmanager*` (classes `xms.g`) vivent dans `node_modules/.../libs` ; le plugin n'ajoute pas ce repo → étape manuelle (= INSTALL-ANDROID officiel Transistorsoft). |
| 4 | `cannot find symbol EVENT_PROVIDERCHANGE/EVENT_AUTHORIZATION/…` (67 err) | `android/variables.gradle` | `googlePlayServicesLocationVersion = '21.0.1'` → le plugin sélectionne l'AAR `tslocationmanager-v21` (API moderne attendue par Capacitor v6.1.5 ; major <21 prendrait l'ancien 3.6.4 sans ces constantes). |

### Optimisation taille
`android/app/build.gradle` filtre les ABI sur `arm64-v8a` + `armeabi-v7a` (téléphones réels ; l'émulateur Apple Silicon est arm64 → test local OK). Pour un émulateur Intel x86_64, ajouter `'x86_64'` au bloc `ndk { abiFilters … }`.

### Release / Play Store (plus tard)
Préférer **compileSdk/targetSdk 35 + AGP 8.7+** (et retirer le force work-runtime) plutôt que rester en 34 ; + clé licence Transistorsoft en `meta-data` du manifest + keystore. Détails : [`BUILD-ANDROID.md`](BUILD-ANDROID.md).

---

## Installer & appairer
- **USB** (débogage USB activé) : `~/Library/Android/sdk/platform-tools/adb install -r app-debug.apk`
- **Sans fil** : envoyer l'APK (Drive/courriel) → ouvrir sur le tél → autoriser « Installer applis inconnues ».
- **1er lancement** → écran d'appairage → scanner le QR (ou coller le lien) du bouton **📱** dans Ops → l'app mémorise le token, arme le geofence, et charge la liste des jobs.
- **Tester le geofence** : se déplacer vers/depuis une adresse de job (rayon 200 m), **app fermée** → vérifier dans Ops que `actual_start/end` se posent.

## CI (git.targo.ca)
Push sur `apps/field-tech/**` → `.gitea/workflows/field-tech-android.yml` build l'APK sur un runner Linux auto-hébergé (**jamais sur erp/prod**) → artefact téléchargeable. Voir [`CI-SETUP.md`](CI-SETUP.md). iOS : `field-tech-ios.yml` (runner macOS + compte Apple, `workflow_dispatch`).

## iOS
Même `src/main.js`. Scaffolder quand le compte Apple Developer est validé : `npx cap add ios` + cocoapods + Xcode + licence Transistorsoft iOS + `NSLocationAlwaysAndWhenInUseUsageDescription` + mode arrière-plan `location`.

## Sécurité
- **Aucun secret dans le repo** : seul figure l'URL publique `https://msg.gigafibre.ca` (`src/main.js`) et le token Mapbox **public** `pk.…` (côté hub `/field`). Les secrets serveur restent dans le hub (`ops_secret.php` / env), jamais ici.
- Tokens d'appairage = **HMAC signés**, sans PII. Keystores / certificats : gitignorés (jamais commités).

## Arborescence
```
apps/field-tech/
├── index.html            # écran d'appairage (coquille)
├── src/main.js           # appairage + init géorepérage + redirection /field
├── capacitor.config.json # appId ca.targo.field
├── vite.config.js        # build → www/
├── android/              # projet natif (4 fixes Gradle commités) — artefacts gitignorés
├── Dockerfile            # build headless (alternative au build local) — voir BUILD-ANDROID.md
├── README.md  BUILD-ANDROID.md  CI-SETUP.md
```
