// ─────────────────────────────────────────────────────
// PRESUPUESTOS — list, detail, lines editor and conversion.
// ─────────────────────────────────────────────────────

const PRESU_ESTADOS = ['Borrador', 'Enviado', 'Aceptado', 'Rechazado', 'Cancelado'];

const presuFmtEur = (n) => n == null || Number.isNaN(Number(n))
  ? '—'
  : new Intl.NumberFormat('es-ES', { style:'currency', currency:'EUR', minimumFractionDigits:2, maximumFractionDigits:2 }).format(Number(n));


const presuEstadoClass = (estado) => {
  const e = String(estado || '').toLowerCase();
  if (e.includes('acept')) return 'ok';
  if (e.includes('rech') || e.includes('cancel')) return 'err';
  if (e.includes('enviado')) return 'info';
  return 'warn';
};

const PresuEstadoPill = ({ estado }) => <span className={`pill ${presuEstadoClass(estado)}`}><span className="d"/>{estado || 'Borrador'}</span>;

const presuIsAceptado = (estado) => String(estado || '').toLowerCase().includes('acept');
const presuIsEnviado = (estado) => String(estado || '').toLowerCase().includes('envi');

// Teléfono español → formato internacional para wa.me (solo dígitos, prefijo 34).
const presuWaPhone = (tlf) => {
  const d = String(tlf || '').replace(/\D/g, '');
  if (!d) return '';
  return d.length === 9 ? `34${d}` : d;
};

// Cuerpo del mensaje compartido por WhatsApp / email.
const presuMensaje = (detail) => {
  const nombre = (detail?.cliente_snapshot?.nombre || '').trim();
  const saludo = nombre ? `Hola ${nombre},` : 'Hola,';
  return `${saludo}\n\nTe enviamos el presupuesto ${detail?.numero_presupuesto || ''} de Kurtenglass (adjunto en PDF).\n\nQuedamos a tu disposición para cualquier consulta.\n\nUn saludo.`;
};

// Generic confirmation modal used by GUT-30 flows.
const PresuConfirmModal = ({ title, children, onCancel, onConfirm, confirmLabel = 'Confirmar', confirmDisabled = false, danger = false }) => (
  <div className="modal-backdrop">
    <div className="modal-card">
      <header>
        <h2>{title}</h2>
        <button className="x" onClick={onCancel}>×</button>
      </header>
      <div style={{padding:'4px 2px', fontSize:13}}>{children}</div>
      <footer>
        <button className="btn ghost" onClick={onCancel}>Cancelar</button>
        <button className={`btn ${danger ? 'primary' : 'primary'}`} style={danger ? {background:'var(--err)', borderColor:'var(--err)'} : undefined} onClick={onConfirm} disabled={confirmDisabled}>{confirmLabel}</button>
      </footer>
    </div>
  </div>
);

// Tabla simple de OCs (numero, estado) usada en los modales de impacto.
const PresuOCsTable = ({ ocs }) => (
  <div className="tbl-wrap" style={{marginTop:8, maxHeight:200, overflow:'auto'}}>
    <table className="tbl">
      <thead><tr><th>Nº</th><th>Estado</th><th>Pedida</th></tr></thead>
      <tbody>
        {(ocs || []).map(oc => (
          <tr key={oc.id}>
            <td className="mono">{oc.numero || oc.id}</td>
            <td>{oc.estado || '—'}</td>
            <td>{oc.pedida ? 'Sí' : 'No'}</td>
          </tr>
        ))}
      </tbody>
    </table>
  </div>
);

