# CLAUDE.md — Crowdlending App Tracker de portefeuille de crowdlending (prêts participatifs). Application full-stack monorepo gérée localement, single-user multi-investisseur. --- ## Système de mémorisation Au début de chaque session, lis le fichier MEMORY.md avant de répondre. Utilises les informations qu'il contient pour orienter ton travail. Ne divulgues pas tes découvertes, contentes-toi d'en prendre connaissance. Lorsque je vous dis « sauvegarde dans ta mémoire », notes immédiatement l'information dans MEMORY.md et confirmes que tu l'as fait. **Où enregistrer les informations :** Appliquez deux critères pour déterminer où enregistrer une information. Critère 1 : L'information prescrit-elle un comportement ? Recherchez des expressions comme « toujours », « jamais », « avant de faire X, faites Y ». Si oui, ajoutez-la à ce fichier (CLAUDE.md) dans la section appropriée. Critère 2 : L'information décrit-elle un fait susceptible d'évoluer ? Coordonnées, état d'avancement d'un projet, décisions, informations que je t'ai demandé de retenir. Si oui, ajoutes-la à MEMORY.md. En cas de doute, suggères le fichier dans lequel elle devrait être enregistrée et demandes-moi confirmation. ## Préférences - Adoptes un ton professionnel et conversationnel. Si le texte ressemble à une note de service, Reformules-le. - Sois concis dans tes réponses (moins de 300 mots), sauf si je te demandes plus de détails. - Utilisez des puces pour les listes, mais rédiges tes explications sous forme de paragraphes. - Donnes-moi une recommandation pertinente. Ne me proposes pas trois options, sauf si je vous demande explicitement des alternatives. ## Règles - Poses toujours des questions pour clarifier la situation avant d'entreprendre une tâche complexe. - Si tu as un doute, exprimez-le. Ne fais pas de suppositions. ## Stack technique | Couche | Tech | |---|---| | Frontend | React 18, Vite, React Router v6 | | Backend | Node.js / Express, better-sqlite3 | | Validation | Zod (backend) | | Auth | JWT (header `Authorization: Bearer`) | | Styles | CSS variables custom (pas de framework UI) | | Build | Vite (frontend) / Node ESM (backend) | **Pas de TypeScript.** Pas de Redux. Pas d'ORM. --- ## Structure du projet ``` crowdlending-app/ ├── frontend/ │ └── src/ │ ├── App.jsx # Routeur principal │ ├── main.jsx # Providers imbriqués │ ├── api.js # Client HTTP centralisé │ ├── styles.css # CSS global + variables thème │ ├── context/ │ │ ├── AuthContext.jsx # JWT, isAdmin │ │ ├── InvestisseurContext.jsx # activeId, activeView, investisseurs │ │ ├── ThemeContext.jsx # dark/light/system │ │ └── UiContext.jsx # sidebar, fontScale, langue, devise, displayMode │ ├── components/ │ │ ├── Layout.jsx # Shell app (sidebar + topbar) │ │ ├── UserMenu.jsx # Menu popup utilisateur │ │ ├── Modal.jsx # Modale générique │ │ ├── CategorySelect.jsx # Multi-select catégories │ │ ├── InteretsChart.jsx # Courbe cumul intérêts (SVG) │ │ ├── InteretsDistributionChart.jsx # Treemap plateformes (SVG) │ │ ├── SoldeChart.jsx # Courbe solde dépôts (SVG) │ │ ├── DistributionChart.jsx # Distribution investissements │ │ └── Logo.jsx │ ├── pages/ │ │ ├── Dashboard.jsx │ │ ├── Investissements.jsx │ │ ├── InvestissementDetail.jsx │ │ ├── Remboursements.jsx │ │ ├── DepotsRetraits.jsx │ │ ├── Fiscal2778.jsx # Récapitulatif fiscal (anciennement "Flat Tax") │ │ ├── Settings.jsx # Hub paramètres (apparence, plateformes, imports…) │ │ ├── MonCompte.jsx # Profil, sécurité, nettoyage données │ │ ├── Admin.jsx # Administration plateforme (admin only) │ │ ├── Login.jsx / Register.jsx │ │ └── SimulRemboursements.jsx │ └── utils/ │ └── format.js # fmtEUR, fmtDate, fmtPct, fmtStatut, memberLabel… └── backend/ └── src/ ├── server.js # Express app + routes montées ├── db/ │ ├── index.js # Init SQLite + toutes les migrations ADD COLUMN │ └── schema.sql # Schéma de référence (CREATE TABLE IF NOT EXISTS) ├── routes/ │ ├── auth.js # /api/auth │ ├── investisseurs.js # /api/investisseurs │ ├── plateformes.js # /api/plateformes │ ├── categories.js # /api/categories │ ├── depotsRetraits.js │ ├── investissements.js │ ├── remboursements.js │ ├── simul.js # /api/simul (projections) │ ├── dashboard.js │ ├── fiscal2778.js │ ├── imports.js │ ├── pfu.js # Taux PFU par année │ ├── notation.js │ ├── garanties.js │ └── admin.js # Requirest requireAdmin middleware ├── middleware/ │ ├── auth.js # requireAuth, requireAdmin │ └── errorHandler.js # HttpError + Zod errors └── jobs/ └── autoStatut.js # Job auto-statut investissements ``` --- ## Base de données (SQLite) Tables principales : | Table | Rôle | |---|---| | `users` | Comptes utilisateurs, champ `role` ('user'/'admin') | | `investisseurs` | Profils investisseurs par user (famille/entreprise), `is_principal` | | `plateformes` | Référentiel plateformes, `domiciliation`, `fiscalite`, `taux_fiscalite_locale` | | `categories_plateforme` | Catégories, junction `plateforme_categories` | | `investissements` | Un prêt par ligne, `investisseur_id`, `statut`, `type_remb`, `freq_interets`, `date_debut_simul` | | `remboursements` | Flux réels : `capital`, `cashback`, `interets_bruts`, `prelev_sociaux`, `prelev_forfaitaire`, `interets_nets`, `net_recu` | | `simul_remboursements` | Projections générées : `capital_prevu`, `interets_prevus`, `total_prevu` | | `depots_retraits` | Mouvements de cash (depot/retrait) par plateforme | | `taux_pfu` | Taux PFU par année (`prelev_sociaux` + `impot_revenu`) | | `imports` | Logs d'imports CSV/XLS | | `notation` | Critères de notation par plateforme | | `garanties` | Référentiel garanties | Vues SQL : `v_solde_plateforme`, `v_synthese_inv`, `v_interets_annuels` **Pattern migration** : toutes les évolutions de schéma sont des `ALTER TABLE ADD COLUMN` dans `backend/src/db/index.js`, protégées par un check `PRAGMA table_info()`. Ne jamais modifier `schema.sql` pour les nouvelles colonnes — ajouter uniquement une migration dans `index.js`. --- ## Contextes React ### `UiContext` — état UI persisté en localStorage ```js const { sidebarCollapsed, toggleSidebar, // cl_ui_sidebar_collapsed fontScale, setFontScale, // cl_fontscale : 'large'|'medium'|'compact' langue, setLangue, // cl_langue : 'fr'|'en' devise, setDevise, // cl_devise : 'EUR'|'USD'|'GBP'|'CHF'|'CAD'|'SGD' displayMode, setDisplayMode, // cl_display_mode : 'net'|'brut' — LE TOGGLE GLOBAL } = useUi(); ``` **`displayMode`** est le toggle Brut/Net global affiché dans la topbar. Il pilote l'affichage des intérêts et rendements sur **toutes les pages**. Défaut : `'net'`. ### `InvestisseurContext` ```js const { activeId, activeView, investisseurs, activeViewMember, activeViewMember } = useInvestisseur(); // activeView : 'single' | 'all' // activeId : id de l'investisseur sélectionné (null si vue 'all') ``` Les appels API utilisent `{ scope: 'all' }` quand `activeView === 'all'`. ### `AuthContext` ```js const { token, user, isAdmin, loading, logout } = useAuth(); ``` ### `ThemeContext` ```js const { theme, setTheme } = useTheme(); // 'light'|'dark'|'system' ``` --- ## Layout & Navigation ### Structure de la sidebar La sidebar contient **uniquement les 5 pages de données** : - Tableau de bord (`/`) - Investissements (`/investissements`) - Dépôts / Retraits (`/depots-retraits`) - Remboursements (`/remboursements`) - Fiscalité (`/2778-sd`) Les pages **Settings, MonCompte, Admin** sont accessibles via le **popup UserMenu** (coin bas gauche sidebar) : - Mon compte → `/compte` - Administration → `/admin` (visible uniquement si `isAdmin`) - Paramètres → `/settings` ### Topbar globale (`Layout.jsx`) Boutons : "+ Ajout Investissement" | "+ Nouveau remboursement" | "+ Nouveau dépôt/retrait" | **Toggle Brut/Net** Le toggle Brut/Net est un `div.display-toggle` avec deux `button.display-toggle-btn`. L'état est géré via `UiContext.displayMode`. --- ## Pattern pages "compte" (Settings, MonCompte, Admin) Ces pages utilisent le layout `account-layout` / `account-sidebar` / `account-content` (classes CSS). Navigation interne par **URL param `?section=`** (pas de state local). ```jsx const section = new URLSearchParams(search).get('section') || 'default'; const setSection = (s) => navigate(`/settings?section=${s}`, { replace: true }); ``` **Settings** — sections disponibles : - `apparence` (défaut) — thème, police, langue, devise - `plateformes` — CRUD plateformes + catégories - `categories` — CRUD catégories de plateformes - `garanties` — référentiel garanties - `pfu` — taux PFU par année - `notation` — critères de notation par plateforme - `imports` — import CSV/XLS (anciennement page `/imports`) **MonCompte** — sections : `profil`, `securite`, `nettoyage` **Admin** — sections : `users`, `create`, `job-logs` Routes dépréciées redirigées : `/preferences → /settings?section=apparence`, `/imports → /settings?section=imports` --- ## Toggle Brut/Net — règles d'application par page Ce toggle global (`displayMode === 'net'` → `netMode`) influence les données affichées. Voici comment chaque page l'implémente : ### Dashboard (`Dashboard.jsx`) - KPI **"Intérêts perçus — Brut/Net"** : Net = `bruts - prelev_sociaux - prelev_forfaitaire` - Table annuelle : dernière colonne bascule entre "Net reçu" (brut) et "Intérêts nets" calculé (net) - Table Échéances du mois : colonne Intérêts → brut ou estimation nette via taux PFU de l'année - Charge `pfuRates` au montage (`api.get('/pfu')`) ### Investissements (`Investissements.jsx`) - KPI **"Intérêts perçus — Brut/Net"** : utilise `interets_percus` (brut, agrégé backend) ou `net_recu_total` (net, agrégé backend via `SUM(r.net_recu)`) - Colonne tableau **"Int. perçus (Brut/Net)"** : idem - Backend retourne les deux : `interets_percus` et `net_recu_total` dans la requête SQL ### InvestissementDetail (`InvestissementDetail.jsx`) - KPI **"Intérêts perçus — Brut/Net"** : brut = `SUM(r.interets_bruts)`, net = `SUM(r.interets_nets)` (depuis remboursements chargés) - KPI **"Rendement annualisé — Brut/Net"** (XIRR) : bascule entre `rendementReelBrut` et `rendementReel` - KPI "Intérêts prévus" **supprimé** (info dans le tableau projections) - Projections : colonne **"Intérêts (Brut / Net estimé)"** — estimation nette via `getRatesForYear(date_prevue)` ### Remboursements (`Remboursements.jsx`) - `netMode` dérivé de `displayMode` (plus de state local `netMode`) - KPI : 3 KPIs (Capital remboursé, Cashback, **Intérêts — Brut/Net**) - Graphique courbe et treemap reçoivent `netMode` en prop (le toggle interne du graphique a été supprimé) - Table Plateformes : **une seule colonne Intérêts** (Brut/Net) — les colonnes séparées bruts/nets supprimées - Projections : colonne Intérêts bascule, avec estimation nette via taux PFU ### Fiscal2778 (`Fiscal2778.jsx`) - Titre : "Fiscalité — Récapitulatif fiscal" (anciennement "Flat Tax (hors France)") --- ## Modèle financier — calculs clés ``` interets_nets = interets_bruts - prelev_sociaux - prelev_forfaitaire net_recu = capital + cashback + interets_nets taux PFU France = prelev_sociaux (17.2%) + impot_revenu (12.8%) = 30% ``` **Estimation nette pour projections** (plateformes non-françaises ou simul) : ```js const rates = pfuRates.find(r => r.annee === year); const reduction = rates ? (rates.prelev_sociaux + rates.impot_revenu) / 100 : 0; const interets_net_estime = interets_bruts * (1 - reduction); ``` **XIRR** — calculé uniquement sur les investissements `statut === 'rembourse'`. Flux bruts = capital + cashback + interets_bruts. Flux nets = net_recu. --- ## Modèle Plateforme (évolution récente) Trois nouveaux champs ajoutés : | Champ | Valeurs | |---|---| | `domiciliation` | `'france'` \| `'zone_europeenne'` \| `'hors_zone_europeenne'` | | `fiscalite` | `'flat_tax'` \| `'sans_fiscalite_locale'` \| `'avec_fiscalite_locale'` | | `taux_fiscalite_locale` | REAL nullable | **Règle métier** (appliquée backend ET frontend) : si `domiciliation === 'france'`, alors `fiscalite` est forcée à `'flat_tax'` et `taux_fiscalite_locale` est null. Les options "Sans fiscalité locale" / "Avec fiscalité locale" ne s'affichent que pour les plateformes non-françaises. Helpers frontend dans `Settings.jsx` : `DOMICILIATION_LABELS`, `FISCALITE_LABELS`, `fmtFiscalite(p)`, `applyDomiciliationChange(state, newDomicil)`, `applyFiscaliteChange(state, newFiscalite)`. --- ## Conventions de code ### Appels API ```js // api.js — client centralisé, gère JWT automatiquement api.get('/investissements', { scope: 'all' }) api.post('/remboursements', payload) api.put(`/investissements/${id}`, payload) api.del(`/plateformes/${id}`) ``` ### Formatage ```js import { fmtEUR, fmtPct, fmtDate, fmtStatut, memberLabel, today } from '../utils/format.js'; ``` ### Composants modaux Utiliser `…} width={680}>`. ### Gestion des erreurs form Pattern : `const [err, setErr] = useState(null)` + `{err &&
{err}
}`. ### Classes CSS utiles - `.kpi` / `.kpi-grid` — cartes KPI - `.card` — conteneur avec fond et bordure - `.topbar` — barre de titre de page - `.account-layout` / `.account-sidebar` / `.account-content` — layout pages Settings/MonCompte/Admin - `.dr-kpi-row` — ligne KPI style Remboursements/Dépôts - `.dr-tabs` / `.dr-tab` — onglets - `.badge` + `.en_cours` / `.rembourse` / `.en_retard` / `.procedure` — badges statut - `.cat-badge` — badge catégorie plateforme - `.display-toggle` / `.display-toggle-btn` / `.display-toggle-btn.active` — toggle Brut/Net topbar - `[data-fontsize="large|medium|compact"]` — taille de police (attribut sur ``) - `[data-theme="dark"]` — thème sombre ### Thème CSS Les variables sont sur `:root` (thème clair) et `[data-theme="dark"]` pour les overrides. Ne jamais utiliser de couleurs hardcodées — toujours `var(--primary)`, `var(--success)`, `var(--danger)`, `var(--text)`, `var(--text-muted)`, `var(--border)`, `var(--surface-2)` etc. --- ## Types de remboursement | Valeur | Label | |---|---| | `in_fine` | Prêt in fine (intérêts périodiques, capital à la fin) | | `amortissable` | Prêt amortissable (capital + intérêts chaque période) | | `differe` | Prêt différé (versement unique à l'échéance) | Fréquences : `mensuel` | `trimestriel` | `in_fine` (pour différé). Statuts investissement : `en_cours` | `rembourse` | `en_retard` | `procedure` | `cloture`. --- ## Imports de données Module `ImportsSection` dans `Settings.jsx`. Supporte CSV/XLS pour : `plateformes`, `investissements`, `remboursements`, `depots_retraits`. Backend : `/api/imports` — parse, valide, insère. --- ## Fonctionnalités admin Accessibles uniquement avec `user.role === 'admin'` : - Page `/admin` : gestion utilisateurs, création compte, logs jobs - Route backend `/api/admin` protégée par `requireAdmin` middleware - Lien "Administration" dans UserMenu visible uniquement si `isAdmin` --- ## Points d'attention pour futures modifications 1. **Brut/Net** : toute nouvelle page affichant des intérêts doit importer `useUi` et dériver `const netMode = displayMode === 'net'`. Appliquer la même logique que les pages existantes. 2. **Migrations DB** : toujours ajouter dans `backend/src/db/index.js` avec guard `PRAGMA table_info()`. Ne pas modifier `schema.sql`. 3. **Routes supprimées** : `/preferences` et `/imports` sont des redirects vers Settings. Ne pas les recréer. 4. **InteretsChart** : le toggle Brut/Net interne a été **supprimé** — le composant reçoit `netMode` en prop depuis le parent, lui-même dérivé de `UiContext`. 5. **Investisseur scope** : pour les vues "tous les investisseurs", passer `{ scope: 'all' }` à l'API. Le backend filtre par `user_id` via le JWT. 6. **Calcul `net_recu_total`** : pour la page Investissements, le backend retourne `SUM(r.net_recu)` renommé `net_recu_total`. Ce champ inclut capital + cashback + intérêts nets — c'est une approximation de "rendement net" par investissement, pas uniquement les intérêts. 7. **Modifications de schéma plateformes** : avant d'ajouter ou de modifier un champ sur `plateformes` ou `plateformes_referentiel`, toujours demander : *"Ce champ doit-il exister dans les deux tables ?"* Si oui, appliquer la migration sur les deux tables ET mettre à jour tous les mécanismes d'héritage : `HERITABLE_FIELDS` (plateformes.js), payload du `push` (referentiel.js), `computeOverridden`, formulaire Settings (dropdown référentiel + badge hérité), et route `reset`.