// === scenes/ficha.jsx — <FichaProducto/> : EL FORMULARIO REAL DE DINAUP, dibujado de verdad ===
//
// Lo cargan las escenas 01 y 02 a tamaño completo y la 03 en miniatura. Aquí dentro NO hay un solo
// beat: todo lo que se mueve entra por props numéricas (0→1). El componente es estático y puro.
//
// EL HALLAZGO QUE GOBIERNA ESTE DIBUJO (verificado en código el 13/08/2026, DinaZen\Components\
// DinaupFlex\Forms\ + dinazen-forms.css): un formulario Dinaup es un LIENZO EN COORDENADAS
// ABSOLUTAS. Cada campo se pinta con position:absolute; left/top/width/height. Se ve SIEMPRE A
// VARIAS COLUMNAS: existe DnzFormMobileLayout.cs solo para reagrupar en vertical cuando la ventana
// baja de 1000 px (.dnz-form-view{min-width:1000px}).
//   ⇒ la lista vertical de pares etiqueta-valor ES EL MODO MÓVIL. Aquí se dibuja el de ESCRITORIO,
//     a dos columnas, y por eso este archivo también se construye en coordenadas absolutas: las
//     anclas del campo protagonista salen de los mismos números que pinta el DOM, no de un ojímetro.
//
// FIDELIDAD (spec verificada):
//   · marco .dnz-form-view #f4f4f4 · cabecera 48px blanca con contadores · tira de modo 2px
//     (#93c5fd edición / #86efac alta) · título grande sin borde con raya inferior · 4 pestañas
//     («General» activa #3481e5) · lienzo #f0f1f3 con cards blancas SIN sombra y SIN título · pie
//     con Guardar verde y Cerrar rojo.
//   · campo: etiqueta .labelinput 11px/500 #6b7280 UPPERCASE con ancho fijo a la izquierda;
//     control 26px/13px, fondo #fff, borde #dfe5ec, radio 6, sombra 0 1px 2px.
//   · bloqueado: #fafafa + rayado diagonal −45°, borde #e2e2e2, texto #8b8b8b, etiqueta #999 y
//     candado a opacity .4 pegado detrás de la etiqueta.
//   · modificado: fondo #f0f9ff, borde #7dd3fc, etiqueta #0369a1 y punto 6px #0ea5e9 con halo.
//   · desplegable y relación: chevron abajo superpuesto al borde derecho a opacity .4 (NO una lupa);
//     la relación además lleva miniatura del registro a la izquierda del control.
//   · casilla: caja radio 4, marcada rellena #3481e5, etiqueta a su DERECHA y SIN mayúsculas.
//   · NINGÚN campo pinta icono de rol: DnzControl.razor no renderiza nunca DataControl.Icon.
//
// LO QUE NO ES PÍXEL REAL, y por qué (regla del plan: «se reproduce la disposición y las etiquetas,
// no el píxel»): las fuentes van escaladas ×~1.45 sobre las reales (13 px sería ilegible a 1920), el
// rayado del bloqueado sube de rgba(0,0,0,.018) a .045 porque .018 desaparece con la compresión de
// vídeo, y el VALOR DEL CAMPO PROTAGONISTA se pinta a +6 px y semibold — es el único campo que la
// voz señala y a 18 px no sostendría el plano de apertura. Todo lo demás son proporciones reales.
//
// REGLAS DEL MOTOR QUE ESTE ARCHIVO CUMPLE: función pura (ni Math.random, ni Date, ni useState/
// useEffect) · lienzo 1920×1080 · columna segura 4:5 (todo lo informativo entre x=568 y x=1352:
// el marco va de 528 a 1392 y su padding de 40 deja el contenido justo en los 784 px útiles) ·
// cero NaN y cero undefined en el DOM · cierre con Object.assign.

// ── Paleta, literal de dinazen-forms.css ────────────────────────────────────────────────────────
const FICHA_COL = {
  marco:'#f4f4f4',        marcoBorde:'#eef1f5',
  cabeceraDe:'#f8fafc',   cabeceraA:'#ffffff',
  lienzo:'#f0f1f3',       card:'#ffffff',        cardBorde:'#eef1f5',
  tiraEdicion:'#93c5fd',  tiraAlta:'#86efac',
  texto:'#1b1d20',        etiqueta:'#6b7280',    suave:'#94a3b8',
  control:'#ffffff',      controlBorde:'#dfe5ec',
  bloqFondo:'#fafafa',    bloqBorde:'#e2e2e2',   bloqTexto:'#8b8b8b',  bloqEtiqueta:'#999999',
  modFondo:'#f0f9ff',     modBorde:'#7dd3fc',    modEtiqueta:'#0369a1', modPunto:'#0ea5e9',
  activa:'#3481e5',       azul:'#0085FF',
  guardar:'#16a34a',      cerrar:'#dc2626',
};

