# 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 `<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/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`.
