/* ── LIAModal : généré quand le lot passe en statut OPTION ── */
function LIAModal({lot,lotIdx,proj,fiche,lc,crm,onClose}) {
  const programmeNom = fiche.adresse||proj.ville||proj.nom;
  const clientNom    = lot.clientNom||'';
  const clientCRM    = lot.clientNom ? findCrmContact(crm, lot.clientNom.split(' ')) : null;
  const clientEmail  = clientCRM?.email||'';
  const foncierStr   = lc?.foncierReel > 0 ? fmtEur(lc.foncierReel) : (lc?.foncier ? fmtEur(lc.foncier) : '');
  const travauxStr   = lc?.travauxReel > 0 ? fmtEur(lc.travauxReel) : (lc?.travaux ? fmtEur(lc.travaux) : '');

  /* Fiche client complète dans le CRM (pour les champs LIA) */
  const clientFull = lot.clientNom
    ? (crm?.clients||[]).find(c=>{
        const terms=lot.clientNom.toLowerCase().split(/\s+/).filter(Boolean);
        return terms.some(t=>(c.nom||'').toLowerCase().includes(t)||(c.prenom||'').toLowerCase().includes(t));
      })
    : null;

  const clientNomPrenom = clientFull
    ? `${clientFull.prenom||''} ${clientFull.nom||''}`.trim()
    : clientNom;
  // Sprint Indivision (étape 3) : résolution du type d'acquisition et du co-acheteur.
  const typeAcq    = lot.typeAcquisition||'seul';
  const isCouple   = typeAcq==='couple';
  const isIndivision = typeAcq==='indivision';
  // Co-acheteur CRM (uniquement renseigné en indivision).
  const coAcheteur = isIndivision && lot.coAcheteurClientId
    ? (crm?.clients||[]).find(c=>c&&c.id===lot.coAcheteurClientId)
    : null;
  // Quote-parts : valeurs explicites uniquement en indivision, vides sinon.
  const qpPrincipal  = isIndivision ? Number(lot.quotePartPrincipalPct ?? 50) : null;
  const qpCoAcheteur = isIndivision && qpPrincipal!=null && !isNaN(qpPrincipal) ? (100 - qpPrincipal) : null;
  const fmtQp = (v) => (v!=null && !isNaN(v)) ? `${v}%` : '';

  const defaultBody =
`Madame, Monsieur${clientNom?' '+clientNom:''},\n\nNous avons le plaisir de confirmer votre intention d'acquisition du lot n\u00b0${lotIdx+1}${lot.type?' ('+lot.type+')':''}  au sein du programme ${programmeNom}.\n\nVous trouverez ci-joint notre Lettre d'Intention d'Achat. Nous vous prions de bien vouloir la compléter et nous la retourner signée dans les meilleurs délais.\n\nNous restons à votre disposition pour toute question.\n\nCordialement,`;

  const [pdfData,setPdfData]       = useState(null);
  const [pdfFilename,setPdfFn]     = useState('LIA_Lot_'+(lotIdx+1)+'.pdf');
  const [generating,setGenerating] = useState(false);
  const [genErr,setGenErr]         = useState('');
  const [emailTo,setEmailTo]       = useState(clientEmail);
  // Sprint Indivision (étape 3) : pré-remplir le CC avec l'email du co-acheteur en indivision.
  // Pour le couple, le conjointEmail du CRM principal sera dans le payload mais l'email LIA reste
  // historiquement adressé au principal uniquement.
  const [emailCc,setEmailCc]       = useState(isIndivision && coAcheteur?.email ? coAcheteur.email : '');
  const [emailSubject,setEmailSubj]= useState("Lettre d\u2019Intention d\u2019Achat \u2014 Lot "+(lotIdx+1)+" \u2014 "+programmeNom);
  const [emailBody,setEmailBody]   = useState(defaultBody);
  const [sending,setSending]       = useState(false);
  const [sent,setSent]             = useState(false);
  const [errMsg,setErrMsg]         = useState('');

  const inp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"6px 10px",fontSize:12,width:"100%"};

  const handleGeneratePdf = async () => {
    setGenerating(true); setGenErr('');
    try {
      const resp = await fetch('/api/lia/generate', {
        method:'POST', headers:{'Content-Type':'application/json'},
        body: JSON.stringify({
          // Programme / lot
          adresse:    programmeNom,
          type:       lot.type||'',
          numero:     String(lotIdx+1),
          surface:    lot.surface||'',
          programme:  programmeNom,
          foncier:    foncierStr,
          travaux:    travauxStr,
          // Acquéreur 1
          clientNomPrenom:       clientNomPrenom,
          clientAdresse:         formatAdresse(clientFull),
          clientTelephone:       clientFull?.telephone       || clientCRM?.telephone || '',
          clientEmail:           clientFull?.email           || clientEmail,
          clientDateNaissance:   clientFull?.dateNaissance   || '',
          clientLieuNaissance:   clientFull?.lieuNaissance   || '',
          clientProfession:      clientFull?.profession      || '',
          clientEtatMatrimonial: clientFull?.typeRelation    || '',
          regimeMatrimonial:     clientFull?.regimeMatrimonial || '',
          // Acquéreur 2 : couple → conjoint* du CRM principal ;
          //               indivision → 2e fiche client CRM (coAcheteur) ;
          //               seul → tout vide.
          conjointNomPrenom:     isCouple     ? `${clientFull?.conjointPrenom||''} ${clientFull?.conjointNom||''}`.trim()
                              :  isIndivision ? `${coAcheteur?.prenom||''} ${coAcheteur?.nom||''}`.trim()
                              :                 '',
          conjointAdresse:       isCouple     ? formatAdresse(clientFull)
                              :  isIndivision ? formatAdresse(coAcheteur)
                              :                 '',
          conjointTelephone:     isCouple     ? (clientFull?.conjointTelephone||'')
                              :  isIndivision ? (coAcheteur?.telephone||'')
                              :                 '',
          conjointEmail:         isCouple     ? (clientFull?.conjointEmail||'')
                              :  isIndivision ? (coAcheteur?.email||'')
                              :                 '',
          conjointDateNaissance: isCouple     ? (clientFull?.conjointDateNaissance||'')
                              :  isIndivision ? (coAcheteur?.dateNaissance||'')
                              :                 '',
          conjointLieuNaissance: isCouple     ? (clientFull?.conjointLieuNaissance||'')
                              :  isIndivision ? (coAcheteur?.lieuNaissance||'')
                              :                 '',
          conjointProfession:    isCouple     ? (clientFull?.conjointProfession||'')
                              :  isIndivision ? (coAcheteur?.profession||'')
                              :                 '',
          // État matrimonial : indivision → propre statut du co-acheteur ; couple → côté serveur fallback sur clientEtatMatrimonial.
          conjointEtatMatrimonial: isIndivision ? (coAcheteur?.typeRelation||'') : '',
          // Quote-parts (vides hors indivision)
          clientQuotePart:        fmtQp(qpPrincipal),
          conjointQuotePart:      fmtQp(qpCoAcheteur),
        }),
      });
      const data = await resp.json();
      if (!resp.ok) throw new Error(data.error||'Erreur génération PDF');
      setPdfData(data.pdf);
      setPdfFn(data.filename||('LIA_Lot_'+(lotIdx+1)+'.pdf'));
    } catch(e) { setGenErr(e.message); }
    setGenerating(false);
  };

  const handleDownload = () => {
    const a = document.createElement('a');
    a.href = 'data:application/pdf;base64,'+pdfData;
    a.download = pdfFilename;
    a.click();
  };

  const handleSend = async () => {
    setSending(true); setErrMsg('');
    try {
      await sendEmailApi({
        to: emailTo, cc: emailCc||undefined,
        subject: emailSubject, body: emailBody,
        attachment: pdfData ? { filename:pdfFilename, content:pdfData, contentType:'application/pdf' } : undefined,
      });
      setSent(true);
    } catch(e) { setErrMsg(e.message); }
    setSending(false);
  };

  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:720,maxWidth:"96vw",maxHeight:"92vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>📄 Lettre d&apos;Intention d&apos;Achat — Lot {lotIdx+1}</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:16}}>{clientNom?<><b>{clientNom}</b> · </>:null}{programmeNom}</div>

        {/* ① Génération PDF */}
        <div style={{background:"#f8fafc",borderRadius:10,padding:16,border:"1px solid #e2e8f0",marginBottom:14}}>
          <div style={{fontWeight:700,fontSize:13,color:"#334155",marginBottom:10}}>① Document PDF (pièce jointe)</div>
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:8,marginBottom:12,fontSize:12,color:"#475569"}}>
            <div><span style={{color:"#94a3b8"}}>Programme : </span>{programmeNom||'—'}</div>
            <div><span style={{color:"#94a3b8"}}>Lot : </span>n°{lotIdx+1}{lot.type?' ('+lot.type+')':''}</div>
            <div><span style={{color:"#94a3b8"}}>Surface : </span>{lot.surface||'—'} m²</div>
            <div><span style={{color:"#94a3b8"}}>Foncier : </span>{foncierStr||'—'}</div>
            <div><span style={{color:"#94a3b8"}}>Travaux : </span>{travauxStr||'—'}</div>
          </div>
          {!pdfData ? (
            <button onClick={handleGeneratePdf} disabled={generating}
              style={{background:generating?"#94a3b8":"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 20px",fontSize:13,fontWeight:700,cursor:generating?"default":"pointer"}}>
              {generating?"⏳ Génération en cours…":"🔄 Générer le PDF"}
            </button>
          ) : (
            <div style={{display:"flex",alignItems:"center",gap:10,flexWrap:"wrap"}}>
              <span style={{background:"#dcfce7",color:"#15803d",borderRadius:6,padding:"5px 12px",fontSize:12,fontWeight:700}}>✅ {pdfFilename}</span>
              <button onClick={handleDownload} style={{background:"#475569",color:"#fff",border:"none",borderRadius:8,padding:"6px 14px",fontSize:12,cursor:"pointer"}}>⬇ Télécharger</button>
              <button onClick={()=>{setPdfData(null);setGenErr('');}} style={{background:"none",border:"1px solid #e2e8f0",borderRadius:8,padding:"6px 14px",fontSize:12,cursor:"pointer",color:"#64748b"}}>↺ Régénérer</button>
            </div>
          )}
          {genErr&&<div style={{color:"#dc2626",fontSize:12,marginTop:8,background:"#fee2e2",borderRadius:6,padding:"6px 10px"}}>❌ {genErr}</div>}
          {!pdfData&&<div style={{fontSize:11,color:"#f59e0b",marginTop:8}}>💡 Nécessite LibreOffice sur le VPS — <code>sudo apt install -y libreoffice</code></div>}
        </div>

        {/* ② Email */}
        <div style={{background:"#f8fafc",borderRadius:10,padding:16,border:"1px solid #e2e8f0",marginBottom:14}}>
          <div style={{fontWeight:700,fontSize:13,color:"#334155",marginBottom:10}}>② Email au client</div>
          {!clientEmail&&<div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#92400e"}}>⚠️ Email du client non trouvé dans le CRM. Vérifiez que le nom du lot correspond à une fiche CRM.</div>}
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8,marginBottom:8}}>
            <div>
              <div style={{fontSize:11,color:"#64748b",marginBottom:3}}>Destinataire (À)</div>
              <input value={emailTo} onChange={e=>setEmailTo(e.target.value)} style={inp} placeholder="email@client.fr"/>
            </div>
            <div>
              <div style={{fontSize:11,color:"#64748b",marginBottom:3}}>Copie (Cc)</div>
              <input value={emailCc} onChange={e=>setEmailCc(e.target.value)} style={inp} placeholder="Optionnel"/>
            </div>
          </div>
          <div style={{marginBottom:8}}>
            <div style={{fontSize:11,color:"#64748b",marginBottom:3}}>Objet</div>
            <input value={emailSubject} onChange={e=>setEmailSubj(e.target.value)} style={inp}/>
          </div>
          <div>
            <div style={{fontSize:11,color:"#64748b",marginBottom:3}}>Corps du message{pdfData?' (le PDF est en pièce jointe)':''}</div>
            <textarea value={emailBody} onChange={e=>setEmailBody(e.target.value)} style={{...inp,height:160,resize:"vertical",lineHeight:1.5}}/>
          </div>
          {pdfData&&<div style={{fontSize:11,color:"#15803d",marginTop:6}}>📎 Pièce jointe : {pdfFilename}</div>}
        </div>

        {errMsg&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#dc2626"}}>❌ {errMsg}</div>}
        {sent&&<div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#15803d"}}>✅ Email envoyé avec succès {pdfData?"(avec PDF en pièce jointe)":""}!</div>}

        <div style={{display:"flex",gap:8,justifyContent:"flex-end",flexWrap:"wrap"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>Fermer</button>
          {!sent&&(
            <button onClick={handleSend} disabled={sending||!emailTo}
              style={{background:sending||!emailTo?"#94a3b8":"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:sending||!emailTo?"default":"pointer"}}>
              {sending?"⏳ Envoi…":pdfData?"📧 Envoyer avec PDF":"📧 Envoyer sans PDF"}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

/* ── NotaireEmailModal : généré quand le lot passe en statut LIA ── */
function NotaireEmailModal({lot,lotIdx,proj,fiche,lc,crm,onClose}) {
  const [sending,setSending]=useState(false);
  const [sent,setSent]=useState(false);
  const [errMsg,setErrMsg]=useState('');
  const [copied,setCopied]=useState(false);
  const programmeNom=fiche.adresse||proj.ville||proj.nom;
  const clientNom=lot.clientNom||'[Nom du client]';
  const notaire=findCrmContact(crm,['gagneur','laure']);
  const clerque=findCrmContact(crm,['monteiro','andrea']);
  const toEmails=[notaire?.email,clerque?.email].filter(Boolean).join(', ');
  const toNames=[notaire?.nom||'Maître Laure Gagneur',clerque?.nom||'Andrea Monteiro'].join(', ');
  const prixVente=lc?.prixFinal?fmtEur(lc.prixFinal):'[prix à confirmer]';
  const emailBody=
`Objet : Dossier compromis — ${programmeNom} — Lot ${lotIdx+1}

Madame Gagneur, Madame Monteiro,

Nous avons le plaisir de vous informer qu'une Lettre d'Intention d'Achat a été acceptée pour le lot n°${lotIdx+1} du programme ${programmeNom}.

Nous vous serions reconnaissants de bien vouloir prendre en charge la rédaction du compromis de vente et de nous recontacter pour la suite de la procédure.

INFORMATIONS DU LOT :
─────────────────────────────────────────
  Programme  : ${programmeNom}
  Lot N°     : ${lotIdx+1}${lot.type?` (${lot.type})`:''}
  Surface    : ${lot.surface||'—'} m²
  Prix TTC   : ${prixVente}
  Acquéreur  : ${clientNom}
─────────────────────────────────────────

Nous restons à votre disposition pour vous fournir tout document complémentaire.

Cordialement,`;
  const sinp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"6px 10px",fontSize:11,width:"100%"};
  const handleCopy=()=>{navigator.clipboard.writeText(emailBody).then(()=>{setCopied(true);setTimeout(()=>setCopied(false),2500);});};
  const handleSend=async()=>{
    setSending(true);setErrMsg('');
    try{await sendEmailApi({to:toEmails,subject:`Dossier compromis — ${programmeNom} — Lot ${lotIdx+1}`,body:emailBody});setSent(true);}
    catch(e){setErrMsg(e.message);}
    setSending(false);
  };
  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:640,maxWidth:"96vw",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>⚖️ Email Notaire — Lot {lotIdx+1} passé sous LIA</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:14}}><b>{clientNom}</b> · {programmeNom}</div>
        {toEmails?(
          <div style={{background:"#f0fdf4",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12}}>
            <span style={{color:"#15803d",fontWeight:700}}>Destinataires : </span>
            <span style={{fontFamily:"monospace"}}>{toEmails}</span>
            <span style={{color:"#64748b",marginLeft:8}}>({toNames})</span>
          </div>
        ):(
          <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12,color:"#92400e"}}>
            ⚠️ Emails des notaires non trouvés dans le CRM. Recherchez "Gagneur" et "Monteiro" dans le CRM.
          </div>
        )}
        <div style={{marginBottom:14}}>
          <div style={{fontSize:11,color:"#64748b",marginBottom:4}}>Email à envoyer au notaire et à sa clerc</div>
          <textarea value={emailBody} readOnly style={{...sinp,height:260,fontFamily:"'Courier New',monospace",resize:"vertical",lineHeight:1.5}}/>
        </div>
        {errMsg&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#dc2626"}}>❌ {errMsg}</div>}
        {sent&&<div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#15803d"}}>✅ Email envoyé aux notaires !</div>}
        <div style={{display:"flex",gap:8,justifyContent:"flex-end",flexWrap:"wrap"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>Fermer</button>
          <button onClick={handleCopy} style={{background:copied?"#16a34a":"#475569",color:"#fff",border:"none",borderRadius:8,padding:"8px 18px",fontSize:13,fontWeight:600,cursor:"pointer"}}>{copied?"✓ Copié !":"📋 Copier"}</button>
          {toEmails&&!sent&&<button onClick={handleSend} disabled={sending} style={{background:sending?"#94a3b8":"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:sending?"default":"pointer"}}>{sending?"⏳ Envoi...":"📧 Envoyer aux notaires"}</button>}
        </div>
      </div>
    </div>
  );
}

/* ── CompromisEmailModal : email de courtoisie au passage COMPROMIS ── */
function CompromisEmailModal({lot,lotIdx,proj,fiche,crm,onClose}) {
  const [sending,setSending]=useState(false);
  const [sent,setSent]=useState(false);
  const [errMsg,setErrMsg]=useState('');
  const [fileData,setFileData]=useState(null); // {filename, contentType, content (base64)}
  const [fileErr,setFileErr]=useState('');

  const programmeNom=fiche.adresse||proj.ville||proj.nom;
  const clientNom=lot&&lot.clientNom||'[Nom du client]';

  // Résolution destinataires depuis CRM
  const client=crm&&lot&&(crm.clients||[]).find(c=>c.id===lot.clientId);
  const cgp=crm&&lot&&(crm.cgps||[]).find(c=>c.id===lot.cgpId);
  const clientEmail=client&&client.email||'';
  // Sprint Indivision (étape 2) : compromis email → copie conjoint si type couple.
  const conjointEmail=lot&&((lot.typeAcquisition||'seul')==='couple')&&client&&client.conjointEmail||'';
  const cgpEmails=cgp&&cgp.emails?cgp.emails.split(/[,;\s]+/).map(e=>e.trim()).filter(Boolean):[];

  // To = client + conjoint si achat en couple
  const toEmails=[clientEmail,conjointEmail].filter(Boolean).join(', ');
  // CC = CGP
  const ccEmails=cgpEmails.join(', ');

  const [to,  setTo ]=useState(toEmails);
  const [cc,  setCc ]=useState(ccEmails);
  const [subj,setSubj]=useState(`Votre compromis de vente — ${programmeNom} — Lot ${lotIdx+1}`);
  const [body,setBody]=useState(
`${clientNom},

Nous avons le plaisir de vous confirmer la signature de votre compromis de vente pour le lot n°${lotIdx+1} du programme ${programmeNom}.

Vous trouverez ci-joint l'exemplaire signé pour vos archives.

La prochaine étape est la signature de l'acte authentique chez le notaire, dont nous vous communiquerons les modalités en temps voulu.

Nous restons à votre disposition pour toute question.

Cordialement,`
  );

  const sinp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"6px 10px",fontSize:12,width:"100%"};

  const handleFile=async(e)=>{
    const file=e.target.files&&e.target.files[0];
    if(!file){setFileData(null);return;}
    if(file.size>10*1024*1024){setFileErr('Fichier trop volumineux (max 10 Mo)');setFileData(null);return;}
    setFileErr('');
    const b64=await new Promise((resolve,reject)=>{
      const reader=new FileReader();
      reader.onloadend=()=>resolve(reader.result.split(',')[1]);
      reader.onerror=reject;
      reader.readAsDataURL(file);
    });
    setFileData({filename:file.name,contentType:file.type||'application/pdf',content:b64});
  };

  const handleSend=async()=>{
    if(!to.trim()){alert('Le destinataire (À) est requis.');return;}
    setSending(true);setErrMsg('');
    try{
      const payload={to:to.trim(),subject:subj.trim(),body:body.trim()};
      if(cc.trim()) payload.cc=cc.trim();
      if(fileData) payload.attachment=fileData;
      await sendEmailApi(payload);
      setSent(true);
    }catch(e){setErrMsg(e.message);}
    setSending(false);
  };

  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}}
      onClick={e=>{if(e.target===e.currentTarget&&!sending)onClose();}}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:660,maxWidth:"96vw",maxHeight:"92vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}}
        onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>📄 Email de courtoisie — Compromis signé</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:14}}><b>{clientNom}</b> · {programmeNom} · Lot {lotIdx+1}</div>

        {!clientEmail&&(
          <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12,color:"#92400e"}}>
            ⚠️ Email client non trouvé dans le CRM — vérifiez la fiche client.
          </div>
        )}

        <div style={{display:"grid",gap:10,marginBottom:14}}>
          <div>
            <label style={{fontSize:11,fontWeight:700,color:"#64748b",display:"block",marginBottom:3}}>À *</label>
            <input value={to} onChange={e=>setTo(e.target.value)} style={sinp} placeholder="email@client.fr"/>
          </div>
          <div>
            <label style={{fontSize:11,fontWeight:700,color:"#64748b",display:"block",marginBottom:3}}>CC (CGP)</label>
            <input value={cc} onChange={e=>setCc(e.target.value)} style={sinp} placeholder="cgp@exemple.fr"/>
          </div>
          <div>
            <label style={{fontSize:11,fontWeight:700,color:"#64748b",display:"block",marginBottom:3}}>Sujet</label>
            <input value={subj} onChange={e=>setSubj(e.target.value)} style={sinp}/>
          </div>
          <div>
            <label style={{fontSize:11,fontWeight:700,color:"#64748b",display:"block",marginBottom:3}}>Corps du message</label>
            <textarea value={body} onChange={e=>setBody(e.target.value)} rows={9}
              style={{...sinp,resize:"vertical",fontFamily:"inherit",lineHeight:1.55}}/>
          </div>
          <div>
            <label style={{fontSize:11,fontWeight:700,color:"#64748b",display:"block",marginBottom:3}}>Compromis signé (PJ)</label>
            <input type="file" accept=".pdf,.doc,.docx" onChange={handleFile}
              style={{fontSize:12,color:"#1e293b"}}/>
            {fileErr&&<div style={{color:"#dc2626",fontSize:11,marginTop:3}}>{fileErr}</div>}
            {fileData&&<div style={{color:"#16a34a",fontSize:11,marginTop:3}}>📎 {fileData.filename}</div>}
            {!fileData&&<div style={{color:"#94a3b8",fontSize:11,marginTop:3}}>Aucun fichier sélectionné — l'email sera envoyé sans pièce jointe</div>}
          </div>
        </div>

        {errMsg&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#dc2626"}}>❌ {errMsg}</div>}
        {sent&&<div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#15803d"}}>✅ Email envoyé au client !</div>}

        <div style={{display:"flex",gap:8,justifyContent:"flex-end",flexWrap:"wrap"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>
            {sent?"Fermer":"Passer sans envoyer"}
          </button>
          {!sent&&(
            <button onClick={handleSend} disabled={sending||!to.trim()}
              style={{background:sending||!to.trim()?"#94a3b8":"#7c3aed",color:"#fff",border:"none",borderRadius:8,
                padding:"8px 22px",fontSize:13,fontWeight:700,cursor:sending?"default":"pointer"}}>
              {sending?"⏳ Envoi…":"📧 Envoyer"}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

/* ── ArchitecteEmailModal : demande d'attestation d'avancement ── */
const AV_PCT={AV10:10,AV40:40,AV70:70,AV95:95};
function ArchitecteEmailModal({lot,lotIdx,proj,fiche,crm,statut,callIdx,projId,onClose,onAttestationSent}) {
  const [sending,setSending]=useState(false);
  const [sent,setSent]=useState(false);
  const [errMsg,setErrMsg]=useState('');
  const [copied,setCopied]=useState(false);
  const programmeNom=fiche.adresse||proj.ville||proj.nom;
  const clientNom=lot.clientNom||'[Nom du client]';
  const pct=AV_PCT[statut]||0;
  const ref=`LOT${lotIdx+1}-${(proj.ville||proj.nom).replace(/\s+/g,'-').toUpperCase().slice(0,15)}-AV${pct}`;
  const architecte=findCrmContact(crm,['karoubi','yael']);
  const archEmail=architecte?.email||'';
  const archNom=architecte?.nom||'Madame Karoubi';
  const emailBody=
`Objet : Demande attestation avancement ${pct}% — ${programmeNom} — Lot ${lotIdx+1} [Réf: ${ref}]

${archNom},

Dans le cadre du programme ${programmeNom}, nous vous prions de bien vouloir nous faire parvenir une attestation d'avancement des travaux correspondant à un taux d'avancement de ${pct}% pour le lot n°${lotIdx+1}.

Cette attestation nous permettra de procéder à l'appel de fonds correspondant auprès de l'acquéreur.

INFORMATIONS :
─────────────────────────────────────────
  Programme   : ${programmeNom}
  Lot N°      : ${lotIdx+1}${lot.type?` (${lot.type})`:''}
  Acquéreur   : ${clientNom}
  Avancement  : ${pct}%
  Référence   : ${ref}
─────────────────────────────────────────

Nous vous remercions de nous transmettre ce document dans les meilleurs délais.

Cordialement,`;
  const sinp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"6px 10px",fontSize:11,width:"100%"};
  const handleCopy=()=>{navigator.clipboard.writeText(emailBody).then(()=>{setCopied(true);setTimeout(()=>setCopied(false),2500);});};
  const handleSend=async()=>{
    setSending(true);setErrMsg('');
    try{
      await sendEmailApi({to:archEmail,subject:`Demande attestation ${pct}% — ${programmeNom} — Lot ${lotIdx+1} [${ref}]`,body:emailBody});
      setSent(true);
      if(onAttestationSent) onAttestationSent({projId,lotIdx,statut,callIdx,ref,clientNom,programmeNom,pct});
    }catch(e){setErrMsg(e.message);}
    setSending(false);
  };
  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:640,maxWidth:"96vw",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>🏗️ Demande d'attestation — Lot {lotIdx+1} — {pct}% d'avancement</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:4}}><b>{clientNom}</b> · {programmeNom}</div>
        <div style={{fontSize:11,color:"#94a3b8",background:"#f0f9ff",borderRadius:6,padding:"6px 10px",marginBottom:14,border:"1px solid #bfdbfe"}}>
          Étape 1/2 — Demande d'attestation à l'architecte. L'appel de fonds client sera déclenché après réception de sa réponse.
        </div>
        {archEmail?(
          <div style={{background:"#f0fdf4",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12}}>
            <span style={{color:"#15803d",fontWeight:700}}>Architecte : </span>
            <span style={{fontFamily:"monospace"}}>{archEmail}</span>
            <span style={{color:"#64748b",marginLeft:8}}>({archNom})</span>
          </div>
        ):(
          <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12,color:"#92400e"}}>
            ⚠️ Email de l'architecte non trouvé dans le CRM. Recherchez "Karoubi" dans le CRM.
          </div>
        )}
        <div style={{marginBottom:14}}>
          <div style={{fontSize:11,color:"#64748b",marginBottom:4}}>Email à l'architecte — référence <b>{ref}</b></div>
          <textarea value={emailBody} readOnly style={{...sinp,height:280,fontFamily:"'Courier New',monospace",resize:"vertical",lineHeight:1.5}}/>
        </div>
        {sent&&(
          <div style={{background:"#dcfce7",border:"1px solid #86efac",borderRadius:8,padding:"10px 12px",marginBottom:12,fontSize:12,color:"#15803d"}}>
            ✅ Email envoyé. L'application surveillera la réponse de l'architecte (bouton 🔔 en haut) et vous proposera d'envoyer l'appel de fonds au client dès réception.
          </div>
        )}
        {errMsg&&<div style={{background:"#fee2e2",border:"1px solid #fca5a5",borderRadius:8,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#dc2626"}}>❌ {errMsg}</div>}
        <div style={{display:"flex",gap:8,justifyContent:"flex-end",flexWrap:"wrap"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>{sent?"Fermer":"Annuler"}</button>
          <button onClick={handleCopy} style={{background:copied?"#16a34a":"#475569",color:"#fff",border:"none",borderRadius:8,padding:"8px 18px",fontSize:13,fontWeight:600,cursor:"pointer"}}>{copied?"✓ Copié !":"📋 Copier"}</button>
          {!sent&&<button onClick={handleSend} disabled={sending} style={{background:sending?"#94a3b8":"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:sending?"default":"pointer"}}>{sending?"⏳ Envoi...":"📧 Envoyer à l'architecte"}</button>}
        </div>
      </div>
    </div>
  );
}

/* ── AppelFondsModal ── */
const APPEL_STAGE_LABELS=["Foncier (vente)","Fondations (10%)","Élévation des murs (30%)","Hors d'eau (30%)","Hors d'air (25%)","Livraison (5%)"];
function AppelFondsModal({lot,lotIdx,proj,fiche,lc,rows,values,crm,callIdxOverride,onClose}) {
  const [copied,setCopied]=useState(false);
  const [selIban,setSelIban]=useState("");

  // Flux du lot dans le Plan Hebdo, triés par date
  const lotRow=rows.find(r=>r.projetId===proj.id&&r.label===`Lot ${lotIdx+1}`);
  const lotVals=useMemo(()=>lotRow
    ?[...values.filter(v=>v.rowId===lotRow.id)].sort((a,b)=>a.weekIso.localeCompare(b.weekIso))
    :[]
  ,[rows,values,lotRow]);

  // callIdx : soit imposé par le statut (callIdxOverride), soit calculé depuis les dates
  const nextIdx=lotVals.findIndex(v=>v.weekIso>=todayIso);
  const callIdx=callIdxOverride!==undefined?callIdxOverride:(nextIdx>=0?nextIdx:Math.max(0,lotVals.length-1));
  const targetVal=lotVals[callIdx]||null;
  const callNum=callIdx+1;
  const totalCalls=lotVals.length||APPEL_STAGE_LABELS.length;
  const stageLabel=APPEL_STAGE_LABELS[callIdx]||`Appel n°${callNum}`;

  const dateStr=targetVal
    ?new Date(targetVal.weekIso+"T12:00:00Z").toLocaleDateString("fr-FR",{day:"numeric",month:"long",year:"numeric"})
    :"—";
  const montantStr=targetVal?fmtEur(Math.abs(targetVal.montant)):"—";
  const clientNom=lot.clientNom||"[Nom du client]";
  const programmeNom=fiche.adresse||(proj.ville||proj.nom);

  // IBAN : priorité à l'IBAN saisi dans la fiche programme, sinon sélecteur CRM
  const progIban=fiche.ibanProgramme||"";
  const ibanLine=progIban||selIban||"[IBAN À COMPLÉTER]";
  const refLine=`LOT${lotIdx+1}-${(proj.ville||proj.nom).replace(/\s+/g,"-").toUpperCase().slice(0,20)}-APPEL${callNum}`;

  // Collecte des IBANs CRM (affiché uniquement si pas d'IBAN programme)
  const allIbans=useMemo(()=>{
    if(progIban) return [];
    const list=[];
    ((crm||{}).entreprises||[]).forEach(e=>{
      if(e.iban) list.push({label:`${e.nom||"?"} — ${e.iban}`,iban:e.iban});
      (e.comptesBancaires||[]).forEach(cb=>{if(cb.iban) list.push({label:`${e.nom||"?"} — ${cb.libelle||cb.banque||"?"} — ${cb.iban}`,iban:cb.iban});});
    });
    return list;
  },[crm,progIban]);

  const emailBody=
`Objet : Appel de fonds n°${callNum}/${totalCalls} — Lot ${lotIdx+1} — ${proj.ville||proj.nom}

Madame, Monsieur ${clientNom},

Nous vous contactons dans le cadre de votre acquisition du lot n°${lotIdx+1}${lot.type?` (${lot.type})`:""}${programmeNom?" au sein du programme " + programmeNom:""}.

Conformément à l'échéancier contractuel, nous vous prions de bien vouloir procéder au règlement de l'appel de fonds suivant :

────────────────────────────────────────
  APPEL DE FONDS N°${callNum} / ${totalCalls}
  Stade        : ${stageLabel}
  Montant TTC  : ${montantStr}
  Échéance     : ${dateStr}
────────────────────────────────────────

COORDONNÉES BANCAIRES
  Bénéficiaire : ${proj.ville||proj.nom}
  IBAN         : ${ibanLine}
  Référence    : ${refLine}

Nous vous remercions de bien vouloir indiquer la référence ci-dessus dans le libellé du virement afin de faciliter le traitement de votre règlement.

Nous restons à votre disposition pour toute question.

Cordialement,`;

  const handleCopy=()=>{
    navigator.clipboard.writeText(emailBody).then(()=>{setCopied(true);setTimeout(()=>setCopied(false),2500);});
  };
  const sinp={background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"6px 10px",fontSize:12,width:"100%"};

  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center",padding:16}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:16,padding:28,width:640,maxWidth:"96vw",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:17,marginBottom:4}}>📧 Appel de fonds — Lot {lotIdx+1}</div>
        <div style={{fontSize:12,color:"#64748b",marginBottom:4}}>
          <b>{clientNom}</b> · {proj.ville||proj.nom}
          {fiche.adresse&&<span style={{marginLeft:6,color:"#94a3b8"}}>{fiche.adresse}</span>}
        </div>
        {/* Badge stade en cours */}
        <div style={{marginBottom:14}}>
          <span style={{background:"#dbeafe",color:"#1d4ed8",borderRadius:6,padding:"3px 10px",fontSize:12,fontWeight:700}}>
            {stageLabel}
          </span>
          {callIdxOverride!==undefined&&<span style={{fontSize:11,color:"#64748b",marginLeft:8}}>déclenché par changement de statut</span>}
        </div>

        {/* Échéancier */}
        {lotVals.length>0?(
          <div style={{background:"#f8fafc",borderRadius:8,border:"1px solid #e2e8f0",padding:12,marginBottom:14}}>
            <div style={{fontSize:11,fontWeight:700,color:"#334155",marginBottom:8}}>Échéancier du lot ({totalCalls} appels)</div>
            <div style={{display:"flex",gap:5,flexWrap:"wrap"}}>
              {lotVals.map((v,i)=>{
                const isPast=v.weekIso<todayIso;
                const isNext=i===callIdx;
                return (
                  <div key={v.weekIso} style={{background:isNext?"#dbeafe":isPast?"#dcfce7":"#f1f5f9",color:isNext?"#1d4ed8":isPast?"#15803d":"#64748b",borderRadius:6,padding:"4px 8px",fontSize:10,fontWeight:isNext?700:400,border:isNext?"2px solid #3b82f6":"1px solid transparent"}}>
                    {APPEL_STAGE_LABELS[i]||`n°${i+1}`}<br/>
                    <span style={{fontWeight:700}}>{fmtEur(Math.abs(v.montant),true)}</span> · {fmtW(v.weekIso)}
                    {isPast&&!isNext&&<span style={{marginLeft:4}}>✓</span>}
                  </div>
                );
              })}
            </div>
          </div>
        ):(
          <div style={{background:"#fef9c3",border:"1px solid #fde68a",borderRadius:8,padding:10,marginBottom:14,fontSize:12,color:"#92400e"}}>
            ⚠️ Aucun flux trouvé dans le Plan Hebdo pour ce lot. Générez les flux prévisionnels depuis la fiche programme.
          </div>
        )}

        {/* IBAN : affichage ou sélecteur CRM */}
        {progIban?(
          <div style={{background:"#f0fdf4",border:"1px solid #86efac",borderRadius:8,padding:"8px 12px",marginBottom:12,fontSize:12}}>
            <span style={{color:"#15803d",fontWeight:700}}>IBAN du programme :</span>
            <span style={{marginLeft:8,fontFamily:"monospace",color:"#1e293b"}}>{progIban}</span>
          </div>
        ):allIbans.length>0&&(
          <div style={{marginBottom:12}}>
            <div style={{fontSize:11,color:"#64748b",marginBottom:4}}>Compte bancaire bénéficiaire (depuis le CRM)</div>
            <select value={selIban} onChange={e=>setSelIban(e.target.value)} style={sinp}>
              <option value="">— Ou saisir manuellement dans l'email —</option>
              {allIbans.map((a,i)=><option key={i} value={a.iban}>{a.label}</option>)}
            </select>
          </div>
        )}

        {/* Email généré */}
        <div style={{marginBottom:14}}>
          <div style={{fontSize:11,color:"#64748b",marginBottom:4}}>Email généré — copiez et collez dans votre client mail</div>
          <textarea value={emailBody} readOnly
            style={{...sinp,height:280,fontFamily:"'Courier New',monospace",fontSize:11,resize:"vertical",lineHeight:1.5}}/>
        </div>

        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 18px",fontSize:13,cursor:"pointer"}}>Fermer</button>
          <button onClick={handleCopy}
            style={{background:copied?"#16a34a":"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 22px",fontSize:13,fontWeight:700,cursor:"pointer",transition:"background .2s"}}>
            {copied?"✓ Copié !":"📋 Copier l'email"}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── FactureReglerModal ── */
// Modal pour intégrer une facture confirmée dans le Plan Hebdo et la marquer Réglée.
function FactureReglerModal({notifId, factureData, proposedAction, projects, rows, fiche, allValues, onSave, onClose}) {
  // facture_data est stocké sous forme de tableau (1 entrée par facture) par
  // lib/invoiceExtract.js. Selon le point d'appel, on reçoit ici soit le tableau
  // brut (bouton « Régler » facture unique), soit déjà un objet (cas multi-facture
  // qui passe facture_data:facture). On normalise pour toujours travailler sur un
  // objet facture, sinon les lectures factureData?.montant_ttc tombent sur le
  // tableau et renvoient undefined → modale vide.
  const fdObj = Array.isArray(factureData) ? (factureData[0] || null) : (factureData || null);
  // Initialisation depuis proposed_action si disponible, sinon valeurs vides
  const pa = proposedAction?.type==="create_flux" ? proposedAction.params : null;
  const [projId,     setProjId]     = useState(pa?.programmeId||"");
  const [rowId,      setRowId]      = useState(pa?.rowId||"");
  const [weekIso,    setWeekIso]    = useState(todayIso);
  const [montant,    setMontant]    = useState(
    fdObj?.montant_ttc ? String(-Math.abs(fdObj.montant_ttc))
    : pa?.montant ? String(-Math.abs(pa.montant)) : ""
  );
  const [redistWeek, setRedistWeek] = useState("");
  const [saving,     setSaving]     = useState(false);

  const selStyle = {width:"100%",padding:"6px 8px",border:"1px solid #e2e8f0",borderRadius:6,fontSize:13,background:"#f8fafc"};
  const projRows = rows.filter(r=>r.projetId===projId);

  // Calcul des flux postérieurs à la semaine choisie sur la ligne sélectionnée
  const parsedMont = parseFloat(String(montant).replace(",",".").replace(/\s/g,""));
  const montVal = isNaN(parsedMont) ? null : parsedMont;
  const futureVals = useMemo(()=>{
    if(!rowId||!weekIso) return [];
    return (allValues||[])
      .filter(v=>v.rowId===rowId && v.weekIso>weekIso)
      .sort((a,b)=>a.weekIso.localeCompare(b.weekIso));
  },[allValues, rowId, weekIso]);
  const showRedist = montVal!==null && montVal!==0 && futureVals.length>0;

  // Montant déjà présent dans la cellule ciblée (ligne + semaine). Le règlement
  // s'AJOUTE à ce montant (handleCellSave additive) → on l'affiche pour éviter la
  // surprise de voir un total différent du montant de la facture.
  const existingCell = useMemo(()=>
    (allValues||[]).find(v=>v.rowId===rowId && v.weekIso===weekIso) || null
  ,[allValues, rowId, weekIso]);
  const existingMont = existingCell ? (Number(existingCell.montant)||0) : 0;

  // Réinitialise la redistribution si la semaine ou la ligne change
  useEffect(()=>{ setRedistWeek(""); },[rowId, weekIso]);

  // Suggestion de ligne depuis le fournisseur (se déclenche au montage et si projId change)
  // Ne s'exécute pas si la rowId est déjà pré-remplie depuis proposed_action
  useEffect(()=>{
    if(!fdObj?.fournisseur || !projId || rowId) return;
    const fournisseurLow = fdObj.fournisseur.toLowerCase();
    const ficheData = fiche && fiche[projId];
    const intervenants = ficheData?.intervenants||[];
    const match = intervenants.find(iv=>
      (iv.entrepriseNom||"").toLowerCase().includes(fournisseurLow) ||
      fournisseurLow.includes((iv.entrepriseNom||"").toLowerCase())
    );
    if(match && match.typeCout){
      const matchRow = projRows.find(r=>r.label.toLowerCase().includes(match.typeCout.toLowerCase()));
      if(matchRow) setRowId(matchRow.id);
    }
  },[projId]);

  const handleSave = async () => {
    if(!rowId||!weekIso||!montant){ alert("Veuillez remplir tous les champs obligatoires."); return; }
    const mont = parseFloat(montant);
    if(isNaN(mont)){ alert("Montant invalide."); return; }
    setSaving(true);
    const redist = redistWeek ? {weekIso:redistWeek, adjustment:-mont} : null;
    await onSave(rowId, weekIso, mont, redist);
    // Passe la facture en "réglée"
    await fetch("/api/agent/notifications/"+notifId+"/facture",{
      method:"PATCH", headers:{"Content-Type":"application/json"},
      body:JSON.stringify({facture_status:"réglée"})
    }).catch(()=>{});
    setSaving(false);
    onClose();
  };

  return (
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.6)",zIndex:3000,display:"flex",alignItems:"center",justifyContent:"center"}}
      onClick={e=>{if(e.target===e.currentTarget)onClose();}}>
      <div style={{background:"#fff",borderRadius:14,padding:24,maxWidth:520,width:"92%",maxHeight:"90vh",overflowY:"auto",boxShadow:"0 8px 40px #0004"}}>
        <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:16}}>
          <div style={{fontWeight:800,fontSize:16}}>💶 Régler la facture & intégrer au Plan Hebdo</div>
          <button onClick={onClose} style={{background:"none",border:"none",fontSize:20,cursor:"pointer",color:"#64748b"}}>✕</button>
        </div>

        {/* Rappel des données de la facture */}
        <div style={{background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:10,padding:12,marginBottom:16,display:"grid",gridTemplateColumns:"auto 1fr",gap:"3px 12px",fontSize:13}}>
          {fdObj?.fournisseur&&<><span style={{color:"#64748b",fontWeight:600}}>Fournisseur</span><span style={{fontWeight:700}}>{fdObj.fournisseur}</span></>}
          {fdObj?.numero_facture&&<><span style={{color:"#64748b",fontWeight:600}}>N° facture</span><span>{fdObj.numero_facture}</span></>}
          {fdObj?.date_facture&&<><span style={{color:"#64748b",fontWeight:600}}>Date</span><span>{new Date(fdObj.date_facture+"T12:00:00Z").toLocaleDateString("fr-FR")}</span></>}
          {fdObj?.montant_ttc!=null&&<><span style={{color:"#64748b",fontWeight:600}}>Montant TTC</span><span style={{fontWeight:700,color:"#1d4ed8"}}>{fmtEur(fdObj.montant_ttc)}</span></>}
        </div>

        <div style={{fontWeight:700,fontSize:13,color:"#334155",marginBottom:10}}>📋 Ajouter au Plan Hebdo</div>
        <div style={{display:"flex",flexDirection:"column",gap:10}}>
          <div>
            <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Programme *</label>
            <select value={projId} onChange={e=>{setProjId(e.target.value);setRowId("");}} style={selStyle}>
              <option value="">— Sélectionner un programme —</option>
              <GroupedProgrammeOptions projects={projects} fiches={fiche}/>
            </select>
          </div>
          <div>
            <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Ligne Plan Hebdo *</label>
            <select value={rowId} onChange={e=>setRowId(e.target.value)} style={selStyle} disabled={!projId}>
              <option value="">— Sélectionner une ligne —</option>
              {projRows.map(r=><option key={r.id} value={r.id}>{r.label}</option>)}
            </select>
          </div>
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10}}>
            <div>
              <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Semaine *</label>
              <select value={weekIso} onChange={e=>setWeekIso(e.target.value)} style={selStyle}>
                {weekDates.map(w=><option key={w} value={w}>{fmtW(w)}</option>)}
              </select>
            </div>
            <div>
              <label style={{fontSize:11,color:"#64748b",display:"block",marginBottom:3,fontWeight:600}}>Montant € (négatif = dépense) *</label>
              <input type="number" step="0.01" value={montant} onChange={e=>setMontant(e.target.value)}
                style={selStyle} placeholder="-12000"/>
            </div>
          </div>
        </div>

        {/* Cumul : la cellule contient déjà un montant sur cette semaine */}
        {existingMont!==0 && montVal!==null && montVal!==0 && (
          <div style={{background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,padding:"8px 12px",marginTop:12,fontSize:12,color:"#1d4ed8"}}>
            ⚠️ Cette ligne contient déjà <b>{fmtEur(existingMont)}</b> sur cette semaine. Ce règlement sera <b>ajouté</b> → nouveau total <b>{fmtEur(existingMont+montVal)}</b>.
          </div>
        )}

        {/* Redistribution des flux postérieurs */}
        {showRedist&&(
          <div style={{background:"#fffbeb",borderRadius:8,border:"1px solid #fde68a",padding:12,marginTop:12}}>
            <div style={{fontSize:12,fontWeight:700,color:"#b45309",marginBottom:4}}>
              ⚖️ Flux ajouté : {montVal>0?"+":""}{fmtEur(montVal,true)}
            </div>
            <div style={{fontSize:11,color:"#92400e",marginBottom:8}}>
              Pour maintenir le total de la ligne, souhaitez-vous reporter {fmtEur(Math.abs(montVal),true)} sur un flux futur ?
            </div>
            <select value={redistWeek} onChange={e=>setRedistWeek(e.target.value)}
              style={{background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,color:"#92400e",padding:"5px 8px",fontSize:12,width:"100%"}}>
              <option value="">— Ne pas redistribuer —</option>
              {futureVals.map(v=>(
                <option key={v.weekIso} value={v.weekIso}>
                  {fmtW(v.weekIso)} : {fmtEur(v.montant)} → {fmtEur(v.montant+(-montVal))}
                </option>
              ))}
            </select>
          </div>
        )}

        <div style={{display:"flex",gap:8,marginTop:18,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{padding:"7px 16px",borderRadius:6,border:"1px solid #e2e8f0",background:"#f8fafc",cursor:"pointer",fontSize:13}}>Annuler</button>
          <button onClick={handleSave} disabled={saving||!rowId||!weekIso||!montant}
            style={{background:saving||!rowId||!weekIso||!montant?"#94a3b8":"#16a34a",color:"#fff",border:"none",borderRadius:7,padding:"8px 18px",cursor:"pointer",fontSize:13,fontWeight:600}}>
            {saving?"Enregistrement…":"✓ Marquer Réglée & Ajouter au Plan"}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── AppelReplanModal ── */
// Modale de replanification des flux à venir de la ligne « Lot N » du plan de
// trésorerie, déclenchée à l'étape 5 du workflow appel de fonds (envoi mail
// client). Garantit l'invariant : sum(cellules ligne) = prixReel.
// L'appel atomique au backend (POST /api/appels-eg/:id/replan) met à jour les
// cellules ET pose appel_client_sent=TRUE en une transaction.
function AppelReplanModal({ appel, prixReel, currentRowValues, lotN, programmeNom, attestNotifId, onConfirm, onCancel }) {
  // Cellules existantes triées, séparées passé / futur (passé = non modifiable ici)
  const initialFuture = useMemo(() => {
    return (currentRowValues || [])
      .filter(v => v.weekIso >= todayIso)
      .sort((a, b) => a.weekIso.localeCompare(b.weekIso))
      .map(v => ({ key: v.weekIso, weekIso: v.weekIso, montant: Number(v.montant) }));
  }, [currentRowValues]);

  const pastCellsSum = useMemo(() => (
    (currentRowValues || [])
      .filter(v => v.weekIso < todayIso)
      .reduce((s, v) => s + Number(v.montant), 0)
  ), [currentRowValues]);

  const [editRows, setEditRows] = useState(initialFuture);
  const [saving,   setSaving]   = useState(false);
  const [error,    setError]    = useState("");

  const futureWeekOptions = useMemo(() => weekDates.filter(w => w >= todayIso), []);

  // Détection des doublons de semaine
  const dupWeeks = useMemo(() => {
    const seen = new Set(), dups = new Set();
    for (const r of editRows) {
      if (r.weekIso && seen.has(r.weekIso)) dups.add(r.weekIso);
      seen.add(r.weekIso);
    }
    return dups;
  }, [editRows]);

  const totalFuture = editRows.reduce((s, r) => s + (Number(r.montant) || 0), 0);
  const total       = pastCellsSum + totalFuture;
  const ecart       = total - prixReel;
  const ecartOk     = Math.abs(ecart) <= 1;
  const allRowsValid = editRows.every(r => r.weekIso && Number(r.montant) >= 0);
  const canValidate = ecartOk && allRowsValid && dupWeeks.size === 0 && !saving;

  const updateRow = (i, field, value) => {
    setEditRows(rs => rs.map((r, idx) => idx === i ? { ...r, [field]: value } : r));
  };
  const addRow = () => {
    setEditRows(rs => [...rs, { key: `new-${Date.now()}-${Math.random()}`, weekIso: '', montant: 0 }]);
  };
  const removeRow = (i) => {
    setEditRows(rs => rs.filter((_, idx) => idx !== i));
  };

  const handleConfirm = async () => {
    setError("");
    // Construit le diff vs état initial
    const newMap = new Map();
    for (const r of editRows) {
      if (!r.weekIso) continue;
      const m = Number(r.montant) || 0;
      newMap.set(r.weekIso, (newMap.get(r.weekIso) || 0) + m);
    }
    const oldMap = new Map(initialFuture.map(r => [r.weekIso, r.montant]));

    const changes = [];
    for (const [weekIso, newM] of newMap) {
      const oldM = oldMap.get(weekIso) ?? 0;
      if (Math.abs(newM - oldM) > 0.005) changes.push({ weekIso, montant: Math.round(newM * 100) / 100 });
    }
    for (const [weekIso, _] of oldMap) {
      if (!newMap.has(weekIso)) changes.push({ weekIso, montant: 0 });
    }
    // Cas dégénéré : aucun changement de plan, mais on doit quand même marquer
    // l'appel comme envoyé côté backend. On envoie un no-op (cellule cible existante).
    if (changes.length === 0) {
      const first = Array.from(newMap)[0];
      if (first) changes.push({ weekIso: first[0], montant: Math.round(first[1] * 100) / 100 });
    }

    setSaving(true);
    try {
      await onConfirm(changes, attestNotifId);
    } catch (e) {
      setError(e?.message || "Erreur lors de la sauvegarde");
      setSaving(false);
    }
  };

  const inp     = { padding: "5px 7px", border: "1px solid #cbd5e1", borderRadius: 6, fontSize: 13, background: "#fff" };
  const numInp  = { ...inp, textAlign: "right", width: "100%" };
  const selStyle = { ...inp, width: "100%" };

  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.55)", zIndex: 3100, display: "flex", alignItems: "center", justifyContent: "center", padding: 16 }}
      onClick={e => { if (e.target === e.currentTarget) onCancel(); }}>
      <div style={{ background: "#fff", borderRadius: 14, padding: 24, width: 640, maxWidth: "96vw", maxHeight: "90vh", overflowY: "auto", boxShadow: "0 8px 40px #0004" }}
        onClick={e => e.stopPropagation()}>

        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
          <div style={{ fontWeight: 800, fontSize: 16 }}>📅 Replanifier les flux à venir</div>
          <button onClick={onCancel} style={{ background: "none", border: "none", fontSize: 20, cursor: "pointer", color: "#64748b" }}>✕</button>
        </div>
        <div style={{ fontSize: 12, color: "#64748b", marginBottom: 14 }}>
          {programmeNom} · Lot {lotN}
        </div>

        {/* Bandeau appel + invariant */}
        <div style={{ background: "#fef3c7", border: "1px solid #fde68a", borderRadius: 8, padding: "10px 12px", marginBottom: 12, fontSize: 12, lineHeight: 1.6 }}>
          Vous allez envoyer un appel de fonds de <b>{fmtEur(Number(appel.montant_client) || 0)}</b> ({appel.pct_delta || appel.eg_pct}%) au client.
          <br/>Ajustez si nécessaire les dates et montants des flux à venir de ce lot — la somme totale de la ligne doit rester égale au <b>prix réel ({fmtEur(prixReel)})</b>.
        </div>

        {/* Flux passés (info) */}
        {pastCellsSum > 0 && (
          <div style={{ background: "#f8fafc", border: "1px solid #e2e8f0", borderRadius: 8, padding: "6px 10px", marginBottom: 10, fontSize: 11, color: "#64748b" }}>
            Flux déjà passés sur cette ligne (non modifiables) : <b>{fmtEur(pastCellsSum)}</b>
          </div>
        )}

        {/* Tableau cellules futures éditables */}
        <div style={{ marginBottom: 10 }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 32px", gap: 6, alignItems: "center", marginBottom: 4, fontSize: 11, fontWeight: 700, color: "#64748b" }}>
            <div>Semaine</div>
            <div style={{ textAlign: "right" }}>Montant (€)</div>
            <div></div>
          </div>
          {editRows.length === 0 && (
            <div style={{ fontSize: 12, color: "#94a3b8", fontStyle: "italic", padding: "8px 0" }}>
              Aucune cellule à venir. Cliquez sur « + Ajouter une cellule ».
            </div>
          )}
          {editRows.map((r, i) => {
            const isDup = r.weekIso && dupWeeks.has(r.weekIso);
            return (
              <div key={r.key} style={{ display: "grid", gridTemplateColumns: "1fr 1fr 32px", gap: 6, alignItems: "center", marginBottom: 4 }}>
                <select value={r.weekIso} onChange={e => updateRow(i, "weekIso", e.target.value)}
                  style={{ ...selStyle, border: isDup ? "1px solid #f87171" : selStyle.border, background: isDup ? "#fef2f2" : selStyle.background }}>
                  <option value="">— Choisir —</option>
                  {futureWeekOptions.map(w => <option key={w} value={w}>{fmtW(w)}</option>)}
                </select>
                <input type="number" step="0.01" min="0" value={r.montant} onChange={e => updateRow(i, "montant", e.target.value)}
                  style={numInp} />
                <button onClick={() => removeRow(i)} title="Supprimer cette cellule"
                  style={{ background: "#fee2e2", border: "1px solid #fca5a5", borderRadius: 6, padding: "3px 6px", cursor: "pointer", color: "#dc2626", fontSize: 13 }}>×</button>
              </div>
            );
          })}
          <button onClick={addRow}
            style={{ background: "#f8fafc", border: "1px dashed #cbd5e1", borderRadius: 6, padding: "5px 12px", cursor: "pointer", fontSize: 12, color: "#475569", marginTop: 4 }}>
            + Ajouter une cellule
          </button>
        </div>

        {/* Doublons */}
        {dupWeeks.size > 0 && (
          <div style={{ background: "#fef2f2", border: "1px solid #fca5a5", borderRadius: 8, padding: "6px 10px", marginBottom: 8, fontSize: 11, color: "#dc2626" }}>
            ⚠ Doublons de semaine : {Array.from(dupWeeks).map(fmtW).join(", ")} — fusionnez-les avant de valider.
          </div>
        )}

        {/* Compteur total / écart */}
        <div style={{
          background: ecartOk ? "#f0fdf4" : "#fff7ed",
          border: ecartOk ? "1px solid #86efac" : "2px solid #fed7aa",
          borderRadius: 8, padding: "10px 12px", marginBottom: 14, fontSize: 12, lineHeight: 1.8,
        }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: "0 16px" }}>
            <span>Total ligne après modifications</span><b>{fmtEur(total)}</b>
            <span>Attendu (prix réel)</span><b>{fmtEur(prixReel)}</b>
            <span>Écart</span><b style={{ color: ecartOk ? "#15803d" : "#c2410c" }}>{ecart >= 0 ? "+" : ""}{fmtEur(ecart)}</b>
          </div>
        </div>

        {error && (
          <div style={{ background: "#fee2e2", border: "1px solid #fca5a5", borderRadius: 8, padding: "8px 12px", marginBottom: 10, fontSize: 12, color: "#dc2626" }}>
            ❌ {error}
          </div>
        )}

        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <button onClick={onCancel} disabled={saving}
            style={{ padding: "7px 16px", borderRadius: 6, border: "1px solid #e2e8f0", background: "#f8fafc", cursor: saving ? "default" : "pointer", fontSize: 13 }}>
            Annuler
          </button>
          <button onClick={handleConfirm} disabled={!canValidate}
            style={{
              background: canValidate ? "#0369a1" : "#94a3b8", color: "#fff", border: "none",
              borderRadius: 7, padding: "8px 22px", cursor: canValidate ? "pointer" : "default", fontSize: 13, fontWeight: 700,
            }}>
            {saving ? "⏳ Validation…" : "✓ Valider le plan & marquer envoyé"}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── FicheDetail ── */
function FicheDetail({proj,fiche,onUpdate,onBack,soldeFinOp,rows,values,crm,onGenerateFlows,onLotStatutChange,onArchitecteEmailSent,onUpdateLotReelFlows}) {
  const prixAchat=toN(fiche.prixAchat), devisTravaux=toN(fiche.devisTravaux);
  const euribor=toN(fiche.euribor), montantCredit=toN(fiche.montantCredit);
  const creditMensuel=montantCredit*(euribor/100+0.025)/12;
  const creditMensuelR=toN(fiche.creditMensuelR);
  const montantCommerce=toN(fiche.montantCommerceRdc);
  const prixDenormandie=prixAchat-montantCommerce;
  const moe=devisTravaux*0.08, creditTotal=creditMensuel*8;
  const achatFNI=prixDenormandie*1.025+toN(fiche.dossierBanque)+creditTotal;
  const montantTravaux=moe+toN(fiche.dp)+toN(fiche.pc)+toN(fiche.plaquette)+toN(fiche.deplacements)+toN(fiche.avocat)+toN(fiche.hommeArt)+toN(fiche.geometre)+toN(fiche.diagnostics)+toN(fiche.loyerMeuble)+toN(fiche.doTrc)+toN(fiche.gfa)+devisTravaux;
  // creditTotalR = somme réelle de la ligne "Crédit mensuel" du Plan Hebdo (voir autoReals.creditMensuel)
  // Fallback : creditMensuelR (override) × 8 si pas de ligne Plan Hebdo trouvée
  const COUTS_FIELDS_P=["gfa","doTrc","dossierBanque","dp","pc","plaquette","deplacements","avocat","hommeArt","geometre","diagnostics","loyerMeuble"];
  const COUTS_FIELDS_R=["gfaR","doTrcR","dossierBanqueR","dpR","pcR","plaqueтteR","deplacementsR","avocatR","hommeArtR","geometreR","diagnosticsR","loyerMeubleR"];
  const lots=fiche.lots||[];
  const prixTotalHorsMarge=achatFNI+montantTravaux;
  const totalMargePct=(toN(fiche.margeBluePct)+toN(fiche.margeCGPpct))/100;
  const caOperation=totalMargePct<1?prixTotalHorsMarge/(1-totalMargePct):prixTotalHorsMarge;
  const dontMargeBlue=caOperation*(toN(fiche.margeBluePct)/100);
  const dontMargeCGP=caOperation*(toN(fiche.margeCGPpct)/100);
  const lotsCalc=useMemo(()=>{
    const totalSP=lots.reduce((s,l)=>s+toN(l.surface)+Math.min(toN(l.ext)/2,8),0);
    const plafondPinelVal=toN(fiche.plafondPinel);
    // Passe 1 : prix brut et prix pondéré par lot
    const pass1=lots.map(l=>{
      const sp=toN(l.surface)+Math.min(toN(l.ext)/2,8);
      const prixBrutCalc=totalSP>0?caOperation*sp/totalSP:0;
      const ponderPct=toN(l.ponderationPct||"");
      const prixPondere=prixBrutCalc*(1+ponderPct/100);
      return {sp,prixBrutCalc,ponderPct,prixPondere};
    });
    const totalPrixPondere=pass1.reduce((s,lp)=>s+lp.prixPondere,0);
    // Passe 2 : prix final normalisé au CA total
    return lots.map((l,idx)=>{
      const {sp,prixBrutCalc,ponderPct,prixPondere}=pass1[idx];
      const prixFinalCalc=totalPrixPondere>0?prixPondere*caOperation/totalPrixPondere:prixBrutCalc;
      const prixFinal=l.prixFinalO!==""?toN(l.prixFinalO):prixFinalCalc;
      const pm=toN(l.surface)>0?prixFinal/toN(l.surface):0;
      const foncier=prixTotalHorsMarge>0?prixFinal*achatFNI/prixTotalHorsMarge:0;
      const travaux=prixFinal-foncier;
      const prixReelVal=toN(l.prixReel);
      const foncierReelCalc=prixReelVal>0&&prixTotalHorsMarge>0?prixReelVal*achatFNI/prixTotalHorsMarge:0;
      const foncierReelOverride=l.foncierReelO!==""&&l.foncierReelO!=null?toN(l.foncierReelO):null;
      const foncierReel=prixReelVal>0&&foncierReelOverride!==null?foncierReelOverride:foncierReelCalc;
      const travauxReel=prixReelVal>0?prixReelVal-foncierReel:0;
      const coefPinel=sp>0?Math.min(0.7+19/sp,1.2):0;
      const loyerPinelCalc=plafondPinelVal>0&&sp>0?Math.round(plafondPinelVal*sp*coefPinel):0;
      return {sp,prixBrutCalc,ponderPct,prixPondere,prixFinalCalc,prixFinal,pm,foncier,travaux,foncierReelCalc,foncierReel,travauxReel,loyerPinelCalc};
    });
  },[lots,caOperation,achatFNI,prixTotalHorsMarge,fiche.plafondPinel]);
  const lotsTotal=lotsCalc.reduce((s,lc)=>s+lc.prixFinal,0);
  const updateLot=(idx,field,val)=>onUpdate("lots",lots.map((l,i)=>i===idx?{...l,[field]:val}:l));
  // Pour les programmes existants, nomProgramme et ville peuvent être vides dans fiche ;
  // on utilise proj.nom / proj.ville comme fallback afin que les champs soient pré-remplis.
  const ficheAffichee={...fiche,nomProgramme:fiche.nomProgramme||proj.nom||"",ville:fiche.ville||proj.ville||""};
  const ff={fiche:ficheAffichee,onUpdate};
  const [lotsHistModal,setLotsHistModal]=useState(null);
  const [appelModal,setAppelModal]=useState(null); // lotIdx (ouverture manuelle)
  const [appelModalPost,setAppelModalPost]=useState(null); // {lotIdx,callIdx} (ouverture auto après changement de statut)
  const [liaModal,setLiaModal]=useState(null); // lotIdx — déclenché statut OPTION
  const [notaireModal,setNotaireModal]=useState(null); // lotIdx — déclenché statut LIA
  const [architecteModal,setArchitecteModal]=useState(null); // {lotIdx,statut,callIdx} — déclenché AV10/40/70/95
  const [compromisEmailModal,setCompromisEmailModal]=useState(null); // {lotIdx} — déclenché statut COMPROMIS
  const [confirmFlows,setConfirmFlows]=useState(false);
  const [confirmReelUpdate,setConfirmReelUpdate]=useState(null); // {lotIdx} — demande de confirmation pour MAJ flux après saisie prix réel / retro réelle
  const [editIntervenant,setEditIntervenant]=useState(null); // null ou {idx:-1 pour nouveau, role, entrepriseId, entrepriseNom, typeCout, contact}
  const [showColumnsMenu,setShowColumnsMenu]=useState(false);
  /* ── Appels de fonds EG : pct_cumule par lot_id (String(idx+1)) ── */
  const [appelsFonds,setAppelsFonds]=useState({});
  // Sprint F-d : extrait en useCallback pour pouvoir refetch après dispatch
  // de l'event 'appels-eg-updated' (envoi d'un appel client → pct_cumule change).
  const loadAppelsFonds=useCallback(()=>{
    if(!proj.id) return;
    fetch('/api/appels-eg?programme_id='+encodeURIComponent(proj.id))
      .then(r=>r.json())
      .then(rows=>{
        const map={};
        (rows||[]).forEach(row=>{
          const k=String(row.lot_id);
          if(map[k]===undefined) map[k]=Number(row.pct_cumule)||0;
        });
        setAppelsFonds(map);
      })
      .catch(()=>{});
  },[proj.id]);
  useEffect(()=>{ loadAppelsFonds(); },[loadAppelsFonds]);
  // Refresh quand un appel client est envoyé (event dispatché par Notifications.jsx)
  useEffect(()=>{
    const onUpdate=(ev)=>{
      if(!ev?.detail?.programme_id||ev.detail.programme_id===proj.id) loadAppelsFonds();
    };
    window.addEventListener('appels-eg-updated',onUpdate);
    return ()=>window.removeEventListener('appels-eg-updated',onUpdate);
  },[proj.id,loadAppelsFonds]);
  const [hiddenLotCols,setHiddenLotCols]=useState(()=>{
    try{const s=localStorage.getItem("tresoimmo_hiddenLotCols");return s?JSON.parse(s):{};}catch(e){return {};}
  });
  const toggleLotCol=(key)=>setHiddenLotCols(h=>{
    const nh={...h,[key]:!h[key]};
    try{localStorage.setItem("tresoimmo_hiddenLotCols",JSON.stringify(nh));}catch(e){}
    return nh;
  });
  const LOT_COL_TOGGLES=[
    {key:"loyerPinel",label:"Loyer Pinel"},
    {key:"prixBrut",label:"Prix brut"},
    {key:"ponderation",label:"Pondération"},
    {key:"prixPondere",label:"Prix pondéré"},
    {key:"foncier",label:"Foncier"},
    {key:"travaux",label:"Travaux"},
    {key:"foncierReel",label:"Foncier réel"},
    {key:"travauxReel",label:"Travaux réels"},
    {key:"retroPct",label:"% Rétro réel"},
    {key:"retroMontant",label:"Montant rétro réel"},
  ];

  /* Auto-génération des flux DÉSACTIVÉE — utiliser le bouton manuel */
  // const prevAcqRef=useRef(fiche.dateAcquisition);
  // const prevDtRef=useRef(fiche.dateDebutTravaux);
  // const isFirstDate=useRef(!fiche.dateAcquisition);

  /* Auto-réel depuis Plan Hebdo (toutes lignes R ET F, car les flux engagés
     sont pertinents quel que soit le statut de la ligne) */
  const autoReals = useMemo(()=>{
    if(!rows||!values) return {};
    const projRowsR=rows.filter(r=>r.projetId===proj.id);
    const match=(kws)=>{
      const found=projRowsR.filter(r=>kws.some(kw=>r.label.toLowerCase().includes(kw.toLowerCase())));
      if(!found.length) return undefined;
      return Math.abs(values.filter(v=>found.some(r=>r.id===v.rowId)).reduce((s,v)=>s+v.montant,0));
    };
    return {
      gfa:         match(["gfa"]),
      doTrc:       match(["do/trc","trc","assurance do","do trc"]),
      dossierBanque: match(["dossier banque","dossier de banque"]),
      dp:          match(["dp","dépôt permis","dépôt de permis"]),
      pc:          match(["pc","permis construire","permis de construire"]),
      plaquette:   match(["plaquette"]),
      deplacements: match(["déplacement"]),
      avocat:      match(["avocat"]),
      hommeArt:    match(["homme de l'art","homme art"]),
      geometre:    match(["géomètre","geometre"]),
      diagnostics: match(["diagnostic"]),
      loyerMeuble: match(["loyer"]),
      travaux:     match(["travaux"]),
      creditMensuel: match(["crédit mensuel","credit mensuel"]),
      moe:         match(["moe"]),
      taxeFonciere: match(["taxe foncière","taxe fonciere"]),
      eauElectricite: match(["eau et électricité","eau électricité","eau"]),
      autres:      match(["autres"]),
    };
  },[rows,values,proj.id]);

  const effectiveR=(fieldR,ar)=>{
    const ov=fiche[fieldR];
    if(ov!==""&&ov!==undefined&&ov!==null) return toN(ov);
    return ar!==undefined?ar:0;
  };

  // Crédit total réel : somme de toute la ligne "Crédit mensuel" du Plan Hebdo.
  // Fallback sur l'override creditMensuelR × 8 si la ligne n'existe pas encore.
  const creditTotalR = (autoReals.creditMensuel!==undefined)
    ? autoReals.creditMensuel
    : creditMensuelR*8;

  const coutsTotalP=COUTS_FIELDS_P.reduce((s,f)=>s+toN(fiche[f]),0)+creditTotal+moe+devisTravaux+toN(fiche.plusvalueTravaux)+toN(fiche.taxeFonciere)+toN(fiche.eauElectricite)+toN(fiche.autres);
  const coutsTotalR=
    effectiveR("gfaR",autoReals.gfa)+
    effectiveR("doTrcR",autoReals.doTrc)+
    effectiveR("dossierBanqueR",autoReals.dossierBanque)+
    creditTotalR+
    effectiveR("moeR",autoReals.moe)+
    effectiveR("travauxR",autoReals.travaux)+
    toN(fiche.plusvalueTravauxR||"")+
    effectiveR("dpR",autoReals.dp)+
    effectiveR("pcR",autoReals.pc)+
    effectiveR("plaqueтteR",autoReals.plaquette)+
    effectiveR("deplacementsR",autoReals.deplacements)+
    effectiveR("avocatR",autoReals.avocat)+
    effectiveR("hommeArtR",autoReals.hommeArt)+
    effectiveR("geometreR",autoReals.geometre)+
    effectiveR("diagnosticsR",autoReals.diagnostics)+
    effectiveR("loyerMeubleR",autoReals.loyerMeuble)+
    effectiveR("taxeFonciereR",autoReals.taxeFonciere)+
    effectiveR("eauElectriciteR",autoReals.eauElectricite)+
    effectiveR("autresR",autoReals.autres);

  const sc=fiche.statut==="En cours"?"#15803d":fiche.statut==="Livré"?"#1d4ed8":"#64748b";
  const sb=fiche.statut==="En cours"?"#dcfce7":fiche.statut==="Livré"?"#dbeafe":"#f1f5f9";

  return (
    <div style={{padding:20,maxWidth:900,margin:"0 auto"}}>
      <div style={{display:"flex",alignItems:"center",gap:12,marginBottom:20}}>
        <button onClick={onBack} style={{background:"#334155",color:"#fff",border:"none",borderRadius:7,padding:"5px 12px",cursor:"pointer",fontSize:12,fontWeight:600}}>← Retour</button>
        <div>
          <div style={{fontWeight:800,fontSize:18}}>{proj.ville?proj.ville+" – ":""}{proj.nom}</div>
          <div style={{fontSize:12,color:C.muted}}>{[fiche.adresse,fiche.codePostal,fiche.ville].filter(Boolean).join(", ")||"Adresse non renseignée"}</div>
        </div>
        <span style={{marginLeft:"auto",fontSize:11,background:sb,color:sc,borderRadius:6,padding:"3px 10px",fontWeight:600}}>{fiche.statut}</span>
      </div>

      {/* Solde fin d'opération */}
      <div style={{background:C.card,borderRadius:10,border:"1px solid "+C.border,padding:14,textAlign:"center",marginBottom:14}}>
        <div style={{fontSize:11,color:C.muted,marginBottom:4}}>
          <span style={{background:"#dbeafe",color:"#1d4ed8",borderRadius:3,padding:"0 6px",fontSize:10,fontWeight:700,marginRight:6}}>R+F</span>
          Solde fin d'opération (total de tous les flux)
        </div>
        <div style={{fontSize:22,fontWeight:800,color:soldeFinOp>=0?"#2563eb":C.red}}>{fmtEur(soldeFinOp)}</div>
      </div>

      <FicheSection title="📍 Informations Opération" color="#38bdf8">
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:12}}>
          <FicheField label="Nom du programme" field="nomProgramme"{...ff}/><FicheField label="Ville" field="ville"{...ff}/>
          <FicheField label="Type d'opération" field="typeOp" opts={["Rénovation","Construction","VEFA","Marchand de biens","Autre"]}{...ff}/>
          <FicheField label="Adresse" field="adresse"{...ff}/><FicheField label="Code postal" field="codePostal"{...ff}/>
          <FicheField label="Statut" field="statut" opts={["En cours","Livré","Abandonné"]}{...ff}/>
          <FicheField label="Prix d'achat immeuble (€)" field="prixAchat"{...ff}/><FicheField label="Montant commerce RdC (€)" field="montantCommerceRdc"{...ff}/>
          <FicheField label="Prix Denormandie (€)" field="prixDenormandie" calc calcVal={fmtEur(prixDenormandie)}{...ff}/><FicheField label="Nombre de lots" field="nbLots"{...ff}/>
          <FicheField label="Devis travaux (€)" field="devisTravaux"{...ff}/><FicheField label="Surface habitable (m²)" field="surfaceTotale"{...ff}/>
          <FicheField label="Parties communes (m²)" field="partiesCommunes"{...ff}/><FicheField label="Extérieurs (m²)" field="exterieurs"{...ff}/>
          <FicheField label="Euribor 3 mois" field="euribor" suffix="%" {...ff}/><FicheField label="Nombre de lots occupés" field="nbLotsOccupes"{...ff}/>
          <FicheField label="Montant du crédit (€)" field="montantCredit"{...ff}/><FicheField label="Plafond Pinel (€/m²)" field="plafondPinel"{...ff}/>
          <FicheField label="DP ou PC ou Rien" field="dpOuPc" opts={["DP","PC","Rien"]}{...ff}/><FicheField label="Date d'acquisition" field="dateAcquisition" type="date"{...ff}/>
          <FicheField label="Date de démarrage des travaux" field="dateDebutTravaux" type="date"{...ff}/><FicheField label="Date de livraison prévue" field="dateLivraison" type="date"{...ff}/>
          <FicheField label="Banque" field="banque"{...ff}/>
          <FicheField label="IBAN du programme (appels de fonds)" field="ibanProgramme"{...ff}/>
          <div style={{gridColumn:"1/-1",display:"flex",alignItems:"center",gap:10,padding:"8px 10px",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:7,marginTop:4}}>
            <input
              type="checkbox"
              id="prevalidationGFA"
              checked={!!fiche.prevalidationGFA}
              onChange={e=>onUpdate("prevalidationGFA",e.target.checked)}
              style={{width:16,height:16,cursor:"pointer",accentColor:"#f59e0b"}}
            />
            <label htmlFor="prevalidationGFA" style={{fontSize:13,fontWeight:600,color:"#92400e",cursor:"pointer",userSelect:"none"}}>
              Pré-validation GFA
            </label>
            <span style={{fontSize:11,color:"#b45309",marginLeft:4}}>(cocher si la GFA demande une prévalidation de chaque facture par email)</span>
          </div>
          {!!fiche.prevalidationGFA&&(()=>{
            const gfaIntervenants=(fiche.intervenants||[]).filter(iv=>iv.role==="Assurance GFA");
            if(gfaIntervenants.length===0) return (
              <div style={{gridColumn:"1/-1",padding:"8px 12px",background:"#fff7ed",border:"1px solid #fed7aa",borderRadius:7,marginTop:4,fontSize:13,color:"#c2410c",fontWeight:600}}>
                ⚠️ Aucun intervenant « Assurance GFA » trouvé dans ce programme. Ajoutez-le dans la section Intervenants.
              </div>
            );
            const emailOptions=[];
            gfaIntervenants.forEach(iv=>{
              const ent=(crm.entreprises||[]).find(e=>e.id===iv.entrepriseId);
              if(ent&&ent.emails){
                ent.emails.split(/[,;]/).map(s=>s.trim()).filter(Boolean).forEach(addr=>{
                  if(!emailOptions.includes(addr)) emailOptions.push(addr);
                });
              }
            });
            if(emailOptions.length===0) return (
              <div style={{gridColumn:"1/-1",padding:"8px 12px",background:"#fff7ed",border:"1px solid #fed7aa",borderRadius:7,marginTop:4,fontSize:13,color:"#c2410c",fontWeight:600}}>
                ⚠️ Aucun email trouvé pour l'intervenant « Assurance GFA ». Renseignez le champ Email(s) de l'entreprise dans le CRM.
              </div>
            );
            /* Pré-sélectionner la première adresse si le champ est vide */
            if(!fiche.gfaEmailContact && emailOptions.length>0) onUpdate("gfaEmailContact",emailOptions[0]);
            return (
              <div style={{gridColumn:"1/-1",display:"flex",alignItems:"center",gap:10,padding:"8px 10px",background:"#f0fdf4",border:"1px solid #86efac",borderRadius:7,marginTop:4}}>
                <span style={{fontSize:12,fontWeight:600,color:"#15803d",flexShrink:0}}>📧 Email GFA :</span>
                <select value={fiche.gfaEmailContact||""} onChange={e=>onUpdate("gfaEmailContact",e.target.value)}
                  style={{flex:1,padding:"5px 8px",border:"1px solid #86efac",borderRadius:6,fontSize:13,background:"#fff",color:"#15803d"}}>
                  {emailOptions.map(addr=><option key={addr} value={addr}>{addr}</option>)}
                </select>
              </div>
            );
          })()}
        </div>
      </FicheSection>

      {/* ── Section Intervenants ── */}
      <FicheSection title="👥 Intervenants" color="#8b5cf6">
        {(fiche.intervenants||[]).length===0&&!editIntervenant&&(
          <div style={{color:"#94a3b8",fontSize:13,marginBottom:8}}>Aucun intervenant renseigné.</div>
        )}
        {(fiche.intervenants||[]).map((iv,idx)=>(
          <div key={idx} style={{display:"flex",alignItems:"center",gap:8,padding:"7px 10px",background:"#faf5ff",borderRadius:7,marginBottom:6,border:"1px solid #e9d5ff"}}>
            <span style={{fontSize:11,fontWeight:700,color:"#7c3aed",background:"#ede9fe",borderRadius:4,padding:"2px 8px",flexShrink:0}}>{iv.role||"—"}</span>
            <span style={{fontSize:13,flex:1,fontWeight:500}}>{iv.entrepriseNom||iv.contact||"—"}</span>
            {iv.contact&&iv.entrepriseNom&&<span style={{fontSize:12,color:"#64748b"}}>{iv.contact}</span>}
            {iv.typeCout&&<span style={{fontSize:11,color:"#6d28d9",background:"#f3e8ff",borderRadius:4,padding:"1px 7px",flexShrink:0}}>{iv.typeCout}</span>}
            <button onClick={()=>setEditIntervenant({...iv,idx})}
              style={{background:"#334155",color:"#fff",border:"none",borderRadius:5,padding:"2px 9px",fontSize:11,cursor:"pointer",fontWeight:600}}>✎</button>
            <button onClick={()=>onUpdate("intervenants",(fiche.intervenants||[]).filter((_,i)=>i!==idx))}
              style={{background:"#dc2626",color:"#fff",border:"none",borderRadius:5,padding:"2px 9px",fontSize:11,cursor:"pointer",fontWeight:600}}>✕</button>
          </div>
        ))}
        {!editIntervenant&&(
          <button onClick={()=>setEditIntervenant({idx:-1,role:"MOE",entrepriseId:"",entrepriseNom:"",typeCout:"",contact:""})}
            style={{background:"#8b5cf6",color:"#fff",border:"none",borderRadius:7,padding:"6px 14px",fontSize:12,cursor:"pointer",fontWeight:600,marginTop:4}}>
            + Ajouter un intervenant
          </button>
        )}
        {editIntervenant&&(
          <div style={{background:"#faf5ff",border:"1px solid #c4b5fd",borderRadius:10,padding:14,marginTop:10}}>
            <div style={{fontWeight:700,fontSize:13,color:"#7c3aed",marginBottom:10}}>
              {editIntervenant.idx===-1?"Nouvel intervenant":"Modifier l'intervenant"}
            </div>
            <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:8,marginBottom:8}}>
              <div>
                <div style={{fontSize:11,color:"#64748b",marginBottom:3,fontWeight:600}}>Rôle</div>
                <select value={editIntervenant.role||""} onChange={e=>setEditIntervenant(v=>({...v,role:e.target.value}))}
                  style={{width:"100%",padding:"5px 7px",border:"1px solid #ddd6fe",borderRadius:5,fontSize:12,background:"#fff"}}>
                  {ROLES_INTERVENANTS.map(r=><option key={r}>{r}</option>)}
                </select>
              </div>
              <div>
                <div style={{fontSize:11,color:"#64748b",marginBottom:3,fontWeight:600}}>Poste coût associé</div>
                <select value={editIntervenant.typeCout||""} onChange={e=>setEditIntervenant(v=>({...v,typeCout:e.target.value}))}
                  style={{width:"100%",padding:"5px 7px",border:"1px solid #ddd6fe",borderRadius:5,fontSize:12,background:"#fff"}}>
                  <option value="">— Aucun —</option>
                  {COUT_ROWS_LABELS.map(l=><option key={l} value={l}>{l}</option>)}
                </select>
              </div>
            </div>
            <div style={{marginBottom:8}}>
              <div style={{fontSize:11,color:"#64748b",marginBottom:3,fontWeight:600}}>Société (depuis le CRM)</div>
              <select value={editIntervenant.entrepriseId||""} onChange={e=>{
                const ent=(crm.entreprises||[]).find(en=>en.id===e.target.value);
                setEditIntervenant(v=>({...v,entrepriseId:e.target.value,entrepriseNom:ent?ent.nom:""}));
              }} style={{width:"100%",padding:"5px 7px",border:"1px solid #ddd6fe",borderRadius:5,fontSize:12,background:"#fff"}}>
                <option value="">— Sélectionner dans le CRM —</option>
                {(crm.entreprises||[]).map(en=><option key={en.id} value={en.id}>{en.nom}</option>)}
              </select>
            </div>
            <div style={{marginBottom:12}}>
              <div style={{fontSize:11,color:"#64748b",marginBottom:3,fontWeight:600}}>Contact / Nom de la personne</div>
              <input type="text" value={editIntervenant.contact||""} onChange={e=>setEditIntervenant(v=>({...v,contact:e.target.value}))}
                placeholder="Ex : Jean Dupont"
                style={{width:"100%",padding:"5px 7px",border:"1px solid #ddd6fe",borderRadius:5,fontSize:12,background:"#fff"}}/>
            </div>
            <div style={{display:"flex",gap:8}}>
              <button onClick={()=>{
                const iv={role:editIntervenant.role,entrepriseId:editIntervenant.entrepriseId,entrepriseNom:editIntervenant.entrepriseNom,typeCout:editIntervenant.typeCout,contact:editIntervenant.contact};
                const list=fiche.intervenants||[];
                if(editIntervenant.idx===-1) onUpdate("intervenants",[...list,iv]);
                else onUpdate("intervenants",list.map((l,i)=>i===editIntervenant.idx?iv:l));
                setEditIntervenant(null);
              }} style={{background:"#8b5cf6",color:"#fff",border:"none",borderRadius:6,padding:"6px 14px",fontSize:12,cursor:"pointer",fontWeight:700}}>
                ✓ Enregistrer
              </button>
              <button onClick={()=>setEditIntervenant(null)}
                style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,padding:"6px 12px",fontSize:12,cursor:"pointer"}}>
                Annuler
              </button>
            </div>
          </div>
        )}
      </FicheSection>

      {/* Bannière génération flux */}
      {onGenerateFlows&&(
        <div style={{background:fiche.dateDebutTravaux?"#f0fdfa":"#f8fafc",border:"1px solid "+(fiche.dateDebutTravaux?"#99f6e4":"#e2e8f0"),borderRadius:10,padding:"12px 16px",marginBottom:14,display:"flex",alignItems:"center",gap:12,flexWrap:"wrap"}}>
          <span style={{fontSize:13,flex:1}}>
            {fiche.dateDebutTravaux
              ? <><b style={{color:"#0f766e"}}>⚡ Flux prévisionnels</b> — date de démarrage : <b>{new Date(fiche.dateDebutTravaux+"T12:00:00Z").toLocaleDateString("fr-FR")}</b>. Les flux prévisionnels (F) existants seront écrasés lors du recalcul. Les flux réels (R) sont préservés.</>
              : <span style={{color:C.muted}}>Renseignez la <b>date de démarrage des travaux</b> pour générer automatiquement les flux prévisionnels dans le Plan Hebdo.</span>
            }
          </span>
          <button onClick={()=>setConfirmFlows(true)} disabled={!fiche.dateDebutTravaux}
            style={{background:fiche.dateDebutTravaux?"#0d9488":"#cbd5e1",color:"#fff",border:"none",borderRadius:8,padding:"8px 18px",fontSize:13,fontWeight:700,cursor:fiche.dateDebutTravaux?"pointer":"default",flexShrink:0}}>
            ⚡ Recalculer les flux
          </button>
        </div>
      )}

      {/* Modale confirmation recalcul flux */}
      {confirmFlows&&(()=>{
        const STATUTS_AVANCES=['COMPROMIS','AV10','AV40','AV70','AV95','LIA','OPTION'];
        const lotsAvances=(lots||[]).filter(l=>STATUTS_AVANCES.includes(l.statutCommercial));
        return (
        <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.45)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={()=>setConfirmFlows(false)}>
          <div style={{background:"#fff",borderRadius:16,padding:28,width:480,maxWidth:"95vw",boxShadow:"0 8px 40px #0003",border:"1px solid #e2e8f0"}} onClick={e=>e.stopPropagation()}>
            <div style={{fontSize:24,textAlign:"center",marginBottom:8}}>⚠️</div>
            <div style={{fontWeight:800,fontSize:16,textAlign:"center",marginBottom:8}}>Recalculer les flux prévisionnels ?</div>
            <div style={{fontSize:13,color:"#64748b",textAlign:"center",marginBottom:6}}>
              Cette action va <b style={{color:"#dc2626"}}>écraser tous les flux prévisionnels (F)</b> existants pour ce programme et les remplacer par les valeurs recalculées.
              {!fiche.dateDebutTravaux&&fiche.dateAcquisition&&<div style={{marginTop:4,fontSize:12,color:"#b45309"}}>⚠️ Date début travaux non saisie — hypothèse : date d'achat + 39 semaines</div>}
            </div>
            {lotsAvances.length>0&&(
              <div style={{fontSize:12,color:"#92400e",background:"#fef3c7",border:"1px solid #fbbf24",borderRadius:6,padding:"8px 12px",marginBottom:10}}>
                <b>⚠️ {lotsAvances.length} lot{lotsAvances.length>1?"s":""} déjà avancé{lotsAvances.length>1?"s":""} :</b>{" "}
                {lotsAvances.map((l,i)=>{
                  const idx=(lots||[]).indexOf(l);
                  return <span key={i}><b>Lot {idx+1}</b> ({l.statutCommercial}){i<lotsAvances.length-1?", ":""}</span>;
                })}<br/>
                Leurs flux prévisionnels (F) seront <b>replacés sur le calendrier générique</b> (dateDebutTravaux ±4 sem), effaçant les dates individuelles issues de leur statut. Utilisez le bouton <b>🔄</b> par lot pour recalculer uniquement un lot spécifique.
              </div>
            )}
            <div style={{fontSize:12,color:"#16a34a",textAlign:"center",background:"#dcfce7",borderRadius:6,padding:"6px 12px",marginBottom:20}}>
              ✓ Les flux réels (R) ne seront pas modifiés.
            </div>
            <div style={{display:"flex",gap:10,justifyContent:"center"}}>
              <button onClick={()=>{if(onGenerateFlows)onGenerateFlows(proj.id);setConfirmFlows(false);}}
                style={{background:"#0d9488",color:"#fff",border:"none",borderRadius:8,padding:"9px 22px",fontSize:13,fontWeight:700,cursor:"pointer"}}>
                ⚡ Confirmer le recalcul
              </button>
              <button onClick={()=>setConfirmFlows(false)}
                style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"9px 18px",fontSize:13,cursor:"pointer"}}>
                Annuler
              </button>
            </div>
          </div>
        </div>
        );
      })()}

      <FicheSection title="💰 Coûts engagés" color="#f59e0b">
        <div style={{fontSize:11,color:"#64748b",marginBottom:10,background:"#fffbeb",borderRadius:6,padding:"6px 10px",border:"1px solid #fde68a"}}>
          ℹ️ Les valeurs <b style={{color:"#15803d"}}>Réel</b> sont calculées automatiquement depuis le Plan Hebdo (lignes dont le nom contient le poste). Cliquez sur <b>✎</b> pour forcer manuellement, <b>↺</b> pour revenir au calcul.
        </div>
        <div style={{overflowX:"auto"}}>
          <table style={{borderCollapse:"collapse",width:"100%"}}>
            <thead>
              <tr style={{background:"#fffbeb",borderBottom:"2px solid #fde68a"}}>
                <th style={{padding:"7px 8px",textAlign:"left",fontSize:11,color:C.muted,fontWeight:600,minWidth:160}}>Poste</th>
                <th style={{padding:"7px 8px",textAlign:"right",fontSize:11,color:"#b45309",fontWeight:700,minWidth:130}}>📋 Prévisionnel</th>
                <th style={{padding:"7px 8px",textAlign:"right",fontSize:11,color:"#15803d",fontWeight:700,minWidth:145}}>✓ Réel</th>
                <th style={{padding:"7px 8px",textAlign:"right",fontSize:11,color:C.muted,fontWeight:600,minWidth:90}}>Écart</th>
              </tr>
            </thead>
            <tbody>
              <CoutRow label="GFA" fieldP="gfa" fieldR="gfaR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.gfa}/>
              <CoutRow label="DO / TRC" fieldP="doTrc" fieldR="doTrcR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.doTrc}/>
              <CoutRow label="Dossier banque" fieldP="dossierBanque" fieldR="dossierBanqueR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.dossierBanque}/>
              <CoutRow label="Crédit mensuel" fieldP="creditMensuel" fieldR="creditMensuelR" fiche={fiche} onUpdate={onUpdate} calcP={creditMensuel}/>
              <CoutRow label="Crédit total (×8)" fieldP="creditMensuel" fieldR="creditMensuelR" fiche={fiche} onUpdate={onUpdate} calcP={creditTotal} calcR={creditTotalR}/>
              <CoutRow label="MOE (8% travaux)" fieldP="_moe" fieldR="moeR" fiche={fiche} onUpdate={onUpdate} calcP={moe} autoReal={autoReals.moe}/>
              <CoutRow label="Travaux" fieldP="devisTravaux" fieldR="travauxR" fiche={fiche} onUpdate={onUpdate} calcP={devisTravaux} autoReal={autoReals.travaux}/>
              <CoutRow label="Plus-value travaux" fieldP="plusvalueTravaux" fieldR="plusvalueTravauxR" fiche={fiche} onUpdate={onUpdate}/>
              <CoutRow label="DP" fieldP="dp" fieldR="dpR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.dp}/>
              <CoutRow label="PC" fieldP="pc" fieldR="pcR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.pc}/>
              <CoutRow label="Plaquette 3D" fieldP="plaquette" fieldR="plaqueтteR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.plaquette}/>
              <CoutRow label="Déplacements" fieldP="deplacements" fieldR="deplacementsR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.deplacements}/>
              <CoutRow label="Avocat" fieldP="avocat" fieldR="avocatR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.avocat}/>
              <CoutRow label="Homme de l'art" fieldP="hommeArt" fieldR="hommeArtR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.hommeArt}/>
              <CoutRow label="Géomètre" fieldP="geometre" fieldR="geometreR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.geometre}/>
              <CoutRow label="Diagnostics" fieldP="diagnostics" fieldR="diagnosticsR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.diagnostics}/>
              <CoutRow label="Loyer meublé" fieldP="loyerMeuble" fieldR="loyerMeubleR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.loyerMeuble}/>
              <CoutRow label="Taxe foncière" fieldP="taxeFonciere" fieldR="taxeFonciereR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.taxeFonciere}/>
              <CoutRow label="Eau et électricité" fieldP="eauElectricite" fieldR="eauElectriciteR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.eauElectricite}/>
              <CoutRow label="Autres" fieldP="autres" fieldR="autresR" fiche={fiche} onUpdate={onUpdate} autoReal={autoReals.autres}/>
              <tr style={{background:"#fffbeb",borderTop:"2px solid #fde68a",fontWeight:700}}>
                <td style={{padding:"7px 8px",fontSize:12,color:C.text}}>TOTAL</td>
                <td style={{padding:"7px 8px",textAlign:"right",fontSize:13,color:"#b45309"}}>{fmtEur(coutsTotalP)}</td>
                <td style={{padding:"7px 8px",textAlign:"right",fontSize:13,color:"#15803d"}}>{fmtEur(coutsTotalR)}</td>
                <td style={{padding:"7px 8px",textAlign:"right",fontSize:12,fontWeight:700,
                  color:(coutsTotalR-coutsTotalP)>0?"#dc2626":(coutsTotalR-coutsTotalP)<0?"#16a34a":"#94a3b8"}}>
                  {(coutsTotalP||coutsTotalR)?((coutsTotalR-coutsTotalP)>=0?"+":"")+fmtEur(coutsTotalR-coutsTotalP,true):"—"}
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        {(() => {
          // Marge CGP réelle : on part du prévisionnel (dontMargeCGP) réparti équitablement entre les lots.
          // Pour chaque lot vendu dont la rétrocession réelle est renseignée (retroMontantR),
          // on remplace sa part prévisionnelle par la valeur réelle — alignement avec la logique
          // de mise à jour des flux (handleLotStatutChange / onUpdateLotReelFlows).
          const nbLotsCGP = Math.max(lots.length, 1);
          const cgpParLotPrev = dontMargeCGP / nbLotsCGP;
          const margeCGPreel = lots.reduce((s,l) => {
            const rm = toN(l.retroMontantR);
            return s + (rm > 0 ? rm : cgpParLotPrev);
          }, 0);
          // Marge Blue réelle = solde fin d'opération (ce qui reste à la fin du programme, tous flux confondus)
          const margeBlueReel = soldeFinOp;
          const subLabel={fontSize:11,color:C.muted,marginBottom:3};
          const calcBox={...FICHE_CALC,padding:"6px 10px"};
          return (
            <div style={{display:"flex",flexDirection:"column",gap:12,marginTop:12}}>
              {/* Marge CGP */}
              <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:10,alignItems:"end",background:"#fdf4ff",border:"1px solid #f5d0fe",borderRadius:8,padding:"10px 12px"}}>
                <FicheField label="Marge CGP (%)" field="margeCGPpct"{...ff}/>
                <div style={{marginBottom:10}}>
                  <div style={subLabel}>Marge CGP prévisionnelle (€) <span style={{color:C.accent,fontSize:10}}>calculé</span></div>
                  <div style={{...calcBox,borderColor:"#f5d0fe",color:"#a21caf"}}>{fmtEur(dontMargeCGP)}</div>
                </div>
                <div style={{marginBottom:10}}>
                  <div style={subLabel}>Marge CGP réelle (€) <span style={{color:"#15803d",fontSize:10}}>Lots vendus</span></div>
                  <div style={{...calcBox,background:"#f0fdf4",borderColor:"#bbf7d0",color:"#15803d"}}>{fmtEur(margeCGPreel)}</div>
                </div>
              </div>
              {/* Marge Blue */}
              <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:10,alignItems:"end",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:8,padding:"10px 12px"}}>
                <FicheField label="Marge Blue (%)" field="margeBluePct"{...ff}/>
                <div style={{marginBottom:10}}>
                  <div style={subLabel}>Marge Blue prévisionnelle (€) <span style={{color:C.accent,fontSize:10}}>calculé</span></div>
                  <div style={{...calcBox,borderColor:"#bfdbfe",color:"#1d4ed8"}}>{fmtEur(dontMargeBlue)}</div>
                </div>
                <div style={{marginBottom:10}}>
                  <div style={subLabel}>Marge Blue réelle (€) <span style={{color:"#15803d",fontSize:10}}>Solde fin op.</span></div>
                  <div style={{...calcBox,background:"#f0fdf4",borderColor:"#bbf7d0",color:margeBlueReel>=0?"#15803d":"#dc2626"}}>{fmtEur(margeBlueReel)}</div>
                </div>
              </div>
            </div>
          );
        })()}
      </FicheSection>

      <FicheSection title="📊 Chiffre d'affaire de l'opération" color="#a855f7">
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:12}}>
          <FicheField label="Achat total FNI (€)" calc calcVal={fmtEur(achatFNI)}{...ff}/>
          <FicheField label="Montant des travaux (€)" calc calcVal={fmtEur(montantTravaux)}{...ff}/>
          <FicheField label="Prix total hors marge (€)" calc calcVal={fmtEur(prixTotalHorsMarge)}{...ff}/>
          <FicheField label="Dont marge Blue (€)" calc calcVal={fmtEur(dontMargeBlue)}{...ff}/>
          <FicheField label="Dont marge CGP (€)" calc calcVal={fmtEur(dontMargeCGP)}{...ff}/>
          <div style={{marginBottom:10}}>
            <div style={{fontSize:11,color:C.muted,marginBottom:3}}>CA Opération (€) <span style={{color:C.accent,fontSize:10}}>calculé</span></div>
            <div style={{...FICHE_CALC,color:C.green,fontSize:16,borderColor:"#a855f7"}}>{fmtEur(caOperation)}</div>
          </div>
        </div>
      </FicheSection>

      <FicheSection title="🏠 Lots" color="#22c55e">
        {lots.filter(l=>toN(l.surface)===0&&l.prixBrutO==="").length>0&&(
          <div style={{background:"#fef9c3",border:"1px solid #f59e0b",borderRadius:6,padding:"8px 12px",marginBottom:10,fontSize:12,color:"#92400e"}}>
            ⚠️ {lots.filter(l=>toN(l.surface)===0&&l.prixBrutO==="").length} lot(s) sans surface renseignée — ils ne reçoivent aucune part du CA.
          </div>
        )}
        <div style={{display:"flex",justifyContent:"flex-end",marginBottom:6,position:"relative"}}>
          <button onClick={()=>setShowColumnsMenu(v=>!v)} style={{background:C.card,border:"1px solid "+C.border,borderRadius:6,padding:"4px 10px",fontSize:11,cursor:"pointer",color:C.muted}}>
            👁 Colonnes {Object.values(hiddenLotCols).filter(Boolean).length>0?"("+Object.values(hiddenLotCols).filter(Boolean).length+" masquée·s)":""}
          </button>
          {showColumnsMenu&&(
            <div style={{position:"absolute",top:30,right:0,background:C.card,border:"1px solid "+C.border,borderRadius:8,boxShadow:"0 4px 12px rgba(0,0,0,0.1)",padding:"8px 10px",zIndex:30,minWidth:200}}>
              <div style={{fontSize:11,fontWeight:700,color:C.muted,marginBottom:6}}>Afficher / masquer</div>
              {LOT_COL_TOGGLES.map(c=>(
                <label key={c.key} style={{display:"flex",alignItems:"center",gap:6,padding:"3px 0",fontSize:12,cursor:"pointer"}}>
                  <input type="checkbox" checked={!hiddenLotCols[c.key]} onChange={()=>toggleLotCol(c.key)}/>
                  <span>{c.label}</span>
                </label>
              ))}
              <div style={{textAlign:"right",marginTop:6}}>
                <button onClick={()=>setShowColumnsMenu(false)} style={{background:"none",border:"none",color:C.accent,fontSize:11,cursor:"pointer"}}>Fermer</button>
              </div>
            </div>
          )}
        </div>
        <div style={{overflowX:"auto"}}>
          <table style={{borderCollapse:"collapse",width:"100%",fontSize:12}}>
            <thead><tr style={{background:"#f1f5f9"}}>
              {[
                {k:"_idx",label:""},
                {k:"_surface",label:"Surface"},
                {k:"_ext",label:"Ext"},
                {k:"_type",label:"Type"},
                {k:"loyerPinel",label:"Loyer Pinel"},
                {k:"prixBrut",label:"Prix brut"},
                {k:"ponderation",label:"Pondérat. (%)"},
                {k:"prixPondere",label:"Prix pondéré"},
                {k:"_prixFinal",label:"Prix final"},
                {k:"_pm",label:"Prix m²"},
                {k:"foncier",label:"Foncier"},
                {k:"travaux",label:"Travaux"},
                {k:"_prixReel",label:"Prix réel"},
                {k:"foncierReel",label:"Foncier réel"},
                {k:"travauxReel",label:"Travaux réels"},
                {k:"retroPct",label:"% Rétro réel"},
                {k:"retroMontant",label:"Montant rétro réel"},
                {k:"_client",label:"Client"},
                {k:"_cgp",label:"CGP"},
                {k:"_statut",label:"Statut"},
                {k:"_flux",label:""},
                {k:"_del",label:""},
              ].filter(c=>!hiddenLotCols[c.k]).map((c,i)=>(
                <th key={i} style={{padding:"6px 8px",textAlign:"right",color:C.muted,fontWeight:600,whiteSpace:"nowrap"}}>{c.label}</th>
              ))}
            </tr></thead>
            <tbody>
              {lots.map((lot,idx)=>{
                const lc=lotsCalc[idx]||{prixBrutCalc:0,ponderPct:0,prixPondere:0,prixFinalCalc:0,prixFinal:0,pm:0,foncier:0,travaux:0};
                const li={background:"#f8fafc",border:"1px solid "+C.border,borderRadius:6,color:C.text,padding:"4px 6px",fontSize:12,width:"100%",textAlign:"right"};
                const liWarn={...li,background:"#fef9c3",borderColor:"#f59e0b",color:"#92400e"};
                const liReadOnly={...li,background:"#e2e8f0",color:"#94a3b8",cursor:"not-allowed"};
                const surfaceManquante=toN(lot.surface)===0&&lot.prixBrutO==="";
                const canEditReel=true; // Toujours éditable — prix réel connu dès la négociation, utilisé pour la LIA au passage OPTION
                const retroPctVal=toN(lot.retroPctR);
                const prixReelVal=toN(lot.prixReel);
                const retroMontantVal=prixReelVal*retroPctVal/100;
                // Comparaison prix réel vs total ligne du lot dans le plan hebdo
                const lotPlanRow=(rows||[]).find(r=>r.projetId===proj.id&&r.label===`Lot ${idx+1}`);
                const planHebdoTotal=lotPlanRow?(values||[]).filter(v=>v.rowId===lotPlanRow.id).reduce((s,v)=>s+Number(v.montant),0):null;
                const prixReelVsPlan=prixReelVal>0&&planHebdoTotal!==null&&planHebdoTotal>0
                  ?(Math.abs(prixReelVal-planHebdoTotal)<=100?'ok':'diff'):null;
                // Mettre à jour automatiquement le montant de rétrocession calculé si prix/taux change
                const onChangePrixReel=(v)=>{
                  const nv={...lot,prixReel:v};
                  const pr=toN(v), pct=toN(lot.retroPctR);
                  if(pr>0&&pct>0) nv.retroMontantR=String(Math.round(pr*pct/100));
                  onUpdate("lots",lots.map((l,i)=>i===idx?nv:l));
                };
                const onChangeRetroPct=(v)=>{
                  const nv={...lot,retroPctR:v};
                  const pr=toN(lot.prixReel), pct=toN(v);
                  if(pr>0&&pct>0) nv.retroMontantR=String(Math.round(pr*pct/100));
                  onUpdate("lots",lots.map((l,i)=>i===idx?nv:l));
                };
                // Options pour dropdown clients et CGPs du CRM
                const crmClients=(crm?.clients||[]);
                const crmCgps=(crm?.cgps||[]);
                return (
                  <tr key={idx} style={{borderBottom:"1px solid "+C.border}}>
                    <td style={{padding:"4px 6px",fontSize:11,whiteSpace:"nowrap",color:C.muted}}>Lot {idx+1}</td>
                    {["surface","ext","type"].map(f=>(
                      <td key={f} style={{padding:"3px 4px"}}>
                        <input value={lot[f]||""} onChange={e=>updateLot(idx,f,e.target.value)}
                          style={f==="surface"&&surfaceManquante?liWarn:li}/>
                      </td>
                    ))}
                    {!hiddenLotCols.loyerPinel&&(
                    <td style={{padding:"3px 4px"}}>
                      {toN(fiche.plafondPinel)>0&&!lot.loyerPinel?(
                        <div style={{...COUT_CALC,padding:"4px 6px",fontSize:12,minWidth:80,textAlign:"right",cursor:"pointer",display:"flex",alignItems:"center",gap:2}}
                          title={"Pinel calculé : "+fiche.plafondPinel+"€/m² × surface utile × coef. Cliquer pour saisir manuellement."}
                          onClick={()=>updateLot(idx,"loyerPinel",String(lc.loyerPinelCalc))}>
                          <span style={{flex:1}}>{lc.loyerPinelCalc>0?fmtEur(lc.loyerPinelCalc):"—"}</span>
                          <span style={{fontSize:10,color:"#94a3b8"}}>✎</span>
                        </div>
                      ):(
                        <input value={lot.loyerPinel||""} onChange={e=>updateLot(idx,"loyerPinel",e.target.value)}
                          style={toN(fiche.plafondPinel)>0&&lot.loyerPinel?{...liWarn}:li}
                          title={toN(fiche.plafondPinel)>0&&lot.loyerPinel?"Valeur manuelle (calculée automatiquement si effacée)":""}/>
                      )}
                    </td>
                    )}
                    {!hiddenLotCols.prixBrut&&(
                    <td style={{padding:"3px 4px"}}>
                      <OverrideCell calcVal={lc.prixBrutCalc} overrideVal={lot.prixBrutO} onSet={v=>updateLot(idx,"prixBrutO",v)} onClear={()=>updateLot(idx,"prixBrutO","")}/>
                    </td>
                    )}
                    {!hiddenLotCols.ponderation&&(
                    <td style={{padding:"3px 4px"}}>
                      <input value={lot.ponderationPct||""} onChange={e=>updateLot(idx,"ponderationPct",e.target.value)}
                        placeholder="0" style={{...li,width:62,textAlign:"right"}}/>
                    </td>
                    )}
                    {!hiddenLotCols.prixPondere&&(
                    <td style={{padding:"3px 8px",textAlign:"right"}}>
                      <div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,borderColor:"#c7d2fe",color:"#4338ca"}}>
                        {lc.prixPondere>0?fmtEur(lc.prixPondere):"—"}
                      </div>
                    </td>
                    )}
                    <td style={{padding:"3px 4px"}}>
                      <OverrideCell calcVal={lc.prixFinalCalc} overrideVal={lot.prixFinalO} onSet={v=>updateLot(idx,"prixFinalO",v)} onClear={()=>updateLot(idx,"prixFinalO","")}/>
                    </td>
                    <td style={{padding:"3px 8px",textAlign:"right"}}><div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12}}>{lc.pm>0?fmtEur(lc.pm):"—"}</div></td>
                    {!hiddenLotCols.foncier&&(
                    <td style={{padding:"3px 8px",textAlign:"right"}}><div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,borderColor:"#bbf7d0",color:"#15803d"}}>{lc.foncier>0?fmtEur(lc.foncier):"—"}</div></td>
                    )}
                    {!hiddenLotCols.travaux&&(
                    <td style={{padding:"3px 8px",textAlign:"right"}}><div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,borderColor:"#fde68a",color:"#b45309"}}>{lc.travaux>0?fmtEur(lc.travaux):"—"}</div></td>
                    )}
                    <td style={{padding:"3px 4px"}}>
                      {(()=>{
                        const prTitle=!prixReelVal?(canEditReel?"Saisir le prix réel de vente":"Disponible dès qu'une option est posée"):!lotPlanRow?`Aucune ligne plan hebdo pour Lot ${idx+1}`:planHebdoTotal===0?`Ligne plan hebdo trouvée mais total = 0 €`:prixReelVsPlan==='ok'?`✓ Cohérent avec le plan hebdo : ${fmtEur(planHebdoTotal)}`:`⚠️ Plan hebdo : ${fmtEur(planHebdoTotal)} (écart : ${fmtEur(Math.abs(prixReelVal-planHebdoTotal))})`;
                        const prStyle=!canEditReel?{...liReadOnly,width:88}:prixReelVsPlan==='ok'?{...li,width:88,border:"1px solid #16a34a",background:"#f0fdf4",color:"#15803d"}:prixReelVsPlan==='diff'?{...li,width:88,border:"1px solid #dc2626",background:"#fef2f2",color:"#dc2626"}:{...li,width:88};
                        return <input
                          value={lot.prixReel||""}
                          onChange={e=>canEditReel&&onChangePrixReel(e.target.value)}
                          disabled={!canEditReel}
                          title={prTitle}
                          className={prixReelVsPlan==='ok'?'prix-reel-ok':prixReelVsPlan==='diff'?'prix-reel-diff':''}
                          style={prStyle}/>;
                      })()}
                    </td>
                    {!hiddenLotCols.foncierReel&&(
                    <td style={{padding:"3px 4px",textAlign:"right"}}>
                      {prixReelVal>0?(
                        <OverrideCell
                          calcVal={lc.foncierReelCalc}
                          overrideVal={lot.foncierReelO}
                          onSet={v=>updateLot(idx,"foncierReelO",v)}
                          onClear={()=>{
                            onUpdate("lots",lots.map((l,i)=>i===idx?{...l,foncierReelO:""}:l));
                          }}/>
                      ):(
                        <div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,borderColor:"#bbf7d0",color:"#15803d",opacity:0.4,textAlign:"right"}}>—</div>
                      )}
                    </td>
                    )}
                    {!hiddenLotCols.travauxReel&&(
                    <td style={{padding:"3px 8px",textAlign:"right"}}><div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,borderColor:"#fde68a",color:"#b45309",opacity:lc.travauxReel>0?1:0.4}}>{lc.travauxReel>0?fmtEur(lc.travauxReel):"—"}</div></td>
                    )}
                    {!hiddenLotCols.retroPct&&(
                    <td style={{padding:"3px 4px"}}>
                      <input
                        value={lot.retroPctR||""}
                        onChange={e=>canEditReel&&onChangeRetroPct(e.target.value)}
                        disabled={!canEditReel}
                        placeholder="%"
                        title={canEditReel?"Taux de rétrocession réel appliqué au prix réel":"Disponible dès qu'une option est posée"}
                        style={canEditReel?{...li,width:60}:{...liReadOnly,width:60}}/>
                    </td>
                    )}
                    {!hiddenLotCols.retroMontant&&(
                    <td style={{padding:"3px 8px",textAlign:"right"}}>
                      <div style={{...FICHE_CALC,padding:"4px 6px",fontSize:12,borderColor:"#fecaca",color:"#b91c1c"}}>
                        {retroMontantVal>0?fmtEur(retroMontantVal):"—"}
                      </div>
                    </td>
                    )}
                    <td style={{padding:"3px 4px"}}>
                      <select
                        value={lot.clientId||""}
                        onChange={e=>{
                          const cid=e.target.value;
                          const cli=crmClients.find(c=>c.id===cid);
                          const nv={...lot,clientId:cid,clientNom:cli?`${cli.prenom||""} ${cli.nom||""}`.trim():""};
                          // Si le client a un CGP lié et qu'aucun CGP n'est saisi, on le propose
                          if(cli&&cli.cgpId&&!lot.cgpId){
                            const cgp=crmCgps.find(g=>g.id===cli.cgpId);
                            if(cgp){
                              nv.cgpId=cgp.id;
                              nv.cgpNom=(cgp.societe?cgp.societe+" – ":"")+`${cgp.prenom||""} ${cgp.nom||""}`.trim();
                            }
                          }
                          onUpdate("lots",lots.map((l,i)=>i===idx?nv:l));
                        }}
                        style={{...li,width:130,textAlign:"left"}}
                        title="Client (depuis CRM — éditez la fiche client dans l'onglet CRM)">
                        <option value="">— Client —</option>
                        {crmClients.map(c=>(
                          <option key={c.id} value={c.id}>{`${c.prenom||""} ${c.nom||""}`.trim()||"(sans nom)"}</option>
                        ))}
                      </select>
                    </td>
                    <td style={{padding:"3px 4px"}}>
                      <select
                        value={lot.cgpId||""}
                        onChange={e=>{
                          const cid=e.target.value;
                          const cgp=crmCgps.find(g=>g.id===cid);
                          const nv={...lot,cgpId:cid,cgpNom:cgp?((cgp.societe?cgp.societe+" – ":"")+`${cgp.prenom||""} ${cgp.nom||""}`.trim()):""};
                          onUpdate("lots",lots.map((l,i)=>i===idx?nv:l));
                        }}
                        style={{...li,width:140,textAlign:"left"}}
                        title="Gestionnaire / CGP (depuis CRM)">
                        <option value="">— CGP —</option>
                        {crmCgps.map(g=>(
                          <option key={g.id} value={g.id}>{(g.societe?g.societe+" – ":"")+`${g.prenom||""} ${g.nom||""}`.trim()||"(sans nom)"}</option>
                        ))}
                      </select>
                    </td>
                    <td style={{padding:"3px 6px",textAlign:"center"}}>
                      <div style={{display:"flex",flexDirection:"column",alignItems:"center",gap:2}}>
                        <StatutLotBadge statut={lot.statutCommercial||"LIBRE"} onClick={()=>setLotsHistModal({lotIdx:idx})}/>
                        {(()=>{
                          const pct=appelsFonds[String(idx+1)];
                          if(!pct||pct<=0) return null;
                          const bg=pct>=95?"#dcfce7":pct>=50?"#fef9c3":"#ffedd5";
                          const col=pct>=95?"#15803d":pct>=50?"#b45309":"#c2410c";
                          return <span style={{background:bg,color:col,borderRadius:3,padding:"1px 5px",fontSize:9,fontWeight:700,whiteSpace:"nowrap"}}>AV {pct}%</span>;
                        })()}
                      </div>
                    </td>
                    <td style={{padding:"3px 4px",textAlign:"center"}}>
                      {canEditReel&&toN(lot.prixReel)>0&&(
                        <button onClick={()=>setConfirmReelUpdate({lotIdx:idx})} title="Mettre à jour les flux réels dans le Plan Hebdo" style={{background:"none",border:"none",color:"#0d9488",cursor:"pointer",fontSize:13,padding:"2px 4px"}}>🔄</button>
                      )}
                    </td>
                    <td style={{padding:"3px 6px",textAlign:"center"}}><button onClick={()=>onUpdate("lots",lots.filter((_,i)=>i!==idx))} style={{background:"none",border:"none",color:"#94a3b8",cursor:"pointer",fontSize:12}}>✕</button></td>
                  </tr>
                );
              })}
              {(()=>{
                const colSpan1 = 1 /*type*/ + (!hiddenLotCols.loyerPinel?1:0) + (!hiddenLotCols.prixBrut?1:0) + (!hiddenLotCols.ponderation?1:0) + (!hiddenLotCols.prixPondere?1:0);
                const colSpan2 = 1 /*pm*/ + (!hiddenLotCols.foncier?1:0) + (!hiddenLotCols.travaux?1:0) + (!hiddenLotCols.foncierReel?1:0) + (!hiddenLotCols.travauxReel?1:0) + 1 /*prixReel*/ + (!hiddenLotCols.retroPct?1:0) + (!hiddenLotCols.retroMontant?1:0) + 1 /*client*/ + 1 /*cgp*/ + 1 /*statut*/ + 1 /*flux*/ + 1 /*del*/;
                return (
                  <tr style={{background:"#f1f5f9",fontWeight:700}}>
                    <td style={{padding:"6px 8px",color:C.muted,fontSize:11}}>Total</td>
                    <td style={{padding:"6px 8px",textAlign:"right",color:C.text}}>{lots.reduce((s,l)=>s+toN(l.surface),0)||""}</td>
                    <td style={{padding:"6px 8px",textAlign:"right",color:C.text}}>{lots.reduce((s,l)=>s+toN(l.ext),0)||""}</td>
                    <td colSpan={colSpan1}></td>
                    <td style={{padding:"6px 8px",textAlign:"right",color:C.green,fontSize:14}}>{fmtEur(lotsTotal)}</td>
                    <td colSpan={colSpan2}></td>
                  </tr>
                );
              })()}
            </tbody>
          </table>
        </div>
        <button onClick={()=>onUpdate("lots",[...lots,EMPTY_LOT()])}
          style={{marginTop:10,background:"none",border:"1px dashed #334155",borderRadius:6,color:C.muted,fontSize:11,padding:"4px 14px",cursor:"pointer"}}>
          + Ajouter un lot
        </button>
        {confirmReelUpdate!==null&&(() => {
          const lIdx=confirmReelUpdate.lotIdx;
          const l=lots[lIdx]||{};
          const lcCur=lotsCalc[lIdx]||{};
          const pr=toN(l.prixReel);
          const rm=toN(l.retroMontantR);
          const pct=toN(l.retroPctR);
          const fonReel=lcCur.foncierReel||0;
          const travReel=lcCur.travauxReel||0;
          const fonForce=l.foncierReelO!==""&&l.foncierReelO!=null;
          const hasRetro=pct>0;
          return (
            <div style={{position:"fixed",inset:0,background:"#000c",display:"flex",alignItems:"center",justifyContent:"center",zIndex:60}}>
              <div style={{background:C.card,borderRadius:12,border:"1px solid "+C.border,padding:24,width:440}}>
                <div style={{fontWeight:700,fontSize:15,marginBottom:8,color:C.text}}>Mettre à jour les flux ?</div>
                <div style={{fontSize:12,color:C.muted,marginBottom:12}}>
                  Valeurs actuelles du <b>Lot {lIdx+1}</b> :<br/>
                  Prix réel : <b style={{color:C.text}}>{fmtEur(pr)}</b><br/>
                  Foncier réel : <b style={{color:C.text}}>{fmtEur(fonReel)}</b> {fonForce?<span style={{color:"#b45309",fontWeight:600}}>(forcé)</span>:<span style={{color:C.muted}}>(calculé)</span>}<br/>
                  Travaux réels : <b style={{color:C.text}}>{fmtEur(travReel)}</b>
                  {hasRetro&&<><br/>Taux rétro : <b style={{color:C.text}}>{pct.toFixed(2)} %</b> · Rétro réelle : <b style={{color:C.text}}>{fmtEur(rm)}</b></>}
                </div>
                <div style={{fontSize:12,color:C.muted,marginBottom:14}}>
                  Voulez-vous recalculer les flux <b>Lot {lIdx+1}</b> (foncier + travaux){hasRetro&&<> et la <b>Marge CGP</b> du programme</>} avec ces valeurs ?<br/>
                  <span style={{color:"#2563eb"}}>ℹ️ Les flux resteront en statut <b>F</b> (Forecast) tant que le lot n&apos;est pas en statut <b>Vendu</b>. Ils passent en <b>R</b> (Réel) au passage en Vendu.</span>
                </div>
                <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
                  <button onClick={()=>setConfirmReelUpdate(null)} style={{background:"#e2e8f0",border:"none",borderRadius:6,color:C.text,padding:"6px 14px",fontSize:12,cursor:"pointer"}}>Annuler</button>
                  <button onClick={()=>{
                    if(onUpdateLotReelFlows) onUpdateLotReelFlows(lIdx);
                    setConfirmReelUpdate(null);
                  }} style={{background:C.accent,border:"none",borderRadius:6,color:"#fff",padding:"6px 14px",fontSize:12,cursor:"pointer",fontWeight:600}}>Confirmer</button>
                </div>
              </div>
            </div>
          );
        })()}
        {appelModal!==null&&(
          <AppelFondsModal
            lot={lots[appelModal]}
            lotIdx={appelModal}
            proj={proj}
            fiche={fiche}
            lc={lotsCalc[appelModal]||{}}
            rows={rows}
            values={values}
            crm={crm}
            onClose={()=>setAppelModal(null)}
          />
        )}
        {appelModalPost!==null&&(
          <AppelFondsModal
            lot={lots[appelModalPost.lotIdx]}
            lotIdx={appelModalPost.lotIdx}
            proj={proj}
            fiche={fiche}
            lc={lotsCalc[appelModalPost.lotIdx]||{}}
            rows={rows}
            values={values}
            crm={crm}
            callIdxOverride={appelModalPost.callIdx}
            onClose={()=>setAppelModalPost(null)}
          />
        )}
        {lotsHistModal&&(
          <HistoriqueStatutModal
            lot={lots[lotsHistModal.lotIdx]}
            lotIdx={lotsHistModal.lotIdx}
            crm={crm}
            onSave={(lotIdx,newStatut,date,clientNom,note,indivisionData)=>{
              if(onLotStatutChange) onLotStatutChange(lotIdx,newStatut,date,clientNom,note,indivisionData);
              else {
                // Fallback (composant sans onLotStatutChange) — n'arrive pas dans le flow App.jsx,
                // mais on garde le path cohérent au cas où FicheDetail serait utilisée standalone.
                const l=lots[lotIdx];
                updateLot(lotIdx,"statutCommercial",newStatut);
                updateLot(lotIdx,"historiqueStatut",[...(l.historiqueStatut||[]),{date,from:l.statutCommercial||"LIBRE",to:newStatut,clientNom,note}]);
                if(clientNom) updateLot(lotIdx,"clientNom",clientNom);
                if(newStatut==="OPTION" && indivisionData){
                  updateLot(lotIdx,"typeAcquisition",indivisionData.typeAcquisition||"seul");
                  updateLot(lotIdx,"coAcheteurClientId",indivisionData.coAcheteurClientId||"");
                  updateLot(lotIdx,"quotePartPrincipalPct",indivisionData.quotePartPrincipalPct!=null?indivisionData.quotePartPrincipalPct:50);
                }
              }
              // Actions automatiques par statut
              if(newStatut==="OPTION"){
                // Générer la LIA et préparer l'email client
                setLiaModal(lotIdx);
              } else if(newStatut==="LIA"){
                // Préparer l'email au notaire
                setNotaireModal(lotIdx);
              } else if(newStatut==="COMPROMIS"){
                // Email de courtoisie au client + CGP avec compromis signé en PJ
                setCompromisEmailModal({lotIdx});
              } else if(["AV10","AV40","AV70","AV95"].includes(newStatut)){
                // Demande d'attestation à l'architecte (l'appel de fonds suivra après sa réponse)
                setArchitecteModal({lotIdx, statut:newStatut, callIdx:STATUS_TO_CALL_IDX[newStatut]});
              } else if(STATUS_TO_CALL_IDX[newStatut]!==undefined){
                // VENDU, LIVRE → appel de fonds direct
                setAppelModalPost({lotIdx, callIdx: STATUS_TO_CALL_IDX[newStatut]});
              }
              setLotsHistModal(null);
            }}
            onClose={()=>setLotsHistModal(null)}
          />
        )}
        {liaModal!==null&&(
          <LIAModal
            lot={lots[liaModal]}
            lotIdx={liaModal}
            proj={proj}
            fiche={fiche}
            lc={lotsCalc[liaModal]||{}}
            crm={crm}
            onClose={()=>setLiaModal(null)}
          />
        )}
        {notaireModal!==null&&(
          <NotaireEmailModal
            lot={lots[notaireModal]}
            lotIdx={notaireModal}
            proj={proj}
            fiche={fiche}
            lc={lotsCalc[notaireModal]||{}}
            crm={crm}
            onClose={()=>setNotaireModal(null)}
      />
        )}
        {architecteModal!==null&&(
          <ArchitecteEmailModal
            lot={lots[architecteModal.lotIdx]}
            lotIdx={architecteModal.lotIdx}
            proj={proj}
            fiche={fiche}
            crm={crm}
            statut={architecteModal.statut}
            callIdx={architecteModal.callIdx}
            projId={proj.id}
            onClose={()=>setArchitecteModal(null)}
            onAttestationSent={onArchitecteEmailSent}
          />
        )}
        {compromisEmailModal!==null&&(
          <CompromisEmailModal
            lot={lots[compromisEmailModal.lotIdx]}
            lotIdx={compromisEmailModal.lotIdx}
            proj={proj}
            fiche={fiche}
            crm={crm}
            onClose={()=>setCompromisEmailModal(null)}
          />
        )}
      </FicheSection>

      <FicheSection title="📝 Notes" color={C.muted}>
        <textarea value={fiche.notes||""} onChange={e=>onUpdate("notes",e.target.value)}
          style={{background:"#f8fafc",border:"1px solid "+C.border,borderRadius:8,color:C.text,padding:"8px 12px",fontSize:13,width:"100%",height:80,resize:"vertical"}}/>
      </FicheSection>
    </div>
  );
}