// ── Geometría del formulario COMPLETO, en coordenadas locales del marco (origen 0,0 arriba-izq).
//    Ancho natural 864 = el recorte 4:5 exacto (528→1392). Padding 40 ⇒ contenido en 784 px.
//    Dos columnas asimétricas (354 / 406) y dos anchos de etiqueta (168 / 200): la columna 2 carga
//    las etiquetas y los valores más largos («PRECIO MEDIO COMPRA», «Suministros Vega»), medidos
//    uno a uno en el navegador. Un formulario Dinaup no tiene rejilla —cada campo lleva su
//    left/top/width— así que columnas distintas son fieles al modelo. Lo que SÍ es una desviación
//    consciente es el ancho de etiqueta por columna: en el producto .labelinput es fijo y una
//    etiqueta larga se corta con puntos suspensivos. Aquí no puede cortarse, porque las etiquetas
//    literales son parte de lo que la pieza enseña (regla del plan: disposición y etiquetas, no
//    píxel). Con estos números NINGUNA etiqueta y NINGÚN valor se recorta: verificado midiendo
//    scrollWidth contra clientWidth de las 33 cajas de texto.
const FICHA_GEO = {
  ancho: 864, alto: 845, padX: 40, contW: 784,
  col1X: 40,  col1W: 354,
  col2X: 418, col2W: 406,
  etiquetaW2: 200,
  cabecera: 54, tira: 3, titulo: 74, pestanas: 52, pie: 54,
  lienzoY: 183, lienzoPad: 16,
  cardX: 20, cardW: 824, cardPadX: 20, cardPadY: 18, cardGap: 14,
  card1: { y: 199, h: 186 },
  card2: { y: 399, h: 230 },
  card3: { y: 643, h: 132 },
  // filas (y local de la fila; x = col1X | col2X). El alto de fila es FICHA_CAMPO.completa.filaH.
  fila: {
    referencia: 217, codigoBarras: 217,
    categoria:  271, marca:        271,
    descripcion:325,
    precio:     417, precioMedioCompra: 417,
    categoriaVenta: 471, proveedor:     471,
    unidadMedida:   525, peso:          525,
    casillas:       579,
    stockDisponible:661, stockInventario:661,
    modoGestion:    715, evaluarStock:   715,
  },
};

// ── Geometría del modo 'zoom': SOLO la fila del campo protagonista, ampliada ────────────────────
const FICHA_GEO_ZOOM = {
  ancho: 864, alto: 178, lienzoPad: 16,
  cardX: 20, cardY: 16, cardW: 824, cardH: 146, cardPadX: 20, cardPadY: 34,
  filaX: 40, filaY: 50,
};

// ── Medidas de UN campo, por modo. Mismo componente, dos escalas. ───────────────────────────────
const FICHA_CAMPO = {
  completa: {
    etiquetaW:168, etiquetaGap:10, controlW:176, filaH:42,
    fEtiqueta:15, fControl:17, fProta:24,
    radio:8, padControl:10, gapMarca:6, gapMini:7,
    punto:9, candado:15, chevron:17, mini:24,
    subrayadoY:-7, subrayadoH:3,
    casilla:26, casillaRadio:6, fCasilla:17, casillaGap:10, casillasSep:40,
  },
  zoom: {
    etiquetaW:300, etiquetaGap:20, controlW:464, filaH:78,
    fEtiqueta:28, fControl:40, fProta:44,
    radio:12, padControl:20, gapMarca:12, gapMini:14,
    punto:16, candado:26, chevron:32, mini:52,
    subrayadoY:-13, subrayadoH:5,
    casilla:44, casillaRadio:9, fCasilla:29, casillaGap:16, casillasSep:56,
  },
};

// ── Colocación por defecto en el lienzo 1920×1080 ───────────────────────────────────────────────
//    completa: x 528..1392 · y 150..995 (informativo 568..1352; el contenido llega a y≈995,
//    que es lo que pide la regla «si no llega a y≈900, está pequeño»).
//    zoom: 864×178 centrado en vertical.
const FICHA_BASE      = { x:528, y:150, ancho:864, alto:845 };
const FICHA_BASE_ZOOM = { x:528, y:451, ancho:864, alto:178 };

// ── Cajas locales de las anclas (mismos números que pinta el DOM) ───────────────────────────────
// `precioEtiquetaTexto` es la caja de las LETRAS «PRECIO DE VENTA», no la de su columna de ancho
// fijo: es la que hay que usar para el anillo del clic derecho y para colgar el menú, porque el
// menú del producto sale del <RadzenLabel>, es decir, del texto. Los anchos (132 y 246) están
// MEDIDOS en el navegador con IBM Plex Sans 500, mayúsculas y letter-spacing 0.02em, a 15 y 28 px.
const FICHA_LOCALES = {
  completa: {
    precioEtiqueta: { x: FICHA_GEO.col1X, y: FICHA_GEO.fila.precio, w: FICHA_CAMPO.completa.etiquetaW, h: FICHA_CAMPO.completa.filaH },
    precioEtiquetaTexto: { x: FICHA_GEO.col1X, y: FICHA_GEO.fila.precio, w: 132, h: FICHA_CAMPO.completa.filaH },
    precioControl:  { x: FICHA_GEO.col1X + FICHA_CAMPO.completa.etiquetaW + FICHA_CAMPO.completa.etiquetaGap,
                      y: FICHA_GEO.fila.precio, w: FICHA_CAMPO.completa.controlW, h: FICHA_CAMPO.completa.filaH },
    precioFila:     { x: FICHA_GEO.col1X, y: FICHA_GEO.fila.precio, w: FICHA_GEO.col1W, h: FICHA_CAMPO.completa.filaH },
    tituloRegistro: { x: FICHA_GEO.padX, y: FICHA_GEO.cabecera + FICHA_GEO.tira, w: FICHA_GEO.contW, h: FICHA_GEO.titulo },
    pestanaGeneral: { x: FICHA_GEO.padX, y: FICHA_GEO.cabecera + FICHA_GEO.tira + FICHA_GEO.titulo, w: 90, h: FICHA_GEO.pestanas },
    cardComercial:  { x: FICHA_GEO.cardX, y: FICHA_GEO.card2.y, w: FICHA_GEO.cardW, h: FICHA_GEO.card2.h },
  },
  zoom: {
    precioEtiqueta: { x: FICHA_GEO_ZOOM.filaX, y: FICHA_GEO_ZOOM.filaY, w: FICHA_CAMPO.zoom.etiquetaW, h: FICHA_CAMPO.zoom.filaH },
    precioEtiquetaTexto: { x: FICHA_GEO_ZOOM.filaX, y: FICHA_GEO_ZOOM.filaY, w: 246, h: FICHA_CAMPO.zoom.filaH },
    precioControl:  { x: FICHA_GEO_ZOOM.filaX + FICHA_CAMPO.zoom.etiquetaW + FICHA_CAMPO.zoom.etiquetaGap,
                      y: FICHA_GEO_ZOOM.filaY, w: FICHA_CAMPO.zoom.controlW, h: FICHA_CAMPO.zoom.filaH },
    precioFila:     { x: FICHA_GEO_ZOOM.filaX, y: FICHA_GEO_ZOOM.filaY, w: 784, h: FICHA_CAMPO.zoom.filaH },
  },
};

