18 KiB
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
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
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
const { token, user, isAdmin, loading, logout } = useAuth();
ThemeContext
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 siisAdmin) - 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).
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, deviseplateformes— CRUD plateformes + catégoriescategories— CRUD catégories de plateformesgaranties— référentiel garantiespfu— taux PFU par annéenotation— critères de notation par plateformeimports— 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
pfuRatesau montage (api.get('/pfu'))
Investissements (Investissements.jsx)
- KPI "Intérêts perçus — Brut/Net" : utilise
interets_percus(brut, agrégé backend) ounet_recu_total(net, agrégé backend viaSUM(r.net_recu)) - Colonne tableau "Int. perçus (Brut/Net)" : idem
- Backend retourne les deux :
interets_percusetnet_recu_totaldans 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
rendementReelBrutetrendementReel - 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)
netModedérivé dedisplayMode(plus de state localnetMode)- KPI : 3 KPIs (Capital remboursé, Cashback, Intérêts — Brut/Net)
- Graphique courbe et treemap reçoivent
netModeen 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) :
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
// 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
import { fmtEUR, fmtPct, fmtDate, fmtStatut, memberLabel, today } from '../utils/format.js';
Composants modaux
Utiliser <Modal open={bool} title="…" onClose={fn} footer={<>…</>} width={680}>.
Gestion des erreurs form
Pattern : const [err, setErr] = useState(null) + {err && <div className="error">{err}</div>}.
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<html>)[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/adminprotégée parrequireAdminmiddleware - Lien "Administration" dans UserMenu visible uniquement si
isAdmin
Points d'attention pour futures modifications
-
Brut/Net : toute nouvelle page affichant des intérêts doit importer
useUiet dériverconst netMode = displayMode === 'net'. Appliquer la même logique que les pages existantes. -
Migrations DB : toujours ajouter dans
backend/src/db/index.jsavec guardPRAGMA table_info(). Ne pas modifierschema.sql. -
Routes supprimées :
/preferenceset/importssont des redirects vers Settings. Ne pas les recréer. -
InteretsChart : le toggle Brut/Net interne a été supprimé — le composant reçoit
netModeen prop depuis le parent, lui-même dérivé deUiContext. -
Investisseur scope : pour les vues "tous les investisseurs", passer
{ scope: 'all' }à l'API. Le backend filtre paruser_idvia le JWT. -
Calcul
net_recu_total: pour la page Investissements, le backend retourneSUM(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. -
Modifications de schéma plateformes : avant d'ajouter ou de modifier un champ sur
plateformesouplateformes_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 dupush(referentiel.js),computeOverridden, formulaire Settings (dropdown référentiel + badge hérité), et routereset.