/* ============================================================
   Pantalla — Mapa con Pulso integrado
   Tres layouts vía Tweak `pulsoLayout`:
     • side     — panel lateral derecho (resultados verticales)
     • floating — tarjeta compacta flotante sobre el mapa
     • bottom   — barra inferior con timeline horizontal
   Sin placa: las tres comparten panel KPI + lista de nodos.
   ============================================================ */

const MapaScreen = ({ plate, onSearch, onNavigate, pulsoLayout = "side", onRegister, onVerNodo }) => {
  const [pinNode, setPinNode] = React.useState(null);
  React.useEffect(() => { if (plate) setPinNode(null); }, [plate]);
  const [quickQuery, setQuickQuery] = React.useState("");
  const [quickError, setQuickError] = React.useState(null);
  const [notFoundPlaca, setNotFoundPlaca] = React.useState("");
  const [similares, setSimilares] = React.useState([]);   // lecturas OCR confundibles que existen
  const [detallesOpen, setDetallesOpen] = React.useState(false);
  // Flash EN VIVO: cuando el auto-refresh trae una captura nueva (ts mayor) de la MISMA placa,
  // anunciar "Nueva captura · [nodo]". El mapa ya re-enfoca solo vía focusNodeId.
  const [liveFlash, setLiveFlash] = React.useState(null);
  const liveRef = React.useRef({ placa: null, ts: 0 });
  React.useEffect(() => {
    if (!plate || !plate.capturas || !plate.capturas.length) { liveRef.current = { placa: null, ts: 0 }; return; }
    const placa = window.SONAR_normalizePlate(plate.placa);
    const last = plate.capturas[plate.capturas.length - 1];
    const ts = Date.parse(last.ts) || 0;
    if (liveRef.current.placa !== placa) { liveRef.current = { placa, ts }; return; }  // placa nueva: baseline, sin flash
    if (ts > liveRef.current.ts) {
      liveRef.current = { placa, ts };
      setLiveFlash({ nodo: window.SONAR_findNode(last.nodo), ts: last.ts });
    }
  }, [plate]);
  React.useEffect(() => {
    if (!liveFlash) return;
    const id = setTimeout(() => setLiveFlash(null), 6000);
    return () => clearTimeout(id);
  }, [liveFlash]);
  const nodes = window.SONAR_NODES;

  // Inventario de la red (todo dato real). Las fronteras no cuentan como "red de detección".
  const camaras = nodes.filter(n => n.kind === "camara");
  const puestos = nodes.filter(n => n.kind === "puesto");
  const fronteras = nodes.filter(n => n.kind === "frontera");
  const red = nodes.filter(n => n.kind !== "frontera");   // cámaras + puestos
  const activos = red.filter(n => n.status === "active").length;
  const conAlerta = red.filter(n => n.status === "alert").length;
  const inactivos = red.filter(n => n.status === "inactive").length;
  const capturasH = red.reduce((s, n) => s + (n.activity || 0), 0);

  const doQuickSearch = async (e, explicit) => {
    if (e) e.preventDefault();
    const q = explicit != null ? explicit : quickQuery;
    if (!q || !q.trim()) return;
    const norm = window.SONAR_normalizePlate(q);
    setQuickError(null);
    setNotFoundPlaca("");
    const found = await window.SonarAPI.getPlate(q);
    if (found) {
      setSimilares((found.similares || []).filter(s => s.placa !== norm));
      onSearch(found);
      setQuickQuery("");
      return;
    }
    // No encontrada → ofrecer lecturas confundibles (OCR) que sí existen.
    const sims = await window.SonarAPI.getPlateSimilares(q);
    setSimilares((sims || []).filter(s => s.placa !== norm));
    setQuickError("No encontrada");
    setNotFoundPlaca(norm);
  };
  const onPickSimilar = (placa) => { setSimilares([]); doQuickSearch(null, placa); };

  // The right column changes width by layout and by plate presence.
  // When the bottom strip is active with a plate, hide the right column
  // entirely — the strip already surfaces the relevant info, and the map
  // needs every pixel.
  const rightColW = (() => {
    if (!plate) return 360;
    if (pulsoLayout === "side") return 440;
    if (pulsoLayout === "floating") return 320;
    if (pulsoLayout === "bottom") return 0;
    return 360;
  })();
  const showRightCol = rightColW > 0;

  return (
    <div className="fade-in" style={{
      height: "100%",
      display: "grid",
      gridTemplateColumns: showRightCol
        ? `minmax(0, 1fr) ${rightColW}px`
        : "minmax(0, 1fr)",
    }}>
      {/* ── MAP COLUMN ────────────────────────────────────── */}
      <div style={{ position: "relative", overflow: "hidden", background: "var(--s-map-bg)" }}>

        {/* Top floating search + filters bar */}
        <div style={{
          position: "absolute", top: 16, left: 16, right: 16,
          zIndex: 10,
          display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap",
          pointerEvents: "none",
        }}>
          <SearchInput
            query={quickQuery}
            setQuery={setQuickQuery}
            onSubmit={doQuickSearch}
            error={quickError}
            hasPlate={!!plate}
            plate={plate}
            onClear={() => onSearch(null)}
            notFoundPlaca={notFoundPlaca}
            onRegister={onRegister}
            similares={similares}
            onPickSimilar={onPickSimilar}
          />
        </div>

        {/* Flash EN VIVO: captura nueva de la placa abierta (auto-refresh) */}
        {liveFlash && (
          <div className="fade-in" style={{
            position: "absolute", top: 70, left: "50%", transform: "translateX(-50%)",
            zIndex: 20, display: "flex", alignItems: "center", gap: 9,
            padding: "9px 14px",
            background: "rgba(11,18,25,0.96)",
            border: "1px solid #4ADE80",
            borderRadius: "var(--g-radius-cta)",
            boxShadow: "0 8px 28px rgba(0,0,0,0.5)",
            pointerEvents: "none", whiteSpace: "nowrap",
          }}>
            <span style={{ width: 8, height: 8, borderRadius: "50%", background: "#4ADE80", boxShadow: "0 0 8px #4ADE80", flexShrink: 0 }}/>
            <span style={{ fontSize: 12, fontWeight: 600, color: "#fff", letterSpacing: 0.2 }}>Nueva captura</span>
            <span style={{ fontSize: 11.5, color: "var(--g-text-mute-dark)" }}>
              {liveFlash.nodo ? window.SONAR_nodeCode(liveFlash.nodo) : "—"}
              {liveFlash.nodo && liveFlash.nodo.nombre ? " · " + liveFlash.nodo.nombre : ""}
              {" · " + window.SONAR_fmtTime(liveFlash.ts)}
            </span>
          </div>
        )}

        {/* Legend (bottom-left) — hidden when bottom strip is active to avoid stacking */}
        {!(plate && pulsoLayout === "bottom") && (
          <Legend/>
        )}

        {/* Map */}
        <window.QuindioMap
          mode={plate ? "pulso" : "overview"}
          plate={plate || null}
          onNodeClick={n => setPinNode(n)}
          zoomBottom={plate && pulsoLayout === "bottom" ? 94 : 14}
          fillToBorder={true}
          focusNodeId={plate ? plate.capturas[plate.capturas.length - 1].nodo : null}
          focusZoom={detallesOpen ? 1.75 : null}
          focusShiftY={detallesOpen ? 320 : 0}
          pinNode={pinNode}
          onVerMas={onVerNodo}
          onPinClose={() => setPinNode(null)}
        />

        {/* Floating result card — V2 */}
        {plate && pulsoLayout === "floating" && (
          <FloatingPulsoCard plate={plate} onNavigate={onNavigate}/>
        )}

        {/* Bottom strip — V3 */}
        {plate && pulsoLayout === "bottom" && (
          <BottomPulsoStrip plate={plate} onNavigate={onNavigate} onExpandedChange={setDetallesOpen}/>
        )}
      </div>

      {/* ── RIGHT COLUMN ──────────────────────────────────── */}
      {showRightCol && (
      <div style={{
        borderLeft: "1px solid var(--g-border-on-dark)",
        background: "var(--s-panel)",
        display: "flex", flexDirection: "column",
        overflow: "hidden",
      }}>
        {plate && pulsoLayout === "side" ? (
          <SidePulsoPanel plate={plate} onNavigate={onNavigate} onClear={() => onSearch(null)}/>
        ) : (
          // KPI + nodes / node-detail panel (shared between no-plate AND floating layouts)
          <>
            <div style={{ padding: "var(--density-pad)", borderBottom: "1px solid var(--g-border-on-dark)" }}>
              <div className="eyebrow eyebrow--accent">Departamento del Quindío</div>
              <div className="kpi-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 12 }}>
                <KPI label="Nodos activos" value={`${activos}/${red.length}`} accent="green" icon="radio-tower"/>
                <KPI label="Con alerta" value={conAlerta} accent="red" icon="alert-triangle"/>
                <KPI label="Inactivos" value={inactivos} accent="mute" icon="power-off"/>
                <KPI label="Capturas / h" value={capturasH.toLocaleString("es-CO")} accent="cyan" icon="scan-line"/>
              </div>
            </div>
            <div className="scroll" style={{ flex: 1, overflow: "auto" }}>
              <SeccionesRed camaras={camaras} puestos={puestos} fronteras={fronteras} onSelect={setPinNode}/>
            </div>
          </>
        )}
      </div>
      )}
    </div>
  );
};