// ── fichaAncla(clave, {x, y, ancho, escala, modo}) → caja EN COORDENADAS DE LIENZO ──────────────
// Sirve para llevar el cursor exactamente a la etiqueta sin adivinar un número, con la ficha
// colocada donde sea y a la escala que sea. Devuelve {x,y,w,h,cx,cy}; cx/cy es el centro.
function fichaAncla(clave, opciones){
  const o = opciones || {};
  const modo = o.modo === 'zoom' ? 'zoom' : 'completa';
  const base = modo === 'zoom' ? FICHA_BASE_ZOOM : FICHA_BASE;
  const x0 = (o.x === null || o.x === undefined) ? base.x : o.x;
  const y0 = (o.y === null || o.y === undefined) ? base.y : o.y;
  const anchoTotal = (o.ancho === null || o.ancho === undefined) ? base.ancho : o.ancho;
  const kAncho = (anchoTotal > 0 && base.ancho > 0) ? anchoTotal / base.ancho : 1;
  const esc = (o.escala === null || o.escala === undefined || !(o.escala > 0)) ? 1 : o.escala;
  const k = esc * kAncho;
  const L = FICHA_LOCALES[modo][clave];
  if (!L) return { x:x0, y:y0, w:0, h:0, cx:x0, cy:y0 };
  return {
    x: x0 + L.x*k, y: y0 + L.y*k, w: L.w*k, h: L.h*k,
    cx: x0 + (L.x + L.w/2)*k, cy: y0 + (L.y + L.h/2)*k,
  };
}

// ── FICHA_ANCLAS: las cajas ya resueltas en la COLOCACIÓN POR DEFECTO. Si la escena mueve o
//    escala la ficha, usa fichaAncla(...) con los mismos x/y/ancho/escala que le pasó al componente.
const FICHA_ANCLAS = {
  precioEtiqueta: fichaAncla('precioEtiqueta'),
  precioEtiquetaTexto: fichaAncla('precioEtiquetaTexto'),
  precioControl:  fichaAncla('precioControl'),
  precioFila:     fichaAncla('precioFila'),
  tituloRegistro: fichaAncla('tituloRegistro'),
  pestanaGeneral: fichaAncla('pestanaGeneral'),
  cardComercial:  fichaAncla('cardComercial'),
  zoom: {
    precioEtiqueta:      fichaAncla('precioEtiqueta',      { modo:'zoom' }),
    precioEtiquetaTexto: fichaAncla('precioEtiquetaTexto', { modo:'zoom' }),
    precioControl:       fichaAncla('precioControl',       { modo:'zoom' }),
    precioFila:          fichaAncla('precioFila',          { modo:'zoom' }),
  },
  base: FICHA_BASE,
  baseZoom: FICHA_BASE_ZOOM,
};

// ── Iconos del formulario (trazo, como los del diálogo) ─────────────────────────────────────────
function FichaIcono({ nombre, size = 20, color = 'currentColor', grosor = 1.8, style }){
  const box = { width:size, height:size, display:'block', flexShrink:0, ...(style || {}) };
  const p = { fill:'none', stroke:color, strokeWidth:grosor, strokeLinecap:'round', strokeLinejoin:'round' };
  if (nombre === 'seccion'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M12 3 L20.5 7.5 V16.5 L12 21 L3.5 16.5 V7.5 Z"/><path d="M3.5 7.5 L12 12 L20.5 7.5"/><path d="M12 12 V21"/></svg>);
  }
  if (nombre === 'impresora'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M7.5 8.5 V4 H16.5 V8.5"/><path d="M5 8.5 H19 V15.5 H16.5 V20 H7.5 V15.5 H5 Z"/><path d="M8.5 17.5 H15.5"/></svg>);
  }
  if (nombre === 'comentarios'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M20 4.5 H4 V15.5 H8 V19.5 L12.6 15.5 H20 Z"/></svg>);
  }
  if (nombre === 'adjuntos'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M14.2 7 L8.6 12.6 A3 3 0 0 0 12.8 16.8 L18.2 11.4 A5 5 0 0 0 11.2 4.4 L5.9 9.7"/></svg>);
  }
  if (nombre === 'galeria'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M4.5 6 H19.5 V18 H4.5 Z"/><path d="M4.5 15 L9.5 10.4 L13.2 14"/><circle cx="15.6" cy="9.6" r="1.3"/></svg>);
  }
  if (nombre === 'cerrar'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M6.5 6.5 L17.5 17.5"/><path d="M17.5 6.5 L6.5 17.5"/></svg>);
  }
  if (nombre === 'chevron'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M6.5 9.5 L12 15 L17.5 9.5"/></svg>);
  }
  if (nombre === 'candado'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M8 10.5 V8 A4 4 0 0 1 16 8 V10.5"/><path d="M6.5 10.5 H17.5 V19.5 H6.5 Z"/></svg>);
  }
  if (nombre === 'check'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M5 12.6 L9.8 17.4 L19 7.6"/></svg>);
  }
  if (nombre === 'ajustes'){
    return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M4 8.5 H20"/><path d="M4 15.5 H20"/><circle cx="9.5" cy="8.5" r="2.2"/><circle cx="15" cy="15.5" r="2.2"/></svg>);
  }
  // rayo
  return (<svg viewBox="0 0 24 24" style={box} {...p}><path d="M13 3 L6 13.2 H11.2 L10.4 21 L18 10.6 H12.6 Z"/></svg>);
}

