# BATTEASY — Prototype de l'application (HTML · CSS · JS)

Prototype **complet, cliquable et testable** de l'app Batteasy, construit pour être présenté au client puis **converti en Flutter avec un minimum d'effort** : l'architecture reproduit déjà la structure d'un projet Flutter (modèles, services, store, router, widgets, écrans, i18n en fichiers séparés).

- Ouvrir : `index.html` (double-clic, aucun serveur nécessaire) — cadre de démonstration (téléphone 390×844 + panneau simulateur).
- **Sur iPhone / plein écran** : `app.html` (même application, sans cadre ; zones sûres réelles, ajout possible à l'écran d'accueil). Paramètres : `?demo=1` (scénario Andrey), `?demo=loki` (scénario Loki, 5 prises en rotation), `?nodev=1` (sans le bouton ⚙︎ Simulateur). Publié pour la démo externe sur **https://batteasy-prototype.pages.dev/app** (Cloudflare Pages, `app.html` est servi sous `/app`, projet `batteasy-prototype` ). **Republier en une commande** après une modification locale : `./deploy.sh` (lit le token dans `~/Documents/API_ACCESS/CLOUDFLARE/`).
- Source fonctionnelle : `../PRESENTATION/index.html` (contrat de construction, 30 écrans + spécifications). Ce prototype en implémente **l'intégralité** et ajoute la page **Profil** (avatar cliquable : photo, identité, réglages globaux).
- Tests automatisés : `index.html?test=1` (35 parcours, ≈ 45 s) ou `BE.tests.run()` dans la console.

## 1. Ce que contient le prototype

| Groupe | Écrans (route) | Dialogues |
|---|---|---|
| A Démarrage & compte | `splash`, `language`, `login`, `signup`, `forgot`, `tutorial` (affiché **une seule fois, juste après la première connexion** de la personne ; mémorisé par compte, revoir via Réglages → Aide) | hors ligne → mode local |
| B Accueil & ajout d'une prise | `home` (vide / liste / charge en cours), **`add-device` : assistant plein écran en 5 étapes** (1 Prise : nom par défaut « Prise N » + profil de l'appareil · 2 Objectif · 3 Programme pré-rempli · 4 Appairage : prêt / recherche / introuvable / liste · 5 Connexion → récapitulatif → **Démarrer la charge**) | permission Bluetooth (style iOS), guide « la prise ne clignote pas », prise déjà associée |
| C Charger (prise existante) | `choose-profile` (modifier l'appareil d'une prise, depuis sa fiche), `charge-now` (4 objectifs, niveau estimé, profil Expert éditable), `session` (live / terminée / sécurité), `set-schedule` (durée, répétition, jours, dates, calendrier, heures), `schedules` (Mes programmes, avec l'option **Mode Expert** repliée par défaut : avertissement « réservé aux professionnels », seuils du profil Personnalisé) | sélecteur de prise, sélecteur d'heure, confirmation arrêt / suppression |
| D Prise | `device`, `firmware` (OTA avec progression), `history` | **renommer**, **retirer** |
| E Réglages | `settings` (langue, prises, Bluetooth, mes programmes, notifications, confidentialité, aide, déconnexion), `devices` (Réglages → Prises : liste des prises, supprimer par la croix, bouton Ajouter une prise), `notif-settings` (Réglages → Notifications : activation par catégorie, chaque catégorie expliquée), `notifications` (volet de la cloche : messages reçus), **`profile` (nouveau)** (photo, nom / e-mail, mot de passe, suppression du compte) — le **tarif électricité est propre à chaque prise** (fiche prise) ; chaque réglage n'existe qu'à un seul endroit | photo / avatar, mot de passe, suppression de compte (double confirmation) |
| F États système | `bt-off`, `out-of-range`, `perm-denied`, `safety-alert` | — |

25 routes + 3 dialogues = les 30 écrans de la présentation (les 3 écrans d'appairage sont les étapes 4–5 de l'assistant), plus Profil. 4 langues (FR / DE / IT / EN, 469 clés chacune), détection de la langue du téléphone, changement à chaud.

**Échelle visuelle** : tout le contenu est agrandi de 15 % par une règle `zoom: 1.15` en fin de `theme.css` (sauf barre d'onglets, cloche, barre d'état). Pour revenir à la taille d'origine, supprimer ce bloc ; l'ancienne version (avant agrandissement, en-têtes sur deux lignes, toggles étroits) est archivée dans `../99_META/ARCHIVES/batteasy_prototype_app_BACKUP_2026-09-18_avant_agrandissement_15pct/`. La version courante est la référence.

## 2. Simulation (aucune prise réelle nécessaire)

Le panneau **Simulateur** (colonne de droite) pilote des prises Batteasy virtuelles dotées d'un « firmware » qui exécute la même logique de charge que l'app (`src/domain/charge_engine.js`) :

- **Téléphone** : Bluetooth ON/OFF, permission (à demander / accordée / refusée), Internet ON/OFF, langue du téléphone.
- **Temps** : vitesse ×1 à ×300 (1 s réelle = n s prise), avance déterministe +10 min / +1 h / +6 h. Les programmes se déclenchent à l'heure simulée, **même téléphone hors de portée**.
- **Prises** : à portée / hors de portée, alimentée / débranchée, LED d'appairage, type de chargeur branché (trottinette 90 W, vélo 160 W, moto 900 W…), niveau de batterie, force du signal, **surchauffe** (déclenche l'arrêt de sécurité), reset 10 s (libère une prise associée à un autre compte).
- **Données** : deux scénarios démo, chargés par le panneau, par l'URL ou automatiquement à la connexion avec le compte correspondant (mot de passe `batteasy1`) :
  - **Andrey Thomson** (`demo@batteasy.com`, `?demo=1`) : 2 prises, **charge en cours sur « Vélo garage »**, programmes, historique, notifications.
  - **Loki Laufeyson** (`loki@batteasy.com`, `?demo=loki`) : mêmes prises et programmes qu'Andrey **plus 5 prises de 5 types d'appareils** (trottinette, vélo, moto, caddie de golf, outillage) qui **se chargent l'une après l'autre** : dès qu'une charge se termine, la suivante démarre 12 s plus tard (modes ~80 % / 100 % / stockage / durée), il y a donc toujours une charge visible. La rotation reprend seule après un rechargement et s'arrête à la déconnexion.
  - Réinitialisation complète possible.
