Specs : rattrapage de l'écart avec le code + règle de synchro continue

Aligne requirements.md et design.md sur l'état réel du code (écart accumulé
depuis le 14/09 en mode dev direct).

requirements.md :
- Intro : compte unique authentifié (au lieu de « sans authentification ».
- Glossaire : Compte, Adresse_Email, Mot_De_Passe, Session, JWT,
  Jeton_Reinitialisation, Aller_Retour, Distance_Effective.
- Exigence 1 : case Aller/Retour. Exigence 10 : dénomination véhicule.
- Exigence 12 : refonte consultation par mois (sélecteur année/mois, liste
  mensuelle groupée par jour repliable, nav jour bornée aux jours avec trajets).
- Exigence 13 : tranches de barème multiples (dernière illimitée, coef 3
  décimales) + recopie du dernier barème.
- Exigence 16 : onboarding relié à l'authentification.
- Nouvelles exigences 17 (Authentification) et 18 (Trajet aller/retour).

design.md :
- Overview + contraintes : compte authentifié, port 2202, Caddy externe HTTPS.
- Déploiement : compose (port 2202, JWT_SECRET_KEY obligatoire, e-mail/MailKit,
  AUTH_FRONTEND_BASE_URL), reverse-proxy Caddy externe, pas de UseHttpsRedirection.
- Nouvelle section « Évolutions majeures » : décisions d'architecture pour l'auth,
  l'onboarding 2 étapes, l'aller/retour, la consultation par mois, le barème
  multi-tranches, la dénomination véhicule et le recalcul ciblé des distances.

Ajoute .kiro/steering/spec-sync.md : règle de mise à jour des specs dans le même
commit que le code, à chaque évolution validée.
This commit is contained in:
2026-09-16 07:19:14 +02:00
parent 6f0e97d41a
commit 73a70142e4
3 changed files with 186 additions and 32 deletions
@@ -2,7 +2,7 @@
## Introduction
L'application **SchotzIAD KM Tracker** est une application web **mono-utilisateur** (sans authentification ni gestion de comptes) permettant de saisir et de suivre ses trajets mensuels (jour, adresse de départ, adresse d'arrivée), de calculer automatiquement les kilomètres parcourus par mois, et de consulter une vue annuelle consolidée des kilomètres parcourus.
L'application **SchotzIAD KM Tracker** est une application web **mono-utilisateur** protégée par un **compte unique authentifié** (adresse e-mail + mot de passe, session par jeton JWT). Elle permet de saisir et de suivre ses trajets mensuels (jour, adresse de départ, adresse d'arrivée, éventuellement en aller/retour), de calculer automatiquement les kilomètres parcourus par mois, et de consulter une vue annuelle consolidée des kilomètres parcourus. L'application ne gère qu'un seul compte : la première utilisation crée ce compte via un parcours d'inscription, et toute utilisation ultérieure exige une authentification.
L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. La saisie des adresses s'appuie sur l'API de la **Base Adresses Nationale (BAN)** (disponible sur data.gouv.fr) pour l'autocomplétion et la résolution des coordonnées géographiques. Le calcul de distance routière utilise l'API **OpenRouteService (ORS)** uniquement lorsque les coordonnées des deux adresses sont connues. Elle cible des utilisateurs souhaitant garder un historique fiable de leurs déplacements, notamment à des fins professionnelles ou de remboursement de frais.
@@ -54,6 +54,14 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
- **Regularisation** : La différence entre le Montant_Reel annuel et le total des Montant_Regle versés sur l'année, indiquant le montant restant à verser (ou à récupérer si négatif).
- **Frais_Reel_Mensuel** : La quote-part mensuelle du Montant_Reel, calculée en appliquant à chaque mois le coefficient de la tranche annuelle applicable (déterminée par le total annuel) : `Frais_Reel_Mensuel = km_du_mois × coefficient_tranche_annuelle`. La somme des douze Frais_Reel_Mensuel plus la base éventuelle de la tranche annuelle est égale au Montant_Reel.
- **Coefficient_Base_Vehicule** : Le coefficient et la base de la tranche du Bareme_Kilometrique correspondant à la Puissance_Fiscale du Véhicule et au total kilométrique annuel, affichés dans l'en-tête des feuilles mensuelles (ex. « Coef. 0,447 / Base 0 »).
- **Compte** : L'unique compte de l'Application, identifié par une adresse e-mail (servant de login) et protégé par un mot de passe. L'Application n'autorise l'existence que d'un seul Compte.
- **Adresse_Email** : L'adresse e-mail de l'utilisateur, utilisée comme identifiant de connexion du Compte et comme destinataire des e-mails de réinitialisation de mot de passe.
- **Mot_De_Passe** : Le secret d'au moins 8 caractères associé au Compte, stocké uniquement sous forme de hachage salé (jamais en clair).
- **Session** : L'état authentifié d'un utilisateur, matérialisé par un jeton JWT signé émis à la connexion, transmis par le Frontend à chaque appel protégé du Backend.
- **JWT** : JSON Web Token signé (HS256) émis par le Backend à la connexion, portant l'identifiant du Compte et une durée de validité configurable (12 heures par défaut).
- **Jeton_Reinitialisation** : Un jeton à usage unique et à durée de vie limitée, envoyé par e-mail, permettant de définir un nouveau Mot_De_Passe sans connaître l'ancien.
- **Aller_Retour** : Un indicateur booléen d'un Trajet signifiant que le déplacement est effectué dans les deux sens. Lorsqu'il est vrai, la Distance comptée du Trajet (Distance_Effective) est le double de la distance simple mesurée entre le départ et l'arrivée.
- **Distance_Effective** : La distance comptée d'un Trajet dans tous les agrégats (liste, totaux, carte, PDF, barème) : égale à la distance simple, ou à son double lorsque le Trajet est un Aller_Retour.
---
@@ -84,6 +92,7 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
17. IF l'utilisateur soumet une adresse saisie manuellement sans passer par l'autocomplétion BAN (coordonnées non disponibles), THEN THE Frontend SHALL afficher un avertissement indiquant que la distance ne pourra pas être calculée automatiquement si les coordonnées sont absentes.
18. THE Frontend SHALL proposer, pour le champ adresse de départ et pour le champ adresse d'arrivée du formulaire de saisie, un choix entre sélectionner une Adresse_Nommee pré-enregistrée du Carnet_Adresses (par sa Dénomination) via un sélecteur et saisir une nouvelle adresse manuellement avec l'autocomplétion BAN.
19. WHEN l'utilisateur sélectionne une Adresse_Nommee (par sa Dénomination) dans le sélecteur associé au champ adresse de départ ou adresse d'arrivée, THE Frontend SHALL pré-remplir ce champ avec le texte d'adresse de l'Adresse_Nommee et associer ses coordonnées géographiques (latitude, longitude) pour transmission au Backend.
20. THE Frontend SHALL afficher, dans le formulaire de saisie d'un Trajet, une case à cocher "Aller/Retour" (Aller_Retour) qui, lorsqu'elle est cochée, indique que la Distance comptée du Trajet doit être doublée sans créer de Trajet supplémentaire ni de ligne additionnelle dans les résumés.
---
@@ -238,7 +247,7 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
6. IF aucune Adresse_Domicile n'est enregistrée dans le Profil, THEN THE Frontend SHALL désactiver le bouton "Utiliser mon domicile" et afficher une infobulle indiquant que l'adresse de domicile n'est pas encore configurée.
7. WHEN l'utilisateur modifie l'Adresse_Domicile dans le Profil et sauvegarde, THE Backend SHALL re-géocoder la nouvelle adresse via l'API BAN pour mettre à jour les coordonnées dans le Profil et dans le Cache_Adresses.
8. IF le géocodage BAN de l'Adresse_Domicile échoue, THEN THE Backend SHALL sauvegarder l'Adresse_Domicile textuelle sans coordonnées et THE Frontend SHALL afficher un avertissement indiquant que l'adresse n'a pas pu être vérifiée.
9. THE Frontend SHALL permettre à l'utilisateur de saisir un Véhicule caractérisé par une plaque d'immatriculation (1 à 15 caractères) et une Puissance_Fiscale (entier positif, exprimé en chevaux fiscaux).
9. THE Frontend SHALL permettre à l'utilisateur de saisir un Véhicule caractérisé par une Dénomination libre (ex. « Marque, Modèle », watermark indicatif « Ex. Marque, Modèle »), une plaque d'immatriculation (1 à 15 caractères) et une Puissance_Fiscale (entier positif, exprimé en chevaux fiscaux).
10. WHEN l'utilisateur enregistre un nouveau Véhicule, THE Frontend SHALL exiger la saisie d'une Date_Effet inférieure ou égale à la date du jour.
11. IF l'utilisateur saisit une Date_Effet postérieure à la date du jour, THEN THE Frontend SHALL rejeter la saisie et afficher un message indiquant que la date d'effet ne peut pas être dans le futur.
12. WHEN le Backend enregistre un nouveau Véhicule, THE Backend SHALL conserver l'historique complet des Véhicules précédents sans les supprimer.
@@ -273,30 +282,32 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
### Requirement 12: Navigation temporelle et mise en page
**User Story:** En tant qu'utilisateur, je veux naviguer facilement entre les années, mois et journées ayant des données, et voir en permanence les totaux et la carte, afin de consulter et saisir mes trajets de façon fluide.
**User Story:** En tant qu'utilisateur, je veux naviguer facilement par année et par mois, consulter d'un coup tous les trajets d'un mois regroupés par jour, cibler une journée précise sur la carte, et voir en permanence les totaux et la carte, afin de consulter et saisir mes trajets de façon fluide.
#### Acceptance Criteria
1. THE Frontend SHALL afficher un Sélecteur_Temporel permettant de choisir successivement une année, puis un mois, puis une journée.
1. THE Frontend SHALL afficher un Sélecteur_Temporel repliable permettant de choisir successivement une année puis un mois ; la sélection ne descend pas jusqu'à la journée. Le libellé de l'en-tête indique le mois et l'année sélectionnés (ex. « Septembre 2026 »).
2. THE Frontend SHALL proposer dans le sélecteur d'année uniquement l'année courante et les années pour lesquelles au moins un Trajet est enregistré.
3. WHEN l'utilisateur sélectionne une année, THE Frontend SHALL afficher les douze mois de cette année et mettre en surbrillance les mois comportant au moins un Trajet.
4. WHEN l'utilisateur sélectionne un mois, THE Frontend SHALL afficher les jours de ce mois et mettre en surbrillance les jours comportant au moins un Trajet.
5. THE Frontend SHALL empêcher la sélection de toute date postérieure à la date courante (aucune date future sélectionnable).
6. THE Frontend SHALL afficher la zone des Trajets de la journée sélectionnée comme zone principale, avec défilement vertical (scroll) lorsque le nombre de Trajets dépasse la hauteur visible.
7. THE Frontend SHALL afficher un Panneau_Totaux latéral toujours visible contenant : le total kilométrique du jour sélectionné, le total kilométrique du mois sélectionné, et le Total_Annuel_Partiel de l'année sélectionnée.
8. THE Panneau_Totaux SHALL calculer le Total_Annuel_Partiel en cumulant uniquement les Trajets de l'année sélectionnée jusqu'au mois sélectionné inclus, sans inclure les mois postérieurs au mois sélectionné.
9. THE Frontend SHALL afficher, sous le Panneau_Totaux, la Carte des Trajets de la journée sélectionnée.
10. THE Frontend SHALL afficher un bouton "Jour précédent" et un bouton "Jour suivant" permettant de naviguer d'une journée à l'autre.
11. WHEN l'utilisateur clique sur "Jour précédent", THE Frontend SHALL sélectionner la journée calendaire immédiatement antérieure et rafraîchir la zone des Trajets, le Panneau_Totaux et la Carte.
12. WHEN l'utilisateur clique sur "Jour suivant", THE Frontend SHALL sélectionner la journée calendaire immédiatement postérieure et rafraîchir la zone des Trajets, le Panneau_Totaux et la Carte.
13. IF la journée suivante est postérieure à la date courante, THEN THE Frontend SHALL désactiver le bouton "Jour suivant".
14. WHEN l'utilisateur change de jour, de mois ou d'année via le Sélecteur_Temporel ou les boutons de navigation, THE Frontend SHALL mettre à jour la zone des Trajets, le Panneau_Totaux et la Carte dans un délai de 1000 ms sans rechargement complet de la page.
15. WHEN le Backend reçoit une requête des années disponibles, THE Backend SHALL retourner la liste des années comportant au moins un Trajet, ainsi que l'année courante si elle n'y figure pas déjà.
16. WHEN le Backend reçoit une requête des jours comportant des Trajets pour un mois et une année donnés, THE Backend SHALL retourner la liste des jours de ce mois pour lesquels au moins un Trajet est enregistré.
17. THE Frontend SHALL afficher un sélecteur de Véhicule permettant de choisir le Véhicule dont les données sont affichées ; par défaut, le Véhicule_Courant est sélectionné.
18. WHEN l'utilisateur sélectionne un Véhicule dans le sélecteur de Véhicule, THE Frontend SHALL filtrer la zone des Trajets, le Panneau_Totaux et la Carte pour n'afficher que les Trajets associés à ce Véhicule.
19. THE Panneau_Totaux SHALL calculer le total du jour, le total du mois et le Total_Annuel_Partiel uniquement à partir des Trajets associés au Véhicule sélectionné.
20. WHEN l'utilisateur change de Véhicule sélectionné, THE Frontend SHALL mettre à jour la zone des Trajets, le Panneau_Totaux et la Carte dans un délai de 1000 ms sans rechargement complet de la page.
4. THE Frontend SHALL empêcher la sélection de tout mois postérieur au mois courant (aucun mois futur sélectionnable).
5. WHEN l'utilisateur sélectionne un mois, THE Frontend SHALL afficher la liste de TOUS les Trajets de ce mois, regroupés par journée et triés par date croissante puis par Position croissante au sein de chaque journée.
6. THE Frontend SHALL afficher chaque journée de la liste mensuelle comme un groupe repliable ; par défaut toutes les journées sont repliées et, au changement de mois, seule la dernière journée comportant des Trajets est dépliée.
7. THE Frontend SHALL afficher la liste mensuelle des Trajets comme zone principale, avec défilement vertical (scroll) lorsque le nombre de Trajets dépasse la hauteur visible, tandis que le Sélecteur_Temporel, la navigation par jour, le Panneau_Totaux et la Carte restent visibles.
8. THE Frontend SHALL afficher un Panneau_Totaux toujours visible contenant : le total kilométrique du jour ciblé sur la Carte, le total kilométrique du mois sélectionné, et le Total_Annuel_Partiel de l'année sélectionnée.
9. THE Panneau_Totaux SHALL calculer le Total_Annuel_Partiel en cumulant uniquement les Trajets de l'année sélectionnée jusqu'au mois sélectionné inclus, sans inclure les mois postérieurs au mois sélectionné.
10. THE Frontend SHALL afficher une bande de navigation par jour (« Jour précédent », date affichée, « Jour suivant ») au-dessus du Panneau_Totaux et de la Carte ; cette date pilote le jour dont les Trajets sont représentés sur la Carte et sert de date par défaut lors de l'ajout ou de l'insertion d'un Trajet.
11. THE Frontend SHALL afficher la Carte des Trajets de la seule journée ciblée par la bande de navigation par jour.
12. WHEN l'utilisateur clique sur « Jour précédent » ou « Jour suivant », THE Frontend SHALL cibler respectivement le jour comportant des Trajets immédiatement antérieur ou postérieur DANS LE MOIS sélectionné, et rafraîchir uniquement la Carte et le Panneau_Totaux du jour, sans recharger la liste mensuelle.
13. WHERE le mois sélectionné est le mois courant, THE Frontend SHALL rendre la journée courante navigable et ciblable même si elle ne comporte aucun Trajet (afin de pouvoir y ajouter un Trajet).
14. IF aucun jour comportant des Trajets n'existe avant (respectivement après) le jour ciblé dans le mois, THEN THE Frontend SHALL désactiver le bouton « Jour précédent » (respectivement « Jour suivant »).
15. WHEN l'utilisateur change de mois via le Sélecteur_Temporel, ou de jour via la bande de navigation, ou de Véhicule, THE Frontend SHALL mettre à jour la liste, le Panneau_Totaux et la Carte concernés dans un délai de 1000 ms sans rechargement complet de la page.
16. WHEN le Backend reçoit une requête des années disponibles, THE Backend SHALL retourner la liste des années comportant au moins un Trajet, ainsi que l'année courante si elle n'y figure pas déjà.
17. WHEN le Backend reçoit une requête des mois comportant des Trajets pour une année donnée, THE Backend SHALL retourner la liste des mois de cette année pour lesquels au moins un Trajet est enregistré.
18. IF le mois sélectionné ne comporte aucun Trajet, THEN THE Frontend SHALL afficher un unique message « Aucun trajet pour ce mois » et des totaux à zéro, sans afficher de message d'erreur (un mois vide n'est pas une erreur).
19. THE Frontend SHALL afficher un sélecteur de Véhicule permettant de choisir le Véhicule dont les données sont affichées ; par défaut, le Véhicule_Courant est sélectionné.
20. WHEN l'utilisateur sélectionne un Véhicule dans le sélecteur de Véhicule, THE Frontend SHALL filtrer la liste mensuelle des Trajets, le Panneau_Totaux et la Carte pour n'afficher que les Trajets associés à ce Véhicule.
21. THE Panneau_Totaux SHALL calculer le total du jour, le total du mois et le Total_Annuel_Partiel uniquement à partir des Trajets associés au Véhicule sélectionné.
22. WHEN l'utilisateur change de Véhicule sélectionné, THE Frontend SHALL mettre à jour la liste mensuelle des Trajets, le Panneau_Totaux et la Carte dans un délai de 1000 ms sans rechargement complet de la page.
---
@@ -306,7 +317,7 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
#### Acceptance Criteria
1. THE Frontend SHALL afficher un menu de saisie du Bareme_Kilometrique par année, permettant de saisir, pour chaque Puissance_Fiscale, les trois tranches de kilométrage : jusqu'à 5 000 km, de 5 001 à 20 000 km, et au-delà de 20 000 km, chacune avec son coefficient (€/km) et sa base (€).
1. THE Frontend SHALL afficher un menu de saisie du Bareme_Kilometrique par année permettant de saisir, pour chaque Puissance_Fiscale, une ou plusieurs Tranches_Bareme définies par une borne minimale de kilométrage, une borne maximale de kilométrage (la dernière tranche pouvant être illimitée, sans borne maximale), un coefficient (€/km sur trois décimales) et une base (€). L'utilisateur peut ajouter plusieurs tranches pour une même Puissance_Fiscale (ex. 0–4 999 km, 5 000–19 999 km, 20 000 km et plus).
2. WHEN l'utilisateur enregistre une Tranche_Bareme, THE Backend SHALL persister la tranche associée à l'année, à la Puissance_Fiscale et à la borne de kilométrage concernées.
3. WHEN le Backend applique le Bareme_Kilometrique au total kilométrique annuel d'un Véhicule pour une année donnée, THE Backend SHALL sélectionner la tranche dont l'intervalle de kilométrage contient le total annuel, puis calculer le montant selon la formule `montant = total_km_annuel × coefficient + base`.
4. IF aucune Tranche_Bareme n'existe pour la Puissance_Fiscale et l'année d'un calcul, THEN THE Backend SHALL retourner le montant zéro et signaler que le barème n'est pas configuré pour cette combinaison.
@@ -314,6 +325,7 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
6. THE Frontend SHALL indiquer explicitement que le Montant_Previsionnel (80%) est une simple indication de versement et que le montant réellement versé est celui saisi par l'utilisateur (Montant_Regle).
7. THE Frontend SHALL afficher tous les montants en euros avec une précision de deux décimales.
8. THE Frontend SHALL afficher, dans l'en-tête de chaque feuille mensuelle, le Coefficient_Base_Vehicule (coefficient et base de la tranche applicable selon la puissance fiscale du Véhicule et le total kilométrique annuel).
9. THE Frontend SHALL proposer un bouton « Recopier le dernier barème connu » qui, pour l'année affichée, recopie l'intégralité des Tranches_Bareme de l'année la plus récente antérieure comportant un barème. IF des Tranches_Bareme existent déjà pour l'année affichée, THEN THE Frontend SHALL demander une confirmation avant de les remplacer intégralement (annule et remplace). THE Frontend SHALL rendre ce bouton inaccessible lorsque l'année affichée est celle du dernier barème connu (aucune année antérieure à recopier).
---
@@ -361,13 +373,15 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
### Requirement 16: Initialisation du profil à la première utilisation
**User Story:** En tant qu'utilisateur, je veux que l'application me présente la saisie de mon profil dès la première connexion, afin de configurer mon identité et mon véhicule avant de commencer à saisir mes trajets.
**User Story:** En tant qu'utilisateur, je veux que l'application me présente la saisie de mon profil juste après la création et la connexion de mon Compte, afin de configurer mon identité et mon véhicule avant de commencer à saisir mes trajets.
> **Note** : Le parcours d'initialisation se déroule en deux étapes. La première étape (création du Compte : Adresse_Email + Mot_De_Passe, puis connexion) est décrite à l'Exigence 17 (Authentification). La présente exigence décrit la seconde étape (identité, matricule, adresse de domicile optionnelle et Véhicule), présentée une fois l'utilisateur authentifié.
#### Acceptance Criteria
1. THE Backend SHALL considérer le Profil comme non configuré tant qu'aucun Profil n'est enregistré avec un nom et un prénom non vides, ou tant qu'aucun Véhicule (plaque, Puissance_Fiscale, Date_Effet) n'est enregistré.
2. THE Backend SHALL exposer un indicateur de configuration du Profil (`estConfigure`) valant vrai lorsque le Profil est un Profil_Configure et faux dans le cas contraire.
3. WHEN l'utilisateur ouvre l'Application, WHILE le Profil est non configuré, THE Frontend SHALL présenter le parcours d'Onboarding (formulaire de saisie du Profil) au lieu de la Vue_Consultation.
3. WHEN l'utilisateur authentifié ouvre l'Application, WHILE le Profil est non configuré, THE Frontend SHALL présenter le parcours d'Onboarding (formulaire de saisie du Profil) au lieu de la Vue_Consultation.
4. WHILE le Profil est non configuré, THE Frontend SHALL bloquer l'accès à la saisie et à la consultation des Trajets et maintenir l'affichage du parcours d'Onboarding.
5. THE Frontend SHALL exiger, dans le parcours d'Onboarding, la saisie du nom (1 à 100 caractères), du prénom (1 à 100 caractères) et du matricule (1 à 8 caractères) avant d'autoriser la validation du Profil.
6. THE Frontend SHALL exiger, dans le parcours d'Onboarding, la saisie d'au moins un Véhicule comportant une plaque d'immatriculation (1 à 15 caractères), une Puissance_Fiscale (entier positif) et une Date_Effet inférieure ou égale à la date du jour avant d'autoriser la validation du Profil.
@@ -378,3 +392,43 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
11. IF le Profil est déjà un Profil_Configure lors de l'ouverture de l'Application, THEN THE Frontend SHALL afficher directement la Vue_Consultation sans présenter le parcours d'Onboarding.
12. IF la persistance du Profil ou du Véhicule échoue au niveau du Backend lors de la soumission du parcours d'Onboarding, THEN THE Frontend SHALL afficher un message d'erreur indiquant l'échec de l'enregistrement, ne pas marquer le Profil comme configuré et conserver les données saisies par l'utilisateur.
13. IF la récupération de l'indicateur `estConfigure` échoue lors de l'ouverture de l'Application, THEN THE Frontend SHALL afficher un message d'erreur indiquant un problème de connexion au serveur et proposer de réessayer, sans présenter la Vue_Consultation.
---
### Requirement 17: Authentification par compte unique
**User Story:** En tant qu'utilisateur, je veux protéger l'accès à mon application par un compte (e-mail + mot de passe) avec la possibilité de réinitialiser mon mot de passe, afin que mes données de trajets restent privées.
#### Acceptance Criteria
1. THE Application SHALL n'autoriser l'existence que d'un seul Compte (application mono-utilisateur).
2. WHEN aucun Compte n'existe et que l'utilisateur ouvre l'Application, THE Frontend SHALL présenter la page de création de compte (première étape de l'initialisation) au lieu de la page de connexion.
3. THE Frontend SHALL exiger, à la création du Compte, une Adresse_Email valide (servant de login) et un Mot_De_Passe d'au moins 8 caractères avant d'autoriser la soumission.
4. WHEN l'utilisateur soumet la création du Compte avec une Adresse_Email valide et un Mot_De_Passe d'au moins 8 caractères, THE Backend SHALL créer le Compte en stockant uniquement un hachage salé du Mot_De_Passe (jamais le mot de passe en clair) et rediriger l'utilisateur vers la page de connexion.
5. IF un Compte existe déjà lors d'une tentative de création de compte, THEN THE Backend SHALL rejeter la création avec une erreur explicite (un seul Compte autorisé).
6. WHEN l'utilisateur soumet la page de connexion avec une Adresse_Email et un Mot_De_Passe correspondant au Compte, THE Backend SHALL émettre un JWT signé (HS256) portant l'identifiant du Compte et une durée de validité configurable (12 heures par défaut), et THE Frontend SHALL conserver ce jeton pour authentifier les appels suivants.
7. IF l'Adresse_Email ou le Mot_De_Passe soumis à la connexion ne correspond pas au Compte, THEN THE Backend SHALL rejeter la connexion avec une erreur d'identifiants invalides, sans indiquer lequel des deux champs est erroné.
8. THE Backend SHALL protéger tous les endpoints de données (Trajets, résumés, totaux, profil, véhicules, barème, carnet d'adresses, export PDF) en exigeant un JWT valide, et SHALL retourner une erreur HTTP 401 en l'absence de jeton valide.
9. WHEN le Backend valide un JWT dont le Compte associé n'existe plus, THE Backend SHALL rejeter la requête (jeton invalide) afin qu'un jeton résiduel ne donne pas accès à une application dont le compte a été supprimé.
10. THE Backend SHALL exposer un endpoint permettant au Frontend de valider la Session courante (existence du Compte et validité du jeton), utilisé par la garde de navigation du Frontend avant d'afficher les vues protégées.
11. WHEN l'utilisateur demande la réinitialisation de son Mot_De_Passe en fournissant son Adresse_Email, THE Backend SHALL générer un Jeton_Reinitialisation à usage unique et à durée de vie limitée, et l'envoyer par e-mail à l'Adresse_Email du Compte.
12. IF l'envoi de l'e-mail de réinitialisation n'est pas possible (service e-mail non configuré ou indisponible), THEN THE Backend SHALL journaliser le lien de réinitialisation en repli (fallback) afin que la réinitialisation reste possible, sans faire échouer la demande.
13. WHEN l'utilisateur suit un Jeton_Reinitialisation valide et fournit un nouveau Mot_De_Passe d'au moins 8 caractères, THE Backend SHALL remplacer le hachage du Mot_De_Passe du Compte et invalider le Jeton_Reinitialisation.
14. IF le Jeton_Reinitialisation est expiré, déjà utilisé ou inconnu, THEN THE Backend SHALL rejeter la réinitialisation avec une erreur explicite sans modifier le Mot_De_Passe.
15. THE Backend SHALL exiger la présence d'une clé secrète de signature JWT au démarrage (variable d'environnement obligatoire) et SHALL refuser de démarrer si elle est absente.
---
### Requirement 18: Trajet aller/retour
**User Story:** En tant qu'utilisateur, je veux marquer un trajet comme aller/retour pour compter le double de la distance sans ressaisir le trajet en sens inverse, afin de simplifier la saisie et de gagner de la place à l'affichage.
#### Acceptance Criteria
1. WHEN l'utilisateur crée ou modifie un Trajet avec l'indicateur Aller_Retour activé, THE Backend SHALL conserver la distance simple mesurée et exposer une Distance_Effective égale au double de cette distance simple.
2. WHEN l'indicateur Aller_Retour est désactivé, THE Backend SHALL exposer une Distance_Effective égale à la distance simple mesurée.
3. THE Backend SHALL utiliser la Distance_Effective (et non la distance simple) dans tous les agrégats : total du jour, Résumé_Mensuel, Résumé_Annuel, Total_Annuel_Partiel, calculs de barème et Export_PDF.
4. THE Frontend SHALL afficher, dans la liste des Trajets, un indicateur visuel « A/R » dans une colonne dédiée en début de ligne pour chaque Trajet Aller_Retour, en maintenant l'alignement des autres colonnes.
5. WHEN l'utilisateur consulte l'infobulle d'un Trajet Aller_Retour sur la Carte, THE Frontend SHALL faire suivre la distance affichée de la mention « (A/R) ».
6. THE Backend SHALL persister l'indicateur Aller_Retour de chaque Trajet et le conserver lors des modifications ne portant pas sur ce champ.