From dd4e8d17b1bcce34f31af79113bf1df1ff01d769 Mon Sep 17 00:00:00 2001 From: odentas Date: Thu, 16 Oct 2025 00:03:58 +0200 Subject: [PATCH] =?UTF-8?q?Correctif=20bases=20sp=C3=A9cifiques=20simulate?= =?UTF-8?q?ur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SIMULATEUR_POSTHOG_TRACKING.md | 254 ++++++++++++++++++++++----- app/(app)/simulateur/page.tsx | 46 ++++- components/Sidebar.tsx | 2 +- public/simulateur-embed.html | 304 ++++++++++++++++++++++++++++++--- 4 files changed, 525 insertions(+), 81 deletions(-) diff --git a/SIMULATEUR_POSTHOG_TRACKING.md b/SIMULATEUR_POSTHOG_TRACKING.md index 61448a7..fbfbbff 100644 --- a/SIMULATEUR_POSTHOG_TRACKING.md +++ b/SIMULATEUR_POSTHOG_TRACKING.md @@ -33,23 +33,63 @@ PostHog ### Nom de l'événement `simulateur_calculation` -### Propriétés +### Propriétés complètes | Propriété | Type | Description | Exemple | |-----------|------|-------------|---------| +| **Paramètres du formulaire** |||| +| `ccn_id` | string | ID de la CCN sélectionnée | `"1285"` | +| `ccn_nom` | string | Nom complet de la CCN | `"1285 – Entreprises Artistiques & Culturelles (CCNEAC)"` | | `categorie` | string | Catégorie du salarié | `"artiste"` ou `"technicien"` | -| `type_calcul` | string | Type de calcul | `"brut"`, `"net"` ou `"cost"` | -| `montant` | number | Montant saisi | `2000` | -| `timestamp` | string | Date/heure de l'événement | `"2025-10-15T14:30:00.000Z"` | +| `statut` | string | Statut professionnel | `"non-cadre"` ou `"cadre"` | +| `abattement_active` | boolean | Abattement pour frais pro activé | `true` ou `false` | +| `abattement_profession` | string | Type de profession (si abattement) | `"drama"`, `"musique"` ou `""` | +| **Cachets, heures et dates** |||| +| `cachets` | number | Nombre de cachets | `10` | +| `heures` | number | Nombre d'heures | `35` | +| `dates_travail` | array | Liste des dates de travail | `["2025-10-15", "2025-10-16"]` | +| `nombre_jours` | number | Nombre de jours travaillés | `2` | +| **Montant et type de calcul** |||| +| `montant_saisi` | number | Montant saisi par l'utilisateur | `2000` | +| `type_calcul` | string | Type de calcul effectué | `"brut"`, `"net"` ou `"cost"` | +| **Résultats calculés** |||| +| `resultat_brut` | number | Salaire brut calculé | `2000.00` | +| `resultat_net` | number | Salaire net avant PAS calculé | `1580.50` | +| `resultat_cost` | number | Coût total employeur calculé | `2456.80` | +| **Métadonnées** |||| +| `plafond_urssaf` | number | Plafond URSSAF appliqué | `3864` | +| `timestamp` | string | Date/heure de la simulation | `"2025-10-16T14:30:00.000Z"` | -### Exemple d'événement +### Exemple d'événement complet ```javascript { event: 'simulateur_calculation', properties: { + // Paramètres du formulaire + ccn_id: '1285', + ccn_nom: '1285 – Entreprises Artistiques & Culturelles (CCNEAC)', categorie: 'artiste', + statut: 'non-cadre', + abattement_active: true, + abattement_profession: 'drama', + + // Cachets, heures et dates + cachets: 10, + heures: 35, + dates_travail: ['2025-10-15', '2025-10-16', '2025-10-17'], + nombre_jours: 3, + + // Montant et type de calcul + montant_saisi: 2000, type_calcul: 'brut', - montant: 2000, - timestamp: '2025-10-15T14:30:00.000Z' + + // Résultats calculés + resultat_brut: 2000.00, + resultat_net: 1580.50, + resultat_cost: 2456.80, + + // Métadonnées + plafond_urssaf: 3864, + timestamp: '2025-10-16T14:30:00.000Z' } } ``` @@ -57,17 +97,56 @@ PostHog ## Fichiers modifiés ### 1. `/public/simulateur-embed.html` -**Ligne 1120+** : Ajout de l'envoi du message via `postMessage` dans le gestionnaire du bouton "Calculer" +**Ligne 1326+** : Ajout de l'envoi du message enrichi via `postMessage` dans le gestionnaire du bouton "Calculer" ```javascript -// 📊 Envoyer un événement PostHog via postMessage au parent +// 📊 Envoyer un événement PostHog enrichi via postMessage au parent try { + // Récupération de toutes les données du formulaire + const ccnElement = document.getElementById('conventionSelect'); + const ccnValue = ccnElement?.value || ''; + const ccnText = ccnElement?.options[ccnElement.selectedIndex]?.text || ''; + + const categorieValue = isTechnicien() ? 'technicien' : 'artiste'; + const statutValue = document.getElementById('statutSelect')?.value || 'non-cadre'; + + // Abattement + const abattementChecked = document.querySelector('input[name="abattement"]:checked')?.value || 'non'; + const professionValue = document.getElementById('professionSelect')?.value || ''; + + // Dates + const datesInput = document.getElementById('datesInput')?.value || ''; + const datesArray = datesInput ? datesInput.split(',').map(d => d.trim()).filter(Boolean) : []; + window.parent.postMessage({ type: 'simulateur_calculation', data: { - categorie: isTechnicien() ? 'technicien' : 'artiste', - type: document.querySelector('input[name="type"]:checked').value, - montant: montant + // Paramètres du formulaire + ccn_id: ccnValue, + ccn_nom: ccnText, + categorie: categorieValue, + statut: statutValue, + abattement_active: abattementChecked === 'oui', + abattement_profession: professionValue, + + // Cachets, heures, dates + cachets: cachets, + heures: heures, + dates_travail: datesArray, + nombre_jours: datesArray.length, + + // Montant saisi et type + montant_saisi: montant, + type_calcul: type, + + // Résultats calculés + resultat_brut: parseFloat(brut.toFixed(2)), + resultat_net: parseFloat(net.toFixed(2)), + resultat_cost: parseFloat(costEmployer.toFixed(2)), + + // Métadonnées + plafond_urssaf: plafondUrssaf, + timestamp: new Date().toISOString() } }, '*'); } catch (e) { @@ -76,28 +155,49 @@ try { ``` ### 2. `/app/(app)/simulateur/page.tsx` -**Lignes 1-35** : Ajout du hook PostHog et de l'écouteur de messages +**Lignes 12-50** : Mise à jour du hook PostHog pour capturer toutes les propriétés enrichies ```typescript -import { usePostHog } from 'posthog-js/react'; - -export default function SimulateurPage() { - const posthog = usePostHog(); - - // 📊 Écouter les messages de l'iframe pour tracker les calculs - useEffect(() => { - const handleMessage = (event: MessageEvent) => { - if (event.data?.type === 'simulateur_calculation') { - const { categorie, type, montant } = event.data.data; +useEffect(() => { + const handleMessage = (event: MessageEvent) => { + if (event.data?.type === 'simulateur_calculation') { + const data = event.data.data; + + posthog?.capture('simulateur_calculation', { + // Paramètres du formulaire + ccn_id: data.ccn_id, + ccn_nom: data.ccn_nom, + categorie: data.categorie, + statut: data.statut, + abattement_active: data.abattement_active, + abattement_profession: data.abattement_profession, - posthog?.capture('simulateur_calculation', { - categorie, - type_calcul: type, - montant, - timestamp: new Date().toISOString() - }); - } - }; + // Cachets, heures, dates + cachets: data.cachets, + heures: data.heures, + dates_travail: data.dates_travail, + nombre_jours: data.nombre_jours, + + // Montant saisi et type + montant_saisi: data.montant_saisi, + type_calcul: data.type_calcul, + + // Résultats calculés + resultat_brut: data.resultat_brut, + resultat_net: data.resultat_net, + resultat_cost: data.resultat_cost, + + // Métadonnées + plafond_urssaf: data.plafond_urssaf, + timestamp: data.timestamp + }); + } + }; + + window.addEventListener('message', handleMessage); + return () => window.removeEventListener('message', handleMessage); +}, [posthog]); +``` window.addEventListener('message', handleMessage); return () => window.removeEventListener('message', handleMessage); @@ -136,7 +236,17 @@ Targeting: ### Dans la console du navigateur Lors d'un clic sur "Calculer", vous devriez voir : ``` -📊 PostHog: Événement simulateur_calculation envoyé { categorie: 'artiste', type: 'brut', montant: 2000 } +📊 PostHog: Événement simulateur_calculation enrichi envoyé { + categorie: 'artiste', + ccn: '1285 – Entreprises Artistiques & Culturelles (CCNEAC)', + type_calcul: 'brut', + montant_saisi: 2000, + resultats: { + brut: 2000, + net: 1580.5, + cost: 2456.8 + } +} ``` ### Dans PostHog @@ -168,27 +278,81 @@ Lors d'un clic sur "Calculer", vous devriez voir : ## Évolutions futures -### Idées d'améliorations -- Tracker également les modifications de paramètres (CCN, catégorie, etc.) -- Ajouter le temps passé sur le simulateur -- Tracker les exports PDF (si fonctionnalité ajoutée) -- Ajouter des événements pour les erreurs de validation +### Analyses possibles avec les données enrichies +Avec toutes ces propriétés, vous pouvez maintenant analyser : -### Autres métriques possibles -```javascript -// Exemple d'événements supplémentaires -posthog.capture('simulateur_ccn_changed', { ccn: '1285' }); -posthog.capture('simulateur_export_pdf', { format: 'pdf' }); -posthog.capture('simulateur_error', { error_type: 'invalid_amount' }); +- **Par CCN** : Quelle convention collective est la plus utilisée ? +- **Par catégorie** : Artiste vs Technicien - ratio d'utilisation +- **Par type de calcul** : Les utilisateurs partent-ils plutôt du brut, net ou coût ? +- **Abattement** : Combien d'utilisateurs utilisent l'abattement pour frais professionnels ? +- **Montants moyens** : Salaire brut moyen, net moyen, coût moyen par CCN +- **Heures et cachets** : Patterns d'utilisation (nombre moyen de cachets, heures par simulation) +- **Périodes** : Analyse temporelle des simulations (jours, semaines, mois) + +### Exemples de requêtes PostHog + +**Salaire brut moyen par CCN** : +```sql +SELECT + ccn_nom, + AVG(resultat_brut) as brut_moyen, + COUNT(*) as nb_simulations +FROM events +WHERE event = 'simulateur_calculation' +GROUP BY ccn_nom +ORDER BY nb_simulations DESC ``` +**Répartition Artiste vs Technicien** : +```sql +SELECT + categorie, + COUNT(*) as total, + AVG(resultat_brut) as brut_moyen +FROM events +WHERE event = 'simulateur_calculation' +GROUP BY categorie +``` + +**Taux d'utilisation de l'abattement** : +```sql +SELECT + abattement_active, + COUNT(*) as total, + (COUNT(*) * 100.0 / SUM(COUNT(*)) OVER ()) as pourcentage +FROM events +WHERE event = 'simulateur_calculation' + AND categorie = 'artiste' +GROUP BY abattement_active +``` + +### Idées d'améliorations futures +- Tracker les exports PDF (si fonctionnalité ajoutée) +- Ajouter le temps passé sur le simulateur +- Tracker les erreurs de validation +- Capturer le device type (mobile/desktop) +- Ajouter des événements pour les modifications de paramètres sans calcul + ## Notes techniques - ✅ Le simulateur est maintenant actif dans la sidebar -- ✅ Les événements PostHog sont trackés pour analyse +- ✅ Les événements PostHog sont trackés avec **toutes les données de simulation** - ✅ Compatible avec les surveys PostHog - ✅ Aucune dépendance externe supplémentaire requise - ✅ Fonctionne en mode production et développement +- ✅ **Données enrichies** : CCN, catégorie, statut, abattement, cachets, heures, dates, montants et résultats complets + +## Données capturées (résumé) +| Catégorie | Données | +|-----------|---------| +| **CCN** | ID et nom complet de la convention | +| **Profil** | Catégorie (artiste/technicien), statut (cadre/non-cadre) | +| **Abattement** | Activation et type de profession | +| **Volumes** | Cachets, heures, dates de travail, nombre de jours | +| **Calcul** | Montant saisi, type de calcul (brut/net/cost) | +| **Résultats** | Brut, Net avant PAS, Coût Total Employeur | +| **Contexte** | Plafond URSSAF, timestamp | ## Date de mise en place -15 octobre 2025 +- **Première version** : 15 octobre 2025 (tracking basique) +- **Version enrichie** : 16 octobre 2025 (toutes les données) diff --git a/app/(app)/simulateur/page.tsx b/app/(app)/simulateur/page.tsx index bcb76c3..7c84274 100644 --- a/app/(app)/simulateur/page.tsx +++ b/app/(app)/simulateur/page.tsx @@ -14,17 +14,49 @@ export default function SimulateurPage() { const handleMessage = (event: MessageEvent) => { // Vérifier que le message vient de notre iframe if (event.data?.type === 'simulateur_calculation') { - const { categorie, type, montant } = event.data.data; + const data = event.data.data; - // Envoyer l'événement à PostHog + // Envoyer l'événement enrichi à PostHog posthog?.capture('simulateur_calculation', { - categorie, - type_calcul: type, - montant, - timestamp: new Date().toISOString() + // Paramètres du formulaire + ccn_id: data.ccn_id, + ccn_nom: data.ccn_nom, + categorie: data.categorie, + statut: data.statut, + abattement_active: data.abattement_active, + abattement_profession: data.abattement_profession, + + // Cachets, heures, dates + cachets: data.cachets, + heures: data.heures, + dates_travail: data.dates_travail, + nombre_jours: data.nombre_jours, + + // Montant saisi et type + montant_saisi: data.montant_saisi, + type_calcul: data.type_calcul, + + // Résultats calculés + resultat_brut: data.resultat_brut, + resultat_net: data.resultat_net, + resultat_cost: data.resultat_cost, + + // Métadonnées + plafond_urssaf: data.plafond_urssaf, + timestamp: data.timestamp }); - console.log('📊 PostHog: Événement simulateur_calculation envoyé', { categorie, type, montant }); + console.log('📊 PostHog: Événement simulateur_calculation enrichi envoyé', { + categorie: data.categorie, + ccn: data.ccn_nom, + type_calcul: data.type_calcul, + montant_saisi: data.montant_saisi, + resultats: { + brut: data.resultat_brut, + net: data.resultat_net, + cost: data.resultat_cost + } + }); } }; diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 745efa6..8388b11 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -458,7 +458,7 @@ export default function Sidebar({ clientInfo, isStaff = false, mobile = false, o onNavigate && onNavigate()} className={`block px-3 py-2 rounded-xl text-sm transition truncate ${ isActivePath(pathname, "/simulateur") ? "bg-gradient-to-r from-indigo-200 via-purple-200 to-pink-200 text-slate-700 font-semibold" : "hover:bg-slate-50" diff --git a/public/simulateur-embed.html b/public/simulateur-embed.html index 2b69bc8..a603e1d 100644 --- a/public/simulateur-embed.html +++ b/public/simulateur-embed.html @@ -141,6 +141,9 @@ + +
+

