Botero PoP City — Plan & Documentation Technique de l'Application

Web App mobile-responsive de découverte urbaine via des parcours de street art et anecdotes culturelles basés sur la géolocalisation.

URL publiée : https://boteropopcity.base44.app Date du document : 11 septembre 2026


Sommaire

  1. Vision & Objectifs
  2. Architecture Générale
  3. Modèle de Données (Entités)
  4. Règles Métier & Logique Applicative
  5. Parcours Utilisateur (User Flows)
  6. Backend — Fonctions & Intégrations
  7. Système Multi-Langues (i18n)
  8. Sécurité & Accès
  9. Frontend — Pages & Composants
  10. Points Techniques Particuliers
  11. Limites Connues

1. Vision & Objectifs

Botero PoP City propose des parcours culturels guidés par GPS à travers le street art et l'histoire urbaine. L'utilisateur :

  • découvre un catalogue de parcours par ville ;
  • achète un parcours (paiement Stripe, code promo optionnel) ;
  • suit un itinéraire cartographié étape par étape ;
  • valide chaque étape sur place par géolocalisation (ou manuellement) ;
  • débloque à chaque étape un contenu culturel (texte + image) ;
  • reçoit un message de fin de parcours et une invitation Instagram.

Monétisation : achat au parcours, codes promotionnels (liés optionnellement à un partenaire), gratuité possible (prix 0 ou promo 100%).

CRM séparé : l'espace d'administration gère les partenaires (personnes morales), codes promo, contenu éditorial et reporting.


2. Architecture Générale