function NewProgrammeModal({onCreate,onClose}) {
  const [ville,setVille]=useState("");
  const [nom,setNom]=useState("");
  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:14,padding:24,width:360,boxShadow:"0 8px 40px #0003"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:16,marginBottom:16}}>Nouveau programme</div>
        <input placeholder="Ville" value={ville} onChange={e=>setVille(e.target.value)}
          style={{background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"8px 12px",fontSize:13,width:"100%",marginBottom:10}}/>
        <input placeholder="Nom du programme" value={nom} onChange={e=>setNom(e.target.value)}
          style={{background:"#f8fafc",border:"1px solid #cbd5e1",borderRadius:8,color:"#1e293b",padding:"8px 12px",fontSize:13,width:"100%",marginBottom:16}}/>
        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 16px",fontSize:13,cursor:"pointer"}}>Annuler</button>
          <button onClick={()=>{if(ville.trim()||nom.trim())onCreate(ville.trim(),nom.trim());}}
            style={{background:"#2563eb",color:"#fff",border:"none",borderRadius:8,padding:"8px 20px",fontSize:13,fontWeight:700,cursor:"pointer"}}>Créer</button>
        </div>
      </div>
    </div>
  );
}

function DeleteProgrammeModal({proj,onConfirm,onClose}) {
  return(
    <div style={{position:"fixed",inset:0,background:"rgba(0,0,0,0.5)",zIndex:2000,display:"flex",alignItems:"center",justifyContent:"center"}} onClick={onClose}>
      <div style={{background:"#fff",borderRadius:14,padding:24,width:400,boxShadow:"0 8px 40px #0003"}} onClick={e=>e.stopPropagation()}>
        <div style={{fontWeight:800,fontSize:16,marginBottom:12}}>Supprimer le programme ?</div>
        <div style={{fontSize:13,color:"#64748b",marginBottom:20}}>
          Cette action supprimera définitivement <b>{proj.ville&&proj.ville+" — "}{proj.nom}</b> et toutes ses données (lots, flux, intervenants). Cette opération est irréversible.
        </div>
        <div style={{display:"flex",gap:8,justifyContent:"flex-end"}}>
          <button onClick={onClose} style={{background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 16px",fontSize:13,cursor:"pointer"}}>Annuler</button>
          <button onClick={onConfirm} style={{background:"#dc2626",color:"#fff",border:"none",borderRadius:8,padding:"8px 20px",fontSize:13,fontWeight:700,cursor:"pointer"}}>Supprimer</button>
        </div>
      </div>
    </div>
  );
}


/* Calcule la variation de solde par rapport à un snapshot passé.
   Retourne aussi snapshotDate (ISO YYYY-MM-DD) pour requêter l'historique des écritures. */
function calcVariation(soldeFinOp, progHistory, daysAgo) {
  const targetDate = new Date();
  targetDate.setDate(targetDate.getDate() - daysAgo);
  const targetStr = targetDate.toISOString().slice(0, 10);
  // Snapshot le plus récent sur ou avant targetDate (liste déjà triée DESC)
  const snap = progHistory.find(h => h.snapshot_date <= targetStr);
  if (!snap) return null;
  const prev = Number(snap.solde);
  if (prev === 0) return null;
  const diff = soldeFinOp - prev;
  const pct  = (diff / Math.abs(prev)) * 100;
  return { diff, pct, snapshotDate: snap.snapshot_date, snapshotSolde: prev };
}

/* Affiche une puce de variation : ↑ +2.3% (+12 340 €) SEM
   Cliquable si onClick fourni : ouvre la modale détaillée des écritures. */
function VariationPuce({variation, label, onClick}) {
  if (!variation) return null;
  const { diff, pct } = variation;
  const isPos   = diff >= 0;
  const isAlert = Math.abs(pct) > 5;
  const color   = isAlert ? '#dc2626' : isPos ? '#15803d' : '#64748b';
  const arrow   = isPos ? '↑' : '↓';
  const sign    = isPos ? '+' : '';
  const baseStyle = {color, fontSize:10, whiteSpace:'nowrap'};
  if (!onClick) {
    return <span style={baseStyle}>{arrow} {sign}{pct.toFixed(1)}% ({sign}{fmtEur(diff)}) {label}</span>;
  }
  return (
    <button
      onClick={e=>{e.stopPropagation(); onClick();}}
      title="Voir les écritures qui expliquent cette variation"
      style={{...baseStyle, background:'none', border:'none', padding:0, margin:0,
        cursor:'pointer', textDecoration:'underline', textDecorationStyle:'dotted',
        textUnderlineOffset:2, fontFamily:'inherit'}}>
      {arrow} {sign}{pct.toFixed(1)}% ({sign}{fmtEur(diff)}) {label}
    </button>
  );
}

/* Modale détaillant les écritures qui expliquent une variation. */
function VariationDetailsModal({proj, periodLabel, variation, currentSolde, onClose}) {
  const [entries, setEntries] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError]     = useState(null);
  const [expanded, setExpanded] = useState(new Set());

  const SOURCE_META = {
    manual:        { label: 'Manuel',     color: '#2563eb', bg: '#eff6ff' },
    status_change: { label: 'Statut lot', color: '#7c3aed', bg: '#f5f3ff' },
    appel_eg:      { label: 'Appel EG',   color: '#d97706', bg: '#fffbeb' },
    facture:       { label: 'Facture',    color: '#059669', bg: '#f0fdf4' },
  };
  const sourceMeta = s => SOURCE_META[s] || { label: s || '?', color: '#6b7280', bg: '#f9fafb' };

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    const params = new URLSearchParams({ projet_id: proj.id, since: variation.snapshotDate, limit: '500' });
    fetch('/api/plan-changes?' + params.toString())
      .then(r => r.ok ? r.json() : Promise.reject(new Error('Erreur ' + r.status)))
      .then(d => { if (!cancelled) setEntries(d.summary || []); })
      .catch(e => { if (!cancelled) setError(e.message); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [proj.id, variation.snapshotDate]);

  // ── Réconciliation ─────────────────────────────────────────────────────────
  const totalExplique = entries.reduce((s, e) => s + (Number(e.delta_total) || 0), 0);
  const variationAffichee = variation.diff;
  const ecart = variationAffichee - totalExplique;
  const seuilOk = Math.abs(ecart) <= 10;

  // ── Helpers formatage ──────────────────────────────────────────────────────
  function fmtDateTime(iso) {
    if (!iso) return '—';
    const d = new Date(iso);
    return d.toLocaleDateString('fr-FR', { day:'2-digit', month:'2-digit', year:'2-digit' })
      + ' ' + d.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' });
  }
  function fmtSigned(n) {
    if (n == null) return '—';
    const v = Number(n);
    const s = Math.abs(v).toLocaleString('fr-FR', { minimumFractionDigits:0, maximumFractionDigits:0 });
    return (v >= 0 ? '+' : '−') + ' ' + s + ' €';
  }
  function deltaColor(n) {
    if (n == null) return C.muted;
    return Number(n) >= 0 ? '#059669' : '#dc2626';
  }
  function toggleExpand(id) {
    setExpanded(prev => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }

  const th = { padding:'8px 12px', textAlign:'left', fontSize:11, fontWeight:700,
               color:C.muted, borderBottom:'1px solid '+C.border, whiteSpace:'nowrap' };
  const td = { padding:'8px 12px', fontSize:13, borderBottom:'1px solid '+C.border, verticalAlign:'top' };

  return (
    <div onClick={e=>{e.stopPropagation(); onClose();}}
      style={{position:'fixed', inset:0, background:'rgba(15,23,42,0.55)', zIndex:1000,
              display:'flex', alignItems:'flex-start', justifyContent:'center', padding:'40px 16px', overflowY:'auto'}}>
      <div onClick={e=>e.stopPropagation()}
        style={{background:C.card, borderRadius:12, maxWidth:980, width:'100%', boxShadow:'0 20px 50px #0004', overflow:'hidden'}}>

        {/* ── En-tête ── */}
        <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start',
                     padding:'18px 24px', borderBottom:'1px solid '+C.border}}>
          <div>
            <div style={{fontSize:16, fontWeight:800, color:C.text}}>
              Variation {periodLabel} — {proj.ville ? proj.ville+' — ' : ''}{proj.nom}
            </div>
            <div style={{fontSize:12, color:C.muted, marginTop:4}}>
              Depuis le snapshot du {variation.snapshotDate}
            </div>
          </div>
          <button onClick={onClose}
            style={{background:'none', border:'none', cursor:'pointer', fontSize:22, color:C.muted, padding:'0 4px'}}>×</button>
        </div>

        {/* ── Réconciliation ── */}
        <div style={{padding:'14px 24px', background:C.bg, borderBottom:'1px solid '+C.border,
                     display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:16}}>
          <div>
            <div style={{fontSize:10, color:C.muted, textTransform:'uppercase', letterSpacing:0.5, marginBottom:2}}>
              Variation affichée
            </div>
            <div style={{fontSize:16, fontWeight:700, color:deltaColor(variationAffichee)}}>
              {fmtSigned(variationAffichee)}
            </div>
            <div style={{fontSize:11, color:C.muted, marginTop:2}}>
              {fmtEur(variation.snapshotSolde)} → {fmtEur(currentSolde)}
            </div>
          </div>
          <div>
            <div style={{fontSize:10, color:C.muted, textTransform:'uppercase', letterSpacing:0.5, marginBottom:2}}>
              Total expliqué (écritures)
            </div>
            <div style={{fontSize:16, fontWeight:700, color:deltaColor(totalExplique)}}>
              {fmtSigned(totalExplique)}
            </div>
            <div style={{fontSize:11, color:C.muted, marginTop:2}}>
              {entries.length} écriture{entries.length !== 1 ? 's' : ''}
            </div>
          </div>
          <div>
            <div style={{fontSize:10, color:C.muted, textTransform:'uppercase', letterSpacing:0.5, marginBottom:2}}>
              Écart
            </div>
            <div style={{fontSize:16, fontWeight:700, color: seuilOk ? '#059669' : '#d97706'}}>
              {fmtSigned(ecart)}
            </div>
            <div style={{fontSize:11, color: seuilOk ? '#059669' : '#d97706', marginTop:2}}>
              {seuilOk ? '✓ Réconciliation OK (≤ 10 €)' : '⚠ Écart non tracé dans les écritures'}
            </div>
          </div>
        </div>

        {!seuilOk && !loading && !error && (
          <div style={{padding:'10px 24px', background:'#fffbeb', borderBottom:'1px solid '+C.border,
                       fontSize:12, color:'#92400e'}}>
            Une partie de la variation ({fmtSigned(ecart)}) n'est pas couverte par les écritures listées —
            probablement liée à un changement de fiche/lots ou à des snapshots manquants sur la période.
          </div>
        )}

        {/* ── Tableau écritures ── */}
        <div style={{maxHeight:'60vh', overflowY:'auto'}}>
          {loading && <div style={{padding:48, textAlign:'center', color:C.muted}}>Chargement…</div>}
          {error && (
            <div style={{margin:24, padding:'12px 16px', background:'#fef2f2', border:'1px solid #fecaca',
                         borderRadius:8, color:'#dc2626', fontSize:13}}>
              Erreur : {error}
            </div>
          )}
          {!loading && !error && entries.length === 0 && (
            <div style={{padding:48, textAlign:'center', color:C.muted, fontSize:14}}>
              Aucune écriture enregistrée sur cette période.
            </div>
          )}
          {!loading && !error && entries.length > 0 && (
            <table style={{width:'100%', borderCollapse:'collapse'}}>
              <thead>
                <tr style={{background:C.bg, position:'sticky', top:0}}>
                  <th style={th}>Date</th>
                  <th style={th}>Lot</th>
                  <th style={th}>Action</th>
                  <th style={th}>Source</th>
                  <th style={{...th, textAlign:'right'}}>Impact</th>
                  <th style={{...th, textAlign:'center'}}>Cellules</th>
                  <th style={{...th, textAlign:'center'}}>Détail</th>
                </tr>
              </thead>
              <tbody>
                {entries.map(entry => {
                  const sm = sourceMeta(entry.source);
                  const isOpen = expanded.has(entry.id);
                  const hasVerbose = entry.verbose && entry.verbose.length > 0;
                  return (
                    <React.Fragment key={entry.id}>
                      <tr style={{background: isOpen ? C.bg : 'transparent'}}>
                        <td style={{...td, fontSize:12, color:C.muted, whiteSpace:'nowrap'}}>
                          {fmtDateTime(entry.created_at)}
                        </td>
                        <td style={{...td, color:C.muted}}>{entry.lot_numero || '—'}</td>
                        <td style={td}>
                          <div style={{fontWeight:500}}>{entry.action_label}</div>
                          {entry.weeks_concernees && (
                            <div style={{fontSize:11, color:C.muted, marginTop:2}}>{entry.weeks_concernees}</div>
                          )}
                        </td>
                        <td style={td}>
                          <span style={{background:sm.bg, color:sm.color, borderRadius:5,
                                        padding:'2px 8px', fontSize:11, fontWeight:700}}>{sm.label}</span>
                        </td>
                        <td style={{...td, textAlign:'right', fontWeight:700, color:deltaColor(entry.delta_total)}}>
                          {fmtSigned(entry.delta_total)}
                        </td>
                        <td style={{...td, textAlign:'center', color:C.muted, fontSize:12}}>{entry.nb_cellules}</td>
                        <td style={{...td, textAlign:'center'}}>
                          {hasVerbose ? (
                            <button onClick={()=>toggleExpand(entry.id)}
                              style={{background:'none', border:'none', cursor:'pointer',
                                      color:C.accent, fontSize:18, lineHeight:1}}>
                              {isOpen ? '▲' : '▼'}
                            </button>
                          ) : (
                            <span style={{color:C.muted, fontSize:11}} title="Détail expiré (> 7 jours)">—</span>
                          )}
                        </td>
                      </tr>
                      {isOpen && hasVerbose && (
                        <tr>
                          <td colSpan={7} style={{padding:0}}>
                            <div style={{background:C.bg, borderTop:'1px solid '+C.border,
                                         borderBottom:'1px solid '+C.border, padding:'8px 16px 12px'}}>
                              <table style={{width:'100%', borderCollapse:'collapse', fontSize:12}}>
                                <thead>
                                  <tr>
                                    <th style={{...th, fontSize:10, padding:'4px 8px'}}>Ligne</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px'}}>Semaine</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px', textAlign:'right'}}>Avant</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px', textAlign:'right'}}>Après</th>
                                    <th style={{...th, fontSize:10, padding:'4px 8px', textAlign:'right'}}>Δ</th>
                                  </tr>
                                </thead>
                                <tbody>
                                  {entry.verbose.map((v, i) => (
                                    <tr key={i}>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, color:C.text}}>{v.rowLabel}</td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, color:C.muted, fontFamily:'monospace'}}>{v.weekIso}</td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, textAlign:'right', color:C.muted}}>
                                        {Number(v.montantAvant).toLocaleString('fr-FR', {minimumFractionDigits:0, maximumFractionDigits:0})} €
                                      </td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, textAlign:'right', fontWeight:600}}>
                                        {Number(v.montantApres).toLocaleString('fr-FR', {minimumFractionDigits:0, maximumFractionDigits:0})} €
                                      </td>
                                      <td style={{padding:'3px 8px', borderBottom:'1px solid '+C.border, textAlign:'right', fontWeight:700, color:deltaColor(v.delta)}}>
                                        {fmtSigned(v.delta)}
                                      </td>
                                    </tr>
                                  ))}
                                </tbody>
                              </table>
                            </div>
                          </td>
                        </tr>
                      )}
                    </React.Fragment>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}