Résultat de la simulation

@@ -314,6 +317,27 @@ function getPlafondUrssaf() { return PMSS * (diffDays / daysInMonth); } +// ===== IRC Tranche 1 Max (T1 cap, AGIRC-ARRCO) ===== +function getIrcT1Max(){ + // Mode intermittent ajusté : 12 × PMSS × (span / (daysInMonth × 0.95)), plafonné à 12 × PMSS + const dates = getSelectedDatesArray(); + if (!dates.length) return 0; + const first = dates[0], last = dates[dates.length-1]; + const diffDays = Math.round((last - first) / (1000*60*60*24)) + 1; + const y = first.getFullYear(); + const m = first.getMonth(); + const daysInMonth = new Date(y, m + 1, 0).getDate(); + const adjusted = PMSS * 12 * (diffDays / (daysInMonth * 0.95)); + return Math.min(adjusted, PMSS * 12); +} +// ===== Helper: split IRC base between T1 and T2 (avant abattement) ===== +function getIrcBasesBeforeAbattement(brut){ + const t1Max = getIrcT1Max(); + const baseT1 = Math.min(brut, Math.max(0, t1Max)); + const baseT2 = Math.max(0, brut - Math.max(0, t1Max)); + return { baseT1, baseT2 }; +} + /* ===== Compléments maladie / AF jour par jour ===== */ const HOURS_PER_CACHET_FOR_COMPLEMENT = 8; // pour les seuils const HOURS_PER_DAY_FOR_COMPLEMENT_IF_CACHET = 6.7; // forfait si cachet ce jour @@ -346,15 +370,40 @@ function getAssietteChomageMaxPourMoisCourant() { const m = dates[0].getMonth(); const daysInMonth = new Date(y, m + 1, 0).getDate(); + // Ajout : nombre de cachets et nbJoursChomage (max des deux) + const cachetsRaw = parseInt(document.getElementById('cachetsInput')?.value, 10); + const cachets = (isNaN(cachetsRaw) || cachetsRaw < 0 || isTechnicien()) ? 0 : cachetsRaw; + const nbJoursChomage = Math.max(nbJours, cachets); + // Règle attendue : // - < 5 jours : plafond chômage = 4 × plafond URSSAF de la période - // - ≥ 5 jours : plafond chômage = 4 × PMSS × (nbJours / daysInMonth) + // - ≥ 5 jours : plafond chômage = 4 × PMSS × (span en jours / daysInMonth) if (diffDays < 5) { const plafUrssaf = getPlafondUrssaf(); return 4 * plafUrssaf; } - return 4 * PMSS * (nbJours / daysInMonth); + return 4 * PMSS * (diffDays / daysInMonth); +} + +// === Debug panel helper for chômage assiette max +function getChomageDebugInfo() { + const dates = getSelectedDatesArray(); + if (!dates.length) return null; + const first = dates[0], last = dates[dates.length-1]; + const diffDays = Math.round((last - first) / (1000*60*60*24)) + 1; + const nbJours = dates.length; + const y = dates[0].getFullYear(); + const m = dates[0].getMonth(); + const daysInMonth = new Date(y, m + 1, 0).getDate(); + const cachetsRaw = parseInt(document.getElementById('cachetsInput')?.value, 10); + const cachets = (isNaN(cachetsRaw) || cachetsRaw < 0 || isTechnicien()) ? 0 : cachetsRaw; + const nbJoursChomage = Math.max(nbJours, cachets); + const plafondShort = 4 * getPlafondUrssaf(); + // Use diffDays for plafondLong as per new rule + const plafondLong = 4 * PMSS * (diffDays / daysInMonth); + const assiette = (diffDays < 5) ? plafondShort : plafondLong; + return { diffDays, nbJours, cachets, nbJoursChomage, daysInMonth, assiette, branch: (diffDays < 5) ? '<5 jours' : '>=5 jours', usedRatioDays: (diffDays < 5) ? null : diffDays }; } // Taux complémentaires selon catégorie @@ -478,6 +527,9 @@ function baseLibelle(code, cat){ "ags": "AGS intermittent", "retraite_t1": isTech ? "Retraite non-cadre Int. T1" : "Retraite artiste Tranche 1", "ceg_t1": isTech ? "CEG non-cadre Int. T1" : "CEG artiste intermittent Tranche 1", + "retraite_t2": isTech ? "Retraite non-cadre Int. T2" : "Retraite artiste Tranche 2", + "ceg_t2": isTech ? "CEG non-cadre Int. T2" : "CEG artiste intermittent Tranche 2", + "cet": isTech ? "CET non-cadre Int." : "CET artiste intermittent", "prevoyance_ta": isTech ? "Prévoyance non-cadre Int. TA" : "Prévoyance non-cadre Artiste Int. TA", "conges_spectacles": "Congés spectacles", "medecine_t1": isTech ? "Médecine du travail int. T1" : "Médecine du travail int.", @@ -514,19 +566,23 @@ function getProfileRates(cat){ } }; } - // Artiste = base existante + // Artiste return { sal: { contrib_solidarite: 0, maladie: 0, vieillesse: 0.28, alloc_fam: 0, at: 0, vieillesse_ta: 4.83, fnal_plaf: 0, maj_chomage: 0, chomage: 2.4, ags: 0, - ceg_t1: 0.86, retraite_t1: 4.44, prevoyance_ta: 0.12, conges_spectacles: 0, + ceg_t1: 0.86, retraite_t1: 4.44, retraite_t2: 10.79, ceg_t2: 1.08, + cet: 0.14, + prevoyance_ta: 0.12, conges_spectacles: 0, casc_svp: 0, fnas: 0, fcap: 0, medecine_t1: 0, cfpc_conv: 0, cfp_ta: 0, paritarisme: 0, csg_deductible: 6.8, csg_imposable: 2.4, rds: 0.5 }, emp: { contrib_solidarite: 0.3, maladie: 4.90, vieillesse: 1.41, alloc_fam: 2.42, at: 1.18, vieillesse_ta: 5.99, fnal_plaf: 0.07, maj_chomage: 0.5, chomage: 9.0, ags: 0.25, - ceg_t1: 1.29, retraite_t1: 4.45, prevoyance_ta: 1.04, conges_spectacles: 15.5, + ceg_t1: 1.29, retraite_t1: 4.45, retraite_t2: 10.80, ceg_t2: 1.62, + cet: 0.21, + prevoyance_ta: 1.04, conges_spectacles: 15.5, casc_svp: 0.4, fnas: 1.45, fcap: 0.25, medecine_t1: 0.32, cfpc_conv: 0.1, cfp_ta: 2.0, paritarisme: 0.016, csg_deductible: 0, csg_imposable: 0, rds: 0 } @@ -538,13 +594,20 @@ function getCotisations(){ const cc = document.getElementById('conventionSelect')?.value; const rates = getProfileRates(cat); - const codes = [ + let codes = [ "contrib_solidarite","maladie","vieillesse","alloc_fam","at", "vieillesse_ta","fnal_plaf","maj_chomage","chomage","ags", "retraite_t1","ceg_t1","prevoyance_ta","conges_spectacles", "medecine_t1","cfpc_conv","cfp_ta","paritarisme", "csg_deductible","csg_imposable","rds" ]; + // Pour ARTISTE, insérer T2 après ceg_t1, puis CET + if (cat === 'artiste') { + const idx = codes.indexOf("ceg_t1"); + if (idx !== -1) { + codes.splice(idx + 1, 0, "retraite_t2","ceg_t2","cet"); + } + } if (cc === "3090") codes.push("casc_svp"); // CCNSVP if (cc === "1285") { codes.push("fnas","fcap"); } // CCNEAC @@ -574,6 +637,47 @@ function getCotisations(){ return { sal, emp, cat }; } +/* ===== Somme des contributions employeur incluses dans l'assiette CSG/CRDS ===== */ +function employerContribsForCSGBase(brut){ + const { emp } = getCotisations(); + const dates = getSelectedDatesArray(); + if (!dates.length) return 0; + + const prevBase = getPrevoyanceBase(brut); + const assietteChomageMax = getAssietteChomageMaxPourMoisCourant(); + + let total = 0; + for (const e of emp){ + let amount = 0; + switch (e.code){ + // ✅ Inclure : chômage / maj. chômage / AGS sur leur assiette plafonnée + case 'chomage': + case 'maj_chomage': + case 'ags': { + const base = Math.min(brut, assietteChomageMax); + amount = base * (e.taux / 100); + break; + } + // ✅ Inclure : prévoyance TA (sur base plafonnée jour) + case 'prevoyance_ta': + amount = prevBase * (e.taux / 100); + break; + // ✅ Inclure : contributions conventionnelles simples + case 'cfpc_conv': + case 'paritarisme': + case 'apec': + amount = brut * (e.taux / 100); + break; + + // ❌ Exclure : IRC (AGIRC-ARRCO) T1/T2 (retraite & CEG), FNAL, maladie, vieillesse, AF, AT, etc. + default: + break; + } + total += amount; + } + return total; +} + /* ===== Prévoyance employeur ===== */ function prevoyanceEmployerAmount(brut) { const { emp } = getCotisations(); @@ -591,7 +695,8 @@ function calculateNet(brut, plafondUrssaf) { const nonAbattementCodes = [ "maj_chomage","chomage","ags","conges_spectacles", "csg_deductible","csg_imposable","rds", - "prevoyance_ta" // ✅ ne pas abattre la prévoyance + "prevoyance_ta", + "cet" ]; const assietteChomageMax = getAssietteChomageMaxPourMoisCourant(); @@ -610,7 +715,17 @@ const nonAbattementCodes = [ } else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) { base = getPrevoyanceBase(brut); } else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) { - base = (brut + prevEmp) * 0.9825; + const incEmp = employerContribsForCSGBase(brut); + base = (brut + incEmp) * 0.9825; + } else if (c.code === "retraite_t1" || c.code === "ceg_t1") { + const { baseT1 } = getIrcBasesBeforeAbattement(brut); + base = baseT1; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "retraite_t2" || c.code === "ceg_t2") { + const { baseT2 } = getIrcBasesBeforeAbattement(brut); + base = baseT2; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "cet") { + base = brut; + appliedAbattement = true; } else { base = brut; } @@ -632,7 +747,8 @@ function computeEmployerCharges(brut, cachets, heures, plafondUrssaf) { const nonAbattementCodes = [ "maj_chomage","chomage","ags","conges_spectacles", "csg_deductible","csg_imposable","rds", - "prevoyance_ta" // ✅ ne pas abattre la prévoyance + "prevoyance_ta", + "cet" ]; // Fusionne codes salariaux/patronaux pour calculer la part patronale @@ -664,7 +780,17 @@ const nonAbattementCodes = [ } else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) { base = getPrevoyanceBase(brut); } else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) { - base = (brut + prevEmp) * 0.9825; + const incEmp = employerContribsForCSGBase(brut); + base = (brut + incEmp) * 0.9825; + } else if (c.code === "retraite_t1" || c.code === "ceg_t1") { + const { baseT1 } = getIrcBasesBeforeAbattement(brut); + base = baseT1; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "retraite_t2" || c.code === "ceg_t2") { + const { baseT2 } = getIrcBasesBeforeAbattement(brut); + base = baseT2; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "cet") { + base = brut; + appliedAbattement = true; } else { base = brut; } @@ -864,7 +990,8 @@ function generateDetailTable(brut, cachets, heures, plafondUrssaf) { const nonAbattementCodes = [ "maj_chomage","chomage","ags","conges_spectacles", "csg_deductible","csg_imposable","rds", - "prevoyance_ta" // ✅ ne pas abattre la prévoyance + "prevoyance_ta", + "cet" ]; const { sal, emp, cat } = getCotisations(); const assietteChomageMax = getAssietteChomageMaxPourMoisCourant(); @@ -907,7 +1034,17 @@ const nonAbattementCodes = [ } else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) { base = getPrevoyanceBase(brut); } else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) { - base = (brut + prevEmp) * 0.9825; + const incEmp = employerContribsForCSGBase(brut); + base = (brut + incEmp) * 0.9825; + } else if (c.code === "retraite_t1" || c.code === "ceg_t1") { + const { baseT1 } = getIrcBasesBeforeAbattement(brut); + base = baseT1; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "retraite_t2" || c.code === "ceg_t2") { + const { baseT2 } = getIrcBasesBeforeAbattement(brut); + base = baseT2; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "cet") { + base = brut; + appliedAbattement = true; } else { base = brut; } @@ -983,7 +1120,8 @@ function buildContributionsStructured(brut, cachets, heures, plafondUrssaf){ const nonAbattementCodes = [ "maj_chomage","chomage","ags","conges_spectacles", "csg_deductible","csg_imposable","rds", - "prevoyance_ta" // ✅ ne pas abattre la prévoyance + "prevoyance_ta", + "cet" ]; const { sal, emp, cat } = getCotisations(); const assietteChomageMax = getAssietteChomageMaxPourMoisCourant(); @@ -1100,7 +1238,17 @@ if (fillonTotal > 0) { } else if (c.code === "prevoyance_ta" || (c.libelle && c.libelle.includes("Prévoyance"))) { base = getPrevoyanceBase(brut); } else if (["csg_deductible","csg_imposable","rds"].includes(c.code)) { - base = (brut + prevoyanceEmployerAmount(brut)) * 0.9825; + const incEmp = employerContribsForCSGBase(brut); + base = (brut + incEmp) * 0.9825; + } else if (c.code === "retraite_t1" || c.code === "ceg_t1") { + const { baseT1 } = getIrcBasesBeforeAbattement(brut); + base = baseT1; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "retraite_t2" || c.code === "ceg_t2") { + const { baseT2 } = getIrcBasesBeforeAbattement(brut); + base = baseT2; // abattement éventuel appliqué plus bas si autorisé + } else if (c.code === "cet") { + base = brut; + appliedAbattement = true; } else { base = brut; } @@ -1159,20 +1307,6 @@ document.getElementById('calcBtn').addEventListener('click', function() { return; } - // 📊 Envoyer un événement PostHog via postMessage au parent - try { - window.parent.postMessage({ - type: 'simulateur_calculation', - data: { - categorie: isTechnicien() ? 'technicien' : 'artiste', - type: document.querySelector('input[name="type"]:checked').value, - montant: montant - } - }, '*'); - } catch (e) { - console.error('Erreur lors de l\'envoi de l\'événement PostHog:', e); - } - const plafondUrssaf = getPlafondUrssaf(); const type = document.querySelector('input[name="type"]:checked').value; @@ -1191,6 +1325,119 @@ document.getElementById('calcBtn').addEventListener('click', function() { ({ net, costEmployer } = calculateSalaries(brut, cachets, heures, plafondUrssaf)); } + // 📊 Envoyer un événement PostHog enrichi via postMessage au parent + try { + // Récupération de toutes les données du formulaire + const ccnElement = document.getElementById('conventionSelect'); + const ccnValue = ccnElement?.value || ''; + const ccnText = ccnElement?.options[ccnElement.selectedIndex]?.text || ''; + + const categorieValue = isTechnicien() ? 'technicien' : 'artiste'; + const statutValue = document.getElementById('statutSelect')?.value || 'non-cadre'; + + // Abattement + const abattementChecked = document.querySelector('input[name="abattement"]:checked')?.value || 'non'; + const professionValue = document.getElementById('professionSelect')?.value || ''; + + // Dates + const datesInput = document.getElementById('datesInput')?.value || ''; + const datesArray = datesInput ? datesInput.split(',').map(d => d.trim()).filter(Boolean) : []; + + window.parent.postMessage({ + type: 'simulateur_calculation', + data: { + // Paramètres du formulaire + ccn_id: ccnValue, + ccn_nom: ccnText, + categorie: categorieValue, + statut: statutValue, + abattement_active: abattementChecked === 'oui', + abattement_profession: professionValue, + + // Cachets, heures, dates + cachets: cachets, + heures: heures, + dates_travail: datesArray, + nombre_jours: datesArray.length, + + // Montant saisi et type + montant_saisi: montant, + type_calcul: type, // 'brut', 'net', ou 'cost' + + // Résultats calculés + resultat_brut: parseFloat(brut.toFixed(2)), + resultat_net: parseFloat(net.toFixed(2)), + resultat_cost: parseFloat(costEmployer.toFixed(2)), + + // Métadonnées + plafond_urssaf: plafondUrssaf, + timestamp: new Date().toISOString() + } + }, '*'); + + console.log('📊 PostHog: Événement simulateur_calculation enrichi envoyé'); + } catch (e) { + console.error('Erreur lors de l\'envoi de l\'événement PostHog:', e); + } + + // —— Alerts & hard stop on extreme simulations —— + const alertsEl = document.getElementById('alerts'); + if (alertsEl) alertsEl.innerHTML = ''; + + // Hard stop > 100 000 € + if (brut > 100000) { + try { + swal({ + title: "Montant très élevé", + text: "Cette simulation dépasse le périmètre de l'outil. Merci de nous contacter pour confirmer ou valider la simulation.", + icon: "warning", + buttons: { + cancel: "Fermer", + support: { + text: "Contacter le support", + value: "support", + closeModal: true + } + } + }).then(value => { + if (value === "support") window.location.href = "/support"; + }); + } catch(e){ + // Fallback simple si SweetAlert indisponible + if (confirm("Montant très élevé. Ouvrir la page support ?")) window.location.href = "/support"; + } + // Stop calculation rendering + document.getElementById('result').innerHTML = ''; + document.getElementById('detailTable').innerHTML = ''; + return; + } + + // Alerte non bloquante > 10 000 € + if (brut > 10000) { + if (alertsEl) { + alertsEl.innerHTML = ` + `; + } + } + + // Ajout du debug chômage + const dbg = getChomageDebugInfo(); + const dbgHtml = dbg ? ` +
+
Debug chômage
+
Branche: ${dbg.branch} — Jours sélectionnés: ${dbg.nbJours}, Cachets: ${dbg.cachets}, Jours retenus chômage: ${dbg.nbJoursChomage}
+
Jours dans le mois: ${dbg.daysInMonth}, DiffDays (span): ${dbg.diffDays}
+
Assiette chômage max calculée: ${fmtEuro(dbg.assiette)}
+
` : ''; + const resultTable = ` @@ -1205,6 +1452,7 @@ document.getElementById('calcBtn').addEventListener('click', function() {

Plafond URSSAF : ${fmtEuro(plafondUrssaf)} €

+ ${dbgHtml} `; document.getElementById('result').innerHTML = resultTable; document.getElementById('detailTable').innerHTML = generateDetailTable(brut, cachets, heures, plafondUrssaf);