// ── Un CAMPO: etiqueta a la izquierda con ancho fijo (en MAYÚSCULAS) + control a su derecha ─────
//    Es la disposición .dnz-control con etiqueta izquierda de DnzControl.razor. Aquí no hay menú
//    contextual: eso lo pinta MenuContextualUnItem sobre las anclas de este archivo.
function CampoFicha({
  etiqueta = '',
  valor = '',
  tipo = 'texto',          // 'texto' | 'desplegable' | 'relacion'
  estado = 'normal',       // 'normal' | 'bloqueado' | 'modificado'
  mini = null,             // inicial de la miniatura del registro relacionado
  geo = null,
  anchoEtiqueta = null,
  anchoControl = null,
  protagonista = false,
  resaltado = false,
  apagado = 0,             // 0→1, apaga SOLO el control (beat «no sobre el cuadro»)
  subrayado = 0,           // 0→1, dibuja el subrayado azul bajo la etiqueta, de izq a der
}){
  const g   = geo || FICHA_CAMPO.completa;
  const ew  = (anchoEtiqueta === null || anchoEtiqueta === undefined) ? g.etiquetaW : anchoEtiqueta;
  const cw  = (anchoControl === null || anchoControl === undefined) ? g.controlW : anchoControl;
  const bloq = estado === 'bloqueado';
  const mod  = estado === 'modificado';
  const ap  = clamp(apagado || 0, 0, 1);
  const sub = clamp(subrayado || 0, 0, 1);
  const txt = (valor === null || valor === undefined) ? '' : String(valor);
  const conChevron = tipo === 'desplegable' || tipo === 'relacion';
  const conMini = tipo === 'relacion' && mini !== null && mini !== undefined && mini !== '';

  const colEtiqueta = bloq ? FICHA_COL.bloqEtiqueta : (mod ? FICHA_COL.modEtiqueta : FICHA_COL.etiqueta);
  const colValor    = bloq ? FICHA_COL.bloqTexto : FICHA_COL.texto;
  const borde       = bloq ? FICHA_COL.bloqBorde : (mod ? FICHA_COL.modBorde : FICHA_COL.controlBorde);
  // rayado diagonal −45° del campo autocalculado (alpha subida de .018 a .045: a .018 el vídeo la come)
  const fondo = bloq
    ? `repeating-linear-gradient(-45deg, rgba(0,0,0,0.045) 0 2px, rgba(0,0,0,0) 2px 6px), ${FICHA_COL.bloqFondo}`
    : (mod ? FICHA_COL.modFondo : FICHA_COL.control);
  const padDer = conChevron ? Math.round(g.padControl + g.chevron*0.55) : g.padControl;
  const fValor = protagonista ? g.fProta : g.fControl;

  return (
    <div style={{position:'relative', display:'flex', alignItems:'center', gap:g.etiquetaGap, height:g.filaH}}>
      {resaltado ? (
        <span style={{position:'absolute', left:-10, right:-10, top:-7, bottom:-7, borderRadius:10,
                      background:'rgba(0,133,255,0.10)', border:'1px solid rgba(0,133,255,0.42)',
                      boxShadow:'0 0 22px rgba(0,133,255,0.28)', pointerEvents:'none'}}/>
      ) : null}

      {/* ── Etiqueta: ancho FIJO, una línea, mayúsculas; el marcador de estado va pegado detrás ── */}
      <div style={{position:'relative', width:ew, flexShrink:0, display:'flex', alignItems:'center', gap:g.gapMarca, minWidth:0}}>
        <span style={{position:'relative', minWidth:0, maxWidth:'100%'}}>
          <span className="f-plex" style={{
            display:'block', fontSize:g.fEtiqueta, fontWeight:500, letterSpacing:'0.02em',
            textTransform:'uppercase', color:colEtiqueta, lineHeight:1.2,
            whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
          }}>{etiqueta}</span>
          {sub > 0 ? (
            <span style={{position:'absolute', left:0, right:0, bottom:g.subrayadoY, height:g.subrayadoH,
                          borderRadius:2, background:FICHA_COL.azul, boxShadow:'0 0 12px rgba(0,133,255,0.45)',
                          transform:`scaleX(${sub})`, transformOrigin:'left'}}/>
          ) : null}
        </span>
        {bloq ? <FichaIcono nombre="candado" size={g.candado} color={FICHA_COL.bloqEtiqueta} grosor={1.9} style={{opacity:0.4}}/> : null}
        {mod ? (
          <span style={{width:g.punto, height:g.punto, borderRadius:'50%', flexShrink:0,
                        background:FICHA_COL.modPunto,
                        boxShadow:`0 0 ${Math.round(g.punto*0.7)}px rgba(14,165,233,0.4)`}}/>
        ) : null}
      </div>

      {/* ── Control ── */}
      <div style={{
        position:'relative', width:cw, height:g.filaH, flexShrink:0,
        display:'flex', alignItems:'center', gap:g.gapMini,
        padding:`0 ${padDer}px 0 ${g.padControl}px`,
        borderRadius:g.radio, background:fondo, border:`1px solid ${borde}`,
        boxShadow:'0 1px 2px rgba(0,0,0,0.04)',
        opacity: lerp(1, 0.35, ap),
        filter: ap > 0 ? `saturate(${lerp(1, 0.55, ap)})` : 'none',
      }}>
        {conMini ? (
          <span className="f-plex" style={{
            width:g.mini, height:g.mini, flexShrink:0, borderRadius:Math.round(g.mini*0.22),
            background:'linear-gradient(135deg,#e8eef6,#cbd6e4)', border:'1px solid #dfe5ec',
            display:'flex', alignItems:'center', justifyContent:'center', lineHeight:1,
            fontSize:Math.round(g.mini*0.48), fontWeight:600, color:'#7b8794',
          }}>{String(mini).charAt(0).toUpperCase()}</span>
        ) : null}
        <span className="f-plex" style={{
          flex:1, minWidth:0, fontSize:fValor, fontWeight: protagonista ? 600 : 400,
          color:colValor, lineHeight:1.2, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
        }}>{txt}</span>
        {conChevron ? (
          <FichaIcono nombre="chevron" size={g.chevron} color={FICHA_COL.texto} grosor={2}
                      style={{position:'absolute', right:-1, top:'50%', marginTop:-Math.round(g.chevron/2), opacity:0.4}}/>
        ) : null}
      </div>
    </div>
  );
}