| Couche | Technologie | |---|---| | Frontend | React + Vite + Tailwind CSS + shadcn/ui | | Cartographie | react-leaflet (OpenStreetMap) | | Backend (BaaS) | Base44 — entités, fonctions, auth, intégrations Core | | Paiement | Stripe (Live Mode) — Checkout Sessions + Webhook | | Auth | Base44 Auth (email/password, Google, Apple optionnelle) | | i18n | Système maison (FR natif + traductions IA EN/ES/IT/DE/NL) | | Hébergement | Base44 (https://boteropopcity.base44.app) |

Structure des dossiers clés :

  • src/pages/
    — Pages de l'app (Home, RouteDetail, RoutePlay, Account, Admin, Welcome, etc.)
  • src/components/
    — Composants réutilisables + composants admin + ui (shadcn)
  • src/lib/
    — i18n, AuthContext, utils, query-client
  • base44/entities/
    — Schémas JSON des entités
  • base44/functions/
    — Fonctions backend (createCheckout, stripeWebhook, sendContactEmail, translateContent)
  • base44/shared/
    — Logique partagée (geo.ts : calcul Haversine)

3. Modèle de Données (Entités)

Toutes les entités héritent automatiquement de :

id
,
created_date
,
updated_date
,
created_by_id
.

3.1 Route (Parcours)

| Champ | Type | Rôle | |---|---|---| |

title
| string (requis) | Nom du parcours (non traduit) | |
description
| string | Description (traduite) | |
city
| string (requis) | Ville de rattachement | |
price
| number (requis) | Prix en EUR (0 = gratuit) | |
image_url
| string | Image de présentation | |
duration_minutes
| number | Durée estimée | |
distance_km
| number | Distance estimée | |
display_order
| number | Ordre d'affichage (défaut 0) | |
published
| boolean | Visible côté client (défaut false) | |
coming_soon
| boolean | Badge "Bientôt disponible" | |
completion_text
| string | Texte affiché à la fin du parcours (traduit) | |
completion_image_url
| string | Image de fin de parcours | |
instagram_image_url
| string | Image du pop-up Instagram de fin | |
translations
| object | Traductions IA ({ en: {...}, es: {...}, ... }) |

3.2 Step (Étape)

| Champ | Type | Rôle | |---|---|---| |

route_id
| string (requis) | Référence vers Route | |
step_order
| number (requis) | Ordre dans le parcours | |
title
| string (requis) | Titre (non traduit) | |
description
| string | Description courte affichée pendant le parcours (traduite, respecte les sauts de ligne) | |
latitude
| number (requis) | Coordonnée GPS | |
longitude
| number (requis) | Coordonnée GPS | |
validation_radius
| number | Rayon de validation en mètres (défaut 50) | |
content
| string | Contenu culturel débloqué à la validation (traduit, respecte les sauts de ligne) | |
image_url
| string | Image de présentation de l'étape | |
validation_image_url
| string | Image affichée dans le pop-up de validation | |
published
| boolean | "À utiliser" — masque/affiche l'étape côté client (défaut true) | |
translations
| object | Traductions IA |

3.3 Purchase (Achat)

| Champ | Type | Rôle | |---|---|---| |

user_id
| string (requis) | Utilisateur acheteur | |
route_id
| string (requis) | Parcours acheté | |
promo_code_id
| string | Code promo utilisé (référence) | |
promo_code
| string | Code promo utilisé (valeur lisible) | |
partner_id
| string | Partenaire lié (via le code promo) | |
original_price
| number | Prix avant remise | |
price_paid
| number (requis) | Prix réellement payé | |
status
| enum |
pending
/
completed
(défaut completed) |

3.4 Progress (Progression)

| Champ | Type | Rôle | |---|---|---| |

user_id
| string (requis) | Utilisateur | |
route_id
| string (requis) | Parcours | |
current_step
| number | Index de l'étape en cours (défaut 0) | |
completed_steps
| number[] | Index des étapes validées | |
manual_steps
| number[] | Étapes validées manuellement | |
validations
| object[] | Détail par validation : { step, method ('manual'|'gps'), date } | |
started_date
| datetime | Date de début | |
completed_date
| datetime | Date de fin (si terminé) | |
status
| enum |
in_progress
/
completed
(défaut in_progress) |

3.5 PromoCode (Code Promotionnel)

| Champ | Type | Rôle | |---|---|---| |

code
| string (requis) | Le code saisi par l'utilisateur (non traduit) | |
discount_percent
| number (requis) | Pourcentage de réduction | |
valid_until
| date | Date d'expiration (optionnelle) | |
max_uses
| number | Nombre max d'utilisations (optionnel) | |
used_count
| number | Compteur d'utilisation (défaut 0) | |
active
| boolean | Actif (défaut true) | |
partner_id
| string | Partenaire lié (optionnel) | |
route_id
| string | Parcours spécifique (optionnel — sinon tous parcours) |

3.6 Partner (Partenaire — Personne Morale)

| Champ | Type | Rôle | |---|---|---| |

name
| string (requis) | Raison sociale (non traduit) | |
legal_form
| enum | SAS, SARL, SASU, EURL, SA, SCI, Association, Auto-entrepreneur, Autre | |
siret
| string | Numéro SIRET | |
contact_name
| string | Nom du contact | |
email
| string | Email | |
phone
| string | Téléphone | |
address
| string | Adresse | |
postal_code
| string | Code postal | |
city
| string | Ville | |
image_url
| string | Logo / image | |
notes
| string | Notes internes | |
active
| boolean | Actif (défaut true) |

3.7 SiteContent (Contenu Éditorial Global)

Entité singleton (premier enregistrement utilisé). Gère : bannière hero, page de présentation (welcome), consignes de sécurité, footer, introduction/clôture FAQ, introduction contact, email + objet par défaut du formulaire de contact. Tous les champs textuels sont traduits via

translations
.

3.8 SitePage (Pages Éditoriales — ex : CGV)

| Champ | Type | Rôle | |---|---|---| |

slug
| string (requis) | Identifiant d'URL (ex : cgv) | |
title
| string (requis) | Titre (non traduit) | |
content
| string | Contenu HTML (traduit) | |
image_url
| string | Image | |
visible
| boolean | Visible (défaut true) | |
translations
| object | Traductions IA |

3.9 FaqItem (Question FAQ)

| Champ | Type | Rôle | |---|---|---| |

question
| string (requis) | Question (traduite) | |
answer
| string (requis) | Réponse (traduite) | |
display_order
| number | Ordre d'affichage | |
visible
| boolean | Visible (défaut true) | |
translations
| object | Traductions IA |

3.10 Review (Avis Client)

| Champ | Type | Rôle | |---|---|---| |

name
| string (requis) | Nom du client (non traduit) | |
city
| string | Ville | |
rating
| number | Note 1-5 (défaut 5) | |
comment
| string (requis) | Témoignage (traduit) | |
avatar_url
| string | Avatar / photo | |
visible
| boolean | Visible (défaut true) | |
translations
| object | Traductions IA |

3.11 User (Utilisateur — Entité Intégrée)

Entité native Base44, non créée manuellement. Champs :

id
,
email
,
full_name
,
role
(
admin
/
user
). Les utilisateurs rejoignent l'app via invitation (
base44.users.inviteUser
) ou inscription.