- **Trafic BLE** : journal des lectures / écritures / notifications par characteristic.

Compte de démonstration : `demo@batteasy.com` / `batteasy1`. Apple / Google : connexion simulée en 1 tap. « Continuer sans compte » = mode Bluetooth local.

## 3. Architecture (miroir d'un projet Flutter)

```
batteasy_prototype_app/
├── index.html                  cadre de démo (téléphone 390×844 + panneau simulateur) — Flutter : main.dart
├── deploy.sh                   publication en une commande sur Cloudflare Pages (batteasy-prototype.pages.dev)
├── app.html                    même app en plein écran pour iPhone (mêmes scripts, sans cadre) — à resynchroniser si la liste des scripts change
├── assets/brand/               logos (charte teal #009989 / gris #3c3c3b / Nunito)
├── src/
│   ├── core/
│   │   ├── theme.css           design system : tokens + composants (= ThemeData + styles)
│   │   ├── utils.js            formatage, validation (= lib/core/utils.dart)
│   │   ├── events.js           émetteur (= ChangeNotifier / Stream)
│   │   ├── i18n.js             service de traduction (= flutter_localizations)
│   │   ├── store.js            état global observable + persistance (= Riverpod / Provider)
│   │   └── router.js           navigation à pile, dialogs, sheets, toasts (= Navigator / GoRouter)
│   ├── i18n/fr.js de.js it.js en.js   dictionnaires (= lib/l10n/app_xx.arb)
│   ├── models/models.js        User, Prefs, Device, Profile, SessionConfig, Session, Schedule, Notification, HistoryEntry (toJson / fromJson)
│   ├── domain/charge_engine.js LOGIQUE MÉTIER PURE : estimation %, cible Wh, critères d'arrêt, config de session (= lib/domain, testable unitairement, portée dans le firmware)
│   ├── services/
│   │   ├── ble_service.js      CONTRAT Bluetooth + profil GATT (UUID, characteristics, codes d'erreur)
│   │   ├── ble_simulator.js    implémentation simulée (prises virtuelles) — remplacée par flutter_blue_plus en prod
│   │   ├── device_service.js   orchestration prises ↔ store ↔ BLE (appairage, sessions, relais, événements, OTA)
│   │   ├── schedule_service.js validation (chevauchement, 8 max), conversion en entrée firmware, synchro
│   │   ├── notification_service.js  centre de notifications, catégories, heures calmes, anti-rafale
│   │   ├── auth_service.js     authentification simulée (= Supabase Auth)
│   │   └── storage_service.js  persistance JSON (= shared_preferences / sqflite)
│   ├── widgets/icons.js, components.js   composants UI purs (= lib/widgets)
│   ├── screens/*.js            un fichier par écran : { watch, init, build, mount, unmount, actions }
│   ├── screens/_goal_form.js   formulaire Objectif partagé (charge-now + assistant) — Flutter : widgets/goal_form.dart
│   ├── screens/_schedule_form.js formulaire Programme partagé (set-schedule + assistant) — Flutter : widgets/schedule_form.dart
│   └── app.js                  composition / injection des dépendances, décision de démarrage
├── dev/simulator_panel.js, demo_data.js   outillage de démo (non livré dans l'app)
└── tests/flows.js              35 parcours automatisés (= integration_test/)
```

### Conventions qui rendent la conversion Flutter directe