// ── Una CASILLA: caja + etiqueta a su DERECHA y SIN mayúsculas (.labelch) ───────────────────────
function CasillaFicha({ etiqueta = '', marcada = false, geo = null, resaltada = false }){
  const g = geo || FICHA_CAMPO.completa;
  return (
    <div style={{position:'relative', display:'inline-flex', alignItems:'center', gap:g.casillaGap}}>
      {resaltada ? (
        <span style={{position:'absolute', left:-10, right:-10, top:-8, bottom:-8, borderRadius:10,
                      background:'rgba(0,133,255,0.10)', border:'1px solid rgba(0,133,255,0.42)',
                      boxShadow:'0 0 22px rgba(0,133,255,0.28)', pointerEvents:'none'}}/>
      ) : null}
      <span style={{
        position:'relative', width:g.casilla, height:g.casilla, flexShrink:0, borderRadius:g.casillaRadio,
        background: marcada ? FICHA_COL.activa : '#fff',
        border:`2px solid ${marcada ? FICHA_COL.activa : FICHA_COL.controlBorde}`,
        display:'flex', alignItems:'center', justifyContent:'center',
      }}>
        {marcada ? <FichaIcono nombre="check" size={Math.round(g.casilla*0.72)} color="#fff" grosor={2.6}/> : null}
      </span>
      <span className="f-plex" style={{fontSize:g.fCasilla, fontWeight:400, color:FICHA_COL.texto, whiteSpace:'nowrap', lineHeight:1.2}}>
        {etiqueta}
      </span>
    </div>
  );
}

// ── Una CARD del lienzo: blanca, radio 8, borde #eef1f5, SIN sombra y SIN título (la caja ES la
//    agrupación). Posicionada en coordenadas absolutas, como el formulario real.
// El borde va como sombra INTERIOR, no como `border`: un borde real empuja 1 px el sistema de
// coordenadas de los hijos y las anclas dejarían de caer donde dicen. Se ve exactamente igual.
function FichaCard({ x, y, w, h, children }){
  return (
    <div style={{position:'absolute', left:x, top:y, width:w, height:h,
                 background:FICHA_COL.card, borderRadius:8,
                 boxShadow:`inset 0 0 0 1px ${FICHA_COL.cardBorde}`}}>
      {children}
    </div>
  );
}

// ── Contador de anotaciones de la cabecera: icono + badge píldora (gris si 0, azul si tiene) ────
function FichaContador({ icono, n }){
  const v = (typeof n === 'number' && isFinite(n)) ? Math.max(0, Math.round(n)) : 0;
  const activo = v > 0;
  return (
    <span style={{display:'inline-flex', alignItems:'center', gap:5}}>
      <FichaIcono nombre={icono} size={20} color={activo ? FICHA_COL.activa : '#9aa1ac'} grosor={1.7}/>
      <span className="f-plex" style={{
        fontSize:15, fontWeight:600, lineHeight:1, padding:'3px 8px', borderRadius:999,
        background: activo ? 'rgba(52,129,229,0.12)' : '#eef1f5',
        color: activo ? FICHA_COL.activa : '#9aa1ac',
      }}>{v}</span>
    </span>
  );
}

