path = "backend/src/db/index.js" with open(path, "r", encoding="utf-8") as f: content = f.read() # 1) Insérer la liste des 11 supports (dupliquée localement, hors de portée du AV_PER_SUPPORTS # du bloc de seed initial) juste avant EXTRA_ENVELOPPES. anchor_supports = " const EXTRA_ENVELOPPES = [\n" assert content.count(anchor_supports) == 1, f"anchor_supports count={content.count(anchor_supports)}" supports_const = """ const AV_PER_SUPPORTS_EXTRA = [ 'Fonds en euros', 'OPCVM / fonds actifs', 'ETF / trackers', 'Titres vifs (actions)', 'Obligations / fonds datés', 'Immobilier papier (SCPI/SCI/OPCI)', 'Private equity (FCPR)', 'Produits structurés', 'Matières premières / métaux précieux', 'Gestion pilotée / mandat', 'Fonds solidaires (90/10)', ]; """ content = content.replace(anchor_supports, supports_const + anchor_supports, 1) # 2) Ajouter les nouvelles enveloppes à la fin du tableau EXTRA_ENVELOPPES : Contrat de # capitalisation, Assurance-vie luxembourgeoise, Girardin (demande du 29/09/26 sur les # enveloppes manquantes / actifs "exotiques"), et Wallet crypto conservé par un tiers # (demande du 29/09/26, distincte de l'auto-conservation et du compte sur exchange). anchor_last_item = """ { nom: 'Participation directe non cotée (startups/PME)', categories: ['Financement participatif / private equity'], description: "Prise de participation directe au capital d'une société non cotée, hors plateforme crowdequity.", plafond_eur: null, regime_fiscal: "Flat tax 30 % sur les plus-values (ou barème) ; réduction d'IR à l'entrée possible sous conditions (IR-PME/Madelin)", supports: [] }, ];""" assert content.count(anchor_last_item) == 1, f"anchor_last_item count={content.count(anchor_last_item)}" new_items = """ { nom: 'Participation directe non cotée (startups/PME)', categories: ['Financement participatif / private equity'], description: "Prise de participation directe au capital d'une société non cotée, hors plateforme crowdequity.", plafond_eur: null, regime_fiscal: "Flat tax 30 % sur les plus-values (ou barème) ; réduction d'IR à l'entrée possible sous conditions (IR-PME/Madelin)", supports: [] }, { nom: 'Contrat de capitalisation', categories: ['Assurance-vie'], description: "Support d'épargne proche de l'assurance-vie (fonds euros + unités de compte), mais sans clause bénéficiaire ni dénouement au décès — entre dans la succession classique. Peut être souscrit par une personne physique ou une personne morale (société, SCI...).", plafond_eur: null, regime_fiscal: "Fiscalité des gains identique à l'assurance-vie pour un particulier (abattement après 8 ans, PFU ou PFL) ; pas de régime de succession spécifique (articles 990 I / 757 B) ; transmissible par donation du vivant, contrairement à l'assurance-vie", supports: AV_PER_SUPPORTS_EXTRA }, { nom: 'Assurance-vie luxembourgeoise', categories: ['Assurance-vie'], description: "Contrat d'assurance-vie souscrit auprès d'un assureur luxembourgeois — triangle de sécurité (actifs ségrégués auprès d'une banque dépositaire distincte de l'assureur), univers d'investissement et devises généralement plus larges qu'un contrat français.", plafond_eur: null, regime_fiscal: "Fiscalité identique à l'assurance-vie française pour un résident fiscal français (abattement après 8 ans, PFU/PFL) ; déclaration du contrat étranger requise (formulaire 3916-3916 bis)", supports: AV_PER_SUPPORTS_EXTRA }, { nom: 'Girardin (industriel / social)', categories: ['Financement participatif / private equity'], description: "Investissement dans un dispositif de défiscalisation outre-mer — financement d'un investissement productif ou de logement social en DOM-TOM, en échange d'une réduction d'impôt immédiate. Capital non récupéré (pas de valeur résiduelle), contrairement à un FCPI/FIP.", plafond_eur: null, regime_fiscal: "Réduction d'IR l'année suivant l'investissement, généralement supérieure au montant investi (effet de levier) ; plafonnement spécifique des niches fiscales outre-mer ; risque de remise en cause fiscale si le monteur/exploitant ne respecte pas ses engagements (durée de conservation 5 ans)", supports: [] }, { nom: 'Wallet crypto (conservation par un tiers)', categories: ['Actifs numériques'], description: "Cryptomonnaies détenues dans un wallet dont les clés sont conservées par un tiers de confiance (service de custody dédié, family office...), par opposition à l'auto-conservation ou à un compte sur une plateforme d'échange.", plafond_eur: null, regime_fiscal: "Flat tax 30 % sur les plus-values de cession (particuliers), déclaration 2086", supports: [] }, ];""" content = content.replace(anchor_last_item, new_items, 1) # 3) Étendre la boucle de traitement pour gérer aussi les supports (jusqu'ici uniquement les # catégories, car aucune des 7 enveloppes du 29/09/26 n'avait de support). anchor_loop = """ const insEnvRefExtra = db.prepare('INSERT OR IGNORE INTO enveloppes_referentiel (nom, description, plafond_eur, regime_fiscal) VALUES (?, ?, ?, ?)'); const getEnvRefIdExtra = db.prepare('SELECT id FROM enveloppes_referentiel WHERE nom = ?'); const getCatEnvIdExtra = db.prepare('SELECT id FROM categories_enveloppes WHERE nom = ?'); const linkEnvCatExtra = db.prepare('INSERT OR IGNORE INTO referentiel_categories_enveloppes (enveloppe_id, categorie_id) VALUES (?, ?)'); db.transaction(() => { for (const { nom, description, plafond_eur, regime_fiscal, categories } of EXTRA_ENVELOPPES) { insEnvRefExtra.run(nom, description ?? null, plafond_eur ?? null, regime_fiscal ?? null); const env = getEnvRefIdExtra.get(nom); if (!env) continue; for (const catNom of categories) { const cat = getCatEnvIdExtra.get(catNom); if (cat) linkEnvCatExtra.run(env.id, cat.id); } } })(); }""" assert content.count(anchor_loop) == 1, f"anchor_loop count={content.count(anchor_loop)}" new_loop = """ const insEnvRefExtra = db.prepare('INSERT OR IGNORE INTO enveloppes_referentiel (nom, description, plafond_eur, regime_fiscal) VALUES (?, ?, ?, ?)'); const getEnvRefIdExtra = db.prepare('SELECT id FROM enveloppes_referentiel WHERE nom = ?'); const getCatEnvIdExtra = db.prepare('SELECT id FROM categories_enveloppes WHERE nom = ?'); const linkEnvCatExtra = db.prepare('INSERT OR IGNORE INTO referentiel_categories_enveloppes (enveloppe_id, categorie_id) VALUES (?, ?)'); const getSuppRefIdExtra = db.prepare('SELECT id FROM supports_referentiel WHERE nom = ?'); const linkEnvSuppExtra = db.prepare('INSERT OR IGNORE INTO enveloppes_supports_disponibles (enveloppe_id, support_id) VALUES (?, ?)'); db.transaction(() => { for (const { nom, description, plafond_eur, regime_fiscal, categories, supports } of EXTRA_ENVELOPPES) { insEnvRefExtra.run(nom, description ?? null, plafond_eur ?? null, regime_fiscal ?? null); const env = getEnvRefIdExtra.get(nom); if (!env) continue; for (const catNom of categories) { const cat = getCatEnvIdExtra.get(catNom); if (cat) linkEnvCatExtra.run(env.id, cat.id); } for (const suppNom of (supports || [])) { const supp = getSuppRefIdExtra.get(suppNom); if (supp) linkEnvSuppExtra.run(env.id, supp.id); } } })(); }""" content = content.replace(anchor_loop, new_loop, 1) with open(path, "w", encoding="utf-8") as f: f.write(content) print("Extension 2 (contrat de capitalisation, AV luxembourgeoise, Girardin, wallet tiers) inserted OK, new length:", len(content))