| Prototype JS | Équivalent Flutter |
|---|---|
| `BE.screens.define("home", { build, mount, unmount, actions, watch })` | `class HomeScreen extends StatefulWidget` — `build()`, `initState()`, `dispose()`, méthodes `onPressed`, `Consumer`/`ref.watch` sur les slices `watch` |
| `ctx.state` + `ctx.setState()` | `State<T>` + `setState()` |
| `ctx.push / pop / replace / reset` | `Navigator.push / pop / pushReplacement / pushAndRemoveUntil` (ou `context.go`) |
| `ctx.dialog({ build, actions })` → `Promise` | `showDialog<T>()` → `Future<T>` |
| `ctx.sheet(...)` | `showModalBottomSheet()` |
| `ctx.toast()` / `router.banner()` | `ScaffoldMessenger.showSnackBar` / `flutter_local_notifications` |
| `data-action="x"` + `data-arg` | `onPressed: () => x(arg)` |
| `data-bind="field"` | `TextEditingController` |
| `store.update(fn, ["devices"])` | `notifyListeners()` / `state = state.copyWith(...)` |
| `BE.models.*` avec `toJson/fromJson` | classes `freezed` + `json_serializable` |
| `BE.ChargeEngine` (fonctions pures) | `lib/domain/charge_engine.dart` (tests unitaires identiques) |
| `BE.BleService` (contrat) / `BE.BleSimulator` | `abstract class BleService` / `BleSimulator` + `BleFlutterBlue` |
| `BE.GATT` | constantes UUID (`lib/services/gatt.dart`) |
| `src/i18n/*.js` | `lib/l10n/app_*.arb` (mêmes clés, mêmes `{placeholders}`) |
| `theme.css` `:root` tokens | `ThemeData` + `ColorScheme` (teal `#009989`, ink `#3c3c3b`, Nunito 400-900) |
| widgets `W.item / W.setting / W.toggle / W.stepper / W.segment / W.calendar / W.timeGrid` | `ListTile`, `SwitchListTile`, widgets custom `BeStepper`, `CupertinoSlidingSegmentedControl`, `CalendarDatePicker`, `TimePicker` |
| `router.watch` (rebuild sur slice) | `ref.watch(provider.select(...))` |
| `BE.morph` (diff DOM en place lors d'un rebuild : pas d'animation, défilement / focus / overlays conservés) | diff de l'arbre de widgets fait nativement par Flutter (`Element.update`) |

Règles à conserver telles quelles lors du portage : toute action utilisateur passe par un service (jamais d'appel BLE depuis un écran) ; les écrans sont des fonctions pures de l'état ; les chaînes viennent exclusivement des dictionnaires ; la logique de charge ne vit que dans `charge_engine` (partagée avec le firmware).

## 4. Logique métier implémentée (résumé)

- **Session de charge** : 4 objectifs (~80 % quotidien, 100 % avant trajet, durée fixe, stockage ~55 %). Le % est **estimé** (la prise ne lit pas le BMS) : Wh fournis × rendement 0,836 / capacité + niveau initial déclaré. Arrêt au premier critère : cible Wh, genou CC→CV (P < 0,9 × P_nom pendant 5 min, après 10 min d'apprentissage), puissance < seuil pendant N min, durée max, sécurité (T° ≥ seuil, P > 2 300 W CH / 3 680 W EU, P > 1,5 × P_nom), aucune puissance 10 min.
- **Programmes** : 8 slots par prise, stockés **dans la prise** (autonomes), badge « à synchroniser » si hors de portée puis synchro automatique, refus des chevauchements.
- **Ajout d'une prise (assistant)** : l'utilisateur configure d'abord (nom « Prise N » incrémenté, profil, objectif, programme pré-rempli), puis appaire : scan filtré 4 s, RSSI → fort / moyen / faible, identification (anneau), refus des prises liées à un autre compte (reset 10 s), séquence connexion → clés → infos → heure → **écriture du nom, du profil et du programme dans la prise**, puis « Démarrer la charge ».
- **Notifications** : 4 catégories (sécurité non désactivable), heures calmes, anti-rafale 15 min par prise, bannière en app + centre.
- **Firmware** : OTA refusée pendant une charge, progression, rollback documenté.
- **Compte** : e-mail / Apple / Google / sans compte, verrouillage après 5 échecs, export CSV, suppression avec double confirmation (LPD / RGPD).

## 5. Tests

`index.html?test=1` exécute 35 parcours de bout en bout (appairage, charge jusqu'à l'arrêt automatique, arrêt manuel, programmation hebdo / mensuelle, chevauchement, renommage, OTA, hors de portée, surchauffe, Bluetooth OFF, permission refusée, notifications, profil, langues, compte, hors ligne, programme autonome, retrait, parité i18n, rendu de tous les écrans dans les 4 langues). Chaque étape a un délai maximal de 40 s, se remet en état en cas d'échec (pas de cascade) et injecte ses prérequis si une étape précédente a échoué. Le rapport est affiché à l'écran et enregistré dans `localStorage["batteasy.testReport"]`.