4. Règles Métier & Logique Applicative

4.1 Affichage des Parcours (côté client)

  • Filtre published === true : seuls les parcours publiés apparaissent sur l'accueil.
  • Tri : par ville (alphabétique), puis par prix croissant.
  • Badge "Bientôt disponible" : si coming_soon === true, la carte est désactivée (non cliquable, filtre gris).
  • Compteur d'étapes : seules les étapes published === true sont comptées côté client.

4.2 Affichage des Étapes

  • Filtre published !== false : les étapes non publiées sont masquées côté client (RoutePlay et Account).
  • Recalcul automatique : le nombre total d'étapes affiché exclut toujours les étapes non publiées.
  • Tri : par step_order croissant.

4.3 Achat & Paiement

  • Double achat bloqué : si un Purchase complété existe déjà ET qu'aucun Progress complété n'existe, l'achat est refusé (erreur already_purchased).
  • Rachat autorisé : si le parcours a été terminé (Progress complété), l'utilisateur peut le racheter — la progression est alors réinitialisée.
  • Code promo : validé côté backend (createCheckout) selon les règles :
    • active === true
    • valid_until non dépassée (si renseignée)
    • used_count < max_uses (si renseigné)
    • route_id correspondant au parcours (si renseigné — sinon valable pour tous)
  • Prix final = price × (1 - discount_percent / 100), arrondi à 2 décimales.
  • Gratuité : si finalPrice <= 0 (prix 0 ou promo 100%), l'accès est accordé directement sans passer par Stripe — un Purchase complété est créé et la progression est réinitialisée si nécessaire.
  • Stripe : sinon, une Checkout Session est créée avec métadonnées (route_id, user_id, promo_code_id, partner_id, original_price, price_paid, base44_app_id).
  • Blocage iframe : le paiement est bloqué si l'app tourne dans l'iframe du builder (le checkout Stripe ne fonctionne que depuis l'app publiée).

4.4 Validation des Étapes (GPS)

  • Calcul de distance : formule de Haversine (base44/shared/geo.ts) entre la position GPS de l'utilisateur et les coordonnées de l'étape.
  • Zone de validation : l'utilisateur est "dans la zone" si la distance <= validation_radius (défaut 50 m).
  • Validation GPS : bouton "Valider l'étape" activé uniquement quand l'utilisateur est dans la zone.
  • Validation manuelle : bouton "Valider manuellement" toujours disponible (hors parcours terminé) — trace la méthode comme manual.
  • Suivi GPS : watchPosition (haute précision, retry en basse précision au timeout) ; trace GPS continue affichée sur la carte.
  • Recentrage carte : panTo au premier fixe GPS uniquement, puis navigation libre.

4.5 Progression

  • Création automatique d'un Progress au démarrage d'un parcours (si inexistant).
  • À chaque validation : ajout de l'index dans completed_steps, ajout d'une entrée dans validations (méthode + date), incrément de current_step.
  • Dernière étape : status passe à completed, completed_date est renseignée, pop-up de fin affiché.
  • Rachat : réinitialisation de current_step, completed_steps, manual_steps, validations, started_date ; completed_date remis à null, status à in_progress.

4.6 Codes Promo — Compteur

  • Incrémenté (used_count + 1) à chaque utilisation réussie — côté backend (createCheckout pour gratuité, stripeWebhook pour paiement Stripe).

4.7 Formulaire de Contact

  • Champs obligatoires : nom, email (validé regex), message.
  • L'email est envoyé via la fonction backend sendContactEmail (pas de mailto).
  • Destinataire et objet configurables dans SiteContent (contact_email, contact_subject).
  • Limites de longueur : nom 100, email 200, message 5000 caractères.

4.8 Traduction Automatique

  • Déclenchement : à l'enregistrement dans les formulaires admin (Route, Step, FaqItem, Review, SiteContent, SitePage).
  • Champ cible : translations (objet { en, es, it, de, nl }).
  • Règles de traduction (prompt IA) :
    • Traduire uniquement les valeurs textuelles, pas les noms de champs.
    • Ne pas traduire : noms propres, noms de villes, noms de marques ("Botero PoP City"), noms de personnes, codes promo, noms de partenaires, titres (parcours/étapes).
    • Traduire : descriptions, contenus culturels, FAQ, avis, contenus éditoriaux.
    • Conserver les balises HTML intactes.
  • Affichage : tField(entity, fieldName) retourne la traduction dans la langue courante, ou le texte par défaut (FR) si absent.

5. Parcours Utilisateur (User Flows)

5.1 Découverte & Achat

  1. Welcome (page de présentation)
  2. Connexion / Inscription
  3. Home (catalogue des parcours par ville)
  4. RouteDetail (détails + achat + code promo)
  5. Stripe Checkout (paiement sécurisé)
  6. Retour sur RouteDetail (payment=success)
  7. RoutePlay (démarrage du parcours)

5.2 Parcours (RoutePlay)

  1. RoutePlay
  2. Pop-up consignes de sécurité (premier démarrage uniquement)
  3. Carte + étape courante (image, titre, description, distance GPS)
  4. Navigation GPS vers la zone de validation
  5. [Dans la zone] → Bouton "Valider l'étape" → Pop-up de validation (image + contenu culturel) → "Prochaine étape" ou "Terminer le parcours"
  6. [Dernière étape] → Pop-up de fin (image + texte) → Pop-up Instagram (invitation à suivre @botero_pop)

5.3 Espace Client (Account)

  1. Account
  2. Profil (nom éditable, déconnexion)
  3. Liste des parcours achetés / commencés
  4. Progression par parcours (barre, statut, étapes validées)
  5. RouteProgressDetail (détail : achat, prix, validations par étape)

5.4 Administration

  1. Admin (réservé role=admin)
  2. Onglets : Parcours | Étapes | Codes promo | Bannière | Pages | Avis | FAQ | Partenaires | Statistiques
  3. CRUD complet + publication + traduction IA automatique à la sauvegarde

5.5 Pages Publiques (sans auth)

  • /welcome — Page de présentation
  • /avis — Avis clients
  • /faq — FAQ
  • /contact — Formulaire de contact
  • /page/:slug — Pages éditoriales (ex : CGV)

6. Backend — Fonctions & Intégrations

6.1 createCheckout

Rôle : Crée une session de paiement Stripe (ou accorde l'accès gratuit). Entrées : { route_id, user_id, promo_code } Logique :

  1. Vérifie le parcours (existe + publié).
  2. Vérifie non double-achat (sauf si parcours déjà terminé).
  3. Valide le code promo (actif, valide, route match, quota).
  4. Calcule le prix final.
  5. Si gratuit (<= 0) : crée directement le Purchase complété, incrémente le code promo, réinitialise la progression si nécessaire → retourne { free: true }.
  6. Sinon : crée une Stripe Checkout Session avec métadonnées → retourne { url }.

URL : https://boteropopcity.base44.app/functions/createCheckout

6.2 stripeWebhook

Rôle : Reçoit les événements Stripe (paiement complété). Déclenchement : Webhook Stripe → checkout.session.completed. Logique :

  1. Vérifie la signature Stripe (STRIPE_WEBHOOK_SECRET).
  2. Sur checkout.session.completed :
    • Crée le Purchase complété depuis les métadonnées.
    • Incrémente used_count du code promo.
    • Réinitialise la progression si le parcours était déjà terminé.

URL : https://boteropopcity.base44.app/functions/stripeWebhook

6.3 sendContactEmail

Rôle : Envoie un email depuis le formulaire de contact. Entrées : { name, email, message } Logique : Valide les champs → récupère le destinataire et l'objet depuis SiteContent → envoie via Core.SendEmail.

6.4 translateContent

Rôle : Traduit des textes via IA (InvokeLLM) vers EN/ES/IT/DE/NL. Sécurité : Réservé aux admins (user.role === 'admin'). Entrées : { texts: { fieldKey: value, ... } } Logique : Prompt IA avec règles de traduction (pas de noms propres, conservation HTML) → parse le JSON retourné → retourne { translations: { en, es, it, de, nl } }.

6.5 Intégrations Core utilisées

| Intégration | Usage | |---|---| | InvokeLLM | Traduction automatique (translateContent) | | SendEmail | Formulaire de contact (sendContactEmail) | | UploadPublicFile | Uploads d'images côté client (formulaires admin) |

6.6 Secrets

  • STRIPE_SECRET_KEY — Clé API Stripe (Live)
  • STRIPE_PUBLISHABLE_KEY — Clé publique Stripe
  • STRIPE_WEBHOOK_SECRET — Secret de signature du webhook
  • STRIPE_TEST_* — Clés de test
  • STRIPE_LIVE_WEBHOOK_CONFIGURED — Flag de configuration

7. Système Multi-Langues (i18n)

7.1 Architecture

  • FR (français) : langue native, texte source dans les entités.
  • EN, ES, IT, DE, NL : traductions IA stockées dans le champ translations de chaque entité.
  • Sélecteur de langue : LanguageSwitcher (composant), stocké dans localStorage.

7.2 Deux mécanismes de traduction

  1. Clés d'interface (t('home.title')) : dictionnaire statique dans src/lib/i18n.jsx — traduit l'UI (boutons, labels, messages).
  2. Contenu dynamique (tField(entity, 'description')) : lit la traduction dans entity.translations[lang][field], fallback sur le texte FR par défaut.

7.3 Règles de traduction des contenus

| Type de contenu | Traduit ? | |---|---| | Titres (parcours, étapes) | Non | | Descriptions | Oui | | Contenus culturels | Oui | | FAQ (questions + réponses) | Oui | | Avis (commentaires) | Oui | | Pages éditoriales (contenu) | Oui | | Contenu éditorial global (SiteContent) | Oui | | Codes promo | Non | | Noms de partenaires | Non | | Noms de clients (avis) | Non | | Noms de marques / villes / personnes | Non |


8. Sécurité & Accès

8.1 Routes Protégées

| Route | Accès | |---|---| | /welcome, /avis, /faq, /contact, /page/:slug | Public (sans auth) | | /login, /register, /forgot-password, /reset-password | Public | | / (Home), /route/:id, /play/:id, /account | Authentifié (ProtectedRoute) | | /admin | Admin uniquement (AdminRoute — role === 'admin') |

8.2 RLS (Row-Level Security)

  • Non configurée explicitement sur les entités métier (accès par défaut).
  • L'isolation des données utilisateur se fait par filtrage explicite (user_id) dans les requêtes SDK.
  • La fonction translateContent vérifie user.role === 'admin'.

8.3 Paiement

  • Stripe Live Mode — paiements réels.
  • Vérification de signature webhook.
  • Blocage du paiement dans l'iframe du builder.

9. Frontend — Pages & Composants

9.1 Pages

| Page | Route | Rôle | |---|---|---| | Welcome | /welcome | Page de présentation + connexion/inscription | | Home | / | Catalogue des parcours (filtre par ville) | | RouteDetail | /route/:id | Détails parcours + achat + code promo | | RoutePlay | /play/:id | Parcours en cours (carte + GPS + validation) | | Account | /account | Espace client (profil + progression) | | Admin | /admin | Back-office (CRUD + stats) | | Reviews | /avis | Avis clients | | Faq | /faq | FAQ | | Contact | /contact | Formulaire de contact | | SitePageView | /page/:slug | Page éditoriale (CGV, etc.) | | Login / Register / ForgotPassword / ResetPassword | — | Auth |

9.2 Composants Clés

| Composant | Rôle | |---|---| | Layout | Layout principal (navbar + footer + Outlet) | | ProtectedRoute | Garde d'auth (redirige vers /welcome si non connecté) | | AdminRoute | Garde admin (redirige si non admin) | | RouteCard | Carte parcours (accueil) | | RouteCarousel | Carrousel parcours (page Welcome) | | RouteProgressDetail | Détail de progression (espace client) | | LanguageSwitcher | Sélecteur de langue | | Footer | Footer (gris anthracite, texte blanc, liens éditoriaux + Instagram) | | ImageUpload | Upload d'images (formulaires admin) | | TranslateButton | Bouton de génération manuelle des traductions IA |

9.3 Composants Admin (formulaires)

RouteForm, StepForm, PromoForm, HeroBannerForm, SitePageForm, ReviewForm, FaqForm, PartnerForm, RouteStats, PromoStats — chacun déclenche autoTranslate à la sauvegarde.


10. Points Techniques Particuliers

10.1 GPS

  • watchPosition (haute précision) avec retry en basse précision au timeout/erreur.
  • Trace GPS continue affichée sur la carte (Polyline bleue).
  • Recentrage au premier fixe uniquement, puis navigation libre.
  • minZoom=2 / maxZoom=19 pour permettre le dézoom libre.
  • Limitation iframe : le GPS ne fonctionne pas dans l'iframe du builder — test sur smartphone via l'URL publiée.

10.2 Affichage des Textes

  • Sauts de ligne : whitespace-pre-line sur les descriptions d'étapes (RoutePlay et Account), whitespace-pre-wrap sur les contenus culturels et textes de fin.
  • Prix : affichés en noir, gras, grande taille, alignés à droite. Prix à 0 → "Gratuit" (gras, noir).
  • Bordures : 3px, couleur noire (via tailwind.config.js borderWidth.DEFAULT = 3px + --border: 0 0% 0%).

10.3 Pop-ups (Dialog)

  • Marges latérales de 1rem sur mobile : w-[calc(100%-2rem)].

10.4 Tri des Parcours

  • Par ville (alphabétique), puis par prix croissant.

10.5 Flux Instagram

  • Intégré sur la page d'accueil juste avant le footer.

10.6 Footer

  • Fond gris anthracite, texte blanc, sans images.
  • Liens : avis, FAQ, CGV, Instagram.

11. Limites Connues

| Limite | Détail | |---|---| | GPS en iframe | Ne fonctionne pas dans l'aperçu du builder — tester sur smartphone via l'URL publiée | | Connexion Apple | Nécessite une activation manuelle via le Dashboard Base44 (Settings → Authentication) | | Icône d'accès admin | Actuellement visible pour tous les utilisateurs | | Multilinguisme IA | Les traductions sont générées par IA — peuvent nécessiter une relecture pour les contenus culturels sensibles |


Document généré le 11 septembre 2026 — reflète l'état actuel du code source.