// =====================================================================================
// <FichaProducto/> — la ficha «Compresor 50 litros», sección Productos, pestaña General.
//
//   t                 — reloj de la escena. El componente es ESTÁTICO por diseño: no lleva ni un
//                       beat dentro; todo lo que se mueve llega ya resuelto en las props 0→1.
//   x, y              — esquina superior izquierda en el lienzo 1920×1080.
//   ancho, alto       — caja natural (por defecto 864×845; en 'zoom', 864×178). Cambiar `ancho`
//                       reescala el conjunto manteniendo proporciones; `alto` solo estira el
//                       lienzo gris (el pie va pegado abajo), no reflota nada.
//   escala            — multiplicador sobre la caja. La miniatura de la escena 03 = ancho menor.
//   precio            — valor del campo protagonista (string o número; se pinta tal cual).
//   estadoPrecio      — 'normal' | 'modificado' (fondo #f0f9ff, borde #7dd3fc, etiqueta #0369a1
//                       y punto azul pegado detrás de la etiqueta).
//   atenuacion        — 0→1 del conjunto: 0 = nítida; 1 = opacity 0.35 + saturate 0.55, que son
//                       los dos números literales del plan para «la ficha se atenúa».
//   resaltarCampo     — clave del campo a destacar con halo azul, o null. Claves: referencia,
//                       codigoBarras, categoria, marca, descripcion, precio, precioMedioCompra,
//                       categoriaVenta, proveedor, unidadMedida, peso, bloquearVenta,
//                       descontinuado, esServicio, esPack, stockDisponible, stockInventario,
//                       modoGestion, evaluarStock.
//   modo              — 'completa' | 'zoom' (solo la fila del protagonista, ampliada).
//   apagarControlPrecio — 0→1: apaga SOLO el cuadro del protagonista («no sobre el cuadro»).
//   subrayadoPrecio     — 0→1: dibuja el subrayado azul bajo su etiqueta («sobre el nombre»).
//   modoAlta          — false = tira azul (edición, la nuestra); true = tira verde (alta).
// =====================================================================================
function FichaProducto({
  t = 0,
  x = null, y = null, ancho = null, alto = null,
  escala = 1,
  modo = 'completa',
  precio = '990,00',
  estadoPrecio = 'normal',
  atenuacion = 0,
  resaltarCampo = null,
  apagarControlPrecio = 0,
  subrayadoPrecio = 0,
  seccion = 'Productos',
  titulo = 'Compresor 50 litros',
  anotaciones = null,
  modoAlta = false,
  zIndex = 5,
  estilo = null,
}){
  const esZoom = modo === 'zoom';
  const G    = FICHA_GEO;
  const GZ   = FICHA_GEO_ZOOM;
  const g    = esZoom ? FICHA_CAMPO.zoom : FICHA_CAMPO.completa;
  const base = esZoom ? FICHA_BASE_ZOOM : FICHA_BASE;

  const px = (x === null || x === undefined) ? base.x : x;
  const py = (y === null || y === undefined) ? base.y : y;
  const anchoTotal = (ancho === null || ancho === undefined) ? base.ancho : ancho;
  const altoTotal  = (alto  === null || alto  === undefined) ? base.alto  : alto;
  const kAncho = (anchoTotal > 0 && base.ancho > 0) ? anchoTotal / base.ancho : 1;
  const esc = (escala > 0) ? escala : 1;
  const k = esc * kAncho;                       // escala total aplicada al marco
  const altoLocal = (altoTotal > 0 && kAncho > 0) ? altoTotal / kAncho : base.alto;

  const at  = clamp(atenuacion || 0, 0, 1);
  const opa = lerp(1, 0.35, at);
  const sat = lerp(1, 0.55, at);

  const modPrecio = estadoPrecio === 'modificado';
  const res = (clave) => resaltarCampo === clave;
  const precioTxt = (precio === null || precio === undefined) ? '' : String(precio);
  const anot = anotaciones || { impresora:0, comentarios:2, adjuntos:1, galeria:0 };

  const marco = {
    position:'absolute', left:px, top:py, width:base.ancho, height:altoLocal,
    transform:`scale(${k})`, transformOrigin:'0% 0%',
    opacity:opa, filter:`saturate(${sat})`,
    background: esZoom ? FICHA_COL.lienzo : FICHA_COL.marco,
    borderRadius:12,
    // borde como sombra interior: ver la nota de FichaCard (las anclas dependen de esto)
    boxShadow:`inset 0 0 0 1px ${FICHA_COL.marcoBorde}, 0 2px 6px rgba(0,0,0,0.06)`,
    overflow:'hidden', zIndex,
    ...(estilo || {}),
  };

  // ── Anchos de etiqueta y control por columna (los números salen de FICHA_GEO, no de la mano) ──
  const eW1 = g.etiquetaW;                                   // columna 1 · 168
  const eW2 = esZoom ? g.etiquetaW : G.etiquetaW2;           // columna 2 · 200
  const cW1 = G.col1W - eW1 - g.etiquetaGap;                 // 176
  const cW2 = G.col2W - eW2 - g.etiquetaGap;                 // 196
  const cWF = G.contW - eW1 - g.etiquetaGap;                 // 606 (fila a dos columnas)

  // ── El campo protagonista, idéntico en los dos modos ──────────────────────────────────────────
  const CampoPrecio = (anchoControl) => (
    <CampoFicha
      etiqueta="Precio de venta" valor={precioTxt} tipo="texto"
      estado={modPrecio ? 'modificado' : 'normal'}
      geo={g} anchoControl={anchoControl} protagonista
      resaltado={res('precio')}
      apagado={apagarControlPrecio}
      subrayado={subrayadoPrecio}
    />
  );

  // ══════════════════════════ MODO ZOOM: solo la fila del protagonista ══════════════════════════
  if (esZoom){
    return (
      <div style={marco}>
        <div style={{position:'absolute', left:GZ.cardX, top:GZ.cardY, width:GZ.cardW, height:GZ.cardH,
                     background:FICHA_COL.card, borderRadius:8,
                     boxShadow:`inset 0 0 0 1px ${FICHA_COL.cardBorde}`}}/>
        <div style={{position:'absolute', left:GZ.filaX, top:GZ.filaY, width:784}}>
          {CampoPrecio(g.controlW)}
        </div>
      </div>
    );
  }

  // ══════════════════════════ MODO COMPLETA ════════════════════════════════════════════════════
  const lienzoH = Math.max(0, altoLocal - G.lienzoY - G.pie);
  const c1 = G.card1, c2 = G.card2, c3 = G.card3;
  const filaCol = (xLocal, yLocal, w) => ({ position:'absolute', left:xLocal, top:yLocal, width:w });

  return (
    <div style={marco}>

      {/* ── 1. Cabecera 48px: icono de sección + «Productos» + (i) · contadores · ✕ ───────────── */}
      <div style={{position:'absolute', left:0, top:0, width:'100%', height:G.cabecera,
                   background:`linear-gradient(180deg, ${FICHA_COL.cabeceraDe}, ${FICHA_COL.cabeceraA})`,
                   display:'flex', alignItems:'center', padding:`0 ${G.padX}px`, gap:10}}>
        <FichaIcono nombre="seccion" size={26} color={FICHA_COL.activa} grosor={1.8}/>
        <span className="f-plex" style={{fontSize:20, fontWeight:600, letterSpacing:'-0.01em', color:FICHA_COL.texto, whiteSpace:'nowrap'}}>
          {seccion}
        </span>
        <span className="f-plex" style={{
          width:21, height:21, borderRadius:'50%', border:`1.6px solid ${FICHA_COL.activa}`,
          color:FICHA_COL.activa, fontSize:13, fontWeight:700, lineHeight:1,
          display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0,
        }}>i</span>
        <span style={{flex:1}}/>
        <FichaContador icono="impresora"   n={anot.impresora}/>
        <FichaContador icono="comentarios" n={anot.comentarios}/>
        <FichaContador icono="adjuntos"    n={anot.adjuntos}/>
        <FichaContador icono="galeria"     n={anot.galeria}/>
        <span style={{width:1, height:26, background:FICHA_COL.marcoBorde, margin:'0 6px'}}/>
        <FichaIcono nombre="cerrar" size={22} color="#9aa1ac" grosor={1.9}/>
      </div>

      {/* ── 2. Tira de modo: 2px bajo la cabecera. Azul = edición (la nuestra) ────────────────── */}
      <div style={{position:'absolute', left:0, top:G.cabecera, width:'100%', height:G.tira,
                   background: modoAlta ? FICHA_COL.tiraAlta : FICHA_COL.tiraEdicion}}/>

      {/* ── 3. Título del registro: un cuadro grande sin borde, con raya inferior ─────────────── */}
      <div style={{position:'absolute', left:0, top:G.cabecera + G.tira, width:'100%', height:G.titulo,
                   padding:`0 ${G.padX}px`, display:'flex', alignItems:'center', background:'transparent'}}>
        <span className="f-plex" style={{fontSize:32, fontWeight:600, letterSpacing:'-0.015em',
                                         color:FICHA_COL.texto, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>
          {titulo}
        </span>
      </div>
      <div style={{position:'absolute', left:G.padX, top:G.cabecera + G.tira + G.titulo - 2,
                   width:G.contW, height:2, background:FICHA_COL.marcoBorde}}/>

      {/* ── 4. Pestañas: General (activa) · Compra y venta · Inventario · Contabilidad ────────── */}
      <div style={{position:'absolute', left:0, top:G.cabecera + G.tira + G.titulo, width:'100%', height:G.pestanas,
                   display:'flex', alignItems:'stretch', padding:`0 ${G.padX}px`, gap:30}}>
        {['General','Compra y venta','Inventario','Contabilidad'].map((p, i)=>(
          <div key={p} style={{position:'relative', display:'flex', alignItems:'center'}}>
            <span className="f-plex" style={{fontSize:18, fontWeight:500, whiteSpace:'nowrap',
                                             color: i === 0 ? FICHA_COL.activa : FICHA_COL.etiqueta}}>{p}</span>
            {i === 0 ? <span style={{position:'absolute', left:0, right:0, bottom:0, height:3, borderRadius:2, background:FICHA_COL.activa}}/> : null}
          </div>
        ))}
      </div>

      {/* ── 5. Lienzo gris con las cards flotando ────────────────────────────────────────────── */}
      <div style={{position:'absolute', left:0, top:G.lienzoY, width:'100%', height:lienzoH, background:FICHA_COL.lienzo}}/>

      {/* ── CARD 1 · identificación ──────────────────────────────────────────────────────────── */}
      <FichaCard x={G.cardX} y={c1.y} w={G.cardW} h={c1.h}>
        <div style={filaCol(G.col1X - G.cardX, G.fila.referencia - c1.y, G.col1W)}>
          <CampoFicha etiqueta="Referencia" valor="CMP-050" geo={g} anchoControl={cW1} resaltado={res('referencia')}/>
        </div>
        <div style={filaCol(G.col2X - G.cardX, G.fila.codigoBarras - c1.y, G.col2W)}>
          <CampoFicha etiqueta="Código de barras" valor="8412345000507" geo={g} anchoEtiqueta={eW2} anchoControl={cW2} resaltado={res('codigoBarras')}/>
        </div>
        <div style={filaCol(G.col1X - G.cardX, G.fila.categoria - c1.y, G.col1W)}>
          <CampoFicha etiqueta="Categoría" valor="Aire comprimido" tipo="desplegable" geo={g} anchoControl={cW1} resaltado={res('categoria')}/>
        </div>
        <div style={filaCol(G.col2X - G.cardX, G.fila.marca - c1.y, G.col2W)}>
          <CampoFicha etiqueta="Marca" valor="Josval" tipo="relacion" mini="J" geo={g} anchoEtiqueta={eW2} anchoControl={cW2} resaltado={res('marca')}/>
        </div>
        <div style={filaCol(G.col1X - G.cardX, G.fila.descripcion - c1.y, G.contW)}>
          <CampoFicha etiqueta="Descripción" valor="Compresor de pistón, calderín 50 L, 2 CV, 8 bar" geo={g} anchoControl={cWF} resaltado={res('descripcion')}/>
        </div>
      </FichaCard>

      {/* ── CARD 2 · comercial — aquí vive el PROTAGONISTA, y a su lado un autocalculado ─────── */}
      <FichaCard x={G.cardX} y={c2.y} w={G.cardW} h={c2.h}>
        <div style={filaCol(G.col1X - G.cardX, G.fila.precio - c2.y, G.col1W)}>
          {CampoPrecio(cW1)}
        </div>
        <div style={filaCol(G.col2X - G.cardX, G.fila.precioMedioCompra - c2.y, G.col2W)}>
          <CampoFicha etiqueta="Precio medio compra" valor="612,40" estado="bloqueado" geo={g} anchoEtiqueta={eW2} anchoControl={cW2} resaltado={res('precioMedioCompra')}/>
        </div>
        <div style={filaCol(G.col1X - G.cardX, G.fila.categoriaVenta - c2.y, G.col1W)}>
          <CampoFicha etiqueta="Categoría de venta" valor="Ventas nacionales" tipo="desplegable" geo={g} anchoControl={cW1} resaltado={res('categoriaVenta')}/>
        </div>
        <div style={filaCol(G.col2X - G.cardX, G.fila.proveedor - c2.y, G.col2W)}>
          <CampoFicha etiqueta="Proveedor habitual" valor="Suministros Vega" tipo="relacion" mini="S" geo={g} anchoEtiqueta={eW2} anchoControl={cW2} resaltado={res('proveedor')}/>
        </div>
        <div style={filaCol(G.col1X - G.cardX, G.fila.unidadMedida - c2.y, G.col1W)}>
          <CampoFicha etiqueta="Unidad de medida" valor="Unidad" tipo="desplegable" geo={g} anchoControl={cW1} resaltado={res('unidadMedida')}/>
        </div>
        <div style={filaCol(G.col2X - G.cardX, G.fila.peso - c2.y, G.col2W)}>
          <CampoFicha etiqueta="Peso por unidad" valor="41,00" geo={g} anchoEtiqueta={eW2} anchoControl={cW2} resaltado={res('peso')}/>
        </div>
        <div style={{position:'absolute', left:G.col1X - G.cardX, top:G.fila.casillas - c2.y, width:G.contW,
                     height:g.casilla + 6, display:'flex', alignItems:'center', gap:g.casillasSep}}>
          <CasillaFicha etiqueta="Bloquear venta" marcada geo={g} resaltada={res('bloquearVenta')}/>
          <CasillaFicha etiqueta="Descontinuado"          geo={g} resaltada={res('descontinuado')}/>
          <CasillaFicha etiqueta="Es servicio"            geo={g} resaltada={res('esServicio')}/>
          <CasillaFicha etiqueta="Es pack"                geo={g} resaltada={res('esPack')}/>
        </div>
      </FichaCard>

      {/* ── CARD 3 · inventario — los dos stocks son autocalculados: rayado y candado ────────── */}
      <FichaCard x={G.cardX} y={c3.y} w={G.cardW} h={c3.h}>
        <div style={filaCol(G.col1X - G.cardX, G.fila.stockDisponible - c3.y, G.col1W)}>
          <CampoFicha etiqueta="Stock disponible" valor="12,00" estado="bloqueado" geo={g} anchoControl={cW1} resaltado={res('stockDisponible')}/>
        </div>
        <div style={filaCol(G.col2X - G.cardX, G.fila.stockInventario - c3.y, G.col2W)}>
          <CampoFicha etiqueta="Stock en inventario" valor="14,00" estado="bloqueado" geo={g} anchoEtiqueta={eW2} anchoControl={cW2} resaltado={res('stockInventario')}/>
        </div>
        <div style={filaCol(G.col1X - G.cardX, G.fila.modoGestion - c3.y, G.col1W)}>
          <CampoFicha etiqueta="Modo de gestión" valor="Por lotes" tipo="desplegable" geo={g} anchoControl={cW1} resaltado={res('modoGestion')}/>
        </div>
        {/* Una casilla NO lleva etiqueta izquierda: caja + texto a su derecha, sin mayúsculas */}
        <div style={{position:'absolute', left:G.col2X - G.cardX, top:G.fila.evaluarStock - c3.y,
                     width:G.col2W, height:g.filaH, display:'flex', alignItems:'center'}}>
          <CasillaFicha etiqueta="Evaluar stock" marcada geo={g} resaltada={res('evaluarStock')}/>
        </div>
      </FichaCard>

      {/* ── 7. Pie: extensiones a la izquierda · Guardar verde con check · Cerrar en rojo ─────── */}
      <div style={{position:'absolute', left:0, bottom:0, width:'100%', height:G.pie,
                   background:'#fff', borderTop:`1px solid ${FICHA_COL.marcoBorde}`,
                   display:'flex', alignItems:'center', padding:`0 ${G.padX}px`, gap:10}}>
        {['ajustes','rayo'].map((n)=>(
          <span key={n} style={{width:38, height:32, borderRadius:7, background:'#f8fafc',
                                border:`1px solid ${FICHA_COL.marcoBorde}`,
                                display:'flex', alignItems:'center', justifyContent:'center'}}>
            <FichaIcono nombre={n} size={19} color="#9aa1ac" grosor={1.8}/>
          </span>
        ))}
        <span style={{flex:1}}/>
        <span style={{display:'inline-flex', alignItems:'center', gap:8, height:34, padding:'0 16px',
                      borderRadius:8, background:FICHA_COL.guardar, boxShadow:'0 1px 3px rgba(22,163,74,0.35)'}}>
          <FichaIcono nombre="check" size={17} color="#fff" grosor={2.6}/>
          <span className="f-plex" style={{fontSize:17, fontWeight:600, color:'#fff', lineHeight:1}}>Guardar</span>
        </span>
        <span className="f-plex" style={{fontSize:17, fontWeight:500, color:FICHA_COL.cerrar, padding:'0 10px', lineHeight:1}}>
          Cerrar
        </span>
      </div>

    </div>
  );
}

Object.assign(window, { FichaProducto, FICHA_ANCLAS, fichaAncla, FICHA_BASE, FICHA_BASE_ZOOM, FICHA_GEO, FICHA_GEO_ZOOM, FICHA_CAMPO, FICHA_COL });