/* ─── Shared: search input ────────────────────────────────── */
const SearchInput = ({ query, setQuery, onSubmit, error, hasPlate, plate, onClear, notFoundPlaca, onRegister, similares = [], onPickSimilar }) => {
  if (hasPlate && plate) {
    // Compact "tracking" pill replaces the input while a plate is locked.
    // Wrapped in the same 380px left-aligned slot so it doesn't shift position.
    return (
      <div style={{ flex: "0 0 240px", display: "flex", justifyContent: "flex-start", pointerEvents: "none" }}>
      <div style={{
        display: "flex", alignItems: "center", gap: 10,
        width: "100%", boxSizing: "border-box",
        minHeight: 44, padding: "0 8px 0 14px",
        background: "rgba(11, 18, 25, 0.92)",
        border: "1px solid rgba(229, 51, 63, 0.35)",
        borderRadius: "var(--g-radius-cta)",
        pointerEvents: "auto",
        boxShadow: "0 8px 24px rgba(0,0,0,0.5)",
      }}>
        <span style={{ fontSize: 10, letterSpacing: 1.4, color: "var(--g-text-mute-dark)" }}>RASTREO</span>
        <span className="plate plate--sm">{plate.placa}</span>
        {plate.marcaciones.length > 0 && (
          <span style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--g-signal-red)",
            boxShadow: "0 0 6px var(--g-signal-red)", animation: "alertPulse 1.4s ease-in-out infinite" }}/>
        )}
        <button className="btn btn--ghost" style={{ padding: "4px 8px", marginLeft: "auto" }} onClick={onClear}>
          <window.Icon name="x" size={14}/>
        </button>
      </div>
      </div>
    );
  }
  return (
    <form onSubmit={onSubmit} style={{ position: "relative", flex: "0 0 240px", pointerEvents: "auto" }}>
      <input
        className="input"
        placeholder="Rastrear placa…"
        value={query}
        onChange={e => setQuery(e.target.value)}
        style={{
          paddingLeft: 14, paddingRight: 42,
          // Resalta sobre el mapa oscuro apoyándose en el cian de diseño (borde + glow).
          background: "rgba(11, 18, 25, 0.92)",
          backdropFilter: "blur(14px)",
          border: "1px solid var(--accent, #4FD4FF)",
          color: "var(--g-text-cream)",
          fontSize: 14, letterSpacing: 1.5,
          textTransform: "uppercase", fontWeight: 500,
          boxShadow: "0 10px 28px rgba(0,0,0,0.55), 0 0 0 1px rgba(79,212,255,0.25), 0 0 22px rgba(79,212,255,0.28)",
        }}
      />
      <button type="submit" aria-label="Rastrear" style={{
        position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)",
        width: 30, height: 30, borderRadius: 8,
        display: "grid", placeItems: "center",
        border: 0, cursor: "pointer",
        background: query ? "var(--accent)" : "transparent",
        color: query ? "#fff" : "var(--g-text-mute-dark)",
        transition: "all 160ms",
      }}>
        <window.Icon name="search" size={16}/>
      </button>
      {(error || (similares && similares.length > 0)) && (
        <div className="fade-in" style={{
          position: "absolute", top: "calc(100% + 8px)", left: 0, width: "100%",
          display: "flex", flexDirection: "column", gap: 8,
          pointerEvents: "auto",
        }}>
          {error && (
            <div style={{
              display: "flex", flexDirection: "column", gap: 10,
              padding: "11px 12px",
              background: "rgba(229, 51, 63, 0.16)",
              border: "1px solid rgba(229, 51, 63, 0.5)",
              borderRadius: "var(--g-radius-cta)",
              boxShadow: "0 8px 24px rgba(0,0,0,0.45)",
            }}>
              <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
                <window.Icon name="search-x" size={16} color="var(--g-signal-red-soft)"/>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--g-signal-red-soft)", letterSpacing: 0.2 }}>
                    Placa no encontrada
                  </div>
                  <div style={{ fontSize: 10.5, color: "var(--g-text-mute-dark)", marginTop: 1 }}>
                    {notFoundPlaca ? `${notFoundPlaca} nunca fue captada por la red ANPR` : "Sin registros en la red ANPR"}
                  </div>
                </div>
              </div>
              {onRegister && (
                <button onClick={() => onRegister(notFoundPlaca)} className="btn" style={{
                  justifyContent: "center", padding: "9px", fontSize: 12,
                  borderColor: "rgba(79,212,255,0.4)", color: "var(--g-celadon)",
                }}>
                  <window.Icon name="shield-plus" size={14} color="var(--g-celadon)"/> Registrar esta placa
                </button>
              )}
            </div>
          )}
          {similares && similares.length > 0 && (
            <div style={{
              display: "flex", flexDirection: "column", gap: 8,
              padding: "10px 12px",
              background: "rgba(11,18,25,0.94)",
              border: "1px solid var(--g-border-on-dark)",
              borderRadius: "var(--g-radius-cta)",
              boxShadow: "0 8px 24px rgba(0,0,0,0.45)",
            }}>
              <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
                <window.Icon name="info" size={14} color="var(--g-celadon)"/>
                <div style={{ fontSize: 11, fontWeight: 600, color: "var(--g-celadon)", letterSpacing: 0.2 }}>
                  Lecturas similares (posible OCR)
                </div>
              </div>
              <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                {similares.map(s => (
                  <button key={s.placa} onClick={() => onPickSimilar && onPickSimilar(s.placa)}
                    title={`${s.veces} captura(s) · ${s.muni || "—"}`}
                    className="btn" style={{
                      padding: "5px 9px", fontSize: 11.5, gap: 6,
                      borderColor: "var(--g-border-on-dark)",
                    }}>
                    <span style={{ fontWeight: 700, letterSpacing: 0.4 }}>{s.placa}</span>
                    <span style={{ fontSize: 10, color: "var(--g-text-mute-dark)" }}>
                      {(s.muni || "")}{s.ultima ? " · " + window.SONAR_relAgo(s.ultima) : ""}
                    </span>
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
      )}
    </form>
  );
};

/* ─── Shared: legend ──────────────────────────────────────── */
const Legend = () => (
  <div style={{
    position: "absolute", bottom: 42, left: 16, zIndex: 10,
    padding: "10px 14px",
    background: "var(--s-glass)",
    backdropFilter: "blur(12px)",
    border: "1px solid var(--g-border-on-dark)",
    borderRadius: "var(--g-radius-card)",
    display: "flex", flexDirection: "column", gap: 6,
    fontSize: 11, letterSpacing: 0.3, lineHeight: 1.3,
    maxWidth: 220,
  }}>
    <div className="eyebrow" style={{ marginBottom: 2 }}>Leyenda</div>
    <LegendItem color="#4ADE80" label="Nodo activo"/>
    <LegendItem color="#F2C61D" label="Puesto de control activo"/>
    <LegendItem color="#FF8A3D" label="Frontera · salida"/>
    <LegendItem color="#E5333F" label="Nodo con alerta" pulse/>
    <LegendItem color="#6B7682" label="Nodo / puesto inactivo" cross/>
  </div>
);

/* ─── V1 SIDE — full Pulso results in right column ────────── */
const SidePulsoPanel = ({ plate, onNavigate, onClear }) => {
  const lastCap = plate.capturas[plate.capturas.length - 1];
  const lastNode = window.SONAR_findNode(lastCap.nodo);
  const muni = window.SONAR_MUNICIPIOS.find(m => m.id === lastNode.municipio);
  const elapsed = window.SONAR_minutesAgo(lastCap.ts);
  const hasAlert = plate.marcaciones.length > 0;

  return (
    <>
      {/* Header */}
      <div style={{ padding: "var(--density-pad)", borderBottom: "1px solid var(--g-border-on-dark)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <span className="plate plate--lg" style={{ fontSize: 22, padding: "8px 14px" }}>{plate.placa}</span>
          <div style={{ flex: 1 }}>
            <div className="eyebrow">{plate.tipo}</div>
            <div style={{ fontSize: 14, fontWeight: 500, marginTop: 2, color: "var(--g-text-cream)" }}>
              {plate.marca} <span style={{ color: "var(--g-text-mute-dark)", fontWeight: 300 }}>· {plate.color}</span>
            </div>
          </div>
          <button className="btn btn--ghost" onClick={onClear} style={{ padding: "4px 8px" }}>
            <window.Icon name="x"/>
          </button>
        </div>
      </div>

      {/* Alert banner */}
      {hasAlert && (
        <div className="alert-banner" style={{ margin: 14, padding: 14 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
            <span className="badge badge--alert">{plate.marcaciones[0].tipo}</span>
            <span style={{ fontSize: 10, color: "var(--g-text-mute-dark)", letterSpacing: 0.4 }}>
              {plate.marcaciones[0].autoridad}
            </span>
          </div>
          <div style={{ fontSize: 14, fontWeight: 500, color: "var(--g-text-cream)", letterSpacing: -0.1 }}>
            {plate.marcaciones[0].motivo}
          </div>
        </div>
      )}

      {/* Last capture */}
      <div style={{ padding: "0 var(--density-pad) 14px" }}>
        <div className="eyebrow eyebrow--accent" style={{ marginBottom: 8 }}>Última captura</div>
        <div style={{
          padding: 12,
          background: hasAlert ? "rgba(229, 51, 63, 0.06)" : "var(--g-surface-on-dark)",
          border: hasAlert ? "1px solid rgba(229, 51, 63, 0.25)" : "1px solid var(--g-border-on-dark)",
          borderRadius: "var(--g-radius-card)",
        }}>
          <div style={{ fontSize: 16, fontWeight: 500, color: "var(--g-text-cream)", letterSpacing: -0.2 }}>
            {lastNode.nombre} <span style={{ color: "var(--g-text-mute-dark)", fontWeight: 300 }}>· {lastNode.alias}</span>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginTop: 10 }}>
            <Stat label="Hora" value={window.SONAR_fmtTime(lastCap.ts)} sub={window.SONAR_fmtDate(lastCap.ts)} mono/>
            <Stat label="Hace" value={window.SONAR_relAgo(lastCap.ts)} sub={muni ? muni.nombre : "—"} mono/>
          </div>
        </div>
      </div>

      {/* Timeline */}
      <div style={{ padding: "0 var(--density-pad) 18px" }}>
        <div className="eyebrow eyebrow--accent" style={{ marginBottom: 14 }}>Línea de tiempo</div>
        <Timeline plate={plate}/>
      </div>

      {/* History */}
      <div className="scroll" style={{ flex: 1, overflow: "auto", padding: "0 var(--density-pad)" }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>Historial cronológico</div>
        {plate.capturas.slice().reverse().map((cap, i, arr) => {
          const node = window.SONAR_findNode(cap.nodo);
          const isLast = i === 0;
          return (
            <div key={cap.ts + cap.nodo} style={{
              display: "grid",
              gridTemplateColumns: "55px 16px 1fr auto",
              alignItems: "center", gap: 10,
              padding: "8px 0",
              borderBottom: i < arr.length - 1 ? "1px solid var(--g-border-on-dark)" : "none",
            }}>
              <div style={{ fontVariantNumeric: "tabular-nums", fontSize: 12, color: isLast ? "var(--g-signal-red-soft)" : "var(--g-text-cream)", display: "flex", flexDirection: "column", lineHeight: 1.15 }}>
                <span>{window.SONAR_fmtTime(cap.ts)}</span>
                <span style={{ fontSize: 9, color: "var(--g-text-mute-dark)", marginTop: 2, letterSpacing: 0.2 }}>{window.SONAR_fmtShortDate(cap.ts)}</span>
              </div>
              <div style={{
                width: isLast ? 10 : 7, height: isLast ? 10 : 7,
                borderRadius: "50%",
                background: isLast ? "var(--g-signal-red)" : "var(--accent, var(--g-celadon))",
                boxShadow: isLast ? "0 0 8px var(--g-signal-red)" : "none",
                margin: "0 auto",
              }}/>
              <div style={{ fontSize: 12, fontWeight: 500, color: "var(--g-text-cream)" }}>
                {node.nombre}
                <span style={{ fontSize: 10, color: "var(--g-text-mute-dark)", fontWeight: 300, marginLeft: 4 }}>· {window.SONAR_nodeCode(node)}</span>
              </div>
              <div className="meta" style={{ fontSize: 10 }}>{window.SONAR_humanAgo(cap.ts)}</div>
            </div>
          );
        })}
      </div>

      {/* Actions */}
      <div style={{
        padding: "var(--density-pad)",
        borderTop: "1px solid var(--g-border-on-dark)",
        display: "grid", gap: 8,
      }}>
        <button className="btn btn--primary" style={{ justifyContent: "center", padding: "11px" }}
          onClick={() => onNavigate("rebote")}>
          <window.Icon name="radar"/> Proyectar Rebote
          <window.Icon name="arrow-right" style={{ marginLeft: "auto" }}/>
        </button>
        <button className="btn" style={{ justifyContent: "center", padding: "10px" }}
          onClick={() => onNavigate("candado")}>
          <window.Icon name="lock"/> Plan Candado
        </button>
      </div>
    </>
  );
};

/* ─── V2 FLOATING — compact card overlay ──────────────────── */
const FloatingPulsoCard = ({ plate, onNavigate }) => {
  const lastCap = plate.capturas[plate.capturas.length - 1];
  const lastNode = window.SONAR_findNode(lastCap.nodo);
  const elapsed = window.SONAR_minutesAgo(lastCap.ts);
  const hasAlert = plate.marcaciones.length > 0;

  return (
    <div className="fade-in" style={{
      position: "absolute", top: 70, left: 16, zIndex: 11,
      width: 320,
      background: "var(--s-glass)",
      backdropFilter: "blur(10px)",
      border: hasAlert ? "1px solid rgba(229, 51, 63, 0.4)" : "1px solid var(--g-border-on-dark)",
      borderRadius: "var(--g-radius-card)",
      boxShadow: "0 14px 40px rgba(0, 0, 0, 0.6)",
      overflow: "hidden",
    }}>
      {/* Header strip */}
      <div style={{
        padding: "10px 14px",
        background: hasAlert ? "rgba(229, 51, 63, 0.14)" : "rgba(79, 212, 255, 0.06)",
        borderBottom: hasAlert ? "1px solid rgba(229, 51, 63, 0.3)" : "1px solid var(--g-border-on-dark)",
        display: "flex", alignItems: "center", gap: 10,
      }}>
        <span className="plate" style={{ fontSize: 14, padding: "4px 10px", letterSpacing: 2 }}>{plate.placa}</span>
        {hasAlert ? (
          <span className="badge badge--alert">{plate.marcaciones[0].tipo}</span>
        ) : (
          <span className="badge badge--active">RASTREO</span>
        )}
        <span style={{ marginLeft: "auto", fontSize: 10, letterSpacing: 1.4, color: "var(--g-text-mute-dark)" }}>
          {plate.marca}
        </span>
      </div>

      {hasAlert && (
        <div style={{ padding: "8px 14px", fontSize: 12, color: "var(--g-signal-red-soft)", borderBottom: "1px solid var(--g-border-on-dark)" }}>
          <window.Icon name="alert-octagon" size={12} style={{ marginRight: 6, verticalAlign: "middle" }}/>
          {plate.marcaciones[0].motivo}
        </div>
      )}

      {/* Last capture */}
      <div style={{ padding: 14 }}>
        <div className="eyebrow eyebrow--accent" style={{ marginBottom: 8 }}>Última captura</div>
        <div style={{ fontSize: 14, fontWeight: 500, color: "var(--g-text-cream)", letterSpacing: -0.1 }}>
          {lastNode.nombre}
        </div>
        <div style={{ display: "flex", gap: 14, marginTop: 8, fontVariantNumeric: "tabular-nums" }}>
          <div>
            <div className="meta">Hora</div>
            <div style={{ fontSize: 14, fontWeight: 500, color: "var(--g-text-cream)" }}>{window.SONAR_fmtTime(lastCap.ts)}</div>
          </div>
          <div>
            <div className="meta">Hace</div>
            <div style={{ fontSize: 14, fontWeight: 500, color: hasAlert ? "var(--g-signal-red-soft)" : "var(--accent, var(--g-celadon))" }}>{window.SONAR_relAgo(lastCap.ts)}</div>
          </div>
          <div>
            <div className="meta">Capturas</div>
            <div style={{ fontSize: 14, fontWeight: 500, color: "var(--g-text-cream)" }}>{plate.capturas.length}</div>
          </div>
        </div>

        {/* Mini timeline as dots */}
        <div style={{ marginTop: 14, display: "flex", alignItems: "center", gap: 4 }}>
          {plate.capturas.map((cap, i) => {
            const isLast = i === plate.capturas.length - 1;
            return (
              <React.Fragment key={cap.ts}>
                <div title={window.SONAR_findNode(cap.nodo).nombre} style={{
                  width: isLast ? 10 : 7, height: isLast ? 10 : 7,
                  borderRadius: "50%",
                  background: isLast ? "var(--g-signal-red)" : "var(--accent, var(--g-celadon))",
                  boxShadow: isLast ? "0 0 6px var(--g-signal-red)" : "none",
                }}/>
                {i < plate.capturas.length - 1 && (
                  <div style={{ flex: 1, height: 1, background: "var(--g-border-on-dark)" }}/>
                )}
              </React.Fragment>
            );
          })}
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", marginTop: 4, fontSize: 10, color: "var(--g-text-mute-dark)", fontVariantNumeric: "tabular-nums" }}>
          <span>{window.SONAR_fmtTime(plate.capturas[0].ts)}</span>
          <span>{window.SONAR_fmtTime(lastCap.ts)}</span>
        </div>
      </div>

      {/* Actions */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1, background: "var(--g-border-on-dark)", marginTop: 4 }}>
        <button className="btn" style={{ borderRadius: 0, border: 0, padding: "10px", background: "var(--s-panel)" }}
          onClick={() => onNavigate("rebote")}>
          <window.Icon name="radar" size={14}/> Rebote
        </button>
        <button className="btn" style={{ borderRadius: 0, border: 0, padding: "10px", background: "var(--s-panel)" }}
          onClick={() => onNavigate("candado")}>
          <window.Icon name="lock" size={14}/> Candado
        </button>
      </div>
    </div>
  );
};

/* ─── Action button — auto icon-only when host is narrow ──── */
const ActionBtn = ({ icon, label, title, primary, onClick }) => {
  const btnRef = React.useRef(null);
  const [iconOnly, setIconOnly] = React.useState(false);
  React.useEffect(() => {
    if (!btnRef.current) return;
    // The button lives inside the bottom strip; collapse to icon-only when
    // its parent's parent (the strip container) is below ~720px wide.
    const strip = btnRef.current.closest('[data-bottom-strip]');
    if (!strip) return;
    const apply = () => setIconOnly(strip.offsetWidth < 560);
    apply();
    const ro = new ResizeObserver(apply);
    ro.observe(strip);
    return () => ro.disconnect();
  }, []);
  return (
    <button
      ref={btnRef}
      className={primary ? "btn btn--primary" : "btn"}
      title={title || label}
      onClick={onClick}
      style={{
        padding: iconOnly ? "10px 11px" : "10px 12px",
        fontSize: 12,
        whiteSpace: "nowrap",
      }}
    >
      <window.Icon name={icon} size={14}/>
      {!iconOnly && <span>{label}</span>}
    </button>
  );
};

/* ─── V3 BOTTOM — expandable horizontal strip ─────────────── */
/* Instantánea on-demand — componente AISLADO. Su barra de progreso simulada hace
   tick cada 160ms; al estar separada, esos re-renders NO tocan el panel de detalle
   (timeline, historial, etc.). Progreso 0→90% y salta a 100% al cargar la imagen. */
const PulsoSnapshot = ({ captura, lastNode, lastCap, placa }) => {
  const [snapState, setSnapState] = React.useState("loading"); // loading | loaded | error
  const [snapPct, setSnapPct] = React.useState(0);
  const [zoomOpen, setZoomOpen] = React.useState(false);
  React.useEffect(() => { setSnapState("loading"); setSnapPct(0); setZoomOpen(false); }, [captura]);
  React.useEffect(() => {
    if (snapState === "loaded") { setSnapPct(100); return; }
    if (snapState !== "loading") return;
    const id = setInterval(() => setSnapPct(p => (p >= 90 ? 90 : p + Math.max(0.6, (90 - p) * 0.05))), 160);
    return () => clearInterval(id);
  }, [snapState]);
  return (
    <div>
      <div className="eyebrow" style={{ marginBottom: 6 }}>Instantánea</div>
      {captura && snapState !== "error" ? (
        <div style={{ position: "relative", borderRadius: 8, border: "1px solid var(--g-border-on-dark)", overflow: "hidden", maxWidth: 460, margin: "0 12px" }}>
          <img src={captura} alt={`Captura ANPR de ${placa}`}
            onLoad={() => setSnapState("loaded")}
            onError={() => setSnapState("error")}
            style={{ width: "100%", height: "auto", display: snapState === "loaded" ? "block" : "none" }}/>
          {snapState === "loading" && (
            <div style={{
              minHeight: 120, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 10,
              color: "var(--g-text-mute-dark)", padding: 14,
            }}>
              <window.Icon name="image" size={20}/>
              <div style={{ fontSize: 11, fontWeight: 500 }}>Cargando instantánea… {Math.round(snapPct)}%</div>
              <div style={{ width: "72%", height: 4, borderRadius: 4, background: "rgba(255,255,255,0.08)", overflow: "hidden" }}>
                <div style={{ width: snapPct + "%", height: "100%", borderRadius: 4, background: "var(--accent, #4FD4FF)", transition: "width 0.25s ease" }}/>
              </div>
            </div>
          )}
          {snapState === "loaded" && (
            <>
              <button onClick={() => setZoomOpen(true)} title="Ampliar" style={{
                position: "absolute", top: 8, right: 8, width: 30, height: 30, borderRadius: 7,
                display: "grid", placeItems: "center", cursor: "pointer",
                border: "1px solid var(--g-border-on-dark)", color: "#fff",
                background: "rgba(11,18,25,0.72)", backdropFilter: "blur(6px)",
              }}>
                <window.Icon name="maximize-2" size={14}/>
              </button>
              <div style={{
                display: "flex", alignItems: "center", gap: 6,
                padding: "7px 9px", fontSize: 10.5, color: "var(--g-text-mute-dark)",
                borderTop: "1px solid var(--g-border-on-dark)", background: "var(--g-surface-on-dark)",
              }}>
                <window.Icon name="cctv" size={11}/>
                <span>{window.SONAR_nodeCode(lastNode)} · {window.SONAR_fmtTime(lastCap.ts)}</span>
                <span style={{ marginLeft: "auto" }}>Confianza {(lastCap.confianza * 100).toFixed(0)}%</span>
              </div>
            </>
          )}
        </div>
      ) : (
        <div style={{
          border: "1px dashed var(--g-border-on-dark)", borderRadius: 8,
          background: "rgba(127, 134, 142, 0.04)", minHeight: 110,
          display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 6,
          color: "var(--g-text-mute-dark)", textAlign: "center", padding: 14,
        }}>
          <window.Icon name="image" size={20}/>
          <div style={{ fontSize: 11, fontWeight: 500 }}>Imagen no disponible</div>
        </div>
      )}
      {zoomOpen && (
        <div onClick={() => setZoomOpen(false)} style={{
          position: "fixed", inset: 0, zIndex: 2000,
          background: "rgba(0,0,0,0.82)", backdropFilter: "blur(4px)",
          display: "flex", alignItems: "center", justifyContent: "center", padding: 40,
        }}>
          <div onClick={e => e.stopPropagation()} style={{ position: "relative", maxWidth: "min(900px, 92vw)" }}>
            <img src={captura} alt={`Captura ANPR de ${placa}`} style={{
              width: "100%", height: "auto", maxHeight: "82vh", objectFit: "contain",
              borderRadius: 10, display: "block", border: "1px solid var(--g-border-on-dark)",
            }}/>
            <button onClick={() => setZoomOpen(false)} title="Cerrar" style={{
              position: "absolute", top: 10, right: 10, width: 34, height: 34, borderRadius: "50%",
              display: "grid", placeItems: "center", cursor: "pointer", zIndex: 1,
              border: "1px solid rgba(255,255,255,0.25)", color: "#fff",
              background: "rgba(11,18,25,0.78)", backdropFilter: "blur(6px)",
            }}>
              <window.Icon name="x" size={16}/>
            </button>
            <div style={{
              marginTop: 10, display: "flex", alignItems: "center", gap: 8, justifyContent: "center",
              fontSize: 12, color: "var(--g-text-cream)",
            }}>
              <span className="plate plate--sm">{placa}</span>
              <span style={{ color: "var(--g-text-mute-dark)" }}>{window.SONAR_nodeCode(lastNode)} · {window.SONAR_fmtTime(lastCap.ts)}</span>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

/* Chip de sentido por captura: entrando (cam-in, verde) / saliendo (cam-out, naranja). */
const DirChip = ({ dir }) => {
  if (dir !== "in" && dir !== "out") return null;
  const inb = dir === "in";
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 3, flexShrink: 0,
      padding: "1px 7px", borderRadius: "var(--g-radius-pill)",
      fontSize: 9.5, fontWeight: 600, letterSpacing: 0.3, whiteSpace: "nowrap",
      color: inb ? "#4ADE80" : "#FF8A3D",
      background: inb ? "rgba(74,222,128,0.12)" : "rgba(255,138,61,0.12)",
      border: `1px solid ${inb ? "rgba(74,222,128,0.30)" : "rgba(255,138,61,0.30)"}`,
    }}>
      <window.Icon name={inb ? "log-in" : "log-out"} size={10}/>
      {inb ? "Entrando" : "Saliendo"}
    </span>
  );
};

const BottomPulsoStrip = ({ plate, onNavigate, onExpandedChange }) => {
  const [expanded, setExpanded] = React.useState(false);
  React.useEffect(() => { if (onExpandedChange) onExpandedChange(expanded); }, [expanded]);
  const [showHist, setShowHist] = React.useState(false);
  const [histPage, setHistPage] = React.useState(0);
  const lastCap = plate.capturas[plate.capturas.length - 1];
  const lastNode = window.SONAR_findNode(lastCap.nodo);
  const muni = window.SONAR_MUNICIPIOS.find(m => m.id === lastNode.municipio);
  const elapsed = window.SONAR_minutesAgo(lastCap.ts);
  const hasAlert = plate.marcaciones.length > 0;
  const firstCap = plate.capturas[0];

  // Histórico completo = capturas recientes + historial de días previos.
  const fullHistory = React.useMemo(() => {
    const extra = plate.historial || [];
    return [...plate.capturas, ...extra].sort((a, b) => new Date(b.ts) - new Date(a.ts));
  }, [plate]);
  // Municipios distintos en lo cargado (fallback). Los TOTALES reales vienen del backend (COUNT),
  // porque el front solo trae las últimas ~200 capturas para que el detalle abra rápido.
  const totalMunis = React.useMemo(
    () => new Set(fullHistory.map(c => window.SONAR_findNode(c.nodo)?.municipio).filter(Boolean)).size,
    [fullHistory]);
  const totalCaps = plate.total_capturas ?? fullHistory.length;
  const totalMunisN = plate.total_municipios ?? totalMunis;
  const PER_PAGE = 6;
  const pageCount = Math.max(1, Math.ceil(fullHistory.length / PER_PAGE));
  const page = Math.min(histPage, pageCount - 1);
  const pageRows = fullHistory.slice(page * PER_PAGE, page * PER_PAGE + PER_PAGE);

  return (
    <div className="fade-in" data-bottom-strip style={{
      position: "absolute", bottom: 16, left: 16, right: 16, zIndex: 11,
      background: "rgba(11, 18, 25, 0.94)",
      border: hasAlert ? "1px solid rgba(229, 51, 63, 0.4)" : "1px solid var(--g-border-on-dark)",
      borderRadius: "var(--g-radius-card)",
      boxShadow: "0 -8px 30px rgba(0, 0, 0, 0.55)",
      overflow: "hidden",
      transition: "all 280ms var(--ease-emphasised)",
    }}>
      {/* ── COMPACT ROW (always visible) ────────────────────── */}
      <div style={{
        display: "flex",
        flexWrap: "nowrap",
        columnGap: 14,
        alignItems: "center",
        padding: "12px 18px",
        minWidth: 0,
      }}>
        {/* Placa block — fixed, never shrinks */}
        <div style={{ display: "flex", alignItems: "center", gap: 10, whiteSpace: "nowrap", flexShrink: 0 }}>
          <span className="plate plate--lg">{plate.placa}</span>
          {hasAlert ? (
            <span className="badge badge--alert">{plate.marcaciones[0].tipo}</span>
          ) : (
            <span className="badge badge--active">SIN ALERTA</span>
          )}
        </div>

        {/* Última captura — single line, inline aligned, shrinkable */}
        <div style={{
          flex: "1 1 140px",
          minWidth: 0,
          display: "flex",
          alignItems: "center",
          gap: 10,
          borderLeft: "1px solid var(--g-border-on-dark)",
          paddingLeft: 14,
          whiteSpace: "nowrap",
          overflow: "hidden",
        }}>
          <span className="eyebrow eyebrow--accent" style={{ flexShrink: 0 }}>Última captura</span>
          <span style={{
            fontSize: 13, fontWeight: 500,
            color: hasAlert ? "var(--g-signal-red-soft)" : "var(--g-text-cream)",
            letterSpacing: -0.1,
            overflow: "hidden", textOverflow: "ellipsis",
            minWidth: 0, flex: "1 1 auto",
          }}>
            {lastNode.nombre} · {muni.nombre}
          </span>
          <DirChip dir={lastCap.direccion}/>
          <span style={{
            fontSize: 11,
            color: "var(--g-text-mute-dark)",
            letterSpacing: 0.3,
            fontVariantNumeric: "tabular-nums",
            flexShrink: 0,
          }}>
            {window.SONAR_fmtShortDate(lastCap.ts)} · {window.SONAR_fmtTime(lastCap.ts)} <span style={{ opacity: 0.7 }}>· hace {window.SONAR_relAgo(lastCap.ts)}</span>
          </span>
        </div>

        {/* Actions — fixed, icon-only at narrow widths */}
        <div style={{
          display: "flex", gap: 6, alignItems: "center", flexShrink: 0,
          marginLeft: "auto",
        }}>
          <ActionBtn
            icon={expanded ? "chevron-down" : "panel-bottom-open"}
            label={expanded ? "Ocultar" : "Detalles"}
            title={expanded ? "Ocultar detalles" : "Ver detalles"}
            onClick={() => { setExpanded(v => !v); setShowHist(false); }}
          />
          <ActionBtn
            icon="waypoints"
            label="Inferencia"
            title="Inferencia de Ruta"
            primary
            onClick={() => onNavigate("rebote")}
          />
          <ActionBtn
            icon="lock"
            label="Candado"
            title="Plan Candado"
            onClick={() => onNavigate("candado")}
          />
        </div>
      </div>

      {/* ── EXPANDED PANEL ──────────────────────────────────── */}
      {expanded && (
        <div className="fade-in scroll" style={{
          borderTop: "1px solid var(--g-border-on-dark)",
          padding: "20px 22px 22px",
          background: "rgba(4, 6, 10, 0.55)",
          maxHeight: "min(480px, 64vh)",
          overflowY: "auto",
          overflowX: "hidden",
        }}>
          {/* ── Header con toggle de vista ───────────────── */}
          <div style={{ display: "flex", alignItems: "baseline", gap: 18, marginBottom: 16 }}>
            <div>
              <div className="eyebrow eyebrow--accent">{showHist ? "Histórico cronológico" : "Línea de tiempo · recorrido confirmado"}</div>
              <div className="meta" style={{ marginTop: 4 }}>
                {showHist
                  ? `${fullHistory.length}${totalCaps > fullHistory.length ? " de " + totalCaps : ""} registros en la red ANPR`
                  : `${totalCaps} capturas · ${totalMunisN} municipio${totalMunisN === 1 ? "" : "s"} · ${plate.capturas.length} en las últimas 24 h`}
              </div>
            </div>
            {showHist && (
            <button className="btn" style={{ marginLeft: "auto", padding: "7px 12px", flexShrink: 0 }}
              onClick={() => setShowHist(false)}>
              <window.Icon name="arrow-left" size={14}/>
              Volver a detalles
            </button>
            )}
          </div>
          {!showHist && (
          <div className="surface" style={{ padding: "14px 36px 18px 24px", overflow: "hidden", background: "var(--g-surface-on-dark)" }}>
            <HorizontalTimeline plate={plate} large onVerHistorico={() => setShowHist(true)}/>
          </div>
          )}

          {/* Instantánea del vehículo (de la última captura) */}
          {/* (Instantánea movida dentro de Resumen de captura) */}


          {/* ── Vista conmutable: histórico (sub) vs detalles (principal) ─ */}
          <div style={{ marginTop: 20 }}>
            {showHist ? (
            <div>
              <div style={{
                border: "1px solid var(--g-border-on-dark)",
                borderRadius: "var(--g-radius-card)",
                background: "var(--g-surface-on-dark)",
                overflow: "hidden",
              }}>
                {pageRows.map((cap, i) => {
                  const node = window.SONAR_findNode(cap.nodo);
                  const mn = window.SONAR_MUNICIPIOS.find(m => m.id === node.municipio);
                  const isLast = page === 0 && i === 0;
                  return (
                    <div key={cap.ts + cap.nodo} style={{
                      display: "grid",
                      gridTemplateColumns: "66px 16px 1fr auto auto",
                      alignItems: "center", gap: 12,
                      padding: "12px 14px",
                      borderBottom: i < pageRows.length - 1 ? "1px solid var(--g-border-on-dark)" : "none",
                    }}>
                      <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
                        <div style={{ fontVariantNumeric: "tabular-nums", fontSize: 14, color: isLast ? "var(--g-signal-red-soft)" : "var(--g-text-cream)", fontWeight: isLast ? 500 : 400, lineHeight: 1.1 }}>
                          {window.SONAR_fmtTime(cap.ts)}
                        </div>
                        <div style={{ fontSize: 11, color: "var(--g-text-mute-dark)", marginTop: 2, letterSpacing: 0.2 }}>
                          {window.SONAR_fmtShortDate(cap.ts)}
                        </div>
                      </div>
                      <div style={{ display: "grid", placeItems: "center" }}>
                        <div style={{
                          width: isLast ? 11 : 8, height: isLast ? 11 : 8,
                          borderRadius: "50%",
                          background: isLast ? "var(--g-signal-red)" : "var(--accent, var(--g-celadon))",
                          boxShadow: isLast ? "0 0 8px var(--g-signal-red)" : "none",
                        }}/>
                      </div>
                      <div style={{ minWidth: 0 }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                          <span style={{ fontSize: 14, fontWeight: 500, color: "var(--g-text-cream)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", minWidth: 0 }}>
                            {node.nombre} {mn && <span style={{ color: "var(--g-text-mute-dark)", fontWeight: 300 }}>· {mn.nombre}</span>}
                          </span>
                          <DirChip dir={cap.direccion}/>
                        </div>
                        <div style={{ fontSize: 12, color: "var(--g-text-mute-dark)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                          {window.SONAR_nodeCode(node)}{node.alias ? ` · ${node.alias}` : ""} · {(cap.confianza * 100).toFixed(0)}%
                        </div>
                      </div>
                      <div className="meta" style={{ fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" }}>
                        {window.SONAR_humanAgo(cap.ts)}
                      </div>
                      {isLast ? (
                        <span className="badge badge--alert" style={{ fontSize: 9 }}>ÚLTIMA</span>
                      ) : <span/>}
                    </div>
                  );
                })}
              </div>
              {/* Paginación tipo correo electrónico */}
              <div style={{
                display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 10,
                marginTop: 10,
              }}>
                <span className="meta" style={{ marginRight: "auto" }}>
                  {page * PER_PAGE + 1}–{Math.min((page + 1) * PER_PAGE, fullHistory.length)} de {fullHistory.length}
                </span>
                <button
                  className="btn"
                  style={{ padding: "6px 9px" }}
                  disabled={page === 0}
                  onClick={() => setHistPage(p => Math.max(0, p - 1))}
                >
                  <window.Icon name="chevron-left" size={14}/>
                </button>
                <span className="meta" style={{ fontVariantNumeric: "tabular-nums", minWidth: 56, textAlign: "center" }}>
                  {page + 1} / {pageCount}
                </span>
                <button
                  className="btn"
                  style={{ padding: "6px 9px" }}
                  disabled={page >= pageCount - 1}
                  onClick={() => setHistPage(p => Math.min(pageCount - 1, p + 1))}
                >
                  <window.Icon name="chevron-right" size={14}/>
                </button>
              </div>
            </div>
            ) : (
            <div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) 484px", gap: 16, alignItems: "start" }}>
              {/* COLUMNA 1 — Estado + Datos apilados */}
              <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
              <div>
                <div className="eyebrow eyebrow--accent" style={{ marginBottom: 8 }}>Estado del vehículo</div>
                <div style={{
                  padding: 14,
                  background: hasAlert ? "rgba(229, 51, 63, 0.08)" : "var(--g-surface-on-dark)",
                  border: hasAlert ? "1px solid rgba(229, 51, 63, 0.3)" : "1px solid var(--g-border-on-dark)",
                  borderRadius: "var(--g-radius-card)",
                }}>
                  {hasAlert ? (
                    <>
                      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
                        <span className="badge badge--alert">{plate.marcaciones[0].tipo}</span>
                        <span style={{ fontSize: 15, fontWeight: 600, color: "var(--g-text-cream)", letterSpacing: -0.2 }}>
                          {plate.marcaciones[0].motivo}
                        </span>
                      </div>
                      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
                        <Stat label="Autoridad" value={plate.marcaciones[0].autoridad || "—"}/>
                        <Stat label="Fecha" value={plate.marcaciones[0].fecha ? window.SONAR_fmtDate(plate.marcaciones[0].fecha) : "—"}/>
                        <Stat label="N.° SECAD" value={plate.marcaciones[0].secad || "—"} mono/>
                        <Stat label="Estado" value="Activa"/>
                      </div>
                    </>
                  ) : (
                    <>
                      <span className="badge" style={{
                        background: "rgba(229, 238, 245, 0.06)",
                        color: "var(--g-text-mute-dark)",
                        border: "1px solid var(--g-border-on-dark)",
                        padding: "3px 8px", borderRadius: "var(--g-radius-pill)",
                        fontSize: 10, letterSpacing: 1.2,
                      }}>SIN MARCACIONES</span>
                      <div style={{ fontSize: 13, color: "var(--g-text-mute-dark)", lineHeight: 1.5, marginTop: 10 }}>
                        Sin alertas activas en SIJIN, Fiscalía ni base de hurto. Vehículo desconocido sin marcaciones.
                      </div>
                    </>
                  )}
                </div>
              </div>

              {/* Datos del vehículo */}
              <div>
                <div className="eyebrow" style={{ marginBottom: 8 }}>Datos del vehículo</div>
                <div style={{
                  padding: 12,
                  background: "var(--g-surface-on-dark)",
                  border: "1px solid var(--g-border-on-dark)",
                  borderRadius: "var(--g-radius-card)",
                  display: "grid",
                  gridTemplateColumns: "1fr 1fr",
                  gap: 10,
                }}>
                  <Stat label="Tipo" value={plate.tipo}/>
                  <Stat label="Marca" value={plate.marca}/>
                  <Stat label="Color" value={plate.color}/>
                  <Stat label="Año" value={plate.año} mono/>
                </div>
              </div>
              </div>{/* /COLUMNA 1 */}

              {/* Resumen de captura */}
              <div>
                <div className="eyebrow" style={{ marginBottom: 8, display: "flex", alignItems: "center", gap: 8 }}>Resumen de captura <DirChip dir={lastCap.direccion}/></div>
                <div style={{
                  padding: 12,
                  background: "var(--g-surface-on-dark)",
                  border: "1px solid var(--g-border-on-dark)",
                  borderRadius: "var(--g-radius-card)",
                }}>
                  <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
                    {/* Datos de la captura */}
                    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
                      <Stat label="Nodo · Municipio" value={`${lastNode.nombre}${muni ? ` · ${muni.nombre}` : ""}`} sub={lastNode.alias ? `${window.SONAR_nodeCode(lastNode)} · ${lastNode.alias}` : window.SONAR_nodeCode(lastNode)}/>
                      <Stat label="Fecha" value={window.SONAR_fmtShortDate(lastCap.ts)} sub={`${window.SONAR_fmtTime(lastCap.ts)} · hace ${window.SONAR_relAgo(lastCap.ts)}`} mono/>
                    </div>
                    {/* Instantánea (componente AISLADO: su barra no re-renderiza el panel) */}
                    <PulsoSnapshot captura={plate.captura} lastNode={lastNode} lastCap={lastCap} placa={plate.placa}/>
                  </div>
                </div>
              </div>
            </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
};

const HorizontalTimeline = ({ plate, large, onVerHistorico }) => {
  // Por defecto las últimas 4 capturas (último nodo + otras tres).
  const caps = plate.capturas.slice(-4);
  const n = caps.length;
  // Puntos EQUIDISTANTES por índice, desplazados a la derecha para dejar
  // espacio al nodo compacto "Ver histórico" en el extremo izquierdo.
  const hasHistBtn = large && onVerHistorico;
  const startPct = hasHistBtn ? 22 : 12;
  const posOf = (i) => n <= 1 ? (startPct + 84) / 2 : startPct + (i / (n - 1)) * (84 - startPct);
  const LINE_Y = large ? 60 : "50%";
  return (
    <div style={{ position: "relative", height: large ? 124 : 32, minWidth: 320 }}>
      <div style={{ position: "absolute", left: `${hasHistBtn ? 9 : posOf(0)}%`, right: 0, top: LINE_Y, height: 1, background: "var(--g-border-on-dark)" }}/>

      {/* Nodo compacto "Ver más" en el extremo izquierdo */}
      {hasHistBtn && (
        <button onClick={onVerHistorico} title="Ver histórico cronológico" style={{
          position: "absolute", left: 0, top: LINE_Y, transform: "translateY(-50%)",
          display: "inline-flex", alignItems: "center", gap: 6,
          padding: "7px 12px", borderRadius: "var(--g-radius-pill)",
          border: "1px solid var(--accent, var(--g-celadon))",
          background: "rgba(79, 212, 255, 0.1)",
          color: "var(--accent, var(--g-celadon))",
          fontFamily: "var(--font-sans)", fontSize: 11.5, fontWeight: 600, letterSpacing: 0.2,
          cursor: "pointer", transition: "background 150ms",
        }}
          onMouseEnter={e => e.currentTarget.style.background = "rgba(79, 212, 255, 0.18)"}
          onMouseLeave={e => e.currentTarget.style.background = "rgba(79, 212, 255, 0.1)"}
        >
          <window.Icon name="history" size={14}/>
          Ver más
        </button>
      )}

      {caps.map((cap, i) => {
        const pct = posOf(i);
        const node = window.SONAR_findNode(cap.nodo);
        const mn = window.SONAR_MUNICIPIOS.find(m => m.id === node.municipio);
        const isLast = i === caps.length - 1;
        return (
          <div key={cap.ts} style={{ position: "absolute", left: `${pct}%`, top: LINE_Y, transform: "translate(-50%, -50%)" }}>
            {/* Nombre del nodo — ARRIBA del punto */}
            {large && (
              <div style={{
                position: "absolute", left: "50%", bottom: "calc(50% + 14px)", transform: "translateX(-50%)",
                whiteSpace: "nowrap", textAlign: "center",
                display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
                fontSize: 13, fontWeight: 600, letterSpacing: 0.2,
                color: isLast ? "var(--g-signal-red-soft)" : "var(--g-text-cream)",
              }}>
                {window.SONAR_nodeCode(node)} · {mn.nombre}
                <DirChip dir={cap.direccion}/>
              </div>
            )}
            <div style={{
              width: isLast ? (large ? 14 : 12) : (large ? 10 : 8),
              height: isLast ? (large ? 14 : 12) : (large ? 10 : 8),
              borderRadius: "50%",
              background: isLast ? "var(--g-signal-red)" : (cap.direccion === "in" ? "#4ADE80" : cap.direccion === "out" ? "#FF8A3D" : "var(--accent, var(--g-celadon))"),
              boxShadow: isLast ? "0 0 10px var(--g-signal-red)" : "none",
              border: "2px solid var(--s-panel)",
            }}/>
            {large ? (
              /* Ubicación + fecha — DEBAJO del punto */
              <div style={{
                position: "absolute", left: "50%", top: "calc(50% + 14px)", transform: "translateX(-50%)",
                textAlign: "center", whiteSpace: "nowrap",
              }}>
                <div style={{ fontSize: 12, color: "var(--g-text-cream)", opacity: 0.85 }}>
                  {node.alias}
                </div>
                <div style={{ fontSize: 11, color: "var(--g-text-mute-dark)", marginTop: 3, fontVariantNumeric: "tabular-nums" }}>
                  {window.SONAR_fmtTime(cap.ts)} · {window.SONAR_fmtShortDate(cap.ts)}
                </div>
              </div>
            ) : (
              <>
                <div style={{
                  position: "absolute", top: 12, left: "50%", transform: "translateX(-50%)",
                  display: "flex", alignItems: "center", gap: 2,
                  fontSize: 9, letterSpacing: 0.8, color: isLast ? "var(--g-signal-red-soft)" : "var(--g-text-mute-dark)",
                  whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums", fontWeight: 500,
                }}>
                  {(cap.direccion === "in" || cap.direccion === "out") && (
                    <window.Icon name={cap.direccion === "in" ? "log-in" : "log-out"} size={9}
                      color={cap.direccion === "in" ? "#4ADE80" : "#FF8A3D"}/>
                  )}
                  {window.SONAR_nodeCode(node)}
                </div>
                <div style={{
                  position: "absolute", bottom: 12, left: "50%", transform: "translateX(-50%)",
                  fontSize: 9, letterSpacing: 0.4, color: isLast ? "var(--g-signal-red-soft)" : "var(--g-text-cream)",
                  whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums",
                }}>
                  {window.SONAR_fmtTime(cap.ts)}
                </div>
              </>
            )}
          </div>
        );
      })}
      {/* NOW marker */}
      <div style={{ position: "absolute", left: "100%", top: LINE_Y, transform: "translate(-100%, -50%)" }}>
        <div style={{ width: 2, height: large ? 30 : 18, background: "var(--g-text-cream)", opacity: 0.55, transform: "translateX(-1px)" }}/>
        <div style={{
          position: "absolute",
          right: 6, top: large ? -18 : -12,
          fontSize: large ? 10 : 9,
          letterSpacing: 1.2,
          color: "var(--g-text-cream)",
          opacity: 0.7,
        }}>AHORA</div>
      </div>
    </div>
  );
};

/* ─── Shared helpers (Stat, KPI, LegendItem, SeccionesRed, Timeline) ── */

const Stat = ({ label, value, sub, mono }) => (
  <div>
    <div className="eyebrow">{label}</div>
    <div style={{
      marginTop: 4,
      fontSize: 16, fontWeight: 500,
      color: "var(--g-text-cream)",
      letterSpacing: mono ? 0.4 : -0.2,
      fontVariantNumeric: mono ? "tabular-nums" : "normal",
    }}>{value}</div>
    {sub && <div className="meta" style={{ marginTop: 2, fontSize: 10 }}>{sub}</div>}
  </div>
);

const LegendItem = ({ color, label, pulse, cross }) => (
  <div style={{ display: "flex", alignItems: "center", gap: 10, color: "var(--g-text-mute-dark)" }}>
    <div style={{ position: "relative", width: 12, height: 12, display: "grid", placeItems: "center" }}>
      <div style={{
        width: 7, height: 7, borderRadius: "50%", background: color,
        boxShadow: pulse ? `0 0 6px ${color}` : "none",
        animation: pulse ? "alertPulse 1.4s ease-in-out infinite" : "none",
      }}>
        {cross && <span style={{
          position: "absolute", inset: 0, display: "grid", placeItems: "center",
          color: "rgba(229,238,245,0.8)", fontSize: 10, lineHeight: 1,
        }}>×</span>}
      </div>
    </div>
    <span>{label}</span>
  </div>
);

const KPI = ({ label, value, accent, icon }) => {
  const c = accent === "green" ? "var(--g-signal-green)" :
            accent === "red"   ? "var(--g-signal-red-soft)" :
            accent === "mute"  ? "var(--g-text-mute-dark)" :
                                 "var(--accent, var(--g-celadon))";
  return (
    <div className="surface kpi-card" style={{ padding: "13px 14px" }}>
      <div className="kpi-card__head">
        {icon && (
          <span className="kpi-card__icon" style={{ color: c }}>
            <window.Icon name={icon} size={18}/>
          </span>
        )}
        <div className="kpi-card__label" style={{ fontSize: 10, letterSpacing: 1.4, textTransform: "uppercase", color: "var(--g-text-mute-dark)" }}>
          {label}
        </div>
      </div>
      <div className="kpi-card__num" style={{
        fontSize: 22, fontWeight: 500, marginTop: 4,
        color: c,
        fontVariantNumeric: "tabular-nums",
        letterSpacing: -0.4,
      }}>{value}</div>
    </div>
  );
};

// Panel derecho (sin placa): solo nodos ANPR y puestos de control ACTIVOS, en grilla
// compacta de códigos. Tocar un código enfoca ese nodo en el mapa (pin + burbuja).
const RedVacio = ({ texto }) => (
  <div style={{ fontSize: 12, color: "var(--g-text-mute-dark)", padding: "2px var(--density-pad)" }}>{texto}</div>
);

const CodigoGrid = ({ items, onSelect, dot = "var(--g-signal-green)" }) => (
  <div style={{ display: "flex", flexWrap: "wrap", gap: 7, padding: "0 var(--density-pad)" }}>
    {items.length === 0 ? <RedVacio texto="Ninguno activo."/> : items.map(n => (
      <button key={n.id} onClick={() => onSelect(n)} title={n.nombre}
        style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 10px", borderRadius: 8,
          background: "var(--g-surface-on-dark)", border: "1px solid var(--g-border-on-dark)", cursor: "pointer", fontFamily: "var(--font-sans)" }}
        onMouseEnter={e => { e.currentTarget.style.borderColor = dot; }}
        onMouseLeave={e => { e.currentTarget.style.borderColor = "var(--g-border-on-dark)"; }}>
        <span style={{ width: 6, height: 6, borderRadius: "50%", background: dot, boxShadow: "0 0 6px " + dot }}/>
        <span style={{ fontSize: 12, fontWeight: 600, letterSpacing: 0.4, color: "var(--accent, var(--g-celadon))" }}>{n.codigo}</span>
      </button>
    ))}
  </div>
);

const RedSeccion = ({ titulo, count, children }) => (
  <section style={{ marginTop: 16 }}>
    <div style={{ display: "flex", alignItems: "baseline", padding: "0 var(--density-pad)", marginBottom: 9 }}>
      <div className="eyebrow">{titulo}</div>
      <div className="meta" style={{ marginLeft: "auto", fontVariantNumeric: "tabular-nums" }}>{count}</div>
    </div>
    {children}
  </section>
);

const SeccionesRed = ({ camaras, puestos, fronteras = [], onSelect }) => {
  const camAct = camaras.filter(n => n.status === "active");
  const puestosAct = puestos.filter(n => n.status === "active");
  return (
    <div style={{ paddingBottom: 14 }}>
      <RedSeccion titulo="Nodos ANPR activos" count={camAct.length}>
        <CodigoGrid items={camAct} onSelect={onSelect}/>
      </RedSeccion>
      <RedSeccion titulo="Puestos de control activos" count={puestosAct.length}>
        <CodigoGrid items={puestosAct} onSelect={onSelect} dot="var(--g-signal-yellow)"/>
      </RedSeccion>
      <RedSeccion titulo="Fronteras · salidas" count={fronteras.length}>
        <CodigoGrid items={fronteras} onSelect={onSelect} dot="#FF8A3D"/>
      </RedSeccion>
      <div style={{ fontSize: 11, color: "var(--g-text-mute-dark)", margin: "16px var(--density-pad) 0", display: "flex", alignItems: "center", gap: 6 }}>
        <window.Icon name="info" size={12}/> Toca un código para ubicarlo en el mapa.
      </div>
    </div>
  );
};

const Timeline = ({ plate }) => {
  const caps = plate.capturas;
  const t0 = new Date(caps[0].ts).getTime();
  const tN = window.SONAR_NOW.getTime();
  const span = Math.max(tN - t0, 60000);
  return (
    <div style={{ position: "relative", height: 50 }}>
      <div style={{ position: "absolute", left: 0, right: 0, top: "50%", height: 1, background: "var(--g-border-on-dark)" }}/>
      {caps.map((cap, i) => {
        const t = new Date(cap.ts).getTime();
        const pct = ((t - t0) / span) * 100;
        const node = window.SONAR_findNode(cap.nodo);
        const isLast = i === caps.length - 1;
        return (
          <div key={cap.ts} style={{ position: "absolute", left: `${pct}%`, top: "50%", transform: "translate(-50%, -50%)" }}>
            <div style={{
              width: isLast ? 12 : 9, height: isLast ? 12 : 9,
              borderRadius: "50%",
              background: isLast ? "var(--g-signal-red)" : (cap.direccion === "in" ? "#4ADE80" : cap.direccion === "out" ? "#FF8A3D" : "var(--accent, var(--g-celadon))"),
              boxShadow: isLast ? "0 0 8px var(--g-signal-red)" : "none",
              border: "2px solid #07090c",
            }}/>
            <div style={{
              position: "absolute", top: -22, left: "50%", transform: "translateX(-50%)",
              fontSize: 9, letterSpacing: 0.6, color: isLast ? "var(--g-signal-red-soft)" : "var(--g-text-mute-dark)",
              whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums",
            }}>
              {window.SONAR_fmtTime(cap.ts)}
            </div>
            <div style={{
              position: "absolute", top: 14, left: "50%", transform: "translateX(-50%)",
              display: "flex", alignItems: "center", gap: 2,
              fontSize: 9, letterSpacing: 1.2, color: isLast ? "var(--g-signal-red-soft)" : "var(--accent, var(--g-celadon))",
              whiteSpace: "nowrap", fontWeight: 500,
            }}>
              {(cap.direccion === "in" || cap.direccion === "out") && (
                <window.Icon name={cap.direccion === "in" ? "log-in" : "log-out"} size={9}
                  color={cap.direccion === "in" ? "#4ADE80" : "#FF8A3D"}/>
              )}
              {window.SONAR_nodeCode(node)}
            </div>
          </div>
        );
      })}
      {/* NOW marker */}
      <div style={{ position: "absolute", left: "100%", top: "50%", transform: "translate(-100%, -50%)" }}>
        <div style={{ width: 2, height: 24, background: "var(--g-text-cream)", opacity: 0.6, transform: "translateX(-1px)" }}/>
        <div style={{ position: "absolute", left: 6, top: -2, fontSize: 9, letterSpacing: 1.4, color: "var(--g-text-cream)" }}>AHORA</div>
      </div>
    </div>
  );
};

window.MapaScreen = MapaScreen;