// Lead selector: search-as-you-type by name / nº presupuesto / teléfono.
const LeadSelector = ({ value, onChange }) => {
  const [q, setQ] = React.useState('');
  const [results, setResults] = React.useState([]);
  const [open, setOpen] = React.useState(false);
  const [loading, setLoading] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('click', close);
    return () => document.removeEventListener('click', close);
  }, []);

  React.useEffect(() => {
    if (!q.trim() || q.trim().length < 2) { setResults([]); return; }
    let alive = true;
    setLoading(true);
    const t = setTimeout(() => {
      API.get(`/api/records?search=${encodeURIComponent(q.trim())}`)
        .then(rows => { if (alive) setResults((rows || []).slice(0, 12)); })
        .catch(() => { if (alive) setResults([]); })
        .finally(() => { if (alive) setLoading(false); });
    }, 250);
    return () => { alive = false; clearTimeout(t); };
  }, [q]);

  const pick = (r) => {
    onChange({
      id: r.id,
      numero_presupuesto: r.numero_presupuesto,
      nombre: r.nombre,
      email: r.email,
      telefono: r.telefono,
      direccion: r.direccion,
      localidad: r.localidad,
      provincia: r.provincia,
    });
    setQ(`${r.numero_presupuesto || ''} · ${r.nombre || ''}`);
    setOpen(false);
  };

  const clear = () => { onChange(null); setQ(''); setResults([]); };

  return (
    <div ref={ref} style={{position:'relative'}}>
      <input
        value={value ? `${value.numero_presupuesto || ''} · ${value.nombre || ''}` : q}
        readOnly={!!value}
        placeholder="Busca por nombre, teléfono o nº presupuesto…"
        onFocus={() => !value && setOpen(true)}
        onChange={e => { setQ(e.target.value); setOpen(true); }}
      />
      {value && (
        <button type="button" className="icon-btn" onClick={clear}
          style={{position:'absolute',right:4,top:4}} title="Quitar lead enlazado">
          <Icon name="x" size={12}/>
        </button>
      )}
      {open && !value && (q.trim().length >= 2) && (
        <div style={{position:'absolute',top:'calc(100% + 2px)',left:0,right:0,zIndex:60,
          background:'var(--surface)',border:'1px solid var(--line)',borderRadius:6,
          boxShadow:'0 8px 24px rgba(0,0,0,.12)',maxHeight:240,overflow:'auto'}}>
          {loading && <div className="muted" style={{padding:8,fontSize:12}}>Buscando…</div>}
          {!loading && results.length === 0 && <div className="muted" style={{padding:8,fontSize:12}}>Sin resultados</div>}
          {!loading && results.map(r => (
            <button key={r.id} type="button" onClick={() => pick(r)}
              style={{display:'block',width:'100%',textAlign:'left',padding:'6px 10px',
                border:'none',background:'transparent',fontSize:12.5,cursor:'pointer',borderBottom:'1px solid var(--line)'}}>
              <strong className="mono">{r.numero_presupuesto || '—'}</strong>
              <span style={{marginLeft:8}}>{r.nombre || '—'}</span>
              <span className="muted" style={{marginLeft:8,fontSize:11}}>{r.telefono || ''} {r.localidad ? `· ${r.localidad}` : ''}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
};

const PresupuestoHeaderForm = ({ initial, leadPreset, onClose, onSaved, onToast }) => {
  const [data, setData] = React.useState({
    // Mismo selector que en leads-form: `vendedor` es el código del comercial
    // (string, p.ej. "KURTEN"). Default = vendedor del presupuesto o del lead.
    vendedor: initial?.vendedor || leadPreset?.vendedor || '',
    estado: initial?.estado || 'Borrador',
    descuento_pct: initial?.descuento_pct ?? 0,
    iva_pct: initial?.iva_pct ?? 21,
    km_ida: initial?.km_ida ?? '',
    observaciones: initial?.observaciones || '',
    incluye: initial?.incluye || '',
    no_incluye: initial?.no_incluye || '',
  });
  const [lead, setLead] = React.useState(leadPreset || null);
  const [saving, setSaving] = React.useState(false);
  // Lista de vendedores: misma fuente que el selector de leads-form
  // (`/api/filters` → vendedores; fallback al listado estático).
  const [vendedores, setVendedores] = React.useState([]);

  React.useEffect(() => {
    let alive = true;
    API.get('/api/filters')
      .then(f => { if (alive) setVendedores(Array.isArray(f?.vendedores) ? f.vendedores : []); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);
  const vendList = vendedores && vendedores.length ? vendedores : window.DATA.VENDEDORES.map(v => v.code);
  const set = (k, v) => setData(d => ({ ...d, [k]: v }));
  const isEdit = !!initial?.id;
  const startedAceptado = isEdit && presuIsAceptado(initial?.estado);
  const isLocked = isEdit && presuIsAceptado(data.estado) && startedAceptado;

  // GUT-30: estado de los modales para cambio de estado desde ACEPTADO y lock.
  const [stateChange, setStateChange] = React.useState(null); // { nuevoEstado, impact, requiereDecision, forceDeleteOCs }
  const [showLockWarn, setShowLockWarn] = React.useState(false);

  const onEstadoChange = (nuevoEstado) => {
    // El cambio de estado se valida al guardar. Aquí solo aplicamos al form.
    set('estado', nuevoEstado);
  };

  const buildPayload = () => {
    const payload = {
      // Igual que leads-form: enviamos el código del vendedor como string.
      // El backend lo resuelve a comercial_id vía resolveComercialId.
      vendedor: data.vendedor || null,
      estado: data.estado,
      descuento_pct: Number(data.descuento_pct) || 0,
      iva_pct: Number(data.iva_pct) || 21,
      km_ida: data.km_ida === '' ? null : Number(data.km_ida),
      observaciones: data.observaciones || null,
      incluye: data.incluye || null,
      no_incluye: data.no_incluye || null,
    };
    if (!isEdit && lead?.id) {
      payload.lead_id = lead.id;
      payload.cliente_snapshot = {
        nombre: lead.nombre || '',
        email: lead.email || '',
        telefono: lead.telefono || '',
        direccion: lead.direccion || '',
        localidad: lead.localidad || '',
        provincia: lead.provincia || '',
      };
    }
    return payload;
  };

  const doSave = async (extra = {}) => {
    setSaving(true);
    const payload = { ...buildPayload(), ...extra };
    try {
      const result = isEdit
        ? await API.put(`/api/presupuestos/${initial.id}`, payload)
        : await API.post('/api/presupuestos', payload);
      onToast && onToast(isEdit ? 'Presupuesto guardado ✓' : `Creado ${result.numero_presupuesto} ✓`);
      onSaved && onSaved(result.id || initial.id);
      onClose && onClose();
    } catch (e) {
      if (e?.status === 409 && e?.data?.error === 'PRESUPUESTO_BLOQUEADO') {
        onToast && onToast('Presupuesto aceptado: cambia el estado antes de editar líneas o lead.', true);
        setShowLockWarn(true);
      } else if (e?.status === 409 && e?.data?.error === 'OC_EN_FLUJO') {
        onToast && onToast('Hay OCs en flujo. Revisa el cambio de estado.', true);
      } else {
        onToast && onToast(e.message || 'Error al guardar presupuesto', true);
      }
    } finally {
      setSaving(false);
    }
  };

  const save = async () => {
    // Si el usuario cambió estado desde ACEPTADO → consultar impacto antes de guardar.
    if (isEdit && startedAceptado && data.estado !== initial.estado) {
      try {
        const r = await API.get(`/api/presupuestos/${initial.id}/state-change-impact`, { nuevoEstado: data.estado });
        const ocs = r?.ocs || [];
        const requiereDecision = !!r?.requiereDecision;
        if (ocs.length === 0) {
          await doSave();
          return;
        }
        setStateChange({ nuevoEstado: data.estado, ocs, requiereDecision, forceDeleteOCs: requiereDecision ? null : false });
        return;
      } catch (e) {
        onToast && onToast(e.message || 'Error al consultar impacto del cambio de estado', true);
        return;
      }
    }
    await doSave();
  };

  const confirmStateChange = async () => {
    if (!stateChange) return;
    if (stateChange.requiereDecision && stateChange.forceDeleteOCs == null) return;
    const extra = { estado: stateChange.nuevoEstado };
    if (stateChange.forceDeleteOCs != null) extra.forceDeleteOCs = !!stateChange.forceDeleteOCs;
    setStateChange(null);
    set('estado', stateChange.nuevoEstado);
    await doSave(extra);
  };

  // Handlers para detectar interacción cuando el presupuesto está bloqueado (estado=ACEPTADO).
  const lockGuard = isLocked
    ? { onFocus: () => setShowLockWarn(true), onMouseDown: (e) => { e.preventDefault(); setShowLockWarn(true); } }
    : {};

  return (
    <div className="modal-backdrop">
      <div className="modal-card presu-modal">
        <header>
          <h2>{isEdit ? `Editar ${initial.numero_presupuesto}` : 'Nuevo presupuesto'}</h2>
          <button className="x" onClick={onClose}>×</button>
        </header>
        {isLocked && (
          <div className="muted" style={{padding:'6px 10px', background:'#fff7e6', border:'1px solid #f0c674', borderRadius:6, marginBottom:8, fontSize:12}}>
            Presupuesto aceptado: edición de líneas y lead bloqueada. Cambia el estado para editar.
          </div>
        )}
        {!isEdit && (
          <div className="field full">
            <label>Lead enlazado {leadPreset ? '(precargado desde el lead)' : '(opcional)'}</label>
            <LeadSelector value={lead} onChange={setLead}/>
          </div>
        )}
        {isEdit && isLocked && (
          <div className="field full">
            <label>Lead enlazado</label>
            <input readOnly value={initial?.cliente_snapshot?.nombre || '—'} {...lockGuard}/>
          </div>
        )}
        <div className="fields-grid">
          <div className="field"><label>Vendedor</label>
            <select value={data.vendedor || ''} onChange={e => set('vendedor', e.target.value)} disabled={isLocked} {...lockGuard}>
              <option value="">—</option>
              {vendList.map(v => <option key={v} value={v}>{v}</option>)}
            </select>
          </div>
          {/* "Aceptado" no es seleccionable a mano: se alcanza solo vía
              "Aceptar y crear pedido". Se muestra como opción únicamente si
              el presupuesto ya está Aceptado (legacy), para poder revertirlo. */}
          <div className="field"><label>Estado</label><select value={data.estado} onChange={e=>onEstadoChange(e.target.value)}>{PRESU_ESTADOS.filter(e => e !== 'Aceptado' || data.estado === 'Aceptado').map(e => <option key={e}>{e}</option>)}</select></div>
          <div className="field"><label>Descuento (%)</label><input type="number" step="0.1" value={data.descuento_pct} onChange={e=>set('descuento_pct', e.target.value)} disabled={isLocked} {...lockGuard}/></div>
          <div className="field"><label>IVA (%)</label><input type="number" step="1" value={data.iva_pct} onChange={e=>set('iva_pct', e.target.value)} disabled={isLocked} {...lockGuard}/></div>
          <div className="field"><label>Km ida</label><input type="number" step="0.1" value={data.km_ida} onChange={e=>set('km_ida', e.target.value)} disabled={isLocked} {...lockGuard}/></div>
          <div className="field full"><label>Incluye</label><textarea rows="2" value={data.incluye} onChange={e=>set('incluye', e.target.value)} disabled={isLocked} {...lockGuard}/></div>
          <div className="field full"><label>No incluye</label><textarea rows="2" value={data.no_incluye} onChange={e=>set('no_incluye', e.target.value)} disabled={isLocked} {...lockGuard}/></div>
          <div className="field full"><label>Observaciones</label><textarea rows="3" value={data.observaciones} onChange={e=>set('observaciones', e.target.value)} disabled={isLocked} {...lockGuard}/></div>
        </div>
        <footer>
          <button className="btn ghost" onClick={onClose}>Cancelar</button>
          <button className="btn primary" onClick={save} disabled={saving}>{saving ? 'Guardando…' : 'Guardar'}</button>
        </footer>
      </div>

      {showLockWarn && (
        <PresuConfirmModal
          title="Presupuesto aceptado"
          confirmLabel="Cambiar estado"
          onCancel={() => setShowLockWarn(false)}
          onConfirm={() => {
            setShowLockWarn(false);
            // Foco en el select de estado para que el usuario lo cambie.
            setTimeout(() => {
              const sel = document.querySelector('.presu-modal select');
              if (sel) sel.focus();
            }, 50);
          }}
        >
          Este presupuesto está en estado <strong>Aceptado</strong> y no se pueden editar líneas, lead ni importes. Cambia el estado para habilitar la edición.
        </PresuConfirmModal>
      )}

      {stateChange && stateChange.requiereDecision && (
        <PresuConfirmModal
          title={`Cambiar estado a ${stateChange.nuevoEstado}`}
          confirmLabel="Aplicar cambio"
          confirmDisabled={stateChange.forceDeleteOCs == null}
          onCancel={() => setStateChange(null)}
          onConfirm={confirmStateChange}
          danger
        >
          <div>Existen órdenes de compra ya pedidas vinculadas a este presupuesto. Decide cómo proceder:</div>
          <PresuOCsTable ocs={stateChange.ocs}/>
          <div style={{marginTop:10, display:'flex', flexDirection:'column', gap:6}}>
            <label><input type="radio" name="forceDel" checked={stateChange.forceDeleteOCs === true} onChange={() => setStateChange(s => ({...s, forceDeleteOCs:true}))}/> Eliminar también las OCs vinculadas <span className="muted">(irreversible)</span></label>
            <label><input type="radio" name="forceDel" checked={stateChange.forceDeleteOCs === false} onChange={() => setStateChange(s => ({...s, forceDeleteOCs:false}))}/> Dejar las OCs huérfanas (sin enlace al presupuesto)</label>
          </div>
        </PresuConfirmModal>
      )}

      {stateChange && !stateChange.requiereDecision && (
        <PresuConfirmModal
          title={`Cambiar estado a ${stateChange.nuevoEstado}`}
          confirmLabel="Confirmar"
          onCancel={() => setStateChange(null)}
          onConfirm={confirmStateChange}
        >
          <div>Se eliminarán {stateChange.ocs.length} órdenes de compra en borrador vinculadas a este presupuesto. ¿Continuar?</div>
          <PresuOCsTable ocs={stateChange.ocs}/>
        </PresuConfirmModal>
      )}
    </div>
  );
};

const PresupuestoLineaModal = ({ presupuestoId, linea, onClose, onSaved, onToast }) => {
  const [productos, setProductos] = React.useState([]);
  const [producto, setProducto] = React.useState(null);
  const [productoId, setProductoId] = React.useState(linea?.producto_id || '');
  const [values, setValues] = React.useState(linea?.especificaciones_valores || {});
  const [unidades, setUnidades] = React.useState(linea?.unidades || 1);
  const [descripcion, setDescripcion] = React.useState(linea?.descripcion_generada || '');
  const [pricing, setPricing] = React.useState(null);
  const [pricingError, setPricingError] = React.useState('');
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    API.get('/api/configurador/productos')
      .then(rows => setProductos(Array.isArray(rows) ? rows : []))
      .catch(e => onToast && onToast(e.message || 'Error al cargar productos', true));
  }, []);

  React.useEffect(() => {
    if (!productoId) { setProducto(null); return; }
    let alive = true;
    API.get(`/api/configurador/producto/${productoId}`)
      .then(p => {
        if (!alive) return;
        setProducto(p);
        setValues(prev => {
          const next = {};
          (p.especificaciones || []).forEach(s => { next[s.nombre_campo] = prev[s.nombre_campo] ?? (s.tipo === 'bool' ? false : ''); });
          return next;
        });
      })
      .catch(e => onToast && onToast(e.message || 'Error al cargar producto', true));
    return () => { alive = false; };
  }, [productoId]);

  React.useEffect(() => {
    if (!producto) return;
    const t = setTimeout(async () => {
      setPricingError('');
      try {
        const r = await API.post('/api/configurador/cotizar', {
          producto_id: producto.id,
          configuracion: cfgCollectSpecs(producto.especificaciones || [], values),
          unidades: Number(unidades) || 1,
        });
        setPricing(r);
      } catch (e) {
        setPricing(null);
        setPricingError(e.message || 'Error al cotizar');
      }
    }, 500);
    return () => clearTimeout(t);
  }, [producto && producto.id, JSON.stringify(values), unidades]);

  const setSpec = (key, value) => setValues(v => ({ ...v, [key]: value }));
  const isEdit = !!linea?.id;

  const save = async () => {
    if (!productoId) { onToast && onToast('Selecciona un producto', true); return; }
    setSaving(true);
    const payload = {
      producto_id: Number(productoId),
      especificaciones_valores: cfgCollectSpecs(producto?.especificaciones || [], values),
      ancho_mm: Number(values.ancho_mm || values.ancho || 0) || null,
      alto_mm: Number(values.alto_mm || values.alto || 0) || null,
      unidades: Number(unidades) || 1,
      descripcion_generada: descripcion || `${producto?.codigo || ''}${values.ancho_mm ? ` ${values.ancho_mm}x${values.alto_mm || '?'}mm` : ''}`.trim() || null,
    };
    try {
      if (isEdit) await API.put(`/api/presupuestos/${presupuestoId}/lineas/${linea.id}`, payload);
      else await API.post(`/api/presupuestos/${presupuestoId}/lineas`, payload);
      await API.post(`/api/presupuestos/${presupuestoId}/recalcular`, {});
      onToast && onToast(isEdit ? 'Línea actualizada ✓' : 'Línea añadida ✓');
      onSaved && onSaved();
      onClose && onClose();
    } catch (e) {
      onToast && onToast(e.message || 'Error al guardar línea', true);
    } finally {
      setSaving(false);
    }
  };

  const byFamily = productos.reduce((acc, p) => {
    const key = p.familia_nombre || 'Productos';
    if (!acc[key]) acc[key] = [];
    acc[key].push(p);
    return acc;
  }, {});

  return (
    <div className="modal-backdrop">
      <div className="modal-card presu-linea-modal">
        <header>
          <h2>{isEdit ? 'Editar línea' : 'Añadir línea'}</h2>
          <button className="x" onClick={onClose}>×</button>
        </header>
        <div className="field">
          <label>Producto</label>
          <select value={productoId} onChange={e => setProductoId(e.target.value)}>
            <option value="">Selecciona producto…</option>
            {Object.entries(byFamily).map(([fam, rows]) => (
              <optgroup key={fam} label={fam}>
                {rows.map(p => <option key={p.id} value={p.id}>{p.codigo} — {p.nombre || p.familia_nombre}</option>)}
              </optgroup>
            ))}
          </select>
        </div>
        {producto && (
          <div className="fields-grid cols-3">
            {(producto.especificaciones || []).map(s => <SpecField key={s.id || s.nombre_campo} spec={s} value={values[s.nombre_campo]} onChange={setSpec}/>)}
            <div className="field"><label>Unidades</label><input type="number" min="1" value={unidades} onChange={e=>setUnidades(e.target.value)}/></div>
          </div>
        )}
        <div className="field">
          <label>Descripción</label>
          <input value={descripcion} onChange={e => setDescripcion(e.target.value)} placeholder="Se autogenera si se deja en blanco"/>
        </div>
        <div className="presu-preview">
          {pricingError && <span style={{color:'var(--err)'}}>{pricingError}</span>}
          {!pricingError && pricing && (
            <>
              <span>{presuFmtEur(pricing.precio_unitario_eur)} / ud</span>
              <strong>{presuFmtEur(pricing.precio_linea_eur)}</strong>
            </>
          )}
          {!pricingError && !pricing && <span className="muted">Cotización pendiente…</span>}
        </div>
        <footer>
          <button className="btn ghost" onClick={onClose}>Cancelar</button>
          <button className="btn primary" onClick={save} disabled={saving}>{saving ? 'Guardando…' : 'Guardar línea'}</button>
        </footer>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────
// GUT-60 · Línea manual: descripción libre + cantidad + precio manual.
// Sin configurador / sin pricing automático. Subtotal = cantidad × precio.
// ─────────────────────────────────────────────────────
const PresupuestoLineaManualModal = ({ presupuestoId, linea, onClose, onSaved, onToast }) => {
  const isEdit = !!linea?.id;
  const [producto, setProducto] = React.useState(linea?.producto_manual || '');
  const [descripcion, setDescripcion] = React.useState(linea?.descripcion_generada || '');
  const [unidades, setUnidades] = React.useState(linea?.unidades ?? 1);
  const [precioUnitario, setPrecioUnitario] = React.useState(
    linea?.precio_unitario != null ? String(linea.precio_unitario) : ''
  );
  const [saving, setSaving] = React.useState(false);

  const uds = Number.parseFloat(unidades);
  const pu = Number.parseFloat(precioUnitario);
  const udsValid = Number.isFinite(uds) && uds > 0;
  const puValid = Number.isFinite(pu) && pu >= 0;
  const subtotal = udsValid && puValid ? +(uds * pu).toFixed(2) : 0;

  const save = async () => {
    if (!producto.trim()) { onToast && onToast('El producto es obligatorio', true); return; }
    if (!udsValid) { onToast && onToast('La cantidad debe ser mayor que 0', true); return; }
    if (!puValid) { onToast && onToast('El precio unitario debe ser ≥ 0', true); return; }
    setSaving(true);
    const payload = {
      producto_manual: producto.trim(),
      descripcion_generada: descripcion.trim() || null,
      unidades: uds,
      precio_unitario: pu,
      precio_linea: subtotal,
      producto_id: null,
    };
    try {
      if (isEdit) {
        await API.put(`/api/presupuestos/${presupuestoId}/lineas/${linea.id}`, payload);
      } else {
        await API.post(`/api/presupuestos/${presupuestoId}/lineas`, payload);
      }
      onToast && onToast(isEdit ? 'Línea actualizada ✓' : 'Línea añadida ✓');
      onSaved && onSaved();
      onClose && onClose();
    } catch (e) {
      onToast && onToast(e.message || 'Error al guardar línea', true);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="modal-backdrop">
      <div className="modal-card">
        <header>
          <h2>{isEdit ? 'Editar línea' : 'Añadir línea'}</h2>
          <button className="x" onClick={onClose}>×</button>
        </header>
        <div className="field full">
          <label>Producto</label>
          <input type="text" value={producto} onChange={e => setProducto(e.target.value)} placeholder="Nombre del producto o servicio"/>
        </div>
        <div className="field full">
          <label>Descripción</label>
          <textarea rows="3" value={descripcion} onChange={e => setDescripcion(e.target.value)} placeholder="Detalles, medidas, observaciones…"/>
        </div>
        <div className="fields-grid">
          <div className="field">
            <label>Cantidad</label>
            <input type="number" min="0" step="1" value={unidades} onChange={e => setUnidades(e.target.value)}/>
          </div>
          <div className="field">
            <label>Precio unitario (€)</label>
            <input type="number" min="0" step="0.01" value={precioUnitario} onChange={e => setPrecioUnitario(e.target.value)}/>
          </div>
          <div className="field">
            <label>Subtotal</label>
            <div className="v mono"><strong>{presuFmtEur(subtotal)}</strong></div>
          </div>
        </div>
        <footer>
          <button className="btn ghost" onClick={onClose}>Cancelar</button>
          <button className="btn primary" onClick={save} disabled={saving}>{saving ? 'Guardando…' : 'Guardar línea'}</button>
        </footer>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────
// Línea desde catálogo de productos: elegir artículo (mosaico con
// fotos) → rellenar variables (desplegables) → descripción auto
// (editable) → medidas + cantidad + precio manual. Alta inline de
// artículos nuevos (se crean en el catálogo automáticamente).
// También admite "línea libre" (texto sin artículo).
// ─────────────────────────────────────────────────────
const PresupuestoLineaCatalogoModal = ({ presupuestoId, linea, onClose, onSaved, onToast }) => {
  const isEdit = !!linea?.id;
  const [articulos, setArticulos] = React.useState([]);
  const [loadingArts, setLoadingArts] = React.useState(true);
  const [search, setSearch] = React.useState('');
  const [familiaF, setFamiliaF] = React.useState('');
  const [showNew, setShowNew] = React.useState(false);

  // Modo: 'pick' (mosaico) | 'catalogo' | 'libre'
  const initialStep = isEdit ? (linea.articulo_id ? 'catalogo' : 'libre') : 'pick';
  const [step, setStep] = React.useState(initialStep);
  const [articulo, setArticulo] = React.useState(null); // artículo seleccionado (con variables)
  const [varValues, setVarValues] = React.useState(linea?.especificaciones_valores || {});
  const [descManual, setDescManual] = React.useState(isEdit); // en edición no regeneramos solos
  const [descripcion, setDescripcion] = React.useState(linea?.descripcion_generada || '');
  const [nombre, setNombre] = React.useState(linea?.producto_manual || linea?.producto_nombre || '');
  const [ancho, setAncho] = React.useState(linea?.ancho_mm != null ? String(linea.ancho_mm) : '');
  const [alto, setAlto] = React.useState(linea?.alto_mm != null ? String(linea.alto_mm) : '');
  const [unidades, setUnidades] = React.useState(linea?.unidades ?? 1);
  const [precioUnitario, setPrecioUnitario] = React.useState(linea?.precio_unitario != null ? String(linea.precio_unitario) : '');
  const [saving, setSaving] = React.useState(false);

  const loadArticulos = React.useCallback(() => {
    setLoadingArts(true);
    return API.get('/api/articulos')
      .then(rows => setArticulos(Array.isArray(rows) ? rows : []))
      .catch(e => onToast && onToast(e.message || 'Error al cargar catálogo', true))
      .finally(() => setLoadingArts(false));
  }, []);
  React.useEffect(() => { loadArticulos(); }, []);

  // En edición de una línea de catálogo, recuperar el artículo origen.
  React.useEffect(() => {
    if (isEdit && linea.articulo_id && !articulo && articulos.length) {
      const a = articulos.find(x => x.id === linea.articulo_id);
      if (a) setArticulo(a);
    }
  }, [articulos]);

  // Regenera la descripción desde la plantilla salvo que se haya editado a mano.
  React.useEffect(() => {
    if (step !== 'catalogo' || !articulo || descManual) return;
    setDescripcion(window.artRenderDescripcion(articulo.descripcion_template, varValues));
  }, [articulo, JSON.stringify(varValues), descManual, step]);

  const pickArticulo = (a) => {
    setArticulo(a);
    setNombre(a.nombre);
    setVarValues({});
    setDescManual(false);
    setDescripcion(window.artRenderDescripcion(a.descripcion_template, {}));
    setStep('catalogo');
  };

  const startLibre = () => {
    setArticulo(null); setNombre(''); setDescripcion(''); setVarValues({}); setStep('libre');
  };

  const uds = Number.parseFloat(unidades);
  const pu = precioUnitario === '' ? null : Number.parseFloat(precioUnitario);
  const udsValid = Number.isFinite(uds) && uds > 0;
  const puValid = pu == null || (Number.isFinite(pu) && pu >= 0);
  const subtotal = udsValid && pu != null && Number.isFinite(pu) ? +(uds * pu).toFixed(2) : 0;

  const setVar = (campo, value) => setVarValues(v => ({ ...v, [campo]: value }));

  const save = async () => {
    if (step === 'libre' && !nombre.trim()) return onToast('El producto es obligatorio', true);
    if (step === 'catalogo' && !articulo) return onToast('Selecciona un artículo', true);
    if (!udsValid) return onToast('La cantidad debe ser mayor que 0', true);
    if (!puValid) return onToast('El precio unitario debe ser ≥ 0', true);
    setSaving(true);
    const pl = pu != null ? subtotal : null;
    const payload = {
      producto_id: null,
      producto_manual: nombre.trim() || null,
      descripcion_generada: descripcion.trim() || null,
      articulo_id: step === 'catalogo' && articulo ? articulo.id : null,
      imagen_url: step === 'catalogo' && articulo ? (articulo.imagen_url || null) : null,
      especificaciones_valores: step === 'catalogo' ? varValues : {},
      ancho_mm: ancho === '' ? null : Number(ancho),
      alto_mm: alto === '' ? null : Number(alto),
      unidades: uds,
      precio_unitario: pu,
      precio_linea: pl,
    };
    try {
      if (isEdit) await API.put(`/api/presupuestos/${presupuestoId}/lineas/${linea.id}`, payload);
      else await API.post(`/api/presupuestos/${presupuestoId}/lineas`, payload);
      await API.post(`/api/presupuestos/${presupuestoId}/recalcular`, {});
      onToast && onToast(isEdit ? 'Línea actualizada ✓' : 'Línea añadida ✓');
      onSaved && onSaved();
      onClose && onClose();
    } catch (e) {
      onToast && onToast(e.message || 'Error al guardar línea', true);
    } finally {
      setSaving(false);
    }
  };

  // ----- Mosaico de selección -----
  const visibles = articulos.filter(a => {
    if (familiaF && a.familia !== familiaF) return false;
    if (search.trim() && !a.nombre.toLowerCase().includes(search.trim().toLowerCase())) return false;
    return true;
  });

  const renderPicker = () => (
    <>
      <div style={{ display: 'flex', gap: 8, marginBottom: 10, alignItems: 'center' }}>
        <div className="tb-search" style={{ flex: 1 }}>
          <Icon name="search" />
          <input autoFocus placeholder="Buscar artículo…" value={search} onChange={e => setSearch(e.target.value)} />
        </div>
        <select className="cfg-select" value={familiaF} onChange={e => setFamiliaF(e.target.value)} style={{ width: 150 }}>
          <option value="">Todas</option>
          {(window.ART_FAMILIAS || []).map(f => <option key={f.value} value={f.value}>{f.label}</option>)}
        </select>
        <button type="button" className="btn ghost" onClick={() => setShowNew(true)}><Icon name="plus" size={12} /> Nuevo</button>
        <button type="button" className="btn ghost" onClick={startLibre}>Línea libre</button>
      </div>
      {loadingArts && <div className="muted" style={{ padding: 20, textAlign: 'center' }}>Cargando catálogo…</div>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))', gap: 10, maxHeight: 420, overflow: 'auto' }}>
        {visibles.map(a => (
          <button type="button" key={a.id} className="panel" onClick={() => pickArticulo(a)}
            style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden', cursor: 'pointer', padding: 0, textAlign: 'left', border: '1px solid var(--line)' }}>
            <div style={{ height: 90, background: 'var(--surface-2,#fafafa)', display: 'flex', alignItems: 'center', justifyContent: 'center', borderBottom: '1px solid var(--line)' }}>
              {a.imagen_url ? <img src={a.imagen_url} alt="" style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} /> : <Icon name="package" size={22} />}
            </div>
            <span style={{ padding: '6px 8px', fontSize: 12, fontWeight: 600 }}>{a.nombre}</span>
          </button>
        ))}
        {!loadingArts && !visibles.length && <div className="muted" style={{ padding: 20, gridColumn: '1/-1', textAlign: 'center' }}>Sin artículos. Crea uno con “Nuevo”.</div>}
      </div>
    </>
  );

  // ----- Formulario de la línea (catálogo o libre) -----
  const renderForm = () => (
    <>
      <div style={{ display: 'flex', gap: 12, marginBottom: 12, alignItems: 'flex-start' }}>
        {step === 'catalogo' && articulo?.imagen_url && (
          <img src={articulo.imagen_url} alt="" style={{ width: 84, height: 84, objectFit: 'contain', border: '1px solid var(--line)', borderRadius: 8, background: 'var(--surface-2,#fafafa)' }} />
        )}
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <strong style={{ fontSize: 14 }}>{step === 'catalogo' ? articulo?.nombre : 'Línea libre'}</strong>
            {!isEdit && <button type="button" className="btn ghost sm" onClick={() => setStep('pick')}><Icon name="arrow-left" size={11} /> Cambiar</button>}
          </div>
          {step === 'libre' && (
            <input style={{ marginTop: 6 }} placeholder="Nombre del producto o servicio" value={nombre} onChange={e => setNombre(e.target.value)} />
          )}
        </div>
      </div>

      {step === 'catalogo' && (articulo?.variables || []).length > 0 && (
        <div className="fields-grid cols-3" style={{ marginBottom: 10 }}>
          {(articulo.variables || []).map(v => (
            <div className="field" key={v.campo}>
              <label>{v.label}</label>
              {v.tipo === 'select' && (v.opciones || []).length
                ? <select value={varValues[v.campo] || ''} onChange={e => setVar(v.campo, e.target.value)}>
                    <option value="">—</option>
                    {v.opciones.map(o => <option key={o} value={o}>{o}</option>)}
                  </select>
                : <input value={varValues[v.campo] || ''} onChange={e => setVar(v.campo, e.target.value)} placeholder={v.label} />}
            </div>
          ))}
        </div>
      )}

      <div className="field full">
        <label>Descripción {step === 'catalogo' && !descManual && <span className="muted" style={{ fontSize: 11 }}>(automática · edítala si quieres)</span>}</label>
        <textarea rows="3" value={descripcion} onChange={e => { setDescripcion(e.target.value); setDescManual(true); }} />
      </div>

      <div className="fields-grid cols-3">
        <div className="field"><label>Ancho (mm)</label><input type="number" min="0" value={ancho} onChange={e => setAncho(e.target.value)} /></div>
        <div className="field"><label>Alto (mm)</label><input type="number" min="0" value={alto} onChange={e => setAlto(e.target.value)} /></div>
        <div className="field"><label>Cantidad</label><input type="number" min="1" value={unidades} onChange={e => setUnidades(e.target.value)} /></div>
        <div className="field"><label>Precio unitario (€)</label><input type="number" min="0" step="0.01" value={precioUnitario} onChange={e => setPrecioUnitario(e.target.value)} /></div>
        <div className="field"><label>Subtotal</label><div className="v mono"><strong>{presuFmtEur(subtotal)}</strong></div></div>
      </div>
    </>
  );

  return (
    <div className="modal-backdrop">
      <div className="modal-card presu-linea-modal" style={{ maxWidth: step === 'pick' ? 720 : 560, width: '92vw' }}>
        <header>
          <h2>{isEdit ? 'Editar línea' : (step === 'pick' ? 'Elegir artículo' : 'Añadir línea')}</h2>
          <button className="x" onClick={onClose}>×</button>
        </header>
        <div style={{ padding: '4px 2px' }}>
          {step === 'pick' ? renderPicker() : renderForm()}
        </div>
        {step !== 'pick' && (
          <footer>
            <button className="btn ghost" onClick={onClose}>Cancelar</button>
            <button className="btn primary" onClick={save} disabled={saving}>{saving ? 'Guardando…' : 'Guardar línea'}</button>
          </footer>
        )}
      </div>

      {showNew && window.ArtModal && (
        <window.ArtModal
          articulo={null}
          onClose={() => setShowNew(false)}
          onSaved={async () => { await loadArticulos(); }}
          onToast={onToast}
        />
      )}
    </div>
  );
};

// Modal de envío: elige canal (WhatsApp / email / manual). En los 3 casos
// se descarga el PDF (mailto y wa.me no permiten adjuntar por URL) y se marca
// el presupuesto como Enviado. WhatsApp/email se deshabilitan si el snapshot
// del cliente no tiene tlf/email.
// Fila copiable usada en la plantilla de email (por si el mailto no abre el
// cliente de correo: el usuario copia y pega en su webmail).
const PresuCopyRow = ({ label, value, multiline, onToast }) => {
  const copy = (e) => {
    const field = e.currentTarget.parentNode.querySelector('input,textarea');
    if (field) { field.focus(); field.select(); }
    if (navigator.clipboard?.writeText) {
      navigator.clipboard.writeText(value).then(
        () => onToast && onToast(`${label} copiado ✓`),
        () => onToast && onToast('Selecciona y copia manualmente', true)
      );
    } else {
      try { document.execCommand('copy'); onToast && onToast(`${label} copiado ✓`); }
      catch { onToast && onToast('Selecciona y copia manualmente', true); }
    }
  };
  return (
    <div style={{marginBottom:10}}>
      <label style={{display:'block',fontSize:11,fontWeight:600,color:'var(--ink-3)',marginBottom:4,textTransform:'uppercase',letterSpacing:.4}}>{label}</label>
      <div style={{display:'flex',gap:6,alignItems:'flex-start'}}>
        {multiline
          ? <textarea readOnly value={value} rows={6} style={{flex:1,resize:'vertical',fontSize:13,lineHeight:1.5,padding:'8px 10px',border:'1px solid var(--line)',borderRadius:8,fontFamily:'inherit'}}/>
          : <input readOnly value={value} style={{flex:1,fontSize:13,padding:'8px 10px',border:'1px solid var(--line)',borderRadius:8}}/>}
        <button className="btn ghost sm" onClick={copy} style={{flexShrink:0}}><Icon name="file" size={12}/> Copiar</button>
      </div>
    </div>
  );
};

const PresupuestoEnviarModal = ({ presupuesto, onClose, onSent, onToast }) => {
  const snap = presupuesto?.cliente_snapshot || {};
  const tlf = snap.telefono || snap.tel || '';
  const email = snap.email || '';
  const waPhone = presuWaPhone(tlf);
  const asunto = `Presupuesto ${presupuesto.numero_presupuesto} · Kurtenglass`;
  const cuerpo = presuMensaje(presupuesto);
  const [busy, setBusy] = React.useState(false);
  const [view, setView] = React.useState('choose'); // 'choose' | 'email-template'

  const descargarPdf = () =>
    API.download(`/api/presupuestos/${presupuesto.id}/pdf`, `${presupuesto.numero_presupuesto}.pdf`);

  const enviar = async (canal) => {
    if (busy) return;
    setBusy(true);
    // El canal se abre DENTRO del gesto del usuario (antes de cualquier
    // await), o el navegador bloquea mailto / window.open.
    if (canal === 'whatsapp') {
      window.open(`https://wa.me/${waPhone}?text=${encodeURIComponent(cuerpo)}`, '_blank');
    } else if (canal === 'email') {
      const url = `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent(asunto)}&body=${encodeURIComponent(cuerpo)}`;
      const a = document.createElement('a'); a.href = url; a.click();
    }
    try {
      await descargarPdf();
      await onSent();
      if (canal === 'email') {
        // El mailto puede no abrir nada si el cliente de correo no está
        // configurado → mostramos plantilla copiable para el webmail.
        setView('email-template');
        onToast && onToast('Marcado como Enviado ✓');
      } else {
        onToast && onToast(canal === 'manual' ? 'PDF descargado · marcado como Enviado ✓' : 'Marcado como Enviado · adjunta el PDF descargado ✓');
        onClose && onClose();
      }
    } catch (e) {
      onToast && onToast(e.message || 'Error al enviar', true);
    } finally {
      setBusy(false);
    }
  };

  const Opt = ({ canal, icon, titulo, sub, disabled }) => (
    <button
      className="btn ghost"
      disabled={disabled || busy}
      onClick={() => enviar(canal)}
      style={{display:'flex',alignItems:'center',gap:12,width:'100%',justifyContent:'flex-start',textAlign:'left',padding:'14px 16px',height:'auto',opacity:disabled?0.5:1}}
    >
      <Icon name={icon} size={20}/>
      <span style={{display:'flex',flexDirection:'column',gap:2}}>
        <strong style={{fontSize:14}}>{titulo}</strong>
        <span style={{fontSize:12,color:'var(--ink-3)'}}>{sub}</span>
      </span>
    </button>
  );

  return (
    <div className="modal-backdrop">
      <div className="modal-card">
        <header>
          <h2>{view === 'email-template' ? 'Enviar por email' : `Enviar presupuesto ${presupuesto.numero_presupuesto}`}</h2>
          <button className="x" onClick={onClose}>×</button>
        </header>

        {view === 'choose' && (
          <div style={{display:'flex',flexDirection:'column',gap:10,padding:'6px 2px'}}>
            <Opt canal="whatsapp" icon="phone" titulo="WhatsApp"
                 sub={tlf ? `Abrir chat con ${tlf}` : 'El cliente no tiene teléfono'}
                 disabled={!waPhone}/>
            <Opt canal="email" icon="mail" titulo="Email"
                 sub={email ? `Redactar correo a ${email}` : 'El cliente no tiene email'}
                 disabled={!email}/>
            <Opt canal="manual" icon="download" titulo="Descargar y enviar manualmente"
                 sub="Descarga el PDF y lo envías por tu canal"/>
          </div>
        )}

        {view === 'email-template' && (
          <div style={{padding:'6px 2px'}}>
            <p style={{fontSize:13,lineHeight:1.5,color:'var(--ink-2,#444)',margin:'0 0 14px'}}>
              Si no se abrió tu programa de correo, copia estos campos y pégalos en tu webmail (Gmail, Outlook…). <strong>Adjunta el PDF descargado.</strong>
            </p>
            <PresuCopyRow label="Para" value={email} onToast={onToast}/>
            <PresuCopyRow label="Asunto" value={asunto} onToast={onToast}/>
            <PresuCopyRow label="Cuerpo" value={cuerpo} multiline onToast={onToast}/>
          </div>
        )}

        <footer>
          <button className="btn ghost" onClick={onClose}>{view === 'email-template' ? 'Cerrar' : 'Cancelar'}</button>
        </footer>
      </div>
    </div>
  );
};

const PresupuestosView = ({ onToast }) => {
  const [rows, setRows] = React.useState([]);
  const [detail, setDetail] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  // GUT-56 — preseleccionar presupuesto si veníamos desde el drawer del lead.
  const pendingId = (typeof window !== 'undefined') ? window.__erpPendingPresupuestoId : null;
  const [selectedId, setSelectedId] = React.useState(pendingId || null);
  React.useEffect(() => {
    if (pendingId) { window.__erpPendingPresupuestoId = null; }
  }, []);
  const [estado, setEstado] = React.useState('');
  const [search, setSearch] = React.useState('');
  const [modal, setModal] = React.useState(null);
  const [reloadKey, setReloadKey] = React.useState(0);

  const toast = (msg, isErr) => onToast ? onToast(msg, isErr) : (isErr && console.error(msg));
  const reload = () => setReloadKey(k => k + 1);

  // GUT-30: estado del flujo de eliminación.
  const [deleteState, setDeleteState] = React.useState(null); // { presupuesto, ocs, forceConfirmed }
  const [showLockWarnList, setShowLockWarnList] = React.useState(false);

  const startDelete = async (presupuesto) => {
    if (!presupuesto?.id) return;
    try {
      const r = await API.get(`/api/presupuestos/${presupuesto.id}/impact`);
      const ocs = r?.ocs || [];
      if (ocs.length === 0) {
        if (!confirm(`¿Eliminar presupuesto ${presupuesto.numero_presupuesto || presupuesto.id}?`)) return;
        await API.del(`/api/presupuestos/${presupuesto.id}`);
        toast('Presupuesto eliminado ✓');
        if (selectedId === presupuesto.id) setSelectedId(null);
        reload();
        return;
      }
      setDeleteState({ presupuesto, ocs, forceConfirmed: false });
    } catch (e) {
      toast(e.message || 'Error al consultar impacto de la eliminación', true);
    }
  };

  const confirmDelete = async () => {
    if (!deleteState) return;
    const { presupuesto, ocs, forceConfirmed } = deleteState;
    const anyPedida = ocs.some(o => o.pedida);
    const force = anyPedida ? forceConfirmed : false;
    try {
      const path = `/api/presupuestos/${presupuesto.id}${force ? '?force=true' : ''}`;
      await API.del(path);
      toast('Presupuesto eliminado ✓');
      if (selectedId === presupuesto.id) setSelectedId(null);
      setDeleteState(null);
      reload();
    } catch (e) {
      if (e?.status === 409 && e?.data?.error === 'OC_EN_FLUJO') {
        const ocs_en_flujo = e.data.ocs_en_flujo || [];
        setDeleteState(s => s ? { ...s, ocs: ocs_en_flujo.length ? ocs_en_flujo : s.ocs } : s);
        toast('Hay OCs en flujo. Marca "Eliminar también OCs pedidas" para continuar.', true);
      } else {
        toast(e.message || 'Error al eliminar presupuesto', true);
      }
    }
  };

  const params = React.useMemo(() => {
    const p = {};
    if (estado) p.estado = estado;
    if (search.trim()) p.search = search.trim();
    return p;
  }, [estado, search]);

  React.useEffect(() => {
    let alive = true;
    setLoading(true);
    API.get('/api/presupuestos', params).then(list => {
      if (!alive) return;
      const next = Array.isArray(list) ? list : [];
      setRows(next);
      if (!selectedId && next[0]) setSelectedId(next[0].id);
      if (selectedId && !next.some(r => r.id === selectedId) && next[0]) setSelectedId(next[0].id);
    }).catch(e => toast(e.message || 'Error al cargar presupuestos', true))
      .finally(() => alive && setLoading(false));
    return () => { alive = false; };
  }, [params, reloadKey]);

  const loadDetail = React.useCallback((id) => {
    if (!id) { setDetail(null); return; }
    setDetail(null);
    API.get(`/api/presupuestos/${id}`)
      .then(setDetail)
      .catch(e => toast(e.message || 'Error al cargar detalle', true));
  }, []);

  React.useEffect(() => { loadDetail(selectedId); }, [selectedId, reloadKey]);

  const recalc = async () => {
    if (!detail) return;
    try {
      await API.post(`/api/presupuestos/${detail.id}/recalcular`, {});
      toast('Precios recalculados ✓');
      reload();
    } catch (e) {
      toast(e.message || 'Error al recalcular', true);
    }
  };

  const deleteLinea = async (linea) => {
    if (!confirm('¿Eliminar esta línea?')) return;
    try {
      await API.del(`/api/presupuestos/${detail.id}/lineas/${linea.id}`);
      await API.post(`/api/presupuestos/${detail.id}/recalcular`, {});
      toast('Línea eliminada');
      reload();
    } catch (e) {
      toast(e.message || 'Error al eliminar línea', true);
    }
  };

  const openPdf = () => {
    if (!detail) return;
    API.download(`/api/presupuestos/${detail.id}/pdf`, `${detail.numero_presupuesto}.pdf`)
      .catch(e => toast(e.message || 'Error al generar PDF', true));
  };

  // Marca el presupuesto como Enviado (si no lo estaba ya). Lo usa el modal
  // de envío tras abrir el canal elegido. Aceptar solo se habilita una vez
  // enviado.
  const markEnviado = async () => {
    if (!presuIsEnviado(detail.estado) && !presuIsAceptado(detail.estado)) {
      await API.put(`/api/presupuestos/${detail.id}`, { estado: 'Enviado' });
    }
    // El reload se difiere al cierre del modal para no desmontar la vista
    // de plantilla de email mientras sigue abierta.
  };

  // Aceptar y crear pedido en 1 acción: convierte con datos del snapshot
  // (sin modal), abre expediente y salta a él.
  const aceptarYConvertir = async () => {
    if (!detail) return;
    if (!confirm(`¿Aceptar ${detail.numero_presupuesto} y crear el pedido? Se abrirá un expediente.`)) return;
    try {
      const snap = detail.cliente_snapshot || {};
      const payload = {
        nombre_cliente: snap.nombre || snap.razon_social || '',
        fecha_pedido: new Date().toISOString().slice(0, 10),
      };
      const r = await API.post(`/api/presupuestos/${detail.id}/convertir-a-pedido`, payload);
      toast(`Pedido creado · Expediente ${r.numero_expediente} ✓`);
      reload();
      if (r.pedido_id) {
        window.dispatchEvent(new CustomEvent('erp:open-expediente', { detail: { pedidoId: r.pedido_id } }));
      }
    } catch (err) {
      toast(err.message || 'Error al aceptar/convertir', true);
    }
  };

  const kpis = React.useMemo(() => ({
    total: rows.length,
    aceptado: rows.filter(r => String(r.estado || '').toLowerCase().includes('acept')).length,
    borrador: rows.filter(r => String(r.estado || '').toLowerCase().includes('borr')).length,
    importe: rows.reduce((s,r) => s + Number(r.total_con_iva || 0), 0),
  }), [rows]);

  return (
    <>
      <div className="page-h">
        <div>
          <h1>Presupuestos</h1>
          <div className="sub">Oferta comercial, líneas configuradas y conversión a pedido</div>
        </div>
        <div className="page-h-actions">
          <button className="btn primary" onClick={() => setModal({ type:'header', data:null })}><Icon name="plus"/> Nuevo</button>
        </div>
      </div>

      <div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)'}}>
        <div className="kpi"><div className="lbl">Presupuestos</div><div className="row"><div className="num">{kpis.total}</div></div></div>
        <div className="kpi"><div className="lbl"><span className="dot" style={{background:'var(--ok)'}}/> Aceptados</div><div className="row"><div className="num">{kpis.aceptado}</div></div></div>
        <div className="kpi"><div className="lbl"><span className="dot" style={{background:'var(--warn)'}}/> Borradores</div><div className="row"><div className="num">{kpis.borrador}</div></div></div>
        <div className="kpi"><div className="lbl">Total visible</div><div className="row"><div className="num" style={{fontSize:18}}>{presuFmtEur(kpis.importe)}</div></div></div>
      </div>

      <div className="toolbar">
        <div className="tb-search">
          <Icon name="search"/>
          <input placeholder="Nº presupuesto, vendedor…" value={search} onChange={e => setSearch(e.target.value)}/>
        </div>
        <select value={estado} onChange={e => setEstado(e.target.value)} className="cfg-select">
          <option value="">Todos los estados</option>
          {PRESU_ESTADOS.map(e => <option key={e} value={e}>{e}</option>)}
        </select>
        <button className="btn ghost" onClick={() => { setSearch(''); setEstado(''); }}><Icon name="x" size={12}/> Limpiar</button>
      </div>

      <div className="presu-layout">
        <div className="panel">
          <div className="panel-h">Listado <span className="badge">{loading ? '…' : rows.length}</span></div>
          <div className="presu-list">
            {loading && <div className="muted" style={{fontSize:12,padding:14}}>Cargando presupuestos…</div>}
            {!loading && rows.length === 0 && <div className="muted" style={{fontSize:12,padding:14}}>Sin presupuestos.</div>}
            {rows.map(p => (
              <button key={p.id} className={`presu-list-item ${selectedId === p.id ? 'on' : ''}`} onClick={() => setSelectedId(p.id)}>
                <span>
                  <strong>{p.numero_presupuesto}{Number(p.revision) > 0 ? ` · Rev. ${p.revision}` : ''}</strong>
                  <small>{p.vendedor || 'Sin vendedor'} · {p.lineas_count || 0} líneas</small>
                </span>
                <span>
                  <span className="mono">{p.total_con_iva != null ? presuFmtEur(p.total_con_iva) : '—'}</span>
                  <PresuEstadoPill estado={p.estado}/>
                </span>
              </button>
            ))}
          </div>
        </div>

        <div className="panel presu-detail">
          {!detail && <div className="panel-b muted" style={{fontSize:12}}>Selecciona un presupuesto.</div>}
          {detail && (
            <>
              <div className="panel-h">
                <span className="mono">{detail.numero_presupuesto}{Number(detail.revision) > 0 ? ` · Rev. ${detail.revision}` : ''}</span>
                <PresuEstadoPill estado={detail.estado}/>
                <span className="tb-spacer"/>
                <button className="btn ghost sm" onClick={() => setModal({ type:'header', data:detail })}><Icon name="edit" size={12}/> Editar</button>
                <button className="btn ghost sm" onClick={recalc}><Icon name="sparkles" size={12}/> Recalcular</button>
                <button className="btn ghost sm" onClick={openPdf}><Icon name="print" size={12}/> PDF</button>
                <button className="btn ghost sm" style={{color:'var(--err)'}} onClick={() => startDelete(detail)}><Icon name="trash" size={12}/> Eliminar</button>
                {/* Acción contextual del ciclo de vida. La puerta es pedido_id:
                    una vez convertido, el destino es el expediente. */}
                {detail.pedido_id && detail.numero_expediente ? (
                  <button className="btn primary sm" onClick={() => window.dispatchEvent(new CustomEvent('erp:open-expediente', { detail: { pedidoId: detail.pedido_id } }))}>
                    <Icon name="package" size={12}/> Ver Expediente {detail.numero_expediente} →
                  </button>
                ) : (
                  <>
                    {/* Enviar siempre; Aceptar solo una vez enviado. */}
                    <button className={`btn ${presuIsEnviado(detail.estado) ? 'ghost' : 'primary'} sm`} onClick={() => setModal({ type:'enviar', data:detail })}>
                      <Icon name="mail" size={12}/> Enviar
                    </button>
                    {presuIsEnviado(detail.estado) && (
                      <button className="btn primary sm" onClick={aceptarYConvertir}><Icon name="check" size={12}/> Aceptar y crear pedido</button>
                    )}
                  </>
                )}
              </div>
              <div className="panel-b">
                <div className="fields-grid cols-3">
                  <div className="field"><label>Vendedor</label><div className="v">{detail.vendedor || '—'}</div></div>
                  <div className="field"><label>Creación</label><div className="v">{detail.fecha_creacion ? new Date(detail.fecha_creacion).toLocaleDateString('es-ES') : '—'}</div></div>
                  <div className="field"><label>Descuento</label><div className="v mono">{detail.descuento_pct || 0}%</div></div>
                  <div className="field"><label>IVA</label><div className="v mono">{detail.iva_pct || 21}%</div></div>
                  <div className="field"><label>Km ida</label><div className="v mono">{detail.km_ida || '—'}</div></div>
                </div>

                <div className="section-title">Líneas</div>
                <div style={{display:'flex',justifyContent:'flex-end',gap:6,marginBottom:8}}>
                  {/* Línea desde catálogo de productos (con alta inline) o línea libre. */}
                  <button className="btn primary" onClick={() => presuIsAceptado(detail.estado) ? setShowLockWarnList(true) : setModal({ type:'line-cat', data:null })}><Icon name="plus"/> Añadir línea</button>
                </div>
                <div className="tbl-wrap">
                  <table className="tbl">
                    <thead>
                      <tr>
                        <th>Producto</th>
                        <th>Descripción</th>
                        <th style={{textAlign:'right'}}>Uds.</th>
                        <th style={{textAlign:'right'}}>PV/ud</th>
                        <th style={{textAlign:'right'}}>Total</th>
                        <th style={{width:72}}></th>
                      </tr>
                    </thead>
                    <tbody>
                      {(!detail.lineas || detail.lineas.length === 0) && (
                        <tr><td colSpan={6} style={{textAlign:'center',padding:24,color:'var(--ink-3)'}}>Sin líneas. Añade la primera.</td></tr>
                      )}
                      {(detail.lineas || []).map(l => (
                        <tr key={l.id}>
                          <td className="strong">
                            <span style={{display:'flex',alignItems:'center',gap:8}}>
                              {l.imagen_url && <img src={l.imagen_url} alt="" style={{width:34,height:34,objectFit:'contain',border:'1px solid var(--line)',borderRadius:4,background:'var(--surface-2,#fafafa)',flexShrink:0}}/>}
                              <span>{l.producto_manual || l.producto_codigo || l.producto_nombre || '—'}</span>
                            </span>
                          </td>
                          <td className="truncate" title={l.descripcion_generada || ''}>{l.descripcion_generada || (l.producto_manual ? '' : (l.producto_nombre || '—'))}</td>
                          <td className="num right">{l.unidades || 1}</td>
                          <td className="num right">{presuFmtEur(l.precio_unitario)}</td>
                          <td className="num right strong">{presuFmtEur(l.precio_linea)}</td>
                          <td>
                            <button className="icon-btn" onClick={() => presuIsAceptado(detail.estado) ? setShowLockWarnList(true) : setModal({ type:'line-cat', data:l })} title="Editar"><Icon name="edit" size={12}/></button>
                            <button className="icon-btn" onClick={() => presuIsAceptado(detail.estado) ? setShowLockWarnList(true) : deleteLinea(l)} title="Eliminar" style={{color:'var(--err)'}}><Icon name="trash" size={12}/></button>
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>

                <div className="presu-totals">
                  <div><span>Subtotal</span><strong>{presuFmtEur(detail.total_sin_iva)}</strong></div>
                  <div><span>IVA</span><strong>{presuFmtEur(detail.total_iva)}</strong></div>
                  <div className="total"><span>Total</span><strong>{presuFmtEur(detail.total_con_iva)}</strong></div>
                </div>

                {(detail.observaciones || detail.incluye || detail.no_incluye) && (
                  <>
                    <div className="section-title">Notas</div>
                    <div className="presu-notes">
                      {detail.incluye && <div><strong>Incluye</strong><span>{detail.incluye}</span></div>}
                      {detail.no_incluye && <div><strong>No incluye</strong><span>{detail.no_incluye}</span></div>}
                      {detail.observaciones && <div><strong>Observaciones</strong><span>{detail.observaciones}</span></div>}
                    </div>
                  </>
                )}

                <div className="section-title">Acciones (CRM)</div>
                <ActionsPanel
                  scopeUrl={`/api/records/presupuesto/${detail.id}/acciones`}
                  itemUrl={(id) => `/api/acciones/${id}`}
                  onToast={toast}
                  onChange={reload}
                />

                {detail.lead_id && (
                  <>
                    <div className="section-title">Lead asociado</div>
                    <button
                      type="button"
                      className="btn ghost"
                      onClick={() => window.dispatchEvent(new CustomEvent('erp:open-lead', { detail: { id: detail.lead_id } }))}
                    >
                      Ver lead
                    </button>
                    <div className="section-title">Acciones del lead asociado</div>
                    <ActionsPanel
                      scopeUrl={`/api/records/${detail.lead_id}/acciones`}
                      itemUrl={(id) => `/api/acciones/${id}`}
                      readOnly
                      onToast={toast}
                      title="Acciones"
                    />
                  </>
                )}
              </div>
            </>
          )}
        </div>
      </div>

      {modal?.type === 'header' && (
        <PresupuestoHeaderForm
          initial={modal.data}
          onClose={() => setModal(null)}
          onSaved={(id) => { if (id) setSelectedId(id); reload(); }}
          onToast={toast}
        />
      )}
      {modal?.type === 'line' && detail && (
        <PresupuestoLineaModal
          presupuestoId={detail.id}
          linea={modal.data}
          onClose={() => setModal(null)}
          onSaved={reload}
          onToast={toast}
        />
      )}
      {modal?.type === 'line-manual' && detail && (
        <PresupuestoLineaManualModal
          presupuestoId={detail.id}
          linea={modal.data}
          onClose={() => setModal(null)}
          onSaved={reload}
          onToast={toast}
        />
      )}
      {modal?.type === 'line-cat' && detail && (
        <PresupuestoLineaCatalogoModal
          presupuestoId={detail.id}
          linea={modal.data}
          onClose={() => setModal(null)}
          onSaved={reload}
          onToast={toast}
        />
      )}
      {modal?.type === 'enviar' && detail && (
        <PresupuestoEnviarModal
          presupuesto={detail}
          onClose={() => { setModal(null); reload(); }}
          onSent={markEnviado}
          onToast={toast}
        />
      )}
      {deleteState && (() => {
        const anyPedida = deleteState.ocs.some(o => o.pedida);
        const onlyBorradores = !anyPedida;
        return (
          <PresuConfirmModal
            title={`Eliminar ${deleteState.presupuesto.numero_presupuesto || ''}`}
            confirmLabel={anyPedida ? 'Eliminar todo' : 'Continuar'}
            confirmDisabled={anyPedida && !deleteState.forceConfirmed}
            onCancel={() => setDeleteState(null)}
            onConfirm={confirmDelete}
            danger
          >
            {onlyBorradores && (
              <div>Se eliminarán también <strong>{deleteState.ocs.length}</strong> órdenes de compra en borrador vinculadas. ¿Continuar?</div>
            )}
            {anyPedida && (
              <>
                <div style={{color:'var(--err)'}}><strong>Atención:</strong> existen OCs ya pedidas vinculadas a este presupuesto. Eliminar es irreversible.</div>
              </>
            )}
            <PresuOCsTable ocs={deleteState.ocs}/>
            {anyPedida && (
              <label style={{display:'flex', gap:6, alignItems:'center', marginTop:10, fontSize:13}}>
                <input type="checkbox" checked={deleteState.forceConfirmed} onChange={e => setDeleteState(s => ({...s, forceConfirmed: e.target.checked}))}/>
                Eliminar también las OCs ya pedidas (irreversible)
              </label>
            )}
          </PresuConfirmModal>
        );
      })()}

      {showLockWarnList && (
        <PresuConfirmModal
          title="Presupuesto aceptado"
          confirmLabel="Cambiar estado"
          onCancel={() => setShowLockWarnList(false)}
          onConfirm={() => {
            setShowLockWarnList(false);
            if (detail) setModal({ type:'header', data:detail });
          }}
        >
          Este presupuesto está en estado <strong>Aceptado</strong>. Cambia el estado para poder editar sus líneas.
        </PresuConfirmModal>
      )}
    </>
  );
};

window.PresupuestosView = PresupuestosView;
window.PresupuestoHeaderForm = PresupuestoHeaderForm;