function ProgrammeCard({proj,fiche,soldeFinOp,history,onSelect,onDelete}) {
  const statut=STATUT_GROUPES.find(s=>s.key===(fiche.statut||"En cours"))||STATUT_GROUPES[0];
  const lots=(fiche.lots||[]);
  const nbLots=lots.length;
  const nbVendus=lots.filter(l=>["VENDU","AV10","AV40","AV70","AV95","LIVRE"].includes(l.statutCommercial||"")).length;
  // Historique filtré sur ce programme, déjà trié DESC par le serveur
  const progHistory=(history||[]).filter(h=>h.programme_id===proj.id);
  const j1 =calcVariation(soldeFinOp,progHistory,1);
  const wtd=calcVariation(soldeFinOp,progHistory,7);
  const mtd=calcVariation(soldeFinOp,progHistory,30);
  const qtd=calcVariation(soldeFinOp,progHistory,90);
  const hasVariations=j1||wtd||mtd||qtd;
  // État de la modale : null ou { variation, periodLabel }
  const [modal, setModal] = useState(null);
  return(
    <div onClick={()=>onSelect(proj.id)}
      style={{background:"#fff",borderRadius:12,border:"1px solid #e2e8f0",padding:16,cursor:"pointer",
        boxShadow:"0 1px 4px #0001",transition:"box-shadow .15s"}}
      onMouseEnter={e=>e.currentTarget.style.boxShadow="0 4px 16px #0002"}
      onMouseLeave={e=>e.currentTarget.style.boxShadow="0 1px 4px #0001"}>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start",marginBottom:8}}>
        <div>
          <div style={{fontWeight:800,fontSize:14,color:"#1e293b",marginBottom:2}}>
            {proj.ville?proj.ville+" — ":""}{proj.nom}
          </div>
          <span style={{background:statut.bg,color:statut.color,borderRadius:10,padding:"2px 10px",
            fontSize:11,fontWeight:700}}>
            <span style={{width:7,height:7,borderRadius:"50%",background:statut.dot,
              display:"inline-block",marginRight:5,verticalAlign:"middle"}}/>
            {statut.label}
          </span>
        </div>
        <button onClick={e=>{e.stopPropagation();onDelete(proj.id);}}
          style={{background:"none",border:"none",cursor:"pointer",color:"#94a3b8",fontSize:16,padding:4}}
          title="Supprimer">🗑</button>
      </div>
      {nbLots>0&&(
        <div style={{fontSize:12,color:"#64748b",marginBottom:8}}>
          {nbVendus}/{nbLots} lot{nbLots>1?"s":""} vendu{nbVendus>1?"s":""}
        </div>
      )}
      <div style={{paddingTop:10,borderTop:"1px solid #e2e8f0"}}>
        <div style={{fontWeight:700,color:soldeFinOp>=0?"#2563eb":"#dc2626",fontSize:13,marginBottom:hasVariations?4:0}}>
          {fmtEur(soldeFinOp)} <span style={{fontSize:10,color:"#94a3b8",fontWeight:400}}>fin d'opération</span>
        </div>
        {hasVariations&&(
          <div style={{display:"flex",flexWrap:"wrap",gap:"6px 10px"}}>
            <VariationPuce variation={j1}  label="24H"  onClick={()=>setModal({variation:j1,  periodLabel:'24H'})}/>
            <VariationPuce variation={wtd} label="SEM"  onClick={()=>setModal({variation:wtd, periodLabel:'sur 7 jours'})}/>
            <VariationPuce variation={mtd} label="MOIS" onClick={()=>setModal({variation:mtd, periodLabel:'sur 30 jours'})}/>
            <VariationPuce variation={qtd} label="TRIM" onClick={()=>setModal({variation:qtd, periodLabel:'sur 90 jours'})}/>
          </div>
        )}
      </div>
      {modal && (
        <VariationDetailsModal
          proj={proj}
          periodLabel={modal.periodLabel}
          variation={modal.variation}
          currentSolde={soldeFinOp}
          onClose={()=>setModal(null)}/>
      )}
    </div>
  );
}

function ProgrammesView({projects,fiches,values,rows,onSelect,onCreate,onDelete}) {
  const [showNew,setShowNew]=useState(false);
  const [history,setHistory]=useState([]);

  // Charge l'historique solde au montage (silencieux si vide — les snapshots n'existent pas encore)
  React.useEffect(()=>{
    fetch('/api/solde-history')
      .then(r=>r.ok?r.json():[])
      .then(data=>setHistory(Array.isArray(data)?data:[]))
      .catch(()=>{});
  },[]);

  const projs=projects.filter(p=>!p.isGlobal);
  const grouped=STATUT_GROUPES.map(sg=>({
    ...sg,
    items:projs.filter(p=>{const s=(fiches[p.id]||EMPTY_FICHE()).statut;return s===sg.key;})
  })).filter(g=>g.items.length>0);
  return(
    <div style={{padding:20,maxWidth:960,margin:"0 auto"}}>
      <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:20}}>
        <div style={{fontWeight:800,fontSize:20,color:"#1e293b"}}>Programmes</div>
        <button onClick={()=>setShowNew(true)}
          style={{background:"#2563eb",color:"#fff",border:"none",borderRadius:8,
            padding:"8px 18px",fontSize:13,fontWeight:700,cursor:"pointer"}}>+ Nouveau programme</button>
      </div>
      {grouped.length===0&&(
        <div style={{textAlign:"center",padding:48,color:"#94a3b8",fontSize:14}}>
          Aucun programme. Cliquez sur "+ Nouveau programme" pour commencer.
        </div>
      )}
      {grouped.map(g=>(
        <div key={g.key} style={{marginBottom:28}}>
          <div style={{fontWeight:700,fontSize:13,color:g.color,marginBottom:10,
            display:"flex",alignItems:"center",gap:8}}>
            <span style={{width:9,height:9,borderRadius:"50%",background:g.dot,display:"inline-block"}}/>
            {g.label}
            <span style={{background:g.bg,borderRadius:10,padding:"1px 8px",fontSize:11}}>{g.items.length}</span>
          </div>
          <div style={{display:"grid",gridTemplateColumns:"repeat(auto-fill,minmax(260px,1fr))",gap:12}}>
            {g.items.map(p=>{
              const soldeFinOp=values.filter(v=>{const r=rows.find(r=>r.id===v.rowId);return r&&r.projetId===p.id&&!r.hidden;}).reduce((s,v)=>s+v.montant,0);
              return(
                <ProgrammeCard key={p.id} proj={p} fiche={fiches[p.id]||EMPTY_FICHE()}
                  soldeFinOp={soldeFinOp} history={history} onSelect={onSelect} onDelete={onDelete}/>
              );
            })}
          </div>
        </div>
      ))}
      {showNew&&<NewProgrammeModal onCreate={(v,n)=>{onCreate(v,n);setShowNew(false);}} onClose={()=>setShowNew(false)}/>}
    </div>
  );
}
