Aller au contenu
SaaS B2B · produit en propre2025 - aujourd'huien production

FleetOra

Le logiciel de gestion des agences de location de voitures en Algérie. Range le cahier.

Signal reçu

Les agences de location algériennes de 10 à 40 véhicules tiennent leur activité sur un cahier et un tableur. Personne ne sait, à un instant donné, quelle voiture est libre la semaine prochaine, quel contrat est en retard, ni quel véhicule perd de l'argent. Le double-booking est la norme, pas l'accident.

Signal émis

Un SaaS complet qui remplace le cahier : parc, clients, contrats, réservations, entretiens, infractions et comptes. Il fonctionne hors ligne - indispensable quand la connexion tombe - et se synchronise sans jamais perdre une version.

Résultats

9
modules métier
1 177
agences adressables
4
paliers d'abonnement
100 %
hors ligne capable

Démonstration interactive

Deux réservations ne peuvent pas se chevaucher

Déplacez la nouvelle demande le long du mois et regardez quand le véhicule est déjà pris.

Réservation confirmée
Réservation annulée
Contrat actif
Nouvelle demande
Créneau librejours 12 à 15

Deux détails que la règle impose et qu'on ne devine pas : les bornes sont inclusives, donc reprendre le véhicule le jour même où il rentre compte comme un conflit. Et la réservation annulée, en pointillés, est ignorée : seuls les statuts qui immobilisent réellement le véhicule bloquent.

Mécanisme reconstruit pour ce site, ce n'est pas une capture du produit. Règle reprise de packages/shared/src/index.ts, hasVehicleOverlap(). Données d'exemple : aucun client ni véhicule réel.

Flux de traitement

De l'interface jusqu'aux données

  1. Interface

    9 vues métier, responsive sans détection de plateforme - une seule base de code, un point de rupture à 820 px. Site vitrine et application dans le même bundle.

  2. Logique métier pure

    packages/shared - calculs de contrat, détection de chevauchement, validation, matrice de droits. Aucune dépendance UI ni réseau : testable sans navigateur.

  3. Politique de synchronisation

    sync-policy.ts - une fonction pure qui prend un instantané et rend une décision : pousser, appliquer, ou ne rien faire. Avec archivage systématique.

  4. Persistance

    Supabase comme source de vérité, localStorage comme cache hors ligne. Isolation par agence, appliquée en RLS côté base et en RPC pour les actions privilégiées.

Décisions d'ingénierie

Les choix qui méritent d'être défendus, et pourquoi

01

La synchronisation est une fonction pure, pas un effet

Le piège classique du local-first : un état vide qui écrase un compte plein, ou une édition locale perdue par un rechargement cloud. J'ai sorti la décision des effets React vers un module isolé qui prend un instantané en entrée et rend une décision explicite - pousser, appliquer, ne rien faire - accompagnée d'une raison et d'un ordre d'archivage. La règle est « le plus récent gagne, avec archivage systématique de la version remplacée » : aucune version n'est jamais détruite sans copie. Un échec de lecture n'est explicitement pas traité comme un compte vide, ce qui empêche d'effacer un compte sur une simple coupure réseau. Conséquence pratique : tous les cas de conflit se testent en ligne de commande, sans navigateur.

  • apps/web/src/sync-policy.ts
  • apps/web/src/backup.ts
02

L'unité d'isolation est l'agence, pas l'utilisateur

La première version isolait les données par compte. Mais une agence, c'est un patron et deux employés qui doivent voir le même parc. J'ai donc déplacé la clé de tenancy vers le propriétaire de l'agence - un utilisateur secondaire lit et écrit la ligne du propriétaire - et superposé trois couches : un remontage complet du composant racine au changement de compte pour garantir zéro état résiduel, la clé d'agence pour le stockage et la synchro, et les politiques RLS côté PostgreSQL. Les droits eux-mêmes sont des fonctions pures dans le package partagé, pas des conditions dispersées dans les composants : la matrice de permissions se teste comme n'importe quel calcul.

  • apps/web/src/main.tsx
  • packages/shared/src/index.ts
03

Un échéancier à double critère : le temps ou l'usage

Une vidange n'est pas due « dans six mois » : elle est due dans six mois OU dans 5 000 km, au premier des deux atteint. Le calculateur évalue les deux branches indépendamment et retient la plus urgente. Détail qui compte : quand la donnée de référence manque, le statut est forcé à « bientôt » plutôt qu'à « à jour » - on préfère alerter à tort que rater une échéance. Le modèle se transpose tel quel à toute maintenance d'actifs : heures de vol, cycles, tonnage.

  • apps/web/src/main.tsx
04

Une clé de service qui ne touche jamais le navigateur

La table du tunnel de conversion est fermée en RLS - un client anonyme ne peut pas y écrire, et c'est voulu. La collecte passe donc par une fonction serverless qui détient seule la clé de service. C'est exactement le rôle d'une fonction serverless dans une SPA : porter le secret que le bundle ne doit pas voir.

  • api/track.mjs
  • supabase/funnel-analytics.sql
05

Une couture de test qui disparaît en production

Tester une application derrière un login en end-to-end demande soit un backend de test, soit une porte dérobée. J'ai pris la porte dérobée, mais conditionnée à la fois au mode développement et à un drapeau local - Vite l'élimine du bundle de production à la compilation. La suite Playwright couvre le parcours complet client → véhicule → contrat, la confirmation de réservation, toutes les vues, et le rendu sur plusieurs largeurs d'écran.

  • apps/web/src/AuthContext.tsx
  • e2e/flows.spec.ts

Pile technique

  • React 19
  • Vite 6
  • TypeScript
  • Supabase
  • PostgreSQL + RLS
  • PWA
  • Playwright
  • Vercel

Ce que ce projet démontre

Trois points à retenir

01

La synchronisation est une fonction pure, pas un effet

Le piège classique du local-first : un état vide qui écrase un compte plein, ou une édition locale perdue par un rechargement cloud.

02

L'unité d'isolation est l'agence, pas l'utilisateur

La première version isolait les données par compte.

03

Un échéancier à double critère : le temps ou l'usage

Une vidange n'est pas due « dans six mois » : elle est due dans six mois OU dans 5 000 km, au premier des deux atteint.

Un projet comparable ?

Ce dossier est la preuve des prestations suivantes