Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
La plateforme tout-en-un pour gérer, promouvoir et développer votre salon de beauté et de coiffure.
Translation pending
BusinessTypeScript · HTMLGitHub @MalekmouelhOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Next.js 16 (App Router) · TypeScript · Tailwind CSS 4 · MongoDB Atlas (Mongoose) · Zod · Claude (IA)
npm install
cp .env.example .env # puis renseigner MONGODB_URI (Atlas) et AUTH_SECRET
npm run db:seed # données de démo (mot de passe : Demo1234)
npm run dev
npm test # tests automatisés (Module 6)
Sans Atlas (hors ligne), une base MongoDB locale sans installation :
npm run db:local # dans un terminal séparé (laisser tourner)
puis MONGODB_URI="mongodb://127.0.0.1:27017" dans .env.
| Variable | Rôle |
|---|---|
MONGODB_URI | Atlas : mongodb+srv://… · local : mongodb://127.0.0.1:27017 |
MONGODB_DB | nom de la base (salonia, ou une base par membre : salonia_malek…) |
AUTH_SECRET | clé de signature des sessions NextAuth (npx auth secret) ; SESSION_SECRET reste accepté |
DEMO_MODE | "true" : comptes de démo sur /login et lien de réinitialisation affiché |
GEMINI_API_KEY | IA de l'équipe (performance, suggestion de praticien, prévision de charge) et autres IA. Vide : calcul sans IA |
ANTHROPIC_API_KEY | IA (assistant de configuration, chatbot). Vide : modes hors ligne |
CRON_SECRET | protège /api/cron/reminders (rappels automatiques) |
| Rôle | Identifiant |
|---|---|
| Propriétaire (3 centres) | [email protected] |
| Gérant (Sousse) | [email protected] |
| Staff (Tunis) | [email protected] ou 22 345 678 |
| Client | [email protected] |
| Super admin | [email protected] |
Page de réservation publique : /reserver/salon-yasmine
| Exigence | Où |
|---|---|
| F2.1 Gestion des services (catégories, durée, tarif de base, description, photos) | /dashboard/catalogue (onglet Services) |
| F2.2 Options de services (variantes avec suppléments de prix et de durée) | Dans le formulaire de service (ServiceModal) |
| F2.3 Tarification différenciée par centre | Surcharge de prix par centre dans ServiceModal et PackModal |
| F2.4 Forfaits & Packs multi-services avec calcul automatique des économies | /dashboard/catalogue (onglet Packs) |
| F2.5 Compétences requises et matériels nécessaires par prestation | ServiceModal (liaison Staff & Équipements) |
| F2.6 Importation automatique du catalogue initial généré par IA | Bannière interactive sur /dashboard/catalogue |
| F2.7 Offres promotionnelles & Codes promo (pourcentages, montants fixes, validité, plafonds) | /dashboard/catalogue (onglet Promotions) |
| F2.8 Galerie photos et compression WebP côté client | ImageUploader intégré dans chaque modal |
| F2.9 Application dynamique dans le parcours de réservation client | /reserver/[slug] (BookingWizard) |
IA du module 2 (cahier des charges §6) :
✨ Générer avec l'IA dans ServiceModal (lib/ai/catalog-ai.ts). Repli par templates contextuels hors-ligne.PackModal.Règles métier & intégration :
resolveItems() calcule la durée cumulée exacte (durée de base + suppléments d'options) et le prix unitaire ajusté (prix spécifique au centre + suppléments d'options).maxUses), et incrémentation atomique du compteur usedCount.| Exigence | Où |
|---|---|
| F3.1 Calendrier par centre et par praticien (jour, semaine, mois) | /dashboard/calendar |
| F3.2 Horaires, pauses, fermetures, blocage de créneaux | /dashboard/availability, « Bloquer du temps » |
| F3.3 Réservation en ligne (service ou pack, praticien, créneau) en 4 étapes | /reserver/[slug] |
| F3.4 Rappels automatiques 24 h avant | /api/cron/reminders (Vercel Cron) + bouton manuel |
| F3.5 Annulations, reports, liste d'attente | calendrier, lien client /reservation/[token] |
| F3.6 Fiche client avec historique | /dashboard/clients |
IA du module 3 (cahier des charges §6) :
lib/booking/noshow.ts), score + facteurs explicatifs, qualité mesurée par l'AUC sur les 20 % les plus récents.GEMINI_API_KEY, offre gratuite) en priorité, sinon Claude (ANTHROPIC_API_KEY), sinon repli par mots-clés FR/AR. Le modèle appelle deux outils (find_slots, propose_booking) : les créneaux viennent toujours de notre algorithme, en heure de Tunis.
L'IA propose, le client confirme.Compte client obligatoire pour confirmer : le parcours /reserver/[slug] est ouvert à tous (prestations,
praticien, créneau), mais la confirmation demande un compte client (/register?type=client). La réservation en
cours est gardée dans l'adresse (?reprise=…, lib/booking/draft.ts) pendant la connexion ou l'inscription.
Le nom et le téléphone viennent du compte (jamais du formulaire) ; la fiche client du salon est reliée au compte
(userId). Après confirmation, le client peut payer l'acompte (30 %) ou la totalité en ligne (page /paiement
du Module 6) ; « Mes rendez-vous » sur son tableau de bord affiche ses rendez-vous de tous les salons et le reste à payer.
Règles métier : pas de double réservation (revérification à l'enregistrement), fuseau Africa/Tunis, annulation/report en ligne jusqu'à 2 h avant, rappels uniquement avec consentement.
Acompte sécurisé par la blockchain (SalonEscrow) : pour un centre configuré, l'acompte est bloqué dans un smart
contract (jeton de test MockTND), remboursé si le client annule 24 h ou plus avant, sinon versé au salon ; le staff
valide la prestation ou l'absence depuis le calendrier. Les clients signent avec MetaMask (connexion « Sign-In With
Ethereum » dans NextAuth, comptes wallet dans une base séparée) et le serveur revérifie chaque transaction. Désactivé
sans variables ESCROW_*. Détails, lancement local et préparation Sepolia : docs/salonescrow.md.
Périmètre réalisé (d'après le résumé du cahier des charges ; le texte intégral n'a pas été relu exigence par exigence) :
| Exigence | Où | État |
|---|---|---|
| F4.1 Fiches employés : poste, compétences, centres d'affectation | /dashboard/equipe (liste, filtre par centre) ; « Ajouter un membre » | Implémenté. La modification du poste et des compétences d'un employé existant n'est pas encore proposée |
| F4.2 Horaires de travail, congés, absences | Bouton « Horaires » (modal à trois états par jour) ; bouton « Absences » (liste, création, modification, suppression) | Implémenté en enregistrement seul. Jours fériés : non |
| F4.3 Affectation des services aux praticiens selon leurs compétences | Compétences enregistrées (texte libre, suggestions tirées de Service.requiredSkills) | Non : aucun filtrage dans les créneaux ni dans getPractitioners |
| F4.4 Pointage et suivi des heures · F4.5 Commissions et performance | — | Non réalisés (priorité I) |
| F4.6 Vue personnelle du staff : planning et rendez-vous | /dashboard/planning (« Mon planning ») ; bouton « Planning » sur les cartes pour OWNER/MANAGER | Implémenté, lecture seule |
Pages et actions
/dashboard/equipe : une carte par membre (personnel et gérants), avec un badge de rôle d'accès (« Gérant » ou « Personnel »), le
rôle/poste, les compétences, les centres, les horaires personnalisés et l'absence en cours ou prochaine (sans motif). Un gérant n'est pas
un praticien : sa carte n'a ni Horaires, ni Absences, ni Planning.lib/team/roles.ts) : Gérant(e) de centre (crée un compte MANAGER), puis
Coiffeur / Coiffeuse, Esthéticienne, Maquilleuse, Prothésiste ongulaire, Masseuse, Réceptionniste (créent un compte STAFF). Le rôle
d'accès et le poste (User.jobTitle) sont déduits par le serveur de la clé du rôle choisi ; l'organisation est celle du créateur ;
toute autre valeur envoyée (rôle d'accès, poste libre, organisation) est ignorée. Il n'y a plus de poste en texte libre à la création.lib/team/service.ts) : « Modifier » change le nom et les centres d'affectation (un ou plusieurs, au moins un,
de l'organisation ; sans changement réel : aucune écriture). « Désactiver » pose User.deactivatedAt : le compte ne peut plus se connecter
(mot de passe ou MetaMask), une session déjà ouverte est coupée à la requête suivante (lib/dal.ts), le gérant disparaît des listes. Rien n'est
supprimé (données et historique conservés). Un gérant ne peut ni se modifier ni se désactiver ; un compte inconnu, d'une autre organisation,
non gérant ou déjà désactivé reçoit toujours « Gérant introuvable. ». Événements d'audit : STAFF_UPDATED, STAFF_DEACTIVATED.User.workingHours) : par jour, trois états. Jour absent = comme le centre ; null = repos ;
"09:00-17:00" = plage (jamais au-delà de minuit). workingHours: null = tous les jours comme le centre.StaffTimeOff) : types congé (LEAVE) et absence exceptionnelle (ABSENCE), journée(s) complète(s) ou plage
horaire d'un seul jour, motif facultatif (120 caractères). Bornes : début au plus 365 jours dans le passé, fin au plus 730 jours
dans le futur, durée au plus 366 jours. Deux absences d'un même employé ne se chevauchent pas (contrôle applicatif, non atomique).
Le nombre de rendez-vous actifs concernés est affiché ; ils ne sont jamais modifiés ni annulés.Droits
| Action | OWNER | MANAGER | STAFF |
|---|---|---|---|
| Voir la liste de l'équipe | tous les membres (personnel et gérants) de l'organisation | membres affectés à au moins un de ses centres | non |
Créer un gérant (MANAGER) | oui, centres de l'organisation | non (refusé côté serveur, même si la requête est falsifiée) | non |
Créer du personnel (STAFF) | oui, centres de l'organisation | oui, uniquement ses centres | non |
| Affecter un gérant à un ou plusieurs centres, modifier son nom | oui (bouton « Modifier » de sa carte, au moins un centre) | non | non |
| Désactiver un gérant (confirmation demandée) | oui | non | non |
| Horaires et absences (voir, créer, modifier, supprimer) | tout employé de l'organisation | uniquement si tous les centres de l'employé sont dans son périmètre (sinon : bouton désactivé et explication) | non |
| Planning | tout employé | rendez-vous limités à ses centres, même pour un employé partagé | le sien seulement (?staff= ignoré) |
Un employé inconnu, d'une autre organisation, qui n'est pas un STAFF ou hors périmètre reçoit toujours le même message (« Employé introuvable. »).
Les droits sont vérifiés côté serveur (lib/team/scope.ts, lib/team/service.ts, lib/data/planning.ts), pas seulement dans la navigation.
Données et audit : champs optionnels jobTitle, skills, workingHours sur User ; collection stafftimeoffs (modèle StaffTimeOff,
lib/models/team.ts). Événements du journal de sécurité : STAFF_CREATED, STAFF_HOURS_UPDATED, STAFF_TIMEOFF_CREATED,
STAFF_TIMEOFF_UPDATED, STAFF_TIMEOFF_DELETED (employé, type et dates ; jamais le mot de passe ni le motif). Une modification sans
changement réel ne produit ni écriture ni événement. Le motif d'une absence n'apparaît que dans le modal « Absences » des utilisateurs autorisés.
Limites connues
lib/booking/ ne les lit pas).
Un compte STAFF créé devient immédiatement réservable dans ses centres, avec les horaires du centre.STAFF), et il n'est pas réservable
(getPractitioners ne lit que les STAFF). On ne peut pas encore changer le rôle d'un membre existant, ni modifier, désactiver ou réactiver le personnel (seuls les gérants le peuvent, par le propriétaire) ; il n'y a pas de réactivation d'un gérant désactivé (à faire en base).DEMO_MODE.lib/booking/ reste, elle, centre par centre.Vérifications réalisées (octobre 2026)
Tests automatiques : npm test (voir lib/team/*.test.ts et lib/booking/*.test.ts) avec une base MongoDB en mémoire ; commande et résultats
du dernier passage dans le compte rendu de l'étape 7. Les tests de lib/booking/ fixent le comportement actuel de la réservation (dont ses limites)
avant toute intégration.
Navigateur, base de développement, sans écriture : liste de l'équipe (FR/AR, propriétaire, gérant, staff), formulaire de création (validation, e-mail déjà utilisé), modal des horaires (validation, enregistrement sans changement).
Navigateur, base MongoDB en mémoire isolée (données jetables, serveur sur un autre port) : création, modification, suppression, chevauchements, rendez-vous concernés, résumés sur les cartes et événements d'audit des absences ; planning (STAFF, OWNER, MANAGER, CLIENT, URL falsifiée, semaines voisines, état vide, français et arabe RTL) ; aucun motif ni donnée client sensible dans les pages ni dans l'audit.
Gérants (navigateur, base isolée, novembre 2026) : affectation à deux centres avec persistance, refus sans centre, « Aucune modification. », désactivation avec confirmation, connexion refusée ensuite, session ouverte coupée ; le gérant ne voit aucun bouton de gestion ; audit sans secret.
Rôles prédéfinis (navigateur, base MongoDB en mémoire isolée, novembre 2026) : le propriétaire crée un gérant et du personnel ; la liste affiche le badge de rôle (français et arabe) ; le gérant créé se connecte, ne voit que ses centres, n'a pas « Gérant(e) de centre » dans la liste et se voit refuser un gérant falsifié (« Seul le propriétaire peut créer un gérant. ») ; aucun mot de passe ni hash dans l'audit.
Données de démonstration de l'équipe : npm run db:seed:team (simulation, n'écrit rien) puis npm run db:seed:team -- --apply. Script additif et
rejouable (scripts/seed-team.ts), limité à l'organisation salon-yasmine : il complète seulement les champs vides (poste, compétences, horaires)
des comptes de démo, ajoute 8 congés/absences (motif « … [démo] ») et ~120 rendez-vous à venir sur 13 jours (note « Démo équipe »), sans chevauchement,
hors horaires du centre ni absences. Pour retirer l'ajout : StaffTimeOff.deleteMany({ reason: /\[démo\]$/ }) et Booking.deleteMany({ notes: "Démo équipe" }).
Contrairement à db:seed, il ne supprime rien.
IA de l'équipe (Gemini, GEMINI_API_KEY ; sans clé : calcul sans IA) : l'IA propose, le propriétaire ou le gérant décide, rien n'est enregistré ni appliqué.
lib/team/performance*.ts, lib/ai/team-performance.ts.lib/team/assignment*.ts, lib/ai/team-assignment.ts.lib/team/forecast*.ts, lib/ai/team-forecast.ts.
Seuls des agrégats anonymes sont envoyés à Gemini (jamais de nom, client, contact ni motif d'absence). Appel Gemini : délai 10 s, un seul nouvel essai.
Vérifié en navigateur sur base isolée avec un vrai appel Gemini. Non faits : prise en compte des horaires individuels dans la capacité, packs dans la suggestion, arabe non testé à l'écran.Parcours vérifiés (navigateur, base isolée, novembre 2026) : création réussie d'un compte STAFF (rôle « Coiffeur / Coiffeuse », badge Personnel) ; enregistrement
réussi des horaires (« Lundi · Repos », « Mardi · 09:00-17:00 ») ; congé créé, sans motif dans la liste ni le planning ; planning d'un employé ; affectation d'un gérant à un autre centre ;
gérant limité à son centre (membres, rôles proposés, ?center= interdit ignoré) ; désactivation d'un gérant puis connexion refusée ; événements d'audit affichés.
À vérifier encore : mise en page sur grand écran ; intégration des horaires et absences aux créneaux (à concevoir avec ses propres tests de non-régression).
| Exigence | Où |
|---|---|
| F6.1 Encaissement (espèces, carte, mobile, paiement fractionné, remise) et acomptes | /dashboard/finances/caisse · bouton « Encaisser » du calendrier |
| F6.2 Paiement en ligne (passerelle locale) | « Lien de paiement » en caisse → page client /paiement/[jeton] |
F6.3 Factures et reçus PDF numérotés (FAC-2026-00001, REC-2026-00001) | /dashboard/finances/factures, /api/finance/invoices/[id], /api/finance/receipts/[id] |
| F6.4 Suivi des dépenses (loyer, produits, salaires…) | /dashboard/finances/depenses |
| F6.5 Tableau de bord : CA, panier moyen, services les plus demandés, comparaison entre centres | /dashboard/finances |
| F6.6 Export PDF et Excel/CSV | menu « Exporter » → /api/finance/export |
IA du module 6 (cahier des charges §6) :
lib/finance/assistant.ts).
Sans clé API : moteur par mots-clés FR/AR sur les mêmes requêtes.lib/finance/analytics.ts).Compléments du module (CRUD complet et règles de gestion) :
| Fonction | Où |
|---|---|
| Historique des paiements (recherche, filtres, remboursement, annulation d'un lien en attente) | /dashboard/finances/paiements |
| Clôture de caisse : comptage des espèces, écart, rapport PDF, verrouillage de la journée, réouverture (propriétaire) | page Caisse + /api/finance/closings/[id] |
| Paramètres financiers : TVA, matricule fiscal, mentions de facture, acompte par défaut, droit d'encaisser du staff, paiement en ligne | /dashboard/finances/parametres (propriétaire) |
| Espace client : ses factures et ses reçus en PDF (cahier §4) | /dashboard/mes-paiements |
| Sécurité du paiement en ligne : 5 essais de carte maximum par lien, puis verrouillage | lib/finance/service.ts |
IA, détection d'anomalies : en plus du CA, des dépenses, des remboursements et des remises, les écarts de caisse
des clôtures sont analysés (écart ≥ 10 DT signalé, ≥ 50 DT important, 3 écarts ou plus sur un centre = alerte
« répétés »). L'assistant dispose d'un outil get_cash_closings.
Règles métier : montants TTC en dinars (3 décimales), TVA 19 % incluse détaillée sur la facture ; facture émise automatiquement au règlement complet, et la prestation passe à « Terminé » ; numérotation séquentielle par organisation et par année (un numéro annulé n'est jamais réutilisé) ; un remboursement ne modifie pas la facture (ligne négative dans ses règlements).
Droits : propriétaire = tout ; gérant = caisse, factures, dépenses et tableau de bord de son centre (pas de saisie des salaires) ; staff = encaisser ses propres rendez-vous, sans remise ni remboursement ; seul le propriétaire annule une facture. Chaque encaissement, remboursement, facture, dépense et export est inscrit au journal de sécurité.
Paiement en ligne : la passerelle de démonstration (lib/finance/gateway.ts) accepte la carte de test
4242 4242 4242 4242 (et refuse 4000 0000 0000 0002). Pour la production, implémenter l'interface
PaymentGateway avec le fournisseur retenu (Konnect, Flouci, ClicToPay…).
Données de démo : npm run db:seed crée aussi ~10 semaines de paiements et factures et 4 mois de dépenses
(npm run db:seed:finance pour les régénérer seules), avec deux anomalies à faire détecter par l'IA :
un jour sans saisie de caisse à Djerba, un achat de produits exceptionnel à Sousse, et des écarts de caisse
(3 manques à Tunis, 1 excédent à Sousse).
| Module | Ce que le Module 3 utilise | Fichier |
|---|---|---|
| 2 — Catalogue (Maya) | collections services et packs (nom, catégorie, durée, prix, centres) | lib/models/catalog.ts |
| 4 — Équipe (Amine) | praticiens = utilisateurs STAFF affectés au centre (centerIds) ; les horaires, absences et compétences du Module 4 ne sont pas encore lus | lib/booking/availability.ts |
| 6 — Finances (Salsabil) | bookings (statut COMPLETED, totalTnd, items) ; le Module 6 passe un RDV réglé à COMPLETED | lib/models/booking.ts, lib/finance/service.ts |
| Socle — Notifications | boîte d'envoi notifications (statut QUEUED) à brancher sur SMS/WhatsApp | lib/models/booking.ts |
NextAuth v5 (Auth.js), fournisseur credentials : connexion par e-mail ou téléphone + mot de passe (bcrypt).
auth.config.ts : configuration sans base (session JWT dans un cookie httpOnly, rôle et organisation dans le jeton,
7 jours avec « Se souvenir de moi », 12 h sinon) — utilisée par proxy.ts.auth.ts : NextAuth() avec la vérification du mot de passe dans MongoDB → auth, signIn, signOut, handlers.app/api/auth/[...nextauth]/route.ts : routes NextAuth.app/actions/auth.ts : connexion (signIn), blocage après 5 échecs, inscription propriétaire ou client, déconnexion
(signOut), mot de passe oublié ; chaque événement est inscrit au journal de sécurité.lib/dal.ts : getCurrentUser() / requireRole() relisent la session (auth()) et l'utilisateur en base à chaque requête.lib/models/ — schémas Mongoose (core, catalog, booking, finance, team) · lib/db.ts — connexion uniquelib/team/ — Module 4 : droits et centres, services d'écriture, validation, horaires, absences, planning, testslib/finance/ — Module 6 : règles métier, rapports, IA, PDF, passerelle de paiement, testslib/dal.ts — Data Access Layer (getCurrentUser, requireRole, centres accessibles)lib/booking/ — disponibilités, création, rappels, prédiction, chatbot, fuseau horairelib/data/ — lectures pour les pages (calendrier, clients, vitrine publique)app/actions/ — Server Actions (toujours revérifiées côté serveur)scripts/seed.ts — jeu de démonstration · scripts/local-db.ts — MongoDB locallib/data/planning.ts) : semaine du lundi au dimanche (heure de Tunis, ?week=YYYY-MM-DD), rendez-vous de tous statuts
(annulés estompés), absences, pauses et fermetures, horaires du jour. Ni téléphone, ni notes, ni prix, ni motif.