// Detalle de solicitud
function DetalleSolicitud() {
  const [sol,     setSol]     = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [bitacora, setBitacora] = React.useState([]);
  const [uploading, setUploading] = React.useState(false);
  const [editOpen, setEditOpen]   = React.useState(false);
  const [editForm, setEditForm]   = React.useState(null);
  const [editSaving, setEditSaving] = React.useState(false);
  const ocFileRef  = React.useRef(null);
  const docFileRef = React.useRef(null);

  function recargar() {
    const id = window._desasfCurrentSolicitud;
    if (!id) return;
    Promise.all([
      DesasfAPI.get('/solicitudes/' + id),
      DesasfAPI.get('/solicitudes/' + id + '/bitacora'),
    ]).then(([s, b]) => { setSol(s); setBitacora(b); })
      .catch(console.error);
  }

  React.useEffect(() => {
    const id = window._desasfCurrentSolicitud;
    if (!id) {
      setLoading(false);
      return;
    }
    Promise.all([
      DesasfAPI.get('/solicitudes/' + id),
      DesasfAPI.get('/solicitudes/' + id + '/bitacora'),
    ]).then(([s, b]) => { setSol(s); setBitacora(b); })
      .catch(console.error)
      .finally(() => setLoading(false));
  }, []);

  async function descargarDoc(doc) {
    if (!sol) return;
    try {
      const tk = DesasfAPI.getToken();
      const res = await fetch(`/api/solicitudes/${sol.id}/documentos/${doc.id}/descargar`, {
        headers: { Authorization: 'Bearer ' + tk },
      });
      if (!res.ok) throw new Error('No se pudo descargar');
      const blob = await res.blob();
      const url  = URL.createObjectURL(blob);
      const a    = document.createElement('a');
      a.href = url; a.download = doc.nombre;
      document.body.appendChild(a); a.click(); a.remove();
      URL.revokeObjectURL(url);
    } catch (e) {
      alert('Error al descargar: ' + e.message);
    }
  }

  async function subirDoc(file, tipo) {
    if (!sol || !file) return;
    setUploading(true);
    try {
      const fd = new FormData();
      fd.append('file', file);
      fd.append('tipo', tipo || 'documento');
      await DesasfAPI.upload(`/solicitudes/${sol.id}/documentos`, fd);
      recargar();
    } catch (e) {
      alert('Error al subir archivo: ' + e.message);
    } finally {
      setUploading(false);
      if (ocFileRef.current)  ocFileRef.current.value  = '';
      if (docFileRef.current) docFileRef.current.value = '';
    }
  }

  async function eliminarDoc(doc) {
    if (!sol) return;
    if (!confirm(`¿Eliminar "${doc.nombre}"?`)) return;
    try {
      await DesasfAPI.delete(`/solicitudes/${sol.id}/documentos/${doc.id}`);
      recargar();
    } catch (e) {
      alert('Error al eliminar: ' + e.message);
    }
  }

  function fmtBytes(n) {
    if (!n) return '—';
    if (n < 1024)        return `${n} B`;
    if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
    return `${(n / 1024 / 1024).toFixed(2)} MB`;
  }

  if (loading) return (
    <div className="app">
      <AppSidebar active="solicitudes"/>
      <Topbar crumbs={['Solicitudes','Cargando…']}/>
      <main className="main" style={{display:'grid',placeItems:'center'}}>
        <div style={{color:'var(--ink-5)'}}>Cargando solicitud…</div>
      </main>
    </div>
  );

  if (!sol) return (
    <div className="app">
      <AppSidebar active="solicitudes"/>
      <Topbar crumbs={['Solicitudes','—']}/>
      <main className="main" style={{display:'grid',placeItems:'center'}}>
        <div style={{color:'var(--ink-5)'}}>Selecciona una solicitud desde la lista.</div>
      </main>
    </div>
  );

  const pasoActual = sol.paso_actual ?? 0;
  const stepsLabels = [
    'Recepción', 'Análisis lab.', 'Diseño aprobado',
    'En producción', 'En tránsito', 'Entrega',
  ];
  const pasos = stepsLabels.map((l, i) => {
    const dbStep = (sol.pasos || [])[i];
    return {
      l,
      d: dbStep?.completado_at
          ? new Date(dbStep.completado_at).toLocaleString('es-PE',{day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit'})
          : (i === pasoActual ? 'En curso' : i === pasoActual + 1 ? 'Pendiente' : '—'),
      done:   i <  pasoActual,
      active: i === pasoActual,
    };
  });

  const iniciales = sol.cliente_nombre
    ? sol.cliente_nombre.split(/\s+/).slice(0,2).map(w => w[0]).join('').toUpperCase()
    : '—';

  const pago = sol.pago;
  const pagoEstado    = sol.pago_estado_agregado || pago?.estado || null;
  const pagadoTotal   = Number(sol.pagado_total || 0);
  const saldoPend     = Number(sol.saldo_pendiente ?? Math.max(0, Number(sol.precio_total || 0) - pagadoTotal));
  const precioTotal   = Number(sol.precio_total || 0);
  const meRol         = (DesasfAPI.getCurrentUser() || {}).rol;
  const puedePagar    = DesasfAPI.hasPerm('pagos.cargar');

  return (
    <div className="app">
      <AppSidebar active="solicitudes"/>
      <Topbar crumbs={['Solicitudes', (sol.proyecto && sol.proyecto.trim()) || sol.ticket]}/>
      <main className="main">
        <div className="page-head">
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 6 }}>
              <span className="id" style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--ink-5)' }}>
                {sol.ticket}{sol.oc_numero ? ` · ${sol.oc_numero}` : ''}
              </span>
              <Pill kind={estadoToKind(sol.estado)}>{estadoLabel(sol.estado)}</Pill>
              {pagoEstado === 'pagado'         && <Pill kind="ok">Pagado</Pill>}
              {pagoEstado === 'confirmado'     && <Pill kind="ok">Pagado</Pill>}
              {pagoEstado === 'pagado_parcial' && <Pill kind="review">Pago parcial · S/ {fmtNum(pagadoTotal, 2)} de {fmtNum(precioTotal, 2)}</Pill>}
              {pagoEstado === 'por_validar'    && <Pill kind="review">Pago por validar</Pill>}
              {pagoEstado === 'rechazado'      && <Pill kind="danger">Pago rechazado</Pill>}
              {!pagoEstado                     && <Pill kind="pending">Sin pago</Pill>}
            </div>
            <h1>{solicitudTitulo(sol)}</h1>
            {sol.proyecto && (sol.proyecto || '').trim() && (
              <div className="muted" style={{ fontSize: 13, marginTop: 4 }}>
                {sol.producto_nombre} · {fmtNum(sol.volumen)} {sol.unidad || 'm³'}
              </div>
            )}
            <p style={{ marginTop: 6 }}>
              {sol.cliente_nombre}
              {sol.vendedor_nombre ? ` · Asistente Técnico comercial: ${sol.vendedor_nombre}` : ''}
            </p>
          </div>
          <div className="row">
            <button className="btn btn--ghost" onClick={() => exportCSV(
              `solicitud-${sol.ticket}.csv`,
              [sol],
              [
                { label: 'Ticket',     value: 'ticket' },
                { label: 'OC',         value: r => r.oc_numero || '' },
                { label: 'Proyecto',   value: r => r.proyecto || '' },
                { label: 'Cliente',    value: 'cliente_nombre' },
                { label: 'RUC',        value: r => r.cliente_ruc || '' },
                { label: 'Producto',   value: 'producto_nombre' },
                { label: 'Volumen',    value: r => fmtNum(r.volumen) + ' ' + (r.unidad || 'm³') },
                { label: 'Estado',     value: r => estadoLabel(r.estado) },
                { label: 'Asistente Técnico comercial', value: r => r.vendedor_nombre || '' },
                { label: 'Precio total', value: r => Number(r.precio_total || 0) },
                { label: 'Crédito proy.', value: r => r.credito_proyecto != null ? Number(r.credito_proyecto) : '' },
                { label: 'Días venc.',   value: r => r.dias_vencimiento ?? '' },
                { label: 'Dirección obra', value: r => r.direccion_obra || '' },
                { label: 'Vencimiento de crédito', value: r => r.eta ? new Date(r.eta).toLocaleDateString('es-PE') : '' },
                { label: 'Notas',      value: r => r.notas_tecnicas || '' },
              ]
            )}>{Icon.download}<span>Exportar</span></button>
            {meRol !== 'cliente' && DesasfAPI.hasPerm('solicitudes.editar') && (
              <button className="btn btn--ghost" onClick={() => {
                setEditForm({
                  proyecto:         sol.proyecto || '',
                  oc_numero:        sol.oc_numero || '',
                  volumen:          sol.volumen ?? '',
                  direccion_obra:   sol.direccion_obra || '',
                  dias_vencimiento: sol.dias_vencimiento ?? '',
                  credito_proyecto: sol.credito_proyecto ?? '',
                });
                setEditOpen(true);
              }}>Editar</button>
            )}
            {puedePagar && pagoEstado !== 'pagado' && pagoEstado !== 'confirmado' && (
              <button className="btn btn--accent" onClick={() => openModal('subir-voucher', { solicitudId: sol.id })}>
                {Icon.cash}<span>{pagoEstado === 'pagado_parcial' ? 'Cargar otro voucher' : 'Cargar voucher'}</span>
              </button>
            )}
            {DesasfAPI.hasPerm('logistica.ver') && (
              <button className="btn btn--primary" onClick={() => navigate(meRol === 'cliente' ? 'logistica-cliente' : 'logistica')}>
                {Icon.truck}<span>Ver tránsito</span>
              </button>
            )}
          </div>
        </div>

        {/* Steps */}
        <div className="card" style={{ marginBottom: 16 }}>
          <div className="card__body">
            <div className="steps">
              {pasos.map((s, i) => (
                <div key={s.l} className={`step ${s.done ? 'done' : ''} ${s.active ? 'active' : ''}`}>
                  <div className="bar"/>
                  <div className="lbl">{i + 1}. {s.l}</div>
                  <div style={{ fontSize: 11, color: 'var(--ink-5)', fontFamily: 'var(--mono)' }}>{s.d}</div>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* Etapa activa — flujo por pasos */}
        <EtapaActiva sol={sol} onChange={recargar}/>

        <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 16 }}>
          {/* Left column */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
            {/* Diseño / mezcla */}
            <div className="card">
              <div className="card__head">
                <h3>Diseño de mezcla</h3>
                <span className="meta">
                  {sol.diseno?.aprobado_por
                    ? `Aprobado por ${sol.diseno.aprobado_por}`
                    : 'Sin diseño registrado'}
                </span>
              </div>
              <div className="card__body">
                {sol.diseno ? (
                  <dl className="dl">
                    <dt>Tipo</dt><dd>{sol.diseno.tipo || '—'}</dd>
                    <dt>Granulometría</dt><dd>{sol.diseno.granulometria || '—'}</dd>
                    <dt>% asfalto</dt><dd>{sol.diseno.pct_asfalto != null ? `${sol.diseno.pct_asfalto}%` : '—'}</dd>
                    <dt>Estabilidad Marshall</dt><dd>{sol.diseno.estabilidad_marshall != null ? `${sol.diseno.estabilidad_marshall} kN` : '—'}</dd>
                    <dt>Flujo</dt><dd>{sol.diseno.flujo != null ? `${sol.diseno.flujo} mm` : '—'}</dd>
                    <dt>Vacíos</dt><dd>{sol.diseno.vacios != null ? `${sol.diseno.vacios}%` : '—'}</dd>
                  </dl>
                ) : (
                  <div style={{ color: 'var(--ink-5)', fontSize: 13, padding: '8px 0' }}>
                    El diseño de mezcla se registrará al pasar al estado <b>Diseño aprobado</b>.
                  </div>
                )}
                {sol.notas_tecnicas && (
                  <>
                    <div className="divider"/>
                    <div style={{ fontSize: 12, color: 'var(--ink-5)', marginBottom: 6 }}>Notas técnicas</div>
                    <div style={{ fontSize: 13, whiteSpace: 'pre-wrap' }}>{sol.notas_tecnicas}</div>
                  </>
                )}
                {sol.notas_cliente && (
                  <>
                    <div className="divider"/>
                    <div style={{ fontSize: 12, color: 'var(--ink-5)', marginBottom: 6 }}>Nota del solicitante</div>
                    <div style={{ fontSize: 13, whiteSpace: 'pre-wrap' }}>{sol.notas_cliente}</div>
                  </>
                )}
              </div>
            </div>

            {/* Producción y despacho — placeholder hasta etapa 6 */}
            <div className="card">
              <div className="card__head">
                <h3>Producción y despacho</h3>
                <Pill kind={estadoToKind(sol.estado)}>{estadoLabel(sol.estado)}</Pill>
              </div>
              <div className="card__body">
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 14 }}>
                  <div className="kpi" style={{ padding: 12 }}>
                    <div className="kpi__label">Volumen</div>
                    <div className="kpi__value" style={{ fontSize: 22 }}>{fmtNum(sol.volumen)} <span className="unit">{sol.unidad || 'm³'}</span></div>
                  </div>
                  <div className="kpi" style={{ padding: 12 }}>
                    <div className="kpi__label">Precio total</div>
                    <div className="kpi__value" style={{ fontSize: 22 }}>S/ {sol.precio_total ? Number(sol.precio_total).toLocaleString('es-PE') : '—'}</div>
                  </div>
                </div>
                <dl className="dl">
                  <dt>Planta</dt><dd className="muted">— (asignar al producir)</dd>
                  <dt>Lote</dt><dd className="muted">—</dd>
                  <dt>Vencimiento de crédito</dt><dd>{sol.eta ? new Date(sol.eta).toLocaleDateString('es-PE',{day:'2-digit',month:'short',year:'numeric'}) : '—'}</dd>
                </dl>
              </div>
            </div>
          </div>

          {/* Right column */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
            {/* Cliente */}
            <div className="card">
              <div className="card__head">
                <h3>Cliente</h3>
                <a className="meta" style={{ color: 'var(--ink-2)', fontWeight: 600, cursor: 'pointer' }}
                  onClick={() => {
                    window._desasfCurrentCliente = sol.empresa_id;
                    navigate('clientes');
                  }}>Ver perfil →</a>
              </div>
              <div className="card__body">
                <div className="row">
                  <div style={{ width: 40, height: 40, borderRadius: 8, background: 'var(--ink-0)', color: 'var(--hi-vis)', display: 'grid', placeItems: 'center', fontFamily: 'var(--cond)', fontWeight: 700 }}>{iniciales}</div>
                  <div>
                    <div style={{ fontWeight: 600 }}>{sol.cliente_nombre}</div>
                    <div className="muted" style={{ fontSize: 12 }}>RUC {sol.cliente_ruc || '—'}</div>
                  </div>
                </div>
              </div>
            </div>

            {/* Datos del proyecto */}
            {(sol.proyecto || sol.direccion_obra || sol.dias_vencimiento != null || sol.credito_proyecto != null) && (
              <div className="card">
                <div className="card__head"><h3>Datos del proyecto</h3></div>
                <div className="card__body">
                  <dl className="dl">
                    {sol.proyecto && (<><dt>Proyecto</dt><dd>{sol.proyecto}</dd></>)}
                    {sol.direccion_obra && (<><dt>Dirección de obra</dt><dd>{sol.direccion_obra}</dd></>)}
                    {sol.dias_vencimiento != null && (<><dt>Vencimiento</dt><dd>{sol.dias_vencimiento} días</dd></>)}
                    {sol.credito_proyecto != null && (<><dt>Crédito autorizado</dt><dd className="num" style={{ fontWeight: 600 }}>S/ {Number(sol.credito_proyecto).toLocaleString('es-PE', { minimumFractionDigits: 2 })}</dd></>)}
                  </dl>
                </div>
              </div>
            )}

            {/* Pago */}
            <div className="card">
              <div className="card__head">
                <h3>Pagos del proyecto</h3>
                {pagoEstado === 'pagado'         && <Pill kind="ok">Pagado</Pill>}
                {pagoEstado === 'confirmado'     && <Pill kind="ok">Pagado</Pill>}
                {pagoEstado === 'pagado_parcial' && <Pill kind="review">Parcial</Pill>}
                {pagoEstado === 'por_validar'    && <Pill kind="review">Por validar</Pill>}
                {pagoEstado === 'rechazado'      && <Pill kind="danger">Rechazado</Pill>}
                {!pagoEstado                     && <Pill kind="pending">Sin pago</Pill>}
              </div>
              <div className="card__body">
                {precioTotal > 0 && (
                  <div style={{ padding: 12, background: 'var(--ink-9)', borderRadius: 6, marginBottom: 12 }}>
                    <div className="row" style={{ justifyContent: 'space-between', fontSize: 12 }}>
                      <span className="muted">Total del proyecto</span>
                      <span className="num" style={{ fontWeight: 700 }}>S/ {fmtNum(precioTotal, 2)}</span>
                    </div>
                    <div className="row" style={{ justifyContent: 'space-between', fontSize: 12, marginTop: 4 }}>
                      <span className="muted">Pagado (confirmado)</span>
                      <span className="num" style={{ fontWeight: 600, color: pagadoTotal > 0 ? 'var(--status-ok)' : 'var(--ink-3)' }}>S/ {fmtNum(pagadoTotal, 2)}</span>
                    </div>
                    <div className="row" style={{ justifyContent: 'space-between', fontSize: 12, marginTop: 4 }}>
                      <span className="muted">Saldo pendiente</span>
                      <span className="num" style={{ fontWeight: 700, color: saldoPend > 0 ? 'var(--status-pending)' : 'var(--ink-3)' }}>S/ {fmtNum(saldoPend, 2)}</span>
                    </div>
                    {precioTotal > 0 && (
                      <div style={{ height: 6, background: '#fff', border: '1px solid var(--line)', borderRadius: 3, overflow: 'hidden', marginTop: 8 }}>
                        <div style={{ width: Math.min(100, (pagadoTotal / precioTotal) * 100) + '%', height: '100%', background: pagadoTotal >= precioTotal ? 'var(--status-ok)' : 'var(--hi-vis)' }}/>
                      </div>
                    )}
                  </div>
                )}

                {(sol.pagos || []).length === 0 ? (
                  <div style={{ color: 'var(--ink-5)', fontSize: 13, marginBottom: 10 }}>
                    Aún no se ha cargado ningún voucher. Puedes registrar el pago (total o parcial) desde aquí.
                  </div>
                ) : (
                  <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden' }}>
                    {(sol.pagos || []).map((p, i) => {
                      const kind = p.estado === 'confirmado' ? 'ok'
                                 : p.estado === 'por_validar' ? 'review'
                                 : p.estado === 'rechazado'   ? 'danger' : 'neutral';
                      const lbl  = p.estado === 'confirmado' ? 'Confirmado'
                                 : p.estado === 'por_validar' ? 'Por validar'
                                 : p.estado === 'rechazado'   ? 'Rechazado' : p.estado;
                      return (
                        <div key={p.id} style={{ padding: '10px 12px', display: 'flex', gap: 10, alignItems: 'center', borderBottom: i < (sol.pagos || []).length - 1 ? '1px solid var(--line)' : 0, background: '#fff' }}>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div className="row" style={{ gap: 8, alignItems: 'center' }}>
                              <span className="k" style={{ fontWeight: 700, fontSize: 12 }}>{p.voucher_ref || '#' + p.id}</span>
                              <Pill kind={kind}>{lbl}</Pill>
                            </div>
                            <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
                              {p.banco || '—'}{p.operacion_num ? ' · op ' + p.operacion_num : ''}
                              {p.cargado_at ? ' · ' + new Date(p.cargado_at).toLocaleDateString('es-PE', { day: '2-digit', month: 'short', year: 'numeric' }) : ''}
                            </div>
                          </div>
                          <span className="num" style={{ fontWeight: 700, fontSize: 13 }}>S/ {fmtNum(p.monto, 2)}</span>
                          {['dev','admin','gerente_general','finanzas'].includes(meRol) && p.estado === 'por_validar' && (
                            <button className="btn btn--ghost btn--sm" onClick={() => {
                              window._desasfCurrentPago = p.id;
                              openModal('validar-voucher');
                            }}>Validar</button>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}

                {puedePagar && pagoEstado !== 'pagado' && pagoEstado !== 'confirmado' && (
                  <button className="btn btn--accent btn--sm" style={{ width: '100%', justifyContent: 'center', marginTop: 12 }}
                    onClick={() => openModal('subir-voucher', { solicitudId: sol.id })}>
                    {Icon.cash}<span>{(sol.pagos || []).length > 0 ? 'Registrar otro pago' : 'Cargar voucher'}</span>
                  </button>
                )}
              </div>
            </div>

            {/* Documentos */}
            {(() => {
              const u = DesasfAPI.getCurrentUser() || {};
              const puedeSubir  = DesasfAPI.hasPerm('solicitudes.documentos.subir');
              const puedeBorrar = DesasfAPI.hasPerm('solicitudes.documentos.eliminar');
              const docs    = sol.documentos || [];
              const ocDoc   = docs.find(d => d.tipo === 'orden_compra');
              const otros   = docs.filter(d => d.tipo !== 'orden_compra');
              return (
                <div className="card">
                  <div className="card__head">
                    <h3>Documentos</h3>
                    <span className="meta">{docs.length} archivo{docs.length === 1 ? '' : 's'}</span>
                  </div>
                  <div className="card__body" style={{ padding: 0 }}>

                    {/* Orden de compra (siempre visible) */}
                    <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--line)', background: 'var(--ink-9)' }}>
                      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
                        <div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--ink-3)' }}>
                          Orden de compra
                        </div>
                        {sol.oc_numero && <span className="k" style={{ fontSize: 11, color: 'var(--ink-5)' }}>{sol.oc_numero}</span>}
                      </div>
                      {ocDoc ? (
                        <div style={{ display: 'flex', alignItems: 'center', gap: 10, background: '#fff', border: '1px solid var(--line)', borderRadius: 6, padding: 10 }}>
                          <div style={{ width: 32, height: 32, borderRadius: 5, background: 'var(--ink-9)', display: 'grid', placeItems: 'center', color: 'var(--ink-3)' }}>{Icon.doc}</div>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{ocDoc.nombre}</div>
                            <div style={{ fontSize: 11, color: 'var(--ink-5)' }}>
                              {fmtBytes(ocDoc.archivo_size)} · {ocDoc.created_at ? new Date(ocDoc.created_at).toLocaleDateString('es-PE',{day:'2-digit',month:'short',year:'numeric'}) : ''}
                            </div>
                          </div>
                          <div className="icon-btn" title="Descargar" onClick={() => descargarDoc(ocDoc)}>{Icon.download}</div>
                          {puedeSubir && (
                            <div className="icon-btn" title="Reemplazar"
                              onClick={() => ocFileRef.current?.click()}
                              style={{ fontSize: 14 }}>{Icon.upload}</div>
                          )}
                          {puedeBorrar && (
                            <div className="icon-btn" title="Eliminar"
                              onClick={() => eliminarDoc(ocDoc)}
                              style={{ fontSize: 16, color: 'var(--status-danger)' }}>×</div>
                          )}
                        </div>
                      ) : puedeSubir ? (
                        <div onClick={() => !uploading && ocFileRef.current?.click()}
                          style={{ border: '1px dashed var(--line-strong)', borderRadius: 6, padding: 12,
                            display: 'flex', alignItems: 'center', gap: 10, cursor: uploading ? 'wait' : 'pointer',
                            background: '#fff', color: 'var(--ink-3)', opacity: uploading ? 0.6 : 1 }}>
                          <div style={{ width: 32, height: 32, borderRadius: 5, background: 'var(--ink-9)', display: 'grid', placeItems: 'center' }}>{Icon.upload}</div>
                          <div style={{ flex: 1 }}>
                            <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-1)' }}>
                              {uploading ? 'Subiendo…' : 'Adjuntar documento de OC'}
                            </div>
                            <div style={{ fontSize: 11, color: 'var(--ink-5)' }}>PDF, imagen, Excel, Word… (máx 50 MB)</div>
                          </div>
                        </div>
                      ) : (
                        <div style={{ fontSize: 12, color: 'var(--ink-5)', fontStyle: 'italic' }}>Sin archivo de OC adjunto.</div>
                      )}
                      <input ref={ocFileRef} type="file"
                        accept=".pdf,.jpg,.jpeg,.png,.webp,.gif,.xls,.xlsx,.doc,.docx,.csv,.txt,.zip"
                        style={{ display: 'none' }}
                        onChange={e => { const f = e.target.files?.[0]; if (f) subirDoc(f, 'orden_compra'); }}/>
                    </div>

                    {/* Otros documentos */}
                    {otros.map(d => (
                      <div key={d.id} style={{ padding: '10px 16px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
                        <div style={{ width: 28, height: 28, borderRadius: 5, background: 'var(--ink-9)', display: 'grid', placeItems: 'center', color: 'var(--ink-3)' }}>{Icon.doc}</div>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.nombre}</div>
                          <div style={{ fontSize: 11, color: 'var(--ink-5)' }}>
                            {d.tipo || 'archivo'} · {fmtBytes(d.archivo_size)} · {d.created_at ? new Date(d.created_at).toLocaleDateString('es-PE',{day:'2-digit',month:'short'}) : ''}
                          </div>
                        </div>
                        <div className="icon-btn" title="Descargar" onClick={() => descargarDoc(d)}>{Icon.download}</div>
                        {puedeBorrar && (
                          <div className="icon-btn" title="Eliminar" onClick={() => eliminarDoc(d)}
                            style={{ fontSize: 16, color: 'var(--status-danger)' }}>×</div>
                        )}
                      </div>
                    ))}

                    {puedeSubir && (
                      <div style={{ padding: 12 }}>
                        <input ref={docFileRef} type="file"
                          accept=".pdf,.jpg,.jpeg,.png,.webp,.gif,.xls,.xlsx,.doc,.docx,.csv,.txt,.zip"
                          style={{ display: 'none' }}
                          onChange={e => { const f = e.target.files?.[0]; if (f) subirDoc(f, 'documento'); }}/>
                        <button className="btn btn--ghost btn--sm"
                          disabled={uploading}
                          onClick={() => docFileRef.current?.click()}>
                          {Icon.plus}<span>{uploading ? 'Subiendo…' : 'Adjuntar otro documento'}</span>
                        </button>
                      </div>
                    )}
                  </div>
                </div>
              );
            })()}

            {/* Bitácora */}
            <div className="card">
              <div className="card__head">
                <h3>Bitácora</h3>
                <span className="meta">{bitacora.length} eventos</span>
              </div>
              <div className="card__body" style={{ padding: '4px 16px' }}>
                {bitacora.map((a) => (
                  <div key={a.id} className={`activity ${a.es_sistema ? 'is-new' : ''}`}>
                    <div className="activity__dot"/>
                    <div className="activity__body">
                      <b>{a.es_sistema ? 'Sistema' : (a.usuario_nombre_real || a.usuario_nombre || '—')}</b> · {a.mensaje}
                      <div className="activity__time">{formatRelTime(a.created_at)}</div>
                    </div>
                  </div>
                ))}
                {bitacora.length === 0 && (
                  <div style={{ padding: 16, color: 'var(--ink-5)', fontSize: 13 }}>Sin eventos en bitácora.</div>
                )}
              </div>
            </div>
          </div>
        </div>
      </main>

      {editOpen && editForm && (
        <div className="modal-overlay" onClick={e => { if (e.target === e.currentTarget) setEditOpen(false); }}>
          <div style={{ width: 560, background: '#fff', borderRadius: 12, boxShadow: '0 30px 80px rgba(0,0,0,0.4)', overflow: 'hidden' }}>
            <div style={{ padding: '16px 22px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <div>
                <div className="muted k" style={{ fontSize: 11 }}>{sol.ticket}</div>
                <h3 style={{ margin: 0, fontFamily: 'var(--cond)', fontWeight: 700 }}>Editar solicitud</h3>
              </div>
              <div className="icon-btn" onClick={() => setEditOpen(false)}>{Icon.x}</div>
            </div>
            <div style={{ padding: 22, maxHeight: '70vh', overflow: 'auto' }}>
              <div style={{ display: 'grid', gap: 12 }}>
                <div>
                  <label className="label">Nombre del proyecto</label>
                  <input className="input" maxLength={120}
                    value={editForm.proyecto}
                    onChange={e => setEditForm({ ...editForm, proyecto: e.target.value })}/>
                </div>
                <div>
                  <label className="label">Dirección de la obra</label>
                  <input className="input" maxLength={255}
                    value={editForm.direccion_obra}
                    onChange={e => setEditForm({ ...editForm, direccion_obra: e.target.value })}/>
                </div>
                <div className="row" style={{ gap: 10 }}>
                  <div style={{ flex: 1 }}>
                    <label className="label">Días de vencimiento</label>
                    <input className="input num" type="number" min="0"
                      value={editForm.dias_vencimiento}
                      onChange={e => setEditForm({ ...editForm, dias_vencimiento: e.target.value })}/>
                  </div>
                  <div style={{ flex: 1 }}>
                    <label className="label">Crédito del proyecto (S/)</label>
                    <input className="input num" type="number" step="0.01" min="0"
                      value={editForm.credito_proyecto}
                      onChange={e => setEditForm({ ...editForm, credito_proyecto: e.target.value })}/>
                  </div>
                </div>
                <div className="row" style={{ gap: 10 }}>
                  <div style={{ flex: 1 }}>
                    <label className="label">N° de orden de compra</label>
                    <input className="input k" maxLength={60}
                      value={editForm.oc_numero}
                      onChange={e => setEditForm({ ...editForm, oc_numero: e.target.value })}/>
                  </div>
                  <div style={{ flex: 1 }}>
                    <label className="label">Volumen ({sol.unidad || 'm³'})</label>
                    <input className="input num" type="number" step="0.01" min="0"
                      value={editForm.volumen}
                      onChange={e => setEditForm({ ...editForm, volumen: e.target.value })}/>
                  </div>
                </div>
              </div>
            </div>
            <div style={{ padding: '14px 22px', borderTop: '1px solid var(--line)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
              <button className="btn btn--ghost" onClick={() => setEditOpen(false)} disabled={editSaving}>Cancelar</button>
              <button className="btn btn--accent" disabled={editSaving} onClick={async () => {
                setEditSaving(true);
                try {
                  await DesasfAPI.put('/solicitudes/' + sol.id, {
                    proyecto:         editForm.proyecto.trim() || null,
                    direccion_obra:   editForm.direccion_obra.trim() || null,
                    dias_vencimiento: editForm.dias_vencimiento === '' ? null : Number(editForm.dias_vencimiento),
                    credito_proyecto: editForm.credito_proyecto === '' ? null : Number(editForm.credito_proyecto),
                    oc_numero:        editForm.oc_numero.trim() || null,
                    volumen:          parseFloat(editForm.volumen) || sol.volumen,
                  });
                  setEditOpen(false);
                  recargar();
                } catch (err) { alert('Error: ' + err.message); }
                finally { setEditSaving(false); }
              }}>{Icon.check}<span>{editSaving ? 'Guardando…' : 'Guardar'}</span></button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════
// EtapaActiva — card del paso actual con docs+nota+botón terminar
// ═══════════════════════════════════════════════
const ETAPA_META = [
  { key: 'recepcion',  label: 'Recepción',          accion: 'Iniciar análisis',     finalLabel: 'Iniciar análisis',
    perm: 'solicitudes.paso.recepcion',  color: 'var(--ink-3)' },
  { key: 'analisis',   label: 'Análisis lab.',      accion: 'Etapa terminada',      finalLabel: 'Etapa terminada · enviar a diseño',
    perm: 'solicitudes.paso.analisis',   color: 'var(--status-transit)', hint: 'Sube el informe de laboratorio y agrega observaciones técnicas.' },
  { key: 'diseno',     label: 'Diseño aprobado',    accion: 'Etapa terminada',      finalLabel: 'Etapa terminada · enviar a producción',
    perm: 'solicitudes.paso.diseno',     color: 'var(--signal)', hint: 'Sube el diseño de mezcla aprobado.' },
  { key: 'produccion', label: 'En producción',      accion: 'Producción terminada', finalLabel: 'Producción terminada · enviar a despacho',
    perm: 'solicitudes.paso.produccion', color: 'var(--status-prod)', hint: 'Sube el certificado/ticket de producción y datos del lote.' },
  { key: 'transito',   label: 'En tránsito',        accion: '',                     finalLabel: '',
    perm: null, color: 'var(--status-pending)', hint: 'El conductor está en ruta. Esta etapa se gestiona desde Logística.' },
  { key: 'entregado',  label: 'Entregado',          accion: '',                     finalLabel: '',
    perm: null, color: 'var(--status-ok)', hint: 'Despacho confirmado en obra.' },
];

function EtapaActiva({ sol, onChange }) {
  const [nota,       setNota]      = React.useState('');
  const [docs,       setDocs]      = React.useState([]); // array de File (pendientes)
  const [saving,     setSaving]    = React.useState(false);
  const fileRef = React.useRef(null);

  const u    = DesasfAPI.getCurrentUser() || {};
  const meta = ETAPA_META.find(e => e.key === sol.estado);
  const paso = sol.paso_actual ?? 0;
  const pasoDB = (sol.pasos || []).find(p => p.paso === paso);

  // Permiso por etapa: cada meta trae su slug (solicitudes.paso.*); en
  // tránsito/entregado no hay acción aquí (lo maneja el conductor en Logística).
  const puedeAccionar = !!meta && !!meta.perm && DesasfAPI.hasPerm(meta.perm);

  if (sol.estado === 'cancelado') {
    return (
      <div className="card" style={{ marginBottom: 16, borderLeft: '3px solid var(--status-danger)' }}>
        <div className="card__body"><b>Solicitud cancelada.</b> No hay etapas activas.</div>
      </div>
    );
  }
  if (!meta) return null;

  // Docs ya subidos a este paso
  const docsPaso = (sol.documentos || []).filter(d => d.paso === paso);

  function elegirArchivos() { fileRef.current?.click(); }
  function onFiles(e) {
    const arr = Array.from(e.target.files || []);
    setDocs([...docs, ...arr]);
    e.target.value = '';
  }
  function quitarDoc(idx) {
    setDocs(docs.filter((_, i) => i !== idx));
  }

  async function subirYAvanzar(soloGuardar = false) {
    if (!puedeAccionar) return;
    setSaving(true);
    try {
      // 1) Subir todos los docs nuevos (uno por uno)
      for (const f of docs) {
        const fd = new FormData();
        fd.append('file', f);
        await DesasfAPI.upload(`/solicitudes/${sol.id}/pasos/${paso}/documentos`, fd);
      }
      // 2) Actualizar nota (si hay)
      if (nota.trim()) {
        await DesasfAPI.put(`/solicitudes/${sol.id}/pasos/${paso}/notas`, { notas: nota.trim() });
      }
      // 3) Avanzar de paso (si no es solo guardar)
      if (!soloGuardar) {
        await DesasfAPI.post(`/solicitudes/${sol.id}/avanzar`, { notas: nota.trim() || null });
      }
      setDocs([]); setNota('');
      onChange && onChange();
    } catch (err) {
      alert('Error: ' + err.message);
    } finally { setSaving(false); }
  }

  async function descargarDoc(doc) {
    try {
      const tk  = DesasfAPI.getToken();
      const res = await fetch(`/api/solicitudes/${sol.id}/documentos/${doc.id}/descargar`, {
        headers: { Authorization: 'Bearer ' + tk },
      });
      if (!res.ok) throw new Error('No se pudo descargar');
      const blob = await res.blob();
      const url  = URL.createObjectURL(blob);
      const a    = document.createElement('a');
      a.href = url; a.download = doc.nombre;
      document.body.appendChild(a); a.click(); a.remove();
      URL.revokeObjectURL(url);
    } catch (e) { alert(e.message); }
  }

  function fmtSize(b) {
    b = Number(b || 0);
    if (b >= 1024*1024) return (b/1024/1024).toFixed(1) + ' MB';
    if (b >= 1024) return (b/1024).toFixed(0) + ' KB';
    return b + ' B';
  }

  const esTransitoOEntregado = ['transito','entregado'].includes(sol.estado);

  return (
    <div className="card" style={{ marginBottom: 16, borderLeft: `4px solid ${meta.color}` }}>
      <div className="card__head">
        <div>
          <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--ink-5)', marginBottom: 2 }}>
            Etapa actual · paso {paso + 1} de 6
          </div>
          <h3 style={{ margin: 0 }}>{meta.label}</h3>
        </div>
        <div className="row" style={{ gap: 8 }}>
          {!puedeAccionar && !esTransitoOEntregado && (
            <span className="meta" style={{ color: 'var(--ink-5)' }}>Solo lectura para tu rol</span>
          )}
          {esTransitoOEntregado && (
            <button className="btn btn--ghost btn--sm" onClick={() => navigate('logistica')}>
              {Icon.truck}<span>Ver en Logística</span>
            </button>
          )}
        </div>
      </div>

      <div className="card__body">
        {meta.hint && (
          <div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 14, padding: '8px 12px', background: 'var(--ink-9)', borderRadius: 6 }}>
            {meta.hint}
          </div>
        )}

        {/* Documentos del paso */}
        {docsPaso.length > 0 && (
          <div style={{ marginBottom: 14 }}>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-5)', letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 6 }}>
              Documentos de esta etapa ({docsPaso.length})
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {docsPaso.map(d => (
                <div key={d.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', background: 'var(--ink-9)', borderRadius: 6 }}>
                  <div style={{ color: 'var(--ink-4)' }}>{Icon.doc}</div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.nombre}</div>
                    <div className="muted" style={{ fontSize: 11 }}>{fmtSize(d.archivo_size)} · {new Date(d.created_at).toLocaleString('es-PE', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}</div>
                  </div>
                  <button className="btn btn--ghost btn--sm" onClick={() => descargarDoc(d)}>{Icon.download}<span>Descargar</span></button>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Nota técnica guardada */}
        {pasoDB?.notas && (
          <div style={{ marginBottom: 14, padding: 12, background: 'var(--ink-9)', borderRadius: 6 }}>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-5)', letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 4 }}>
              Nota técnica guardada
            </div>
            <div style={{ fontSize: 13, whiteSpace: 'pre-wrap', color: 'var(--ink-2)' }}>{pasoDB.notas}</div>
          </div>
        )}

        {/* Acción del paso */}
        {puedeAccionar && !esTransitoOEntregado && (
          <>
            {/* Recepción: solo botón de iniciar análisis */}
            {sol.estado === 'recepcion' ? (
              <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                <button className="btn btn--accent" onClick={() => subirYAvanzar(false)} disabled={saving}>
                  {Icon.arrow}<span>{saving ? 'Procesando…' : meta.finalLabel}</span>
                </button>
              </div>
            ) : (
              <>
                {/* Otros pasos: archivos + nota + botón */}
                <input ref={fileRef} type="file" multiple
                  onChange={onFiles}
                  accept=".pdf,.jpg,.jpeg,.png,.webp,.gif,.xls,.xlsx,.doc,.docx,.csv,.txt,.zip"
                  style={{ display: 'none' }}/>

                <label className="label">Documentos adjuntos (opcional, varios)</label>
                <div onClick={elegirArchivos}
                  style={{ border: '1px dashed var(--line-strong)', borderRadius: 6, padding: 12,
                    display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
                    background: 'var(--ink-9)', color: 'var(--ink-3)', marginBottom: 8 }}>
                  <div style={{ width: 32, height: 32, borderRadius: 6, background: '#fff', border: '1px solid var(--line)',
                    display: 'grid', placeItems: 'center', color: 'var(--ink-3)' }}>{Icon.upload}</div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-1)' }}>Subir archivos</div>
                    <div style={{ fontSize: 11, color: 'var(--ink-5)' }}>PDF, imagen, Excel, Word… (varios a la vez)</div>
                  </div>
                </div>

                {docs.length > 0 && (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 8 }}>
                    {docs.map((f, i) => (
                      <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: 'var(--status-pending-bg)', borderRadius: 4 }}>
                        <span style={{ fontSize: 12, flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{f.name}</span>
                        <span className="muted" style={{ fontSize: 11 }}>{fmtSize(f.size)}</span>
                        <div className="icon-btn" style={{ color: 'var(--status-danger)', fontSize: 14 }} onClick={() => quitarDoc(i)}>×</div>
                      </div>
                    ))}
                  </div>
                )}

                <label className="label" style={{ marginTop: 10 }}>Nota técnica (opcional)</label>
                <textarea className="input" rows="3" style={{ height: 'auto', padding: 10 }}
                  value={nota} onChange={e => setNota(e.target.value)}
                  placeholder="Observaciones, resultados de laboratorio, parámetros de mezcla, lote, planta, etc."/>

                <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 14 }}>
                  {(docs.length > 0 || nota.trim()) && (
                    <button className="btn btn--ghost" onClick={() => subirYAvanzar(true)} disabled={saving}>
                      {saving ? 'Guardando…' : 'Solo guardar (sin avanzar)'}
                    </button>
                  )}
                  <button className="btn btn--accent" onClick={() => subirYAvanzar(false)} disabled={saving}>
                    {Icon.check}<span>{saving ? 'Procesando…' : meta.finalLabel}</span>
                  </button>
                </div>
              </>
            )}
          </>
        )}
      </div>
    </div>
  );
}

window.DetalleSolicitud = DetalleSolicitud;
window.EtapaActiva      = EtapaActiva;
