Initial commit
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user