<?xml version="1.0" encoding="UTF-8"?>
<!--
  XSD Histrix ERP — versión 1.0

  Cobertura: los atributos y elementos que el motor interpreta de verdad. Si algo no está declarado
  acá, el motor no lo lee: no es una omisión, es la respuesta.

  Grafías: el motor compara los nombres de atributo de forma **exacta y sensible a mayúsculas**, sin
  normalizar. `PDFancho` y `pdfancho` son atributos distintos, y cuál funciona depende del atributo
  y del tag — la descripción de cada uno lo aclara. No asumir que existe la variante camelCase o
  lowercase de algo: si no está declarada, no se lee.

  Aliases: cuando existen las dos formas (español/inglés, camelCase/lowercase) se declaran las dos y
  la descripción dice cuál es la que conviene usar. Ojo que "alias" no siempre significa
  "equivalente": varios pares escriben propiedades distintas o se evalúan con criterios distintos, y
  eso está anotado caso por caso.

  Booleanos: el motor no tiene un único criterio. Según el atributo puede exigir el literal "true",
  tratar la ausencia como activo, o mirar sólo si el atributo **está presente** — en ese último caso
  `attr="false"` también lo activa y la única forma de desactivarlo es omitirlo. Cada atributo con
  esa trampa lo dice en su descripción; conviene leerla antes de escribir el valor por defecto.

  Raíces: el motor no chequea el nombre del elemento raíz, así que la lista es una convención de
  este schema, no una restricción del runtime.
    - `<histrix>`  — documento principal (cualquier tipo: consulta, ficha, fichaing, ing, nosql, etc.).
    - `<abm>`      — forma legacy de `<histrix>` (mismo contenido).
    - `<include>`  — fragmento incluido por otro XML via `<fieldGroup xml="..." dir="...">`.
                     El nombre de la raíz de un fragmento es indistinto para el motor.
-->
<xs:schema
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  elementFormDefault="unqualified"
  attributeFormDefault="unqualified"
  version="1.0">

  <!-- =======================
       Tipos básicos reutilizables
       ======================= -->
  <!--
    Los cuatro tipos booleanos. Histrix no tiene un único criterio para evaluar un flag, así que el
    NOMBRE DEL TIPO dice cuál se aplica y qué hay que escribir. Vale la pena mirarlo antes de poner
    un valor, sobre todo antes de escribir "false":

      BoolEstricto      → sólo "true" activa. "false" u omitirlo lo desactivan. El caso normal.
      BoolDefaultActivo → viene ACTIVO. Sólo "false" lo apaga; omitirlo lo deja prendido.
      BoolPresencia     → sólo acepta "true". Cualquier valor activa, así que escribir "false"
                          ACTIVA la función: para desactivarla hay que OMITIR el atributo.
      BoolTrueFalse     → criterio mixto o no verificado: leer la descripción del atributo.
  -->
  <xs:simpleType name="BoolEstricto">
    <xs:annotation>
      <xs:documentation>
        Booleano textual: sólo el literal `true` en minúscula activa la función. Cualquier otro
        valor, y también omitir el atributo, la deja desactivada — así que `TRUE` en mayúsculas
        **no** la activa, y `false` es equivalente a no escribir nada.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
      <xs:enumeration value="false" />
      <xs:enumeration value="no">
        <xs:annotation>
          <xs:documentation>Legacy: cuenta como "false" por no ser `true`, no por diseño. No hay un `si` complementario. En código nuevo escribir "false".</xs:documentation>
        </xs:annotation>
      </xs:enumeration>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="BoolDefaultActivo">
    <xs:annotation>
      <xs:documentation>
        Booleano **activo por defecto**: si el atributo no está, la función corre igual. El único
        valor con efecto es `false`, que la apaga. Escribir `true` es redundante.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
      <xs:enumeration value="false" />
      <xs:enumeration value="no">
        <xs:annotation>
          <xs:documentation>Forma legacy equivalente a "false" en los atributos que la contemplan (`borra`, `modifica`, `inserta`). En código nuevo escribir "false".</xs:documentation>
        </xs:annotation>
      </xs:enumeration>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="BoolPresencia">
    <xs:annotation>
      <xs:documentation>
        **Sólo acepta `true`, y a propósito.** El motor no mira el valor de estos atributos sino si
        están presentes, así que `attr="false"` **activa** la función igual que `attr="true"` — es el
        error más frecuente con estos flags. Para desactivarla hay que **omitir el atributo**.

        El schema restringe el valor a `true` justamente para que un `="false"` se marque como error
        en vez de fallar en silencio en runtime.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="ModoAutocompletado">
    <xs:annotation>
      <xs:documentation>
        Modo del helper de ayuda de un campo. **No es un booleano ni el atributo HTML
        `autocomplete`**: los únicos valores con efecto son `true` (autocompletado sobre el input) y
        `full` (autocompletado + popup de la ayuda). Cualquier otro valor —incluido `on` u `off`—
        cae en el comportamiento por defecto, que es sólo el popup de búsqueda: o sea que `off` no
        apaga nada.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
      <xs:enumeration value="full" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="ActXmlValor">
    <xs:annotation>
      <xs:documentation>
        Qué hace el `&lt;jseval&gt;` después de calcular: `true` (por defecto) manda el valor al
        servidor con un request por cada cambio; `false` lo resuelve entero en el browser — es la
        forma recomendada para aritmética pura, porque en una grilla con cálculos en cascada el
        default genera cientos de requests. También acepta el **nombre de un XML** (`algo.xml`), y en
        ese caso el request va contra ese XML.

        El patrón sólo admite esas tres formas justamente para que un `fasle` se marque como error.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="true|false|[A-Za-z0-9_./-]+\.xml" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="BoolTrueFalse">
    <xs:annotation>
      <xs:documentation>
        Booleano textual `"true"` | `"false"` cuyo **criterio de evaluación no es uniforme**: el
        motor lo lee de más de una manera según el camino, o no está verificado cuál usa. En estos
        casos omitir el atributo no equivale a escribir ninguno de los dos valores, así que conviene
        ponerlo explícito y leer la descripción del atributo antes.

        Si un atributo tiene un criterio único y claro, está tipado con uno de los otros tres:
        `BoolEstricto`, `BoolDefaultActivo` o `BoolPresencia`.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
      <xs:enumeration value="false" />
      <xs:enumeration value="no">
        <xs:annotation>
          <xs:documentation>Legacy: cuenta como "false" por no ser `true`, no por diseño. No hay un `si` complementario. En código nuevo escribir "false".</xs:documentation>
        </xs:annotation>
      </xs:enumeration>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="BoolJsLiteral">
    <xs:annotation>
      <xs:documentation>
        Booleano que **se copia tal cual a la configuración de la grilla del navegador**: por eso no
        acepta ni `no` ni `1`/`0` — cualquier otro texto no es un booleano válido del otro lado y
        rompe el armado de la grilla en lugar de simplemente no aplicarse.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
      <xs:enumeration value="false" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="GanttEscala">
    <xs:annotation>
      <xs:documentation>Unidad de la escala de tiempo de un diagrama de Gantt.</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="hours" />
      <xs:enumeration value="days" />
      <xs:enumeration value="weeks" />
      <xs:enumeration value="months" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="OnClearFormValor">
    <xs:annotation>
      <xs:documentation>
        Qué hacer con el campo al limpiar el formulario. `uniqid` es el único valor que el motor
        reconoce: cualquier otro texto no hace nada.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="uniqid" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="Bool01">
    <xs:annotation>
      <xs:documentation>Booleano entero: "0" | "1".</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="0" />
      <xs:enumeration value="1" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="KanbanRol">
    <xs:annotation>
      <xs:documentation>
        Papel que cumple el campo dentro de la tarjeta de un tablero `tipo="kanban"`. La lista es
        cerrada: `KanbanData::ROLES` la valida y un rol mal escrito se reporta como error de
        configuración en pantalla, no se ignora.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="title">
        <xs:annotation><xs:documentation>Encabezado de la tarjeta. Uno por tablero; sin él el tablero no se dibuja. Si el campo lleva un `&lt;helper&gt;`, la tarjeta muestra el valor crudo (el texto del control es vacío).</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="body">
        <xs:annotation><xs:documentation>Texto descriptivo, recortado a tres líneas.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="tag">
        <xs:annotation><xs:documentation>Etiqueta corta al pie. **Admite varios campos**: cada uno se dibuja como una etiqueta aparte.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="person">
        <xs:annotation><xs:documentation>Responsable. Si el campo es un combo, se muestra el texto resuelto, no el id.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="age">
        <xs:annotation><xs:documentation>Antigüedad. El campo es una fecha y la tarjeta muestra el tiempo transcurrido ("12h", "3d"). Acepta ISO y `dd/mm/yyyy`.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="color">
        <xs:annotation><xs:documentation>Color del borde de la tarjeta, tomado del **valor** del campo (un color CSS). Pisa el color de la columna: sirve para pintar por regla de negocio (vencido, urgente) en lugar de por estado.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="action">
        <xs:annotation><xs:documentation>Botón al pie de la tarjeta, normalmente un `&lt;helper type="link"&gt;`. **El campo no debe ocultarse**: con `noshow="true"` el helper viaja como metadata y la tarjeta muestra el valor crudo, y con `colstyle="display:none;"` el botón hereda ese `display:none`. Dejarlo visible no molesta, porque la tabla que alimenta al tablero ya está oculta. Admite varios campos.</xs:documentation></xs:annotation>
      </xs:enumeration>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="OrdenTipo">
    <xs:annotation>
          <xs:documentation>
            Dirección de ordenamiento. En el `ORDER BY` del SQL da igual la caja, pero **el orden en
            memoria de la tabla temporal sólo reconoce `DESC` en mayúsculas**: con `orderType="desc"` la
            consulta sale ordenada y la grilla temporal no. Para que las dos coincidan, escribir
            `ASC`/`DESC` en mayúsculas.
          </xs:documentation>
        </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="ASC" />
      <xs:enumeration value="DESC" />
      <xs:enumeration value="asc" />
      <xs:enumeration value="desc" />
      <xs:enumeration value="Asc" />
      <xs:enumeration value="Desc" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="JoinTipo">
    <xs:annotation>
      <xs:documentation>Tipo de JOIN.</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="left" />
      <xs:enumeration value="right" />
      <xs:enumeration value="inner" />
      <xs:enumeration value="full" />
      <xs:enumeration value="LEFT" />
      <xs:enumeration value="RIGHT" />
      <xs:enumeration value="INNER" />
      <xs:enumeration value="FULL" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="PdfFontStyle">
    <xs:annotation>
          <xs:documentation>
            Aplicación de un estilo de fuente en el PDF. **No es un booleano**: el valor dice *a qué
            parte de la celda* se aplica el estilo. El motor pinta la celda en dos pasadas —primero el
            valor, después la etiqueta— y compara el atributo contra la pasada en curso: "true" aplica
            sólo al valor, "label" sólo a la etiqueta y "both" a las dos. "false" no coincide con
            ninguna pasada, o sea que no aplica el estilo (es la forma de anularlo).
            **El estilo se consulta sólo al imprimir fichas y etiquetas**: en las columnas de una grilla
            no se mira, así que ahí ninguno de los cuatro valores cambia nada.
          </xs:documentation>
        </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="true" />
      <xs:enumeration value="false" />
      <xs:enumeration value="label" />
      <xs:enumeration value="both" />
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="HelperTipo">
    <xs:annotation>
          <xs:documentation>
            Tipo de helper (UI/ayudas). Un valor que no esté en esta lista **no construye ningún helper
            y se ignora en silencio** — el campo queda sin ayuda y no hay error en ningún log.

            Pero el valor **se guarda igual** en el campo antes de que el motor decida qué helper armar,
            y otras partes lo comparan para saber si el campo tiene combo, ayuda o contenedor embebido.
            Por eso un `type` mal escrito no sólo pierde la ayuda: además puede **desactivar la
            grabación inline** de ese campo o el modo `tags`.
          </xs:documentation>
        </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="combo" />
      <xs:enumeration value="comboex" />
      <xs:enumeration value="link" />
      <xs:enumeration value="external" />
      <xs:enumeration value="parameter" />
      <xs:enumeration value="object" />
      <xs:enumeration value="inline" />
      <xs:enumeration value="media" />
      <xs:enumeration value="tags" />
      <xs:enumeration value="import">
        <xs:annotation><xs:documentation>Importa datos de otro XML al formulario. Se procesa por un camino aparte del resto de los tipos de helper, equivalente al tag `&lt;importar&gt;`: acepta los mismos `&lt;field&gt;`/`&lt;parameter&gt;` y los atributos del botón.</xs:documentation></xs:annotation>
      </xs:enumeration>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="CampoTipo">
    <xs:annotation>
      <xs:documentation>
        Tipo del campo: decide el render, el formato y la validación. El valor se resuelve a una clase
        de tipo del motor y, **si no existe una clase para ese valor, el campo cae en silencio en el
        campo genérico**: pierde el comportamiento del tipo y también la inferencia por la metadata de
        la columna, sin ningún error. Por eso el enum es cerrado: un valor no listado casi siempre es
        un typo que ya está fallando en producción.

        Para resolver la clase el motor normaliza el valor (el guión bajo equivale a un espacio y el
        capital inicial es indiferente), pero **guarda además el texto crudo del tag** y lo compara
        de forma case-sensitive en más de una docena de lugares: armado del SQL, formato en el PDF,
        mapas. O sea que **las variantes de grafía no son equivalentes** aunque carguen la misma
        clase — resuelven el tipo y después no pasan ninguna de esas comparaciones. Escribir el valor
        exactamente como figura en la lista, con una excepción documentada más abajo: `geoPoint` va en
        camelCase.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <!-- Fechas y horas -->
      <xs:enumeration value="date" />
      <xs:enumeration value="datetime" />
      <xs:enumeration value="time" />
      <xs:enumeration value="hora" />
      <xs:enumeration value="month" />
      <xs:enumeration value="timestamp" />
      <!-- Texto -->
      <xs:enumeration value="text" />
      <xs:enumeration value="varchar" />
      <xs:enumeration value="char" />
      <xs:enumeration value="character" />
      <xs:enumeration value="longtext" />
      <xs:enumeration value="mediumtext" />
      <xs:enumeration value="simpletext">
        <xs:annotation>
          <xs:documentation>
            **No equivale a omitir `&lt;tipo&gt;`.** No hay clase para este valor, así que el campo
            cae en el genérico; y como el tag trae un tipo no vacío, el motor ya no consulta el tipo
            real de la columna: el campo queda **sin tipo**, ni siquiera `varchar`. Para un campo de
            texto plano conviene no escribir el tag.
          </xs:documentation>
        </xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="html" />
      <xs:enumeration value="editor" />
      <xs:enumeration value="simpleditor">
        <xs:annotation><xs:documentation>Idéntico a `editor`: no cambia ningún comportamiento (la clase CSS que lo distingue la pone el propio editor).</xs:documentation></xs:annotation>
      </xs:enumeration>
      <!-- Numéricos -->
      <xs:enumeration value="integer" />
      <xs:enumeration value="int" />
      <xs:enumeration value="bigint" />
      <xs:enumeration value="mediumint" />
      <xs:enumeration value="smallint" />
      <xs:enumeration value="tinyint" />
      <xs:enumeration value="numeric" />
      <xs:enumeration value="decimal" />
      <xs:enumeration value="hfloat" />
      <xs:enumeration value="custom_numeric" />
      <xs:enumeration value="enclosed_integer" />
      <xs:enumeration value="enclosed_numeric" />
      <xs:enumeration value="meter" />
      <!-- Controles -->
      <xs:enumeration value="check" />
      <xs:enumeration value="toggle" />
      <xs:enumeration value="flipswitch" />
      <xs:enumeration value="radio" />
      <xs:enumeration value="select" />
      <xs:enumeration value="button" />
      <xs:enumeration value="color" />
      <xs:enumeration value="tags" />
      <!-- Archivos y binarios -->
      <xs:enumeration value="file" />
      <xs:enumeration value="files" />
      <xs:enumeration value="dir" />
      <xs:enumeration value="aws" />
      <xs:enumeration value="base64pdf" />
      <!-- Identificadores y formatos validados -->
      <xs:enumeration value="cuit" />
      <xs:enumeration value="email" />
      <xs:enumeration value="url" />
      <xs:enumeration value="tel" />
      <xs:enumeration value="isbn" />
      <xs:enumeration value="password" />
      <!-- Geo y gráficos -->
      <xs:enumeration value="map">
        <xs:annotation><xs:documentation>**No-op**: como tipo de campo no agrega nada. El mapa lo dibuja la vista `&lt;histrix tipo="map"&gt;` a partir de un campo `geoPoint`.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="geopoint">
        <xs:annotation><xs:documentation>Punto geográfico. **Si el campo alimenta una vista `tipo="map"` hay que escribirlo `geoPoint`, en camelCase**: el mapa compara el texto del tag y con `geopoint` no encuentra el punto, así que no dibuja nada.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="geopoly">
        <xs:annotation><xs:documentation>Renderiza igual que un `geopoint`: hereda el input del punto y el control de polígono nunca se alcanza. Sirve para documentar la intención, no cambia el comportamiento.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="grafico" />
      <xs:enumeration value="qr" />
      <!-- Otros -->
      <xs:enumeration value="rrule" />
      <xs:enumeration value="cron" />
      <!-- Alias que el motor reescribe internamente al tipo canónico -->
      <xs:enumeration value="string" />   <!-- -> varchar -->
      <xs:enumeration value="float" />    <!-- -> hfloat  -->
      <!-- Variantes que la normalización de la factory vuelve equivalentes.
           No usar en código nuevo. -->
      <xs:enumeration value="Date">
        <xs:annotation><xs:documentation>**No equivale a `date`**: la clase resuelve igual, pero las comparaciones del motor son contra el texto crudo del tag y ninguna matchea `Date`, así que el campo pierde el tratamiento de fecha. Usar `date`.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="Integer">
        <xs:annotation><xs:documentation>**No equivale a `integer`**: la clase resuelve, pero el texto crudo `Integer` no pasa ninguna de las comparaciones de tipo del motor. Usar `integer`.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="custom numeric">
        <xs:annotation><xs:documentation>**No equivale a `custom_numeric`**: la clase resuelve, pero el formato numérico del PDF compara contra `custom_numeric` y con el espacio se pierde. Usar `custom_numeric`.</xs:documentation></xs:annotation>
      </xs:enumeration>
      <xs:enumeration value="geoPoint">
        <xs:annotation>
          <xs:documentation>
            **Es la grafía obligatoria para los mapas, no una variante a evitar.** La vista
            `tipo="map"` busca el tipo escrito exactamente así, en camelCase, para saber qué campo
            trae el punto; con `geopoint` en minúsculas no lo encuentra y el mapa sale vacío.
            En un campo que no alimenta un mapa las dos grafías rinden igual.
          </xs:documentation>
        </xs:annotation>
      </xs:enumeration>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="OperadorCondicion">
    <xs:annotation>
      <xs:documentation>Operadores SQL para condiciones. Enum cerrado: arreglar el XML si trae un valor no listado.</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:enumeration value="=" />
      <xs:enumeration value="!=" />
      <xs:enumeration value="&lt;" />
      <xs:enumeration value="&gt;" />
      <xs:enumeration value="&lt;=" />
      <xs:enumeration value="&gt;=" />
      <xs:enumeration value="&lt;&gt;" />
      <xs:enumeration value="IN" />
      <xs:enumeration value="in" />
      <xs:enumeration value="LIKE" />
      <xs:enumeration value="like" />
      <xs:enumeration value="NOT LIKE" />
      <xs:enumeration value="not like" />
      <xs:enumeration value="is null" />
      <xs:enumeration value="is not null" />
      <xs:enumeration value="IS NULL" />
      <xs:enumeration value="IS NOT NULL" />
      <xs:enumeration value="IS" />
      <xs:enumeration value="is" />
      <xs:enumeration value="IS NOT" />
      <xs:enumeration value="is not" />
      <xs:enumeration value="NOT" />
      <xs:enumeration value="not" />
      <xs:enumeration value="NOT IN" />
      <xs:enumeration value="not in" />
      <xs:enumeration value="BETWEEN" />
      <xs:enumeration value="between" />
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="OperadorElementType">
    <xs:annotation>
          <xs:documentation>
            Elemento `&lt;operador&gt;` (o alias `&lt;operator&gt;`) dentro de `&lt;field&gt;`.
            Contenido = operador SQL (`OperadorCondicion`); atributos opcionales `modificador` y `oplogico` para refinar el comportamiento del filtro.
          </xs:documentation>
        </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="OperadorCondicion">
        <xs:attribute name="modificador" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Modificador del operador: transforma el valor antes de comparar (ej. `UPPER`, `LOWER`, `DATE`).</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="oplogico" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Operador lógico para concatenar con el siguiente filtro: "and" (default) | "or".</xs:documentation></xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <!--=====================================
    Grupos de atributos PDFS
    =====================================-->

  <xs:attributeGroup name="pdfGrup">
    <xs:annotation>
      <xs:documentation>
        Atributos para renderizado PDF. Aplican a `&lt;field&gt;`, `&lt;label&gt;` y `&lt;fieldGroup&gt;`
        (los de nivel documento están en el `&lt;histrix&gt;`). **No afectan la exportación a
        XLS/CSV**: la exportación no lee ningún atributo `PDF*`.

        **La grafía es exacta y case-sensitive: NO hay normalización de mayúsculas.** El motor busca
        el atributo tal como está escrito, y el fallback a minúsculas que existía está desactivado.
        Por eso `PDFxxx` y `pdfxxx` son atributos distintos, y cuál funciona depende del atributo:
        `PDFcolWidth`, `PDFwidth`, `PDFnolabel` y `PDFsize` van en camelCase; `pdfancho`, `pdffill` y
        `pdflineas` van en minúscula. Los únicos renombres que hace el motor son tres casos puntuales
        que valen **sólo** dentro de `&lt;label&gt;` (ver `PDFancho`) y el par `pdffill`/`PDFfill`.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="PDFcolWidth" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Ancho de **columna** de la tabla PDF. Número SIN unidad (ej. "20"): agregar "px" rompe el
          layout. Fijarlo además desactiva el reajuste automático de esa columna, así que no se
          ensancha para acomodar el contenido — el texto se parte en varias líneas.
          **No es alias de `PDFwidth` ni de `pdfancho`**: son tres anchos distintos (ver `PDFwidth`).
          La grafía es exacta: `PDFcolwidth` con `w` minúscula no la lee nadie.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFwidth" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Ancho de la grilla embebida de un campo, y en la vista card el ancho de la tarjeta.
          **No es alias de `PDFcolWidth` ni de `pdfancho`**: son tres anchos distintos —
          `PDFcolWidth` es el ancho de columna de la tabla PDF y `pdfancho` el de la celda del valor
          en ficha. La grafía es exacta: `pdfwidth` en minúscula no la lee nadie.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pdflineas" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          Recuadro de las celdas de una grilla **embebida** en un campo (`helper type="object"`).
          Viene activo: sólo el literal "false" lo apaga. Tiene efecto únicamente en campos con
          contenedor interno; en un campo común no hace nada.
          La grafía es exacta y en minúscula: `PDFlineas` no la lee nadie. Para el recuadro de la
          grilla principal el atributo es `PDFlines`, y va en el `&lt;histrix&gt;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFsize" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Tamaño de fuente en puntos de esta celda. Es **la** grafía de nivel campo; el motor acepta
          además `PDFfontsize` (con `s` minúscula) como segunda forma.
          El global del documento es `PDFfontSize` en el `&lt;histrix&gt;` (otro atributo).
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFfontsize" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          Segunda grafía aceptada para el tamaño de fuente del campo, **con `s` minúscula**: el motor
          toma `PDFsize` y, si no está, ésta. Preferir `PDFsize`. Ojo con la trampa: `PDFfontSize`
          (con `S` mayúscula) **no** se lee a nivel campo — ése es el atributo global y va en el
          `&lt;histrix&gt;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFalign" type="xs:string" default="L" use="optional">
      <xs:annotation>
        <xs:documentation>
          Alineación de la celda: `L` (izquierda), `R` (derecha), `C` (centro), `J` (justificada).
          **El default declarado acá no es el real**: con el atributo ausente la alineación la decide
          el tipo del campo — los numéricos salen a la derecha, y `check`, `hora` y `file` al centro.
          Sólo escribiendo el atributo se fuerza otra.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFbold" type="PdfFontStyle" use="optional">
      <xs:annotation>
        <xs:documentation>
          Negrita. No es un booleano: selecciona a qué parte de la celda aplica (ver `PdfFontStyle`).
          **Sólo se consulta al imprimir fichas y etiquetas**: en las columnas de una grilla no se
          mira, así que ahí no tiene efecto.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFitalic" type="PdfFontStyle" use="optional">
      <xs:annotation>
        <xs:documentation>
          Itálica. Mismo mecanismo que `PDFbold`: no es un booleano, elige a qué parte de la celda
          aplica (ver `PdfFontStyle`). **Sólo se consulta al imprimir fichas y etiquetas**; en las
          columnas de una grilla no tiene efecto.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFbackgroundColor" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Color de fondo de la celda. **Hex de 6 dígitos y sin `#`** (`RRGGBB`): si el valor no mide
          exactamente 6 caracteres se resuelve como blanco y sin ningún aviso, así que `#RRGGBB`
          (7 caracteres) no pinta nada. Los nombres de color tampoco andan: "orange" mide justo 6,
          no se descarta y se interpreta como hex basura.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFborder" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Borde de la celda. **`PDFborder="true"` no dibuja ningún borde**: el valor viaja crudo al
          generador de PDF, que entiende `1` (recuadro completo) o las letras **en mayúscula** `L`,
          `T`, `R`, `B` — izquierda, arriba, derecha, abajo — y sus combinaciones (`LR`). Cualquier
          otro valor deja la celda sin borde.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFbox" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Envuelve el contenido en un box. Útil para destacar zonas.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFnoborder" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Suprime el borde de la celda de la **etiqueta** al imprimir ficha o etiqueta, y de paso
          apaga su relleno. **No anula el borde del valor**: la celda del valor sigue tomando su
          borde de `PDFborder`.
          Ojo: el motor sólo mira si el atributo está presente, no su valor, así que
          `PDFnoborder="false"` también suprime el borde. Para no suprimirlo hay que **omitir el
          atributo**. La grafía es exacta: `pdfnoborder` en minúscula no la lee nadie.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFnolabel" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Omite el label del campo en el PDF — útil en etiquetas térmicas y planillas compactas.
          **La grafía es exacta**: `pdfnolabel` en minúscula no la lee nadie (hay 20 usos escritos
          así en el repo, todos inertes). Y el motor lo consulta con criterios distintos en distintos
          puntos de la impresión, así que `PDFnolabel="false"` no es un negador confiable: para que
          el label salga, omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pdffill" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          Nivel de gris del fondo de la celda, 0 (negro) a 255 (blanco). **Es la única grafía que
          fija el color** — toda en minúscula. Los valores en uso son 195 y 192.
          **No es un booleano**: el número va crudo al generador de PDF, así que un texto como "true"
          no rellena nada y aborta la impresión con un error de tipo.
          En un `&lt;label&gt;` se puede escribir igual: es la misma propiedad.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFfill" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No fija ningún color.** Su único efecto es la *presencia*: declararlo con cualquier valor
          **suprime el gris claro por defecto** (240,240,240) y la celda queda con el color que
          hubiera quedado seteado antes. Para elegir el color hay que usar `pdffill` en minúscula,
          que es la grafía que llega al relleno.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pdftitulo" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Fila de títulos de una grilla **embebida** en un campo. Viene activa: sólo `pdftitulo="false"` la saca. Como `pdflineas`, sólo tiene efecto en campos con contenedor interno.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFcellHeight" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Alto mínimo de fila en número de líneas. Aplicarlo en una columna uniformiza el alto de
          toda la fila (alto = mayor de las columnas × 1,3 × tamaño de fuente). **Se ignora en los
          campos `tipo="check"`, `tipo="grafico"` y `tipo="file"`**, que calculan su alto por su
          cuenta.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFcolor" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Color del texto de la celda. Mismo formato que `PDFbackgroundColor`: **hex de 6 dígitos y
          sin `#`**. Con `#` son 7 caracteres, el motor lo resuelve como blanco y el texto queda
          invisible; y un nombre de 6 letras como "orange" se lee como hex basura. En los dos casos,
          sin aviso.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFlineHeight" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Alto de celda **absoluto en mm**, no un multiplicador: pisa el alto calculado. Un `1.2`
          escrito pensando que era un factor deja la celda de 1,2 mm y el texto encimado. El
          multiplicador de interlineado es otro atributo, `PDFheigthRel` (sic, con el typo), que
          este schema todavía no declara.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFheader" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Indica que el campo/label es parte del header del PDF (se repite en cada página).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFfooter" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Indica que el campo es parte del footer del PDF.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFaddPage" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Si "true", fuerza salto de página antes de renderizar este elemento.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFrestorePosition" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Restaura la posición X/Y del cursor antes de renderizar (para componer cajas superpuestas).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFrotate" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Rota el texto N grados.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFscaleX" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Escala horizontal del texto (en %).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFtopMargin" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es un margen en mm**: suma N **líneas** al alto de la fila, y sólo en campos que
          embeben otro contenedor (una grilla o una ficha hija). En un campo común no hace nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFpageBreak" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Controla el salto de página automático. "false" lo desactiva.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="posx" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Posición X de la celda, en mm desde el borde izquierdo de la página. Para desplazar en
          relativo respecto de donde iría la celda está `offsetX`, que se suma siempre.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="posy" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Posición Y de la celda en mm, con **tres formas mágicas** además de la coordenada absoluta:
          `posy="-1"` la ubica debajo de todo lo ya impreso; `posy="0"` o el atributo ausente la deja
          en la misma línea que el campo anterior; `posy="+N"` (con el `+`) la ubica N mm debajo de
          lo ya impreso. Para desplazar en relativo también está `offsetY`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="posX" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias camelCase de `posx`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="posY" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias camelCase de `posy`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="offsetY" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Desplazamiento vertical **en mm** que se suma siempre a la posición de la celda.
          **No es un booleano**: `offsetY="true"` aborta la impresión con un error de tipo.
          Y su valor por defecto no es 0 sino el offset superior de página del documento (3 u 8 mm
          según el caso), así que `offsetY="0"` no equivale a omitirlo: omitido, la celda ya arranca
          corrida por ese offset.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="offsetX" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Desplazamiento horizontal **en mm** que se suma siempre a la posición de la celda.
          **No es un booleano**: `offsetX="true"` aborta la impresión con un error de tipo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>


  <!--==================================
    Grupo de atributos identificador
    ==================================-->
  <xs:attributeGroup name="idGrup">
    <xs:annotation>
      <xs:documentation>Atributos de identificación de campos/elementos.</xs:documentation>
    </xs:annotation>
    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Identificador del campo. Coincide con el nombre de columna en la tabla SQL (salvo `local="true"` o `&lt;expression&gt;`).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="id_cab" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Referencia al `id` del campo cabecera del que depende este detalle. Usado en INSERT/UPDATE de grids.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="id_padre" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>En INSERT de grid embebido: toma el valor del campo del formulario padre. Útil para propagar la PK del padre al detalle.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="id_temp" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>En INSERT: toma el valor del campo temporal (de la tabla base del grid) en lugar del campo de la fila actual.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="alias" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias SQL de la columna. **No es sólo un nombre alternativo: reemplaza el prefijo de tabla con el que se califica la columna**, pisando la tabla base del `&lt;histrix&gt;` y lo que diga `tabla`. Si el valor no es un alias de tabla realmente presente en el `FROM`, la consulta sale con SQL inválido y sin ningún aviso. Sirve para resolver colisiones de nombres en JOINs, teniendo presente que lo que se escribe es el prefijo, no el `AS`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="lastId" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Trae a este campo el id autogenerado por otro movimiento. **El valor no es un booleano ni "el INSERT anterior": es el nombre del XML de movimiento** del que se toma el id, así que hay que nombrarlo exactamente. Sólo aplica a campos de XMLs de `&lt;movimientos&gt;`, y además de llenar el campo publica el placeholder `[__nombre__]` (el mismo nombre que se escribió) para reutilizar ese id en el resto del movimiento.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>

  <!--==================================
    Grupo de atributos Imagen
    ==================================-->
  <xs:attributeGroup name="ImagenGrup">
    <xs:annotation>
      <xs:documentation>Atributos para campos de tipo imagen.</xs:documentation>
    </xs:annotation>
    <xs:attribute name="imageWidth" type="xs:integer" default="100" use="optional">
      <xs:annotation><xs:documentation>Ancho de la imagen del campo, en píxeles. **El `default="100"` vale sólo donde el atributo se respeta**: campos `&lt;tipo&gt;grafico&lt;/tipo&gt;` y dos ramas de la impresión. En campos `file` el ancho por defecto es el que calcula el motor, y una de las ramas del PDF arranca en 50. Fuera de esos casos escribirlo no cambia nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="imageHeight" type="xs:integer" use="optional" />
    <xs:attribute name="grafico" type="xs:string" use="optional" />
    <xs:attribute name="imageScale" type="xs:decimal" use="optional" />
  </xs:attributeGroup>

  <!--==================================
    Grupo de atributos Calendario
    ==================================-->
  <xs:attributeGroup name="KanbanGrup">
    <xs:annotation>
      <xs:documentation>
        Atributos para campos de un tablero `tipo="kanban"`.

        A diferencia del grupo `calendar*`, que marca el campo por **presencia** del atributo, acá el
        papel se declara con el **valor** (`kanban="title"`): un único atributo por campo, con la
        lista de roles cerrada.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="kanban" type="KanbanRol" use="optional">
      <xs:annotation>
        <xs:documentation>
          Papel del campo dentro de la tarjeta: `title`, `body`, `tag`, `person`, `age`, `color` o
          `action`. Un campo sin este atributo no se dibuja en la tarjeta, aunque siga estando en la
          consulta —así se declaran el campo de estado (`statusField`) y la clave, normalmente con
          `noshow="true"`.

          `tag` y `action` admiten varios campos; el resto, uno. El tablero necesita al menos un
          campo con `kanban="title"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>

  <xs:attributeGroup name="CalendarGrup">
    <xs:annotation>
      <xs:documentation>Atributos para campos de tipo calendario.</xs:documentation>
    </xs:annotation>
    <xs:attribute name="calendarSubject" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo cuyo valor es el título del evento en el calendario. **Se evalúa por presencia**: cualquier valor lo marca, incluido `"false"`, y el `default="false"` declarado no equivale a omitirlo. Para que el campo no sea el título hay que no escribir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarId" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que aporta el identificador del evento. **Se evalúa por presencia**, como todo el grupo `calendar*`: `calendarId="false"` lo marca igual; para que no participe hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarColor" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo cuyo valor pinta el fondo del evento. **Se evalúa por presencia**: cualquier valor lo activa, incluido `"false"`; para desactivarlo hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarRecurringRule" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que lleva la regla de repetición del evento. **Se evalúa por presencia**: `"false"` lo marca igual y el `default="false"` declarado no equivale a omitirlo. La única forma de que el campo no aporte la regla es no escribir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarStartDate" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que aporta la fecha de inicio del evento. **Se evalúa por presencia**: `"false"` lo marca igual; para que no participe hay que omitir el atributo. Tiene además un fallback: si ningún campo lo declara, el calendario usa el **primer campo de tipo fecha** del XML.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarEndDate" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que aporta la fecha de fin del evento. **Se evalúa por presencia**: `"false"` lo marca igual; para que no participe hay que omitir el atributo. `calendarStopDate` escribe **la misma** propiedad, así que declarar uno en un campo y el otro en otro deja la fecha de fin indefinida: usar uno solo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarStartTime" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que aporta la hora de inicio del evento. **Se evalúa por presencia**, como todo el grupo `calendar*`: `"false"` lo marca igual; para que no participe hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarEndTime" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que aporta la hora de fin del evento. **Escribe la misma propiedad que `calendarStopTime`**: son dos nombres para el mismo dato, y declarar uno en un campo y el otro en otro deja la hora de fin indefinida. **Se evalúa por presencia**: `"false"` lo marca igual; para que no participe hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarDescription" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo cuyo valor es la descripción del evento. **Se evalúa por presencia**, como todo el grupo `calendar*`: `"false"` lo marca igual; para que no participe hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarStopTime" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo como hora de fin del evento. **Es el mismo dato que `calendarEndTime`** (los dos escriben la misma propiedad): declarar uno en un campo y el otro en otro deja la hora de fin indefinida. Como el resto de los `calendar*` de campo, el motor sólo mira que el atributo **esté presente**, no su valor: `calendarStopTime="false"` marca el campo igual.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarTextColor" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que aporta el color del texto del evento. Se evalúa por presencia, igual que `calendarColor`: cualquier valor lo activa, incluido `"false"`; para desactivarlo hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarEditable" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que habilita la edición del evento desde el calendario. **Se evalúa por presencia**, como todo el grupo `calendar*`: cualquier valor lo marca, incluido `"false"`; para que el campo no participe hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarCheck" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          En vistas de calendario marca el campo que se muestra como casilla de tildar al lado de
          cada evento en el panel de listado. Tildarla o destildarla graba 1 o 0 en ese campo del
          registro en el momento, sin abrir el formulario, avisa el resultado y refresca el
          calendario. Trampa: para saber qué registro actualizar usa el campo marcado con
          calendarId, sin ese campo no graba; y como se detecta por presencia, calendarCheck="false"
          lo prende igual.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarStopDate" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          En vistas de calendario marca el campo que tiene la fecha de fin del evento, y con eso el
          evento se dibuja abarcando hasta esa fecha; al arrastrar o estirar el evento, la fecha
          nueva se escribe en ese campo. Es exactamente lo mismo que calendarEndDate: los dos
          apuntan al mismo lugar interno, así que ponerlos en campos distintos es ambiguo y gana el
          último que aparezca en el XML. Si ningún campo lo declara, el fin se toma del primer campo
          de tipo fecha. Se detecta por presencia: con "false" también queda marcado.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarSum" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          No produce ningún efecto. El motor lo reconoce y se acuerda de qué campo quedó marcado,
          pero después nadie usa ese dato: no totaliza nada ni muestra nada en el calendario. Para
          totalizar una columna el atributo vivo es suma.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  <!-- =======================
       Grupos de atributos reutilizables
       ======================= -->
  <xs:attributeGroup name="UiFlags">
    <xs:annotation>
      <xs:documentation>
        Atributos de control de UI/visibilidad/estado para `&lt;field&gt;` y `&lt;fieldGroup&gt;`.
        Cubre: visibilidad, edición, validación, exportación, búsqueda, formato y propagación entre formularios.
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="deshabilitado" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          Campo de sólo lectura. Los campos con `&lt;expression&gt;` lo asumen automáticamente;
          `deshabilitado="false"` los fuerza editables — y en los checkbox es además el escape para
          poder editarlos cuando la vista deshabilitó todos los checks.

          **Sin `default` declarado a propósito**: el motor lo evalúa con más de un criterio según el
          tipo de campo, así que escribir el valor por defecto no equivale a omitir el atributo.

          `disabled` es la otra grafía del mismo flag y tiene **menor precedencia**: si el campo
          declara las dos, gana `deshabilitado`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="local" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Campo local: existe en el formulario pero NO se mapea a una columna SQL. **NO funciona
          junto con `&lt;expression&gt;`**: si el campo tiene expression, el motor ignora `local`.

          Sólo el literal `local="true"` lo hace local. **`local="false"` y el legacy
          `local="local"` dejan el campo en el peor estado**: sigue entrando al `SELECT` y al
          `WHERE` — si la columna no existe la consulta revienta con `1054 Unknown column` — pero
          queda excluido del INSERT, así que su valor no se graba. Para que el campo sea uno normal
          hay que omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="noshow" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Oculta el campo en la UI: el input se renderiza con `display:none` y **queda en el DOM**,
          así que conserva su valor, se postea y las `&lt;validation&gt;` (client-side) lo siguen
          encontrando.

          **Un campo con `noshow="true"` nunca se exporta a XLS/CSV**, ni agregándole
          `export="true"`: el chequeo del ocultamiento es posterior e independiente. Para tener la
          columna en el export y no en pantalla va `colstyle="display:none;"`. La única exportación
          que ignora el ocultamiento es la de XML.

          No es lo mismo que `oculto`, que directamente no renderiza el campo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="autoinc" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Marca el campo como autoincremental (PK con `AUTO_INCREMENT`): el motor lo excluye del
          INSERT y después recupera el id generado para rellenarlo.

          **`autoinc="false"` no es lo mismo que omitir el atributo**: el campo queda excluido del
          INSERT igual — su valor no se graba, en silencio — y la recuperación del id, que exige el
          literal `"true"`, no corre. O sea que no se inserta ni se rellena. Para que el campo grabe
          como uno normal hay que omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="esClave" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Marca el campo como clave primaria: entra al `WHERE` y al UPDATE, y su input queda
          obligatorio. Para claves compuestas, marcar todos los campos que la forman. Es crítico en
          las ayudas popup: si ninguna columna está marcada, elegir una fila no llena el campo
          destino.

          **`esClave="false"` no desactiva nada**: el campo se sigue tratando como clave. Para que
          no lo sea hay que omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="noZero" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**No valida nada: es presentación.** Con `noZero="true"` el valor 0 se muestra como celda vacía en lugar de "0". No rechaza el cero ni impide grabar.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="activa" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Marca el campo como **controlador**: habilita o deshabilita en el browser los campos que lo referencian con `activador`. No recalcula valores, y si ningún campo le apunta con `activador` no hace nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="oculto" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es alias de `noshow`: son dos mecanismos distintos.** Con `oculto="true"` el campo no
          se renderiza — no llega al HTML —, se excluye del UPDATE y sale del export. Con
          `noshow="true"` el input se dibuja con `display:none` y sigue en el DOM.

          La diferencia importa porque las `&lt;validation&gt;` son client-side y buscan el input:
          con `oculto` la condición nunca dispara, con `noshow` sí.

          **`oculto="false"` oculta el campo igual**: cualquier valor no vacío lo activa. Para que el
          campo se vea hay que omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="sincelda" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Si "true", el campo se renderiza SIN su `&lt;td&gt;` contenedor. Útil para juntar inputs en la misma celda.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="habilita" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **La dirección es la inversa de lo que sugiere el nombre.** El valor es el `id` del campo
          **controlador**, y el que se deshabilita es **este** campo: con `habilita="pagado"`, este
          campo queda deshabilitado mientras `pagado` valga falso. Escribirlo al revés deshabilita el
          campo equivocado.

          Se resuelve en el servidor al renderizar y sobre el último valor grabado del controlador
          — no hay reacción en el browser cuando el controlador cambia. Cuentan como falso sólo `0`,
          `"0"` y `"false"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="activador" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          `id` del campo del que depende **este** campo: nace deshabilitado mientras el campo
          referenciado no valga exactamente `1` (con `"S"`, `"true"` o `"X"` queda deshabilitado).
          Para que se habilite en vivo al cambiar el otro campo, ese campo tiene que llevar
          `activa="true"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="tabla" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de la tabla SQL a la que pertenece la columna. Requerido cuando varias tablas del JOIN tienen una columna con el mismo nombre (evita SQL ambiguo).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="rowspan" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Atributo HTML `rowspan` del `&lt;td&gt;` que contiene el campo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="labelColspan" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Atributo HTML `colspan` de la celda de la **etiqueta**, no la del input — para esa está `colspan`. El valor pasa crudo al HTML. **Se ignora si el campo tiene `sincelda="true"`**: ahí la etiqueta se emite sin `&lt;td&gt;` y las propiedades de celda no aplican. Escrito en minúscula (`labelcolspan`) no lo lee nadie: la grafía se compara exacta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          CSS inline del campo. **No separa el `&lt;td&gt;` del input**: `style`, `Formstyle` y
          `formStyle` terminan los tres aplicados al input **y** al `&lt;td&gt;`, y **se concatenan
          sin `;` de separación** — `style="color:red"` junto con `Formstyle="width:5em"` produce
          `color:redwidth:5em` y se pierden las dos reglas. Conviene usar uno solo por campo; si se
          usan varios, cerrar cada valor con `;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="colstyle" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          CSS inline de la columna en grillas. `colstyle="display:none;"` oculta la columna pero
          conserva el campo en el form vertical de entrada y **sí lo exporta a XLS/CSV**: es la
          forma de tener una columna en el export sin mostrarla en pantalla, que `noshow` no
          permite. En el PDF, en cambio, una columna oculta así queda excluida igual que con
          `noshow`, y `print="true"` no la recupera.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="filterStyle" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          CSS inline del campo en la barra de filtros. Útil para angostar (`width:0em;`) o resaltar
          un filtro, pero **no queda contenido ahí**: al armar la barra el motor pisa con este valor
          el `style` del campo y no lo restaura, así que el mismo CSS puede reaparecer después en la
          celda de la grilla y en el formulario.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="Formstyle" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          CSS inline del campo. **No es "el del input" en contraposición al `&lt;td&gt;` de
          `style`**: los dos se aplican al input y al `&lt;td&gt;`, y se concatenan sin `;` de
          separación. `formStyle` (camelCase) tampoco lo reemplaza: se suma. Ver la trampa completa
          en `style`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="modpos" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Modificador de posición de la celda. `"nobr"` evita el salto de línea (mantiene la siguiente celda en el mismo renglón en forms verticales de grids `ing`).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="Class" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Clase CSS del `&lt;td&gt;` de la celda en la grilla. **No es alias de `clase`**, que va a la `class` del input: cambiar uno por el otro no produce el mismo HTML.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tdWidth" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Ancho de la celda del campo. **No es CSS**: se emite como el atributo HTML `width` del `&lt;td&gt;` y como ancho de la columna en la grilla, así que sólo acepta píxeles o porcentaje — `tdWidth="10em"` lo ignora el navegador.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="clase" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Clase CSS del **input** del campo, y de la celda en columnas editables. A nivel campo
          **no tiene default**.

          No es alias de `Class`: ése va a la clase del `&lt;td&gt;` de la grilla. Cambiar uno por el
          otro no produce el mismo HTML.

          El mismo nombre existe además a nivel `&lt;histrix&gt;`, y ahí sí tiene default — en un
          grid `ing` vale `ParentingClass` y en otras vistas la clase por defecto de la vista. Ese
          default **no aplica al campo**.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="linkdes" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto descriptivo del link cuando el campo es renderizado como enlace.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="linkWidth" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Ancho de la ventana que abre el `linkint` de este campo. Hay dos formas de setear lo mismo y al motor le da igual cuál se use: `linkWidth` en el `&lt;field&gt;`, o `width` dentro de un `&lt;helper type="link"&gt;`. **Se ignora si el campo tiene `linktab="true"`**: con eso el link abre en una pestaña del navegador y no se le pasa ni ancho ni alto.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="linkHeight" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alto de la ventana que abre el `linkint` de este campo. Mismo mecanismo y misma excepción que `linkWidth`: con `linktab="true"` no se aplica.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="copia" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **La dirección es la inversa de lo que sugiere el nombre.** El valor es el `id` del campo
          **destino**: al cambiar este campo, su valor se copia **hacia** el campo nombrado.
          Escribirlo esperando traer el valor del otro campo no copia nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="linkmodal" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Si "true", el link del campo abre en modal en vez de nueva pestaña.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="suma" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Totaliza la columna al pie en grillas, consultas y PDF.

          **Efecto lateral**: si el campo se pasa como `&lt;parameter&gt;` a otro XML, lo que viaja
          es el total acumulado y no el valor de la fila; para mandar el de la fila hay que agregar
          `forceCellValue="true"`.

          Al imprimir, el motor sólo mira si el atributo está presente, no su valor: `suma="false"`
          totaliza igual, y para no totalizar hay que omitirlo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="print" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          `print="false"` excluye el campo del PDF. **`print="true"` no puede forzar lo contrario**:
          las exclusiones son independientes y se suman, así que un campo con `noshow`, `oculto` o
          `colstyle="display:none;"` no se imprime aunque lleve `print="true"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="public" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Si "true", expone el valor del campo del form padre al grid hijo embebido (vía sesión PHP). Necesario para que `&lt;if&gt;` del grid lea el valor.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="editable" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          Es opt-**in** de la edición inline: `editable="true"` hace editable esta columna dentro de
          la grilla. **`editable="false"` no bloquea nada** — sólo hace algo acompañando a `linkint`,
          donde suprime el link. Para que un campo sea de sólo lectura va `deshabilitado`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="colspan" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Atributo HTML `colspan` del `&lt;td&gt;` contenedor.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="fullSearch" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Cambia cómo se arma la condición **de este campo**: parte el texto buscado por espacios o
          comas y ANDea un `like '%palabra%'` por cada token sobre la misma columna, así que
          "juan perez" matchea aunque las palabras vengan en otro orden o con texto en el medio.
          **No tiene relación con `conBusqueda`**, que es otra función y no interviene.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="break" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es un salto de línea, es un corte de control.** `break="true"` convierte el campo en
          campo de corte del listado: cada vez que su valor cambia entre filas se cierra el grupo y
          se emiten los subtotales y promedios parciales — agrupando las filas en la grilla y en el
          PDF. El salto de línea lo controla `modpos`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="maxsize" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>**Es alias de `maxlength`, no del `size` del input**: limita la cantidad de caracteres que se pueden tipear. El ancho visible sale del elemento `&lt;size&gt;`. Usarlo creyendo que es ancho termina truncando el dato.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="multiple" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Permite múltiples valores (en selects o file inputs).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="lblsize" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>**Ancho de la celda de la etiqueta en el PDF** de una ficha, no tamaño de fuente, y sin ningún efecto en HTML. El ancho de la celda del valor es `pdfancho`. Para el tamaño de la etiqueta va el atributo `size` del `&lt;label&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="forceCellValue" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Escape de `suma`: en un campo con `suma="true"` que se pasa como `&lt;parameter&gt;` a otro
          XML, lo que viaja es el total acumulado de la columna; con `forceCellValue="true"` viaja el
          valor de la fila. **Sin `suma` no hace nada**, y no tiene relación con
          `&lt;expression&gt;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="color" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**No dibuja un swatch**: toma el valor del campo como color y lo aplica de `background-color` al input y a la celda, con el texto encima. El valor tiene que ser un color válido para CSS.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="noselect" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es UI, es SQL.** `noselect="true"` saca el campo de la lista del `SELECT` pero
          conserva sus `&lt;condicion&gt;` en el `WHERE`/`HAVING`: es la forma de filtrar por una
          columna sin traerla, el caso típico de los filtros sobre consultas agrupadas. Requiere el
          literal `"true"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="export" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          **Sólo puede excluir, nunca incluir.** `export="false"` saca el campo del XLS/CSV;
          `export="true"` no agrega nada, porque el ocultamiento se chequea después y por separado:
          un campo con `noshow="true"` u `oculto="true"` **nunca** se exporta, ni con
          `export="true"`. Para tener la columna en el export y no en pantalla va
          `colstyle="display:none;"`. La única exportación que ignora el ocultamiento es la de XML.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="maxlength" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Atributo HTML `maxlength` del input.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="max" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Valor numérico máximo permitido.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="min" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Valor numérico mínimo permitido.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="repeat" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **Son dos atributos distintos con el mismo nombre, según el tag donde se escriba.**
          Este `attributeGroup` lo comparten `&lt;field&gt;` y `&lt;fieldGroup&gt;`, así que la
          declaración es una sola pero la semántica no:

          - En `&lt;fieldGroup repeat="N"&gt;` = **loop**: clona el grupo N veces. El valor puede ser
            un número o el `id` de un campo que contenga el número; dentro del grupo queda disponible
            el sufijo `__repeat__`.
          - En `&lt;field repeat="false"&gt;` = **no repetir el valor** en filas consecutivas iguales
            de la grilla, y además desactiva el ordenamiento de esa columna. Es equivalente
            a `norepeat="true"`. Un número en un `&lt;field&gt;` no hace nada: sólo se compara contra
            el literal "false".
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="validar" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Activa la validación obligatoria del campo (similar a `required`).
        **Sin `default` declarado a propósito**: `Data/Field:2076` trata la ausencia como validar
        (`== 'false'` para desactivar) y `View/UI:1079` exige "true" explícito.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="errorMessage" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Mensaje que aparece si la validación del campo falla.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="mask" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Máscara de entrada (formato `9999-9999-X`, etc.).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="rss" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No es una URL: es el nombre del elemento del `&lt;item&gt;` que este campo alimenta** cuando el contenedor se exporta como feed RSS. Valores útiles: `title`, `description`, `link`, `pubDate`. El mapeo va del campo al feed, no del feed al campo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="display" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**Mueve el campo de lugar en la pantalla de consulta.** Los únicos valores que el motor reconoce son `toolbar` y `filter` (los busca como substring): el campo se renderiza en la barra de herramientas o en la barra de filtros y **deja de tener columna en la grilla**. Cualquier otro valor no hace nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="cols" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Atributo HTML `cols` para `&lt;textarea&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="enablecopy" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>**No agrega ningún botón de copiar.** En campos con `&lt;expression&gt;` o deshabilitados evita el atributo HTML `disabled` y deja el input en `readonly`, que es lo que permite seleccionar el texto con el mouse para copiarlo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="email" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Dirección de email asociada al campo (para botón "enviar mail").</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="sort" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Si "false", desactiva el ordenamiento por esta columna en la grilla. Ojo que `repeat="false"` (o `norepeat="true"`) lo desactiva también, como efecto lateral.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="linktab" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Si "true", el link del campo abre en nueva tab del navegador.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="disabled" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Otra grafía de `deshabilitado`: **no se emite al HTML**, el motor la normaliza al mismo flag interno. Tiene **menor precedencia** — si el campo declara las dos, gana `deshabilitado`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="hidden" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Renderiza el input como `type="hidden"`: **el valor se sigue posteando** y la celda y la etiqueta se dibujan igual, así que no sirve para ocultar el campo. Para eso van `noshow` (lo deja en el DOM) u `oculto` (no lo renderiza).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="showValor" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**Nada que ver con combos.** Aplica a campos que embeben otro contenedor (los de `showObjTabla` / helper `type="object"`) y decide si, además de la grilla hija, se muestra el valor propio del campo. En un combo no tiene efecto: el atributo parecido que sí vive ahí es `showKey`, que va en el `&lt;field&gt;` del helper.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="seconds" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>En campos de hora, incluye los segundos en el formato visible. **Trampa: escribir el valor documentado como default apaga la función.** El motor arranca con los segundos prendidos por un `true` booleano interno y compara por identidad estricta; cualquier valor escrito en el XML llega como texto y ya no coincide, así que `seconds="true"` **deja de imprimir los segundos** igual que `seconds="false"`. Para tener segundos hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="numberPrecision" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Cantidad de decimales para campos numéricos.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="grupo" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No agrupa radios ni arma fieldsets.** Tiene dos usos: en un campo de la tabla, cuando el campo aporta una condición **activa el `&lt;join grupo="…"&gt;` del mismo nombre** (JOIN condicional: la tabla se suma sólo si se filtró por ese campo); y dentro de `&lt;filters&gt;` agrupa los filtros de la barra.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tooltip" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Tooltip del campo. Dos caminos, los dos a nivel `&lt;field&gt;`: en `tipo="button"` se pasa
          al botón (junto con `icon`, `text`, `style` y `name`), y en la grilla sale como el `title`
          de la celda — o sea que escribir `title="…"` en el `&lt;field&gt;` termina también acá.
          **No va en el `&lt;histrix&gt;`**: ahí no lo lee nadie, aunque haya ~716 XMLs con
          `&lt;histrix tooltip="false"&gt;` que no hacen nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="autocomplete" type="ModoAutocompletado" use="optional">
      <xs:annotation><xs:documentation>**No es el `autocomplete` de HTML: es el modo del helper de ayuda del campo.** Valores con efecto: `"true"` (autocompletar mientras se tipea) y `"full"` (autocompletar más botón que abre el popup de ayuda). `"on"` y `"off"` caen al comportamiento por defecto: no prenden ni apagan nada. Alias español: `autocompletar`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="size" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No es el `size` del input** — ése sale del elemento `&lt;size&gt;`. Como atributo en minúscula sólo tiene efecto en campos `geoPoint`, donde fija el zoom/tamaño del mapa.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="norepeat" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>En grillas y en el PDF, no vuelve a imprimir el valor de la columna en filas consecutivas que lo repiten: sólo se ve en el primer renglón de cada bloque. Efecto lateral: **también desactiva el ordenamiento de esa columna**. `repeat="false"` en el `&lt;field&gt;` es la otra forma de escribir lo mismo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="watermark" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No es el placeholder del input: es exclusivo del PDF.** Estampa el texto como marca de agua sobre la imagen de un campo `file` al imprimir; en HTML no hace nada. Si el valor coincide con el `id` de otro campo del XML, se usa el **valor** de ese campo en lugar del texto literal, así que la marca puede salir de un dato de la fila.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="prefijo" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto antepuesto al valor del campo en render. También aceptado como elemento `&lt;prefijo&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="getStyle" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de un campo del row cuyo valor se usa como CSS inline del campo actual (ej. `getStyle="color_celda"` aplica `style` desde la columna `color_celda` de la fila).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="height" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alto del control. Usado en tipo `file` (image upload), `qr` (alto del código QR, default 240) y campos similares. Acepta px o valor numérico.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="buttonStyle" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>CSS inline aplicado al botón generado por el campo (usado en `tipo="button"` o helpers tipo link). También válido a nivel `&lt;fieldGroup&gt;` para estilar todos sus botones.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="download" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**Es un booleano, no un nombre de archivo.** Con `download="true"` la celda de un campo `file` muestra un botón de descarga en lugar de la miniatura. Tiene hermanos con la misma mecánica que el XSD todavía no declara: `open`, `viewer` y `svg`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="replace" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No reemplaza texto en el valor mostrado.** Es un flag (`replace="true"`) que marca al campo destino de un `&lt;detalle&gt;` como **clave de matching** cuando los datos vuelven de un `&lt;importar&gt;`: es el campo por el que el motor busca el renglón del grid que tiene que actualizar. Equivale a marcarlo `esClave` sólo para ese ida y vuelta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="oplogico" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Operador lógico con el que la condición de este campo se une a la **anterior**: `"and"` (default) | `"or"`. Ojo con el nombre: conecta hacia atrás, no con la condición siguiente. Efecto lateral de `"or"`: se pierde el reemplazo, así que la condición se **agrega** a la que el mismo campo ya hubiera puesto en vez de sustituirla, y el campo termina aportando dos condiciones ORadas.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="update" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>`update="false"` excluye el campo del UPDATE generado. **No lo pone read-only**: el input se sigue editando y lo que se tipea simplemente no se graba; para bloquear la edición va `deshabilitado`. La comparación es contra el literal en minúscula: `update="FALSE"` no hace nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="noupdate" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>**No es el inverso de `update` y no toca el SQL** (para dejar un campo afuera del UPDATE va `update="false"`). Viaja al HTML y se evalúa **por presencia** en el browser: en un combo evita que los atributos del `&lt;option&gt;` elegido se propaguen a los campos homónimos del formulario. Cualquier valor lo activa, incluido `"false"`; para desactivarlo hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="orderType" type="OrdenTipo" use="optional">
      <xs:annotation><xs:documentation>Dirección de orden por defecto del campo: `"ASC"` | `"DESC"`. **`tipoOrden` no es un alias simétrico**: si están los dos escritos gana `tipoOrden`. Y el orden de la **tabla temporal** sólo reconoce `"DESC"` en mayúscula, así que `orderType="desc"` ordena el SQL pero deja la memoria en ascendente. Escribirlo siempre en mayúscula.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="desactiva" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Inverso de `activa` sobre el campo **controlador**: en vez de habilitar, deshabilita en el browser los campos que lo referencian con `activador`. **`habilita` no participa de este mecanismo.**</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="having" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es una condición: es un flag de ruteo.** Decide si la `&lt;condicion&gt;` de este campo
          va al `HAVING` o al `WHERE`.

          **Sólo tiene efecto si el campo tiene `&lt;expression&gt;`**, y el default depende del
          motor: en MySQL/SQLite la *ausencia* manda la condición al `HAVING` y `having="false"` la
          baja al `WHERE`; en **SQL Server es al revés** — hace falta `having="true"` explícito. Por
          eso no se declara un `default` acá.

          Se puede escribir en el `&lt;field&gt;` o en el `&lt;expression&gt;`, que es la forma
          dominante en el repo. En una `&lt;condicion&gt;` **no** se lee.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="import" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**Es un opt-out booleano, no un modo de importación.** El único valor con efecto es `import="false"`, que deja el campo afuera de la importación desde archivo; cualquier otro valor, y la ausencia, lo dejan participando.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="noEmpty" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Con `"true"` **oculta la columna entera** cuando ninguna fila del resultado tiene valor en ese campo — no suprime filas — y omite su total al pie si la suma da 0. Útil en consultas con columnas que sólo tienen sentido para ciertos filtros.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="rememberValue" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Conserva el valor del campo entre sesiones del mismo usuario (persistencia en perfil).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="onRefresh" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Comportamiento al refrescar la fila/form. Ej: "focus" devuelve el foco al campo después del refresco.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="editableSelect" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>En combos: permite tipear texto libre además de elegir de la lista. **Trampa: `editableSelect="false"` lo activa igual**, y el `default="false"` declarado no equivale a omitirlo. La única forma de tener un combo cerrado es no escribir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="helpButton" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**Al revés de lo que sugiere el nombre: oculta el input del campo** y convierte el botón del helper de ayuda en un botón etiquetado que abre el popup. Si el campo no tiene helper de ayuda, lo único que hace es esconder el input y dejarlo sin forma de cargarse.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="autominchars" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Cantidad mínima de caracteres antes de disparar el autocompletado (default 3).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="selectExpand" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Renderiza el combo como listbox de 5 filas visibles (`size="5"` en el `&lt;select&gt;`), **siempre, no al hacer foco**: la lista queda desplegada todo el tiempo y ocupa el alto de 5 opciones.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="ignoreOption" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**No ignora la opción vacía del combo: hace lo contrario de descartar el valor.** Cuando el valor guardado no figura entre las opciones, con `"true"` se muestra ese valor crudo en la celda en vez de dejarla vacía. Sirve para no perder de vista datos históricos cuyo código ya no está en la tabla de opciones.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xlsFormat" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Formato de celda aplicado al exportar a planilla, en el format string de la planilla (ej. `"#.##0,00"`). Sobreescribe el formato que el motor elige según el `&lt;tipo&gt;` del campo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xmlField" type="xs:string" default="field" use="optional">
      <xs:annotation><xs:documentation>Nombre del tag XML usado al exportar este campo con `tipo="exportXML"`. Default: "field".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xmlCdata" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>En la exportación a XML, `xmlCdata="false"` emite el valor del campo **crudo**, sin envolverlo en `&lt;![CDATA[…]]&gt;`. El motor envuelve por defecto y **sólo reacciona al literal `"false"`**: con `"true"` o cualquier otro valor el CDATA se mantiene, así que escribirlo en positivo es redundante. Se usa cuando el campo ya contiene XML anidado y el CDATA lo escaparía, dejando los tags como texto en la salida.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="Arbol" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Rol del campo en una estructura jerárquica. Valores reales:
          "padre" (columna que apunta al nodo padre), "hijo" (clave del nodo, la que se usa para
          recursionar), y para árboles nested-set "left" | "right" | "order" (con fallback a las
          columnas `lft`/`rgt` si no se declaran).

          **Preferir la grafía en minúscula `arbol`, no ésta.** Escribiendo `arbol` el motor setea
          *las dos* propiedades y quedan contentos todos los lectores. Escribiendo `Arbol` sólo queda
          la mayúscula, y entonces fallan los lectores que buscan la minúscula: el treemap y el
          organigrama (donde además se usan valores numéricos por nivel) y la vista de árbol.
          En el repo hay 0 usos de `Arbol=` y 71 de `arbol=`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="nodeTitle" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>En un `tipo="arbol"`, usa este campo como título de cada nodo **en el gráfico** del árbol, no en el árbol HTML. Pisa el default, que es el campo `esClave` — o sea que sin este atributo cada caja del gráfico se rotula con el id. Se ve sólo al abrir el botón "vista de árbol", que aparece únicamente cuando la vista es un árbol. Requiere el literal `"true"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="arbol" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **Grafía recomendada** para el rol del campo en el árbol (mismos valores que `Arbol`).
          Es la única que funciona en todos los caminos: escrita así, el motor setea *además* la
          propiedad con mayúscula, así que la leen tanto la construcción del árbol y de la consulta
          (que buscan la mayúscula) como los gráficos y la vista de árbol (que buscan la minúscula).
          La normalización es unidireccional: al revés no funciona.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pdfancho" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Ancho de la celda del **valor** en el layout vertical del PDF de una ficha. El ancho de la
          **etiqueta** es `lblsize`. No tiene nada que ver con `PDFcolWidth`, que es el ancho de
          columna de una tabla PDF.

          Tres formas de setear la misma propiedad: este atributo en el `&lt;field&gt;`, el tag hijo
          `&lt;pdfancho&gt;` y el atributo `PDFancho` del `&lt;label&gt;`. **`PDFancho` escrito en el
          `&lt;field&gt;` no lo lee nadie** — hay 250 usos así en el repo que no hacen nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="formStyle" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Tercera grafía del CSS inline del campo. **No reemplaza a `Formstyle`**: si están los dos,
          los valores se suman, y sin `;` de separación entre ellos. Ver `style`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="tagGroup" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>En un campo `&lt;tipo&gt;tags&lt;/tipo&gt;`: nombre del grupo de tags del que sale la lista de sugerencias y donde se guardan los nuevos. Sin él el campo comparte el grupo por defecto con todos los demás.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="cellMaxLength" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Corta el texto de la celda a N caracteres en la grilla (el valor completo se sigue guardando y viaja al formulario). Útil para columnas de observaciones que ensanchan la tabla.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="htmlspecialchars" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Con "true" escapa el valor del campo al mostrarlo, de modo que el HTML que venga en el dato se vea como texto en vez de interpretarse. Requiere el literal "true".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="esclave" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Forma legacy **en minúscula**, y no es lo mismo que `esClave`: funciona como su **negador**.
          Con `esClave="true" esclave="false"` el campo deja de tratarse como clave — que es el único
          modo de desactivarlo, porque `esClave="false"` no lo desactiva.
          Es una rareza heredada: en código nuevo, si el campo no es clave, no declarar `esClave`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="defaultvalue" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Valor por defecto que se emite al input y que el formulario usa al limpiarse. No confundir con el elemento `&lt;valor&gt;`, que fija el valor inicial del campo server-side.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="autoing" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Se emite como atributo del input para que el ingreso de la fila avance solo al completar el campo (grids `ing`). Se usa junto a `&lt;complete&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="open" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>En campos `&lt;tipo&gt;file&lt;/tipo&gt;`: con "true" la celda muestra un botón para **abrir** el archivo en vez de la miniatura. Hermanos: `download` (descargar), `viewer` (visor embebido) y `svg`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="viewer" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>En campos `&lt;tipo&gt;file&lt;/tipo&gt;`: con "true" la celda muestra un botón que abre el archivo en el **visor embebido** en vez de la miniatura.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="muestra" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>En un `&lt;field&gt;` dentro de `&lt;tabla_ex&gt;` (la forma legacy del combo): con "true" el texto visible de la opción incluye también la clave, no sólo la descripción. El equivalente en un `&lt;helper type="combo"&gt;` es `showKey`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarAllDay" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Marca el campo que indica si el evento ocupa todo el día. Como el resto de los `calendar*` de campo, se evalúa por presencia: cualquier valor lo marca.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="icon" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Sólo en `&lt;tipo&gt;button&lt;/tipo&gt;`: clase de icono del botón (ej. `ui-icon-search`). Junto con `text`, `style`, `tooltip` y `name` es uno de los atributos que el campo-botón pasa al botón que genera.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="text" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Sólo en `&lt;tipo&gt;button&lt;/tipo&gt;`: con "false" el botón muestra únicamente el icono, sin texto.</xs:documentation></xs:annotation>
    </xs:attribute>
    <!-- PDF -->
    <xs:attributeGroup ref="pdfGrup" />
    <!-- Imagen -->
    <xs:attributeGroup ref="ImagenGrup" />
    <xs:attribute name="PDFimg" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          En la impresión PDF de fichas, tarjetas y etiquetas hace que el contenido del campo se
          dibuje como imagen dentro de la celda en lugar de imprimirse como texto (el texto de la
          celda pasa a ser la palabra "Imagen"). Sólo el valor "true" lo activa. El ancho se saca de
          un parámetro ancho que tiene que venir dentro del propio valor del campo, como en los
          enlaces a miniaturas; si no viene, la imagen sale a su tamaño natural y normalmente
          desborda la celda.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFtitle" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Texto que se imprime como renglón de título, con fondo, arriba de la grilla embebida de un
          campo cuando el comprobante sale en PDF; además se reserva un renglón extra de alto para
          ese título. Sólo aplica a campos declarados como tabla embebida, y hay una condición fácil
          de pasar por alto: el título se dibuja únicamente si el campo también trae declarado
          showValor; el alto extra, en cambio, se reserva siempre, así que sin showValor queda un
          renglón vacío.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="mandatory" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Vuelve obligatoria la condición de ese campo: si el campo no llegó a tener ninguna
          condición armada (por ejemplo, un filtro que el usuario dejó vacío), la consulta no se
          ejecuta —ni la de datos ni la del contador— y la pantalla queda vacía con cero registros,
          sin ningún mensaje. Sirve para listados pesados que no deben traer nada hasta que se elija
          un filtro. Dos trampas: alcanza con que el atributo esté escrito con cualquier valor para
          que actúe, incluso mandatory="false" (sólo lo apagan el valor 0 o el vacío, o sacar el
          atributo); y si el campo es local nunca genera condición, así que la pantalla queda vacía
          para siempre.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="idName" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Hace que la columna destino se resuelva en tiempo de ejecución: en vez de grabar en la
          columna que corresponde al id del campo, graba en la columna cuyo nombre sale del valor de
          otro campo del formulario, que es el que se nombra acá. Permite altas contra columnas
          variables (por ejemplo, elegir la columna del mes a impactar desde un combo). Trampas: si
          el campo referenciado está vacío o no existe, el campo simplemente no se graba y no hay
          aviso; y sólo funciona en el alta (insert/replace), en la modificación se ignora por
          completo. Tampoco actúa sobre campos locales, con expresión, ni de otra tabla que la base.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="liveSum" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Le agrega una marca a la celda de esa columna, pero hoy esa marca no la usa nadie: no hay
          estilo asociado ni cálculo que la lea, así que el atributo no produce ningún efecto
          visible. Se activa con cualquier valor —incluido liveSum="false"— y se apaga sacando el
          atributo. Para totalizar una columna en vivo el atributo que sí funciona es suma.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="defaultValue" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          Escrito con V mayúscula actúa sobre el encabezado de la columna, no sobre el campo: el
          valor se publica en el encabezado y, en columnas de tipo check que muestran el tilde de
          "marcar todos", defaultValue="1" hace que ese tilde del encabezado arranque marcado. Dos
          advertencias: que arranque marcado no marca los renglones (los renglones se marcan sólo
          cuando el usuario clickea el tilde), y únicamente el valor 1 lo activa. Ojo con la
          confusión de mayúsculas: defaultvalue todo en minúscula es otro atributo distinto, que sí
          llega al campo del formulario y lo rellena con ese valor al limpiar el formulario o al
          salir del campo dejándolo vacío.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="forceSize" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Levanta el recorte automático del ancho del input en el formulario de carga de una grilla
          de ingreso. Sin este atributo, un campo con tamaño declarado mayor a 40 se dibuja igual
          con 40 de ancho en las grillas de ingreso; con forceSize="true" se respeta el tamaño
          declarado, con el techo general de 77 que sigue valiendo en todo lo que no sea ficha. Sólo
          afecta el ancho del input, no el de la columna de la grilla.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="showInForm" type="BoolDefaultActivo" use="optional" default="true">
      <xs:annotation>
        <xs:documentation>
          Con showInForm="false" el campo desaparece del formulario de carga (el renglón de inputs
          de la grilla), pero la columna sigue existiendo en la grilla y el campo sigue formando
          parte de la grabación y de los cálculos. Es el inverso de esconder la columna: acá se
          esconde el input y se deja ver el dato. Sólo tiene efecto en las grillas de ingreso y en
          el abm en línea; en fichas y formularios comunes se ignora en silencio.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="lazyLoad" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          En una columna que muestra una grilla embebida, evita ejecutar la consulta interna
          mientras se dibuja el listado: deja un lugar reservado y recién trae el contenido cuando
          ese renglón se acerca a la pantalla (se anticipa unos 200 píxeles al scrollear). Sirve
          para listados de muchos renglones donde cada renglón abría su propia consulta.
          Consecuencia: si el renglón nunca se llega a ver, esa consulta no se hace nunca, y el
          contenido cargado así no está disponible al imprimir o exportar el listado.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="clear" type="BoolDefaultActivo" use="optional" default="true">
      <xs:annotation>
        <xs:documentation>
          Con clear="false" el campo conserva el valor que tiene cuando se limpia el formulario
          (después de grabar, o al pedir uno nuevo), en vez de volver a vacío o a su valor por
          defecto. Es útil para datos que se repiten carga tras carga, como fecha o depósito.
          Trampa: no protege a los combos ni a las listas desplegables, que se reinician igual
          porque se resuelven antes de mirar este atributo.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="cuit" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Se copia tal cual al campo del formulario, pero nada en la interfaz lo mira: no valida ni
          formatea nada, así que escribirlo no cambia el comportamiento. La validación de CUIT de
          verdad —dígito verificador y máscara 99-99999999-9— la da el tipo de dato cuit del campo,
          no este atributo.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="onClearForm" type="OnClearFormValor" use="optional">
      <xs:annotation>
        <xs:documentation>
          Con onClearForm="uniqid", cada vez que se limpia el formulario el campo se rellena con un
          identificador único nuevo (13 caracteres hexadecimales) en lugar de quedar vacío. Sirve
          para tokens o claves temporales que tienen que ser distintas en cada carga. Es el único
          valor que el motor reconoce: cualquier otro texto no hace nada. No aplica a combos ni
          listas desplegables, y se ejecuta aun cuando el campo tenga clear="false".
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="transparent" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Acompaña a color="true" (o a un campo de tipo color): además de pintar el fondo de la
          celda y del input con el color que trae el valor, pinta el texto de ese mismo color, así
          que el código hexadecimal queda invisible y se ve solamente el recuadro pintado. Aplica
          tanto en la columna de la grilla como en el input del formulario. Trampa: solo se activa
          escribiendo "true", y si el campo no tiene color="true" (ni es de tipo color) no hace
          absolutamente nada.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="titleColor" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Sirve únicamente en la vista de árbol cuando se la pide dibujada como imagen. Marcado en
          un campo cuyo valor es un color en hexadecimal, ese color se usa para el título de cada
          nodo del dibujo. Si en cambio se usa textColor, el color va al cuerpo del nodo y el título
          se calcula solo, 25 tonos más oscuro; titleColor es para fijarlo a mano. Trampa: exige
          color="true" en el mismo campo y valor no vacío, y no tiene ningún efecto en el árbol HTML
          normal.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="svg" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          En campos de archivo cambia la forma de insertar la imagen en la grilla o el formulario:
          la marca como contenido SVG y la pide sin el parámetro de ancho, así que sale en su tamaño
          natural en lugar de escalada al ancho del campo. Como toma ese camino alternativo, ese
          render pierde las clases de zoom, el texto alternativo y el imageStyle del campo. Trampa:
          es hermano de download, open y viewer, y cualquiera de esos tres gana si está puesto,
          dejando a svg sin efecto.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pintado" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Hace que, cada vez que Histrix llena el formulario con los datos de un renglón de la
          grilla, el contenido de ese campo quede seleccionado (pintado), de modo que la primera
          tecla que toque el usuario lo reemplace en vez de agregarse. Trampa: se activa por
          presencia del atributo, así que pintado="false" también lo prende; para apagarlo hay que
          borrar el atributo.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="treeSum" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          No produce ningún efecto en el motor actual. Era el atributo con el que el árbol viejo
          acumulaba los importes de los hijos hacia los nodos padre; ese renderizador se reescribió
          y hoy la suma hacia arriba del árbol se declara con suma. Si tenés XMLs que todavía lo
          traen, ese campo no está sumando nada por este atributo.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="inputType" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Fuerza el control con el que se dibuja el campo sin cambiarle el tipo de dato. Los valores que
          cambian algo son `textArea` (caja multilínea) y `select` (lista desplegable); cualquier otro
          cae en el input común.

          **Es la opción de menor prioridad** y la pisan varias cosas: si el campo tiene combo u
          opciones pasa a lista, si su tipo ya impone un control (archivo, fecha) gana el tipo, y si el
          `&lt;size&gt;` es 100 o más el motor lo pasa a multilínea por su cuenta. El valor no queda en
          el HTML, así que no hay rastro en el input para diagnosticar.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="imageStyle" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          En campos de archivo que se muestran como miniatura, se inyecta tal cual como estilo inline de
          la imagen (por ejemplo para redondear una foto de perfil).

          Se aplica **sólo a la miniatura**: si el campo está configurado para abrirse con botón o con
          visor, o si el archivo es un SVG, no llega a usarse. No confundirlo con `style`, que estiliza
          la celda; y no sirve para el ancho, que lo sigue definiendo el `&lt;size&gt;` del campo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="avatar" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          En campos de archivo o imagen: nombre de otro campo de la misma consulta que actúa de
          reemplazo cuando el archivo no existe. En vez de dejar la celda vacía dibuja un círculo con
          las iniciales del texto de ese campo, del tamaño que indique el `&lt;size&gt;`.

          Sólo actúa en la celda de la grilla o listado, y sólo cuando falta la imagen: si el archivo
          existe se muestra la foto y el atributo se ignora.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>

  <!-- =======================
       Elementos hoja reutilizables
       ======================= -->
  <xs:complexType name="LabelType">
    <xs:annotation>
      <xs:documentation>Etiqueta legible por usuario.</xs:documentation>
    </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="modpos" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              Modificador de posición. El valor que el motor entiende es `nobr`: con
              `modpos="nobr"` la etiqueta o el campo no pasa al renglón siguiente — vale en el ABM,
              en los `ing` con `subtipo="vertical"`, en la barra de filtros, en el PDF y en la
              planilla exportada. Es la forma canónica, con ~15.000 usos.
              **`nobr` es el valor de este atributo, no un atributo en sí**: escribir
              `nobr="true"` no hace nada, no se declara a propósito.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="style" type="xs:string" use="optional" />
        <xs:attribute name="size" type="xs:decimal" use="optional" />
        <xs:attribute name="Formstyle" type="xs:string" use="optional" />
        <xs:attribute name="formStyle" type="xs:string" use="optional" />
        <xs:attribute name="labelStyle" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              CSS de la etiqueta. El motor lo lee junto con `Formstyle` al armar el `&lt;label&gt;` y
              su celda, y también lo aplica a la etiqueta de un checkbox. Se puede escribir acá o en
              el `&lt;field&gt;`: los atributos del `&lt;label&gt;` se copian al mismo campo.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="getStyle" type="xs:string" use="optional" />
        <xs:attribute name="colspan" type="xs:integer" use="optional" />
        <xs:attribute name="rowspan" type="xs:integer" use="optional" />
        <xs:attribute name="sincelda" type="BoolEstricto" use="optional" />
        <xs:attribute name="colstyle" type="xs:string" use="optional" />
        <xs:attribute name="pdfancho" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Ancho de la celda del valor en la ficha impresa. Misma propiedad que `PDFancho` acá y que el `&lt;pdfancho&gt;` del campo — ver la doc de `pdfancho` en `UiFlags`.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="PDFancho" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              Ancho de la celda del **valor** en la ficha impresa. Es el único lugar donde esta
              grafía funciona: el renombre que la habilita se aplica **sólo** al tag
              `&lt;label&gt;`. Escrita en el `&lt;field&gt;` no la lee nadie — ahí va `pdfancho` en
              minúscula — y hay unos 250 usos de `PDFancho` en `&lt;field&gt;` que no hacen nada.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attributeGroup ref="pdfGrup" />
        <xs:attribute name="xml" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              Convierte la etiqueta en un link que abre otro XML: el valor es el XML destino. Sirve
              para colgar una pantalla de consulta o de configuración del propio título del campo,
              sin gastar una columna ni un botón.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="HelpType">
    <xs:annotation>
      <xs:documentation>Ayuda contextual del campo.</xs:documentation>
    </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string" />
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="ExpressionType">
    <xs:annotation>
          <xs:documentation>Expresión (SQL) para valores calculados.</xs:documentation>
        </xs:annotation>

    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="having" type="BoolTrueFalse" use="optional" default="false" />
        <xs:attribute name="sql" type="BoolEstricto" use="optional" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="TipoType">
    <xs:annotation>
      <xs:documentation>Tipo de campo.</xs:documentation>
    </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="CampoTipo">
        <xs:attribute name="aletras" type="BoolPresencia" use="optional">
          <xs:annotation>
            <xs:documentation>
              Imprime el importe en letras. **El motor lo evalúa por presencia y con comparación
              laxa: `aletras="false"` ENCIENDE la conversión igual** — cualquier valor no vacío la
              prende. Para no convertir hay que **omitir** el atributo; el `default="false"` de esta
              declaración es engañoso.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="Type" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              El único valor con efecto es `password`: hace que el campo se trate como contraseña y
              habilita la encriptación del valor (ver `encode`). Cualquier otro valor es inerte,
              incluido `Type="Image"`, que es el más escrito del repo y no hace nada.
              Va con **T mayúscula**: escrito `type` no lo lee nadie.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="encode" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              Algoritmo con el que se encripta el valor, y **sólo se consulta cuando el
              `&lt;tipo&gt;` lleva `Type="password"`**. Valores reconocidos: `encrypt` y `plain`;
              **cualquier otro valor, o la ausencia del atributo, aplica `md5()`**.
              No confundir con el `encode` del `&lt;field&gt;`, que es la simbología del código de
              barras.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="url" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Prefijo de ruta/URL de los archivos del campo (`file`/`files`): se antepone al `path` del contenedor para leer el archivo y para armar el link de descarga.</xs:documentation></xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="SizeType">
    <xs:annotation>
      <xs:documentation>Tamaño/máximo de longitud visual o de dato.</xs:documentation>
    </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:decimal" />
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="OptionType">
    <xs:annotation>
      <xs:documentation>Opción con valor fijo y etiqueta.</xs:documentation>
    </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="value" type="xs:string" use="optional" />
        <xs:attribute name="valor" type="xs:string" use="optional" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="OptionsType">
    <xs:annotation>
      <xs:documentation>Contenedor de opciones de un campo.</xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element name="option" type="OptionType" minOccurs="1" maxOccurs="unbounded" />
      <xs:element name="opcion" type="OptionType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>Alias español de `&lt;option&gt;`; el motor lee las dos grafías.</xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="HelperTypeCplx" mixed="true">
    <xs:annotation>
          <xs:documentation>
            Helper UI asociado a un campo. Tipos (`type`):
            - "combo": dropdown alimentado por SQL (1er field = VALUE, 2do field = texto visible).
            - "comboex": **idéntico a "combo"** — el motor los atiende con la misma rama.
            - "link": botón/icono que abre otro XML como popup (consulta, ficha, fichaing).
            - "external": ayuda que abre una tabla popup para elegir un registro.
            - "parameter": parámetro de configuración editable desde el panel nativo de parámetros.
            - "object": grilla/ficha embebida inline en el campo (no popup).
            - "inline": contenedor embebido, misma rama que "object" y "tags".
            - "tags": campo de etiquetas múltiples, misma rama que "object".
            - "media": botón que abre la imagen/archivo del campo.
            - "import": trae datos de otro XML al form.

            **Este complexType valida cuatro elementos distintos**: `&lt;helper&gt;` y las formas legacy
            `&lt;tabla_ex&gt;`, `&lt;ayuda&gt;` y `&lt;ayuda_ex&gt;`. Por eso hay hijos y atributos
            declarados acá que sólo tienen efecto en uno de los cuatro — y por eso cada declaración
            aclara en qué forma y en qué `type` se lee. Un atributo escrito en el `type` equivocado se
            descarta sin aviso.

            Las `&lt;condicion&gt;` SQL del combo NO soportan `'[__campo__]'` directamente — usar `&lt;actualiza&gt;` desde el form padre + `&lt;detalle&gt;` para alimentar el combo dinámicamente.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="field" type="FieldType">
        <xs:annotation><xs:documentation>Campo del helper (columna del combo o filtro de la ayuda).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="campo" type="FieldType">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`. El motor lo toma como fallback cuando no hay `&lt;field&gt;`, pero sólo en `&lt;tabla_ex&gt;` y `&lt;ayuda_ex&gt;`, las formas legacy; dentro de un `&lt;helper&gt;` no hay fallback y los campos escritos así se pierden. En código nuevo usar `&lt;field&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="option" type="OptionType">
        <xs:annotation><xs:documentation>Opción fija del combo (no proviene de SQL).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="order" type="OrderType">
        <xs:annotation><xs:documentation>Ordenamiento del combo/helper.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="orden" type="OrderType">
        <xs:annotation>
          <xs:documentation>
            Alias español de `&lt;order&gt;`, pero **sólo funciona en `&lt;tabla_ex&gt;` y
            `&lt;ayuda_ex&gt;`**. Dentro de un `&lt;helper&gt;` es **inerte**: ahí el motor busca
            únicamente `&lt;order&gt;` y no tiene fallback al español. Los 11 combos del repo que lo
            usan caen al orden por defecto (por el 2º campo del combo) y el síntoma es "el combo
            sale desordenado". En un `&lt;helper&gt;` usar `&lt;order&gt;`.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="label" type="xs:string">
        <xs:annotation><xs:documentation>Etiqueta del **parámetro**: se lee sólo en `type="parameter"` y es el nombre con el que el parámetro aparece en el panel nativo de parámetros. Si se omite, se usa el `id` del helper. En los otros tipos de helper es inerte.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="defaultValue" type="xs:string">
        <xs:annotation><xs:documentation>Valor por defecto del **parámetro de configuración**, no del combo: se lee sólo en `type="parameter"` y es el valor que toma el parámetro mientras nadie lo haya guardado en `HTXOPTIONS`. En los otros tipos de helper es inerte.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="parameter" type="parameterTipo">
        <xs:annotation><xs:documentation>Parámetro pasado del padre al XML del helper. Source = campo del padre, target = campo en el XML hijo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="clave" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Campo que aporta la clave de la ayuda. **Sólo se lee dentro de `&lt;ayuda_ex&gt;`**, una
            de las formas legacy que comparten este complexType. En un
            `&lt;helper type="external"&gt;` el nombre del hijo es `&lt;key&gt;`, no éste. 35 XMLs lo
            usan bajo `&lt;ayuda_ex&gt;`.
            Si no se declara ni `&lt;clave&gt;` ni `&lt;key&gt;`, el motor toma los dos primeros campos
            visibles: el 1º como clave y el 2º como descripción.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="key" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Campo que aporta la clave de la ayuda, **en un `&lt;helper type="external"&gt;`**. Es el
            equivalente moderno de `&lt;clave&gt;`, que sólo funciona en `&lt;ayuda_ex&gt;`. Mismo
            fallback: sin él se usan los dos primeros campos visibles del helper.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="description" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Descripción del parámetro, para `type="parameter"`: es el texto explicativo que se
            muestra en el **panel nativo de parámetros** junto al valor editable.
            Ojo que el alias en español `&lt;descripcion&gt;` **no** lo lee nadie.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="descripcion" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Se acepta por compatibilidad, pero **el motor no lo lee**: sólo busca la forma en
            inglés. Para que el texto llegue al panel de parámetros hay que usar
            `&lt;description&gt;`.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="values">
        <xs:annotation>
          <xs:documentation>
            Valores posibles de un parámetro (`type="parameter"`), para que el panel los ofrezca
            como lista en lugar de un texto libre. Dos formas, excluyentes: con `xml` (+ `dir` y
            `&lt;parameter&gt;` opcionales) los valores salen de una consulta; sin `xml`, se
            enumeran a mano con `&lt;value id="..."&gt;`.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element name="value">
              <xs:annotation><xs:documentation>Valor fijo: el `id` es lo que se guarda, el texto lo que se muestra.</xs:documentation></xs:annotation>
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:string">
                    <xs:attribute name="id" type="xs:string" use="required" />
                  </xs:extension>
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
            <xs:element name="parameter">
              <xs:annotation><xs:documentation>Parámetro que se pasa al XML de `values` (pares `name`/`value`).</xs:documentation></xs:annotation>
              <xs:complexType>
                <xs:attribute name="name" type="xs:string" use="required" />
                <xs:attribute name="value" type="xs:string" use="optional" />
              </xs:complexType>
            </xs:element>
          </xs:choice>
          <xs:attribute name="xml" type="xs:string" use="optional">
            <xs:annotation><xs:documentation>XML que provee los valores. Si está, se ignoran los `&lt;value&gt;` fijos.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="dir" type="xs:string" use="optional">
            <xs:annotation><xs:documentation>Directorio del XML de valores.</xs:documentation></xs:annotation>
          </xs:attribute>
        </xs:complexType>
      </xs:element>
      <xs:element name="set">
        <xs:annotation>
          <xs:documentation>
            Inyecta una propiedad arbitraria en el contenedor embebido del helper: el `id` es el
            nombre de la propiedad y el texto del tag es el valor. Es la única vía declarativa para
            setear algo que el helper no expone como atributo, y por eso mismo no hay validación
            posible del nombre ni del valor: lo que se escriba se asigna tal cual.
            Funciona en los combos y en `type="external"`.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="id" type="xs:string" use="required">
                <xs:annotation><xs:documentation>Nombre de la propiedad a setear en el contenedor del helper.</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="group" type="groupType">
        <xs:annotation>
          <xs:documentation>
            `GROUP BY` de la consulta del helper. Sirve para desduplicar el combo cuando la tabla
            de origen repite el valor a mostrar (típico al venir de un JOIN con fan-out).
            Se lee tanto en los combos como en `type="external"`.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="value" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Campo que aporta la **descripción** de la ayuda en un `&lt;helper type="external"&gt;`:
            el que se muestra al usuario, mientras `&lt;key&gt;` aporta el valor que se graba.
            Sin él, la descripción sale del 2º campo visible del helper.
            No confundirlo con los `&lt;value&gt;` de adentro de `&lt;values&gt;`, que son la lista
            de opciones de un parámetro.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="type" type="HelperTipo" use="optional">
      <xs:annotation><xs:documentation>Tipo de helper: combo | comboex | link | external | parameter | object | inline | tags | media | import. Un valor no listado no arma ningún helper y se ignora sin error, pero igual queda guardado en el campo y puede desactivarle la grabación inline — ver `HelperTipo`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="required" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Para `type="parameter"`: marca el parámetro como obligatorio en el panel nativo. Hay que escribir el literal `"true"`; cualquier otro valor equivale a "false".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="empty" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Opción vacía del combo (`type="combo"`/`"comboex"`). No es booleano: si el valor no es `''`, `'true'` ni `'false'`, se usa como **texto del placeholder** de esa opción, así que `empty="Seleccione…"` funciona y es el uso interesante. En `&lt;tabla_ex&gt;` el atributo equivalente es `vacio`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xml" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>XML que alimenta el helper. Lo usan todos los tipos que construyen un contenedor (combo, comboex, external, parameter, object, tags, inline, import) y también `type="link"`, donde es el XML que se abre en el popup. En combo/external puede omitirse si el helper declara sus propios `&lt;field&gt;` y trae un `id`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML del helper. **Se concatena al directorio del XML que lo declara**, no lo reemplaza: `dir="ing"` desde un XML que vive en `caja/` busca en `caja/ing/`. Para dar una ruta desde la raíz del árbol de XMLs, empezarla con `/`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="id" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No es un id de debug: es funcional y sensible.** En `type="parameter"` es la clave con la que el parámetro se guarda en `HTXOPTIONS`, así que renombrarlo pierde el valor ya configurado (y si no hay `&lt;label&gt;`, también es la etiqueta que se muestra). En combo/external sin `xml`, su presencia es lo que hace que el motor arme el contenedor del helper.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="width" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Ancho del popup del helper (px o vw). Lo leen `type="link"`, `type="media"` y `type="import"`; en combo/external/parameter no tiene efecto.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="vacio" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Opción vacía del combo. **Sólo se lee en `&lt;tabla_ex&gt;`**, que es la forma legacy del
          combo y comparte este complexType con `&lt;helper&gt;`. En un
          `&lt;helper type="combo"&gt;` el atributo equivalente es `empty`.

          No es booleano: si el valor no es `''`, `'false'` ni `'true'`, se usa como **texto del
          placeholder** de la opción vacía — o sea que `vacio="Seleccione…"` funciona y es el uso
          interesante.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="title" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Título del popup del helper.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="icon" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Icono mostrado en el botón del helper, con los nombres del set de iconos de la UI (ej. `ui-icon-wrench`). Sólo tiene efecto en `type="link"`, `type="media"` y `type="import"`; en combo/external/parameter se ignora.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="modal" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Si "true", el popup es modal (bloquea la interacción con el padre hasta cerrarlo). Sólo lo lee `type="link"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="multiple" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Habilita la selección múltiple del combo (`type="combo"`/`"comboex"`). Hay que escribir el literal `"true"`: cualquier otro valor lo deja apagado. En los otros tipos se ignora.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="text" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Si "false", suprime el texto del botón del helper y deja sólo el icono. Sólo aplica a `type="link"` y `type="media"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="ventint" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          Se acepta por compatibilidad, pero **el motor no lo lee**: no hay ninguna lectura de este
          atributo (verificado en 2026-07). Lo que existe es una **clase CSS** `ventint` que el
          motor agrega por su cuenta al abrir ventanas internas — no sale de acá. Omitirlo no
          cambia nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="height" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alto del popup del helper. Lo leen `type="link"`, `type="media"` y `type="import"`; en combo/external/parameter no tiene efecto.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="titulo" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Título del popup, pero **no es un alias universal de `title`**: en `type="link"` y `type="media"` el motor lee sólo `title` y acá `titulo` es inerte. El único tipo que lee las dos formas es `type="import"`, y ahí `title` gana si están las dos.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>CSS inline del **botón** del helper de `type="link"`/`"media"`, no del contenedor del campo: se concatena después de los `style`, `colstyle` e `inputstyle` del propio campo, así que por venir último sus reglas ganan sobre las de ellos.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="label" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto del botón que abre el popup, **sólo en `type="import"`**. En los demás tipos el atributo es inerte; para `type="parameter"` la etiqueta va en el elemento `&lt;label&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="reposition" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Reposiciona el popup del helper respecto del control que lo abre. Sólo lo leen `type="link"` y `type="media"`, y hay que escribir el literal `"true"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tag" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Tag HTML con el que se dibuja el botón del helper (por defecto `a`). Sólo lo lee `type="link"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="ajax" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          El combo no viene con las opciones adentro del HTML: se piden al servidor a medida que el
          usuario escribe. **Es la solución para combos con muchas filas**, donde armar el select
          completo hace lenta (o imposible) la carga de la pantalla.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="expand" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>Arranca el helper desplegado en lugar de cerrado.</xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="editable" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Permite escribir un valor que no esté en la lista: el combo deja de ser cerrado y acepta
          texto libre además de las opciones.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="close" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Sólo en `type="link"`: nombre de la función JavaScript que se ejecuta cuando el popup se
          cierra. Es el gancho para refrescar la pantalla padre con lo que el hijo grabó.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="ifType" mixed="true">
    <xs:annotation>
          <xs:documentation>
            Estructura condicional de un `&lt;field&gt;`:
            `&lt;if exp="..."&gt;&lt;true&gt;X&lt;/true&gt;&lt;false&gt;Y&lt;/false&gt;&lt;/if&gt;`.
            `exp` se evalúa como PHP en el servidor y el campo toma el contenido de la rama que
            corresponda. Los identificadores sin comillas se reemplazan por el valor de los
            `&lt;field&gt;` declarados en el XML: **un identificador que no esté declarado como
            `&lt;field&gt;` hace fallar la evaluación en silencio y se toma la rama `&lt;false&gt;`** —
            el síntoma es un campo vacío sin ningún error (y si ese campo es el `idCampoCond` de una
            tabla, el registro no graba).

            La comparación entera va dentro de `exp`: **`operador` y `valor` no se leen** — el parser los
            guarda pero la evaluación del `&lt;if&gt;` nunca los consume, así que los 75 XMLs que escriben
            `&lt;if operador="..."&gt;` no obtienen nada de eso.

            **Sólo el `&lt;if&gt;` de un `&lt;field&gt;` tiene esta forma.** El de `&lt;order&gt;`,
            `&lt;group&gt;`, `&lt;filters&gt;`, `&lt;fieldGroup&gt;` e `&lt;include&gt;` es otra cosa: no
            lleva `exp` ni ramas, es texto PHP que decide si el bloque entero se procesa.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="1">
      <xs:sequence>
        <xs:element name="true" type="xs:string">
          <xs:annotation><xs:documentation>Valor/expresión devuelto si la condición es truthy.</xs:documentation></xs:annotation>
        </xs:element>
        <xs:element name="false" type="xs:string" minOccurs="0">
          <xs:annotation><xs:documentation>Valor/expresión devuelto si la condición es falsy. Opcional: sin esta rama, cuando la condición da falso el campo queda vacío.</xs:documentation></xs:annotation>
        </xs:element>
      </xs:sequence>
      <xs:sequence>
        <xs:element name="verdadero" type="xs:string">
          <xs:annotation>
            <xs:documentation>
              Alias español legacy de `&lt;true&gt;`. El motor lo reconoce explícitamente como forma
              vieja: al encontrarlo activa el modo de compatibilidad del parser. Funciona, pero en
              código nuevo va `&lt;true&gt;`.
            </xs:documentation>
          </xs:annotation>
        </xs:element>
        <xs:element name="falso" type="xs:string" minOccurs="0">
          <xs:annotation>
            <xs:documentation>Alias español legacy de `&lt;false&gt;`.</xs:documentation>
          </xs:annotation>
        </xs:element>
      </xs:sequence>
    </xs:choice>
    <xs:attribute name="exp" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Expresión PHP a evaluar. Los `&lt;field&gt;` declarados son referenciables por nombre sin comillas.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="actualizaType">
    <xs:annotation>
          <xs:documentation>
            Al cambiar el campo padre, propaga su valor a otros campos/combos/grilla/helpers.
            Cada `&lt;field&gt;` declara un destino. Para combos: el valor se inserta en un campo del combo con `&lt;detalle&gt;` que define el nombre lógico.

            De cada `&lt;field&gt;` destino sólo se leen `id`, `destino` y `xml`. Cualquier otro atributo
            (`oculto`, por ejemplo, que 23 XMLs escriben acá) y el texto del tag se ignoran.
          </xs:documentation>
        </xs:annotation>
    <xs:sequence>
      <xs:element name="field" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Destino de la actualización: campo, combo o grid embebido.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="id" type="xs:string" use="required">
                <xs:annotation><xs:documentation>`id` del campo o helper destino.</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="destino" type="xs:string" use="optional">
                <xs:annotation><xs:documentation>Si el destino es un combo/grid: nombre del subcampo dentro del helper (matchea con `&lt;detalle&gt;`).</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="xml" type="xs:string" use="optional">
                <xs:annotation><xs:documentation>XML del helper destino (cuando ambiguo).</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="campo" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;` como destino de la actualización.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="id" type="xs:string" use="required">
                <xs:annotation><xs:documentation>`id` del campo o helper destino.</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="destino" type="xs:string" use="optional">
                <xs:annotation><xs:documentation>Nombre del subcampo dentro del helper destino.</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="xml" type="xs:string" use="optional">
                <xs:annotation><xs:documentation>XML del helper destino (cuando ambiguo).</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="parametroType">
    <xs:annotation>
          <xs:documentation>
            Parámetros para helpers de tipo "parameter".
            `id` (o `xml`) puede omitirse cuando se usa `retorna` sobre un set de fields locales.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="field" type="FieldType">
        <xs:annotation><xs:documentation>Campo parámetro pasado al XML hijo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="campo" type="FieldType">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="id" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>ID del XML helper al que se le pasan los parámetros.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xml" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML helper destino.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="retorna" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Lista CSV de campos cuyo valor debe regresar al padre cuando el helper se cierra.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML helper.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="jsevalType">
    <xs:annotation>
          <xs:documentation>
            Evaluación de código JavaScript al cambiar el campo. El resultado se asigna al destino.
            Debe estar presente uno de `campodestino` o `fielddestino` (alias).

            Regla crítica: para cálculos aritméticos puros usar `actxml="false"` — sin ese atributo el motor envía un PUT al server por cada cambio (en grillas grandes esto explota en cientos de requests innecesarios).
          </xs:documentation>
        </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="campodestino" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>`id` del campo destino donde se asigna el resultado del JS.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="fielddestino" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Alias inglés de `campodestino`.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="actxml" type="ActXmlValor" use="optional">
          <xs:annotation><xs:documentation>Default "true": envía request al server por cada cambio. "false": el JS se resuelve en el browser sin request (preferido para aritmética pura).</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="total" type="BoolEstricto" use="optional">
          <xs:annotation><xs:documentation>Si "true", el JS calcula el total de columna (footer del grid).</xs:documentation></xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <!-- =======================
       Campo de formulario
       ======================= -->
  <xs:complexType name="FieldType">
    <xs:annotation>
      <xs:documentation>
        Campo de formulario o columna de grid/consulta.
        Es el bloque fundamental: representa una columna SQL, un campo local (`local="true"`),
        un valor calculado (`&lt;expression&gt;`) o un control de UI sin persistencia.
        Aliases del tag: `&lt;field&gt;` (canónico) y `&lt;campo&gt;` (español).
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="label" type="LabelType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Etiqueta legible del campo (header de columna o label del input). Se usa también como clave en logs de auditoría.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="expression" type="ExpressionType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Expresión SQL para valores calculados. Va al SELECT (o HAVING si hay GROUP BY). Soporta `'[__campo__]'` para inyectar valores de campos locales.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="expresion" type="ExpressionType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Alias español de `&lt;expression&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="tipo" type="TipoType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Tipo del campo (date, numeric, check, file, geoPoint, etc.). Determina render y validación.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="formato" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Formato de visualización (ej. "dd/mm/yyyy" en fechas, "#.##0,00" en numéricos).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="format" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Alias inglés de `&lt;formato&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="size" type="SizeType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Tamaño visual del input o ancho de columna.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="maxsize" type="xs:integer" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Máximo de caracteres del input (atributo HTML `maxlength`).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="parametro" type="parametroType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Parámetros pasados al XML referenciado por `obj`/`linkint` (popups, helpers de tipo "parameter").</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="options" type="OptionsType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Lista de opciones fijas para radio/select (no dependen de SQL).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="opciones" type="OptionsType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Alias español de `&lt;options&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="help" type="HelpType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Texto de ayuda contextual del campo (tooltip).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="helper" type="HelperTypeCplx" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Helper UI: combo dinámico, link a otro XML, popup de ayuda, grilla embebida, etc.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="tabla_ex" type="HelperTypeCplx" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Tabla externa (legacy). Equivalente a `&lt;helper type="object"&gt;` o `&lt;ayuda_ex&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="ayuda" type="HelperTypeCplx" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Helper popup de ayuda inline (combo o lookup).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="ayuda_ex" type="HelperTypeCplx" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Ayuda externa (popup completo desde otro XML).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="valor" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Valor por defecto del campo. Con `eval="true"` se evalúa como PHP (ej. `date('d/m/Y')`).</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="eval" type="BoolEstricto" default="false" use="optional">
                <xs:annotation><xs:documentation>Si "true", el contenido se evalúa como expresión PHP en el server.</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="default" type="BoolEstricto" use="optional">
                <xs:annotation><xs:documentation>Si "true", se aplica sólo como default (no pisa valor existente al editar).</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="retval" type="xs:string" use="optional">
                <xs:annotation>
                  <xs:documentation>
                    **Sólo tiene sentido junto a `eval="true"`.** Sin `retval`, el contenido tiene que
                    ser una expresión PHP que devuelva el valor. Con `retval`, el contenido se ejecuta
                    como una secuencia de sentencias (se pueden declarar variables, hacer un `if`, un
                    loop) y `retval` nombra la variable de la que sale el valor final.

                    Además, con `retval` **no se hace la sustitución de `[__campo__]`** en el cuerpo:
                    el código llega tal cual se escribió.
                  </xs:documentation>
                </xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="operador" type="OperadorElementType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Operador de la condición de filtro/JOIN del campo (=, !=, like, in, between, etc.). Acepta atributos `modificador` (modifica el valor antes de comparar) y `oplogico` (and/or para concatenar con el siguiente filtro).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="operator" type="OperadorElementType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Alias inglés de `&lt;operador&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="condicion" type="CondicionType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Condición SQL del campo. Va al WHERE (o HAVING si `having="true"`/hay GROUP BY).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="condition" type="CondicionType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Alias inglés de `&lt;condicion&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="attribute" type="AttributeType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>
            **El texto del tag no es un valor: es el `id` de otro campo.** El motor copia el valor de
            ese otro campo a la propiedad Histrix que nombra el atributo `id` de este tag, o sea que
            sirve para hacer condicional un atributo del campo según un dato de la fila:
            `&lt;attribute id="print"&gt;muestra_iva&lt;/attribute&gt;` imprime la columna sólo si el
            campo `muestra_iva` de esa fila trae valor.
            **No inyecta atributos HTML** (`data-*`, `aria-*`): para eso no hay mecanismo acá.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="if" type="ifType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Condicional `&lt;if exp="..."&gt;&lt;true&gt;X&lt;/true&gt;&lt;false&gt;Y&lt;/false&gt;&lt;/if&gt;`. Los identificadores sin comillas se reemplazan por valores de `&lt;field&gt;` declarados; si no existen, evalúa a null/false.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="setvar" type="xs:string" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>
            Guarda el valor del campo en una propiedad del contenedor de esa instancia, con el nombre
            que dice el texto del tag. **No es una variable de sesión**: vive lo que vive la
            instancia. La contracara para leerla es `&lt;getvar&gt;`, que este schema no declara.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="detalle" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Nombre lógico del campo destino al volver de un popup hijo (mecanismo `&lt;importar resume&gt;`). El padre matchea por este nombre.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="prefijo" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Texto que se antepone al valor del campo al renderizar (en pantalla y PDF). Útil para etiquetas tipo "$ 1.000" o "Total: 5". También se acepta como atributo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="habilita" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            **Está al revés de lo que sugiere el nombre**: el texto del tag es el `id` del campo
            *controlador*, y el que se habilita o deshabilita es **el campo que declara
            `&lt;habilita&gt;`**. Escribirlo como dice el nombre deshabilita el campo equivocado.
            Se resuelve server-side al renderizar (no hay handler JS) y mira el **último valor
            grabado** del controlador, no lo que el usuario acaba de tipear. Cuentan como falso sólo
            `0`, `'0'`, `'false'` y `false`.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="jseval" type="jsevalType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Evaluación JS al cambiar el campo. Resultado va al `campodestino`. Para cálculos puros usar `actxml="false"` (evita request al server por cada cambio).</xs:documentation></xs:annotation>
      </xs:element>
      <!-- Campos dependientes declarativos: estado de UI resuelto en el navegador.
           XmlParser::readFieldStateRules los agrega como fórmulas sintéticas con destino
           reservado (__VISIBLE/__REQUIRED/__ENABLED) y actxml="false"; el cliente los
           aplica en Histrix.applyFieldState. -->
      <xs:element name="visibleWhen" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            Muestra u oculta **este** campo según una condición: el tag va sobre el campo controlado,
            no sobre el que dispara la condición. Al ocultarlo esconde la celda entera del campo y la
            de su `&lt;label&gt;`, y le **saca el `required`**, para que el formulario no quede trabado
            pidiendo un campo que no se ve.

            La condición se escribe como la de un `&lt;jseval&gt;`: los `id` de otros campos van **sin
            comillas** y el motor los reemplaza por su valor, así que `exige_obs == 1` muestra el campo
            cuando `exige_obs` vale 1. Ese reemplazo **convierte el valor a número**, de modo que la
            comparación contra un número funciona y la comparación contra texto —`== 'A'`— no es
            confiable.

            Se resuelve entero en el navegador: no genera SQL, no pide nada al servidor y **se
            re-evalúa con cada cambio** de cualquier campo del formulario, incluidos los que llena un
            combo o un helper. **No es lo mismo que `&lt;if&gt;` o `noshow`**, que deciden en el
            servidor una sola vez, al armar la pantalla, y no vuelven a mirar nada.

            Es el reemplazo declarativo del `&lt;customScript&gt;` que había que escribir para esto. Si
            se declara más de una vez en el mismo campo, vale la última.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="requiredWhen" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            Hace obligatorio **este** campo mientras se cumpla la condición, y le saca la marca cuando
            deja de cumplirse. Va sobre el campo que se vuelve obligatorio.

            **Un campo oculto nunca queda obligatorio**: si la condición da verdadera pero la celda del
            campo está escondida —por un `&lt;visibleWhen&gt;` o por cualquier otro motivo— el
            `required` no se aplica.

            La condición se escribe igual que la de `&lt;visibleWhen&gt;`, con las mismas reglas de
            reemplazo y la misma reactividad; el detalle está ahí. Si se declara más de una vez en el
            mismo campo, vale la última.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="enabledWhen" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            Habilita **este** campo mientras se cumpla la condición y lo deshabilita cuando deja de
            cumplirse. Va sobre el campo que se habilita.

            Deshabilitar es sólo apagar el control: el campo sigue en la pantalla y en el formulario, a
            diferencia de `&lt;visibleWhen&gt;`, que lo esconde. Tampoco toca el `required`.

            La condición se escribe igual que la de `&lt;visibleWhen&gt;`, con las mismas reglas de
            reemplazo y la misma reactividad; el detalle está ahí. Si se declara más de una vez en el
            mismo campo, vale la última.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="eval" type="xs:string" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>
            **No es JavaScript: es PHP que corre server-side al parsear el XML**, y decide si el campo
            existe. Si el código no devuelve un valor verdadero, el `&lt;field&gt;` no se agrega al
            contenedor: no se renderiza, no entra al SELECT y no graba. La forma habitual es
            `if (...) return true;`.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="preparam" type="preparamType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Condición pre-filtrado aplicada a este campo antes de la query principal.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="importar" type="importarType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Configura popup hijo que alimenta el grid via este campo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="actualiza" type="actualizaType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Al cambiar este campo, envía su valor a otros campos/combos/grids/helpers (refrescos en cascada).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="paring" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>
            Alimenta con una **condición SQL** al contenedor embebido del campo (el que abre `obj` /
            `linkint` / el helper). **No es el `&lt;paring&gt;` de `&lt;cierraproceso&gt;`**, que es
            otro mecanismo: ése mapea campos del padre al XML de cierre.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="destino" type="xs:string" use="required">
                <xs:annotation><xs:documentation>`id` del campo del contenedor embebido sobre el que se arma la condición.</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="operador" type="xs:string" use="optional">
                <xs:annotation><xs:documentation>Operador **SQL** de la condición (`=`, `!=`, `like`, `in`…), no un operador de asignación: `+=` y `-=` no significan nada acá. Si se omite, `=`.</xs:documentation></xs:annotation>
              </xs:attribute>
              <xs:attribute name="reemplazo" type="BoolDefaultActivo" default="true" use="optional">
                <xs:annotation><xs:documentation>**Está invertido**: con el atributo ausente la condición ya **reemplaza** la que tuviera ese campo. El valor útil es `reemplazo="false"`, que la **acumula** en vez de reemplazarla.</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="jsfunction" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Función JS asociada a un evento del campo (onfocus, onblur, onkeypress...). El atributo `event` declara el nombre del evento.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="event" type="xs:string" use="required">
                <xs:annotation><xs:documentation>Nombre del evento DOM (sin "on": "focus", "blur", "change"...).</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <!-- Estos se escriben como TAG hijo de <field>, no como atributo -->
      <xs:element name="pdfancho" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Ancho de la columna del **valor** en la ficha impresa. Su contraparte para la etiqueta es el atributo `size` del `&lt;label&gt;`. También se puede fijar con `PDFancho` en el `&lt;label&gt;`, que escribe la misma propiedad.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="complete" type="xs:string" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Expresión que el motor evalúa al completarse el campo. Ej: `&lt;complete&gt;numero + 1&lt;/complete&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="checklabel" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Etiqueta que acompaña al checkbox, a la derecha del control.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="htmllabel" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            Texto alternativo del encabezado de columna, y **sólo se usa cuando la etiqueta normal
            está vacía**. Ojo con el nombre: el único camino que lo renderiza **escapa el HTML**, así
            que los tags salen a la vista como texto en vez de aplicarse.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="conditionalDisplay" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            **No es una expresión: es el `id` de otro campo.** Si el valor de ese campo es falsy,
            este campo no se renderiza. Si el `id` no existe en el XML la búsqueda devuelve falso y
            el render **revienta con error**, así que el nombre tiene que estar declarado como
            `&lt;field&gt;` (puede ser `noshow="true"`).
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="onformchange" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Código a ejecutar cuando cambia cualquier campo del form.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="pdf" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            **Código PHP que reemplaza el dibujado de este campo en el PDF.** El contenido se
            ejecuta al imprimir la celda y, si devuelve algo, ese valor se usa en lugar del render
            normal del campo. Con esto se resuelven celdas que el motor no sabe dibujar (un código
            de barras, una imagen calculada, un bloque con formato propio).

            **Es ejecución de código arbitrario dentro del XML**: cualquiera que pueda editar el
            XML puede correr PHP en el servidor. Usarlo sólo si no hay alternativa declarativa, y
            tener en cuenta que un error de sintaxis acá tira abajo la impresión entera, no sólo
            la celda.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="valauto" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Si "true", al renderizar nuevo registro el `&lt;valor&gt;` se aplica como valor (vs sólo como default).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="orderName" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de columna o expresión que se usa en el `ORDER BY` en lugar del campo. Es la forma de ordenar por un campo calculado o por otra columna que la mostrada — el `&lt;expression&gt;` escrito dentro de un `&lt;field&gt;` de `&lt;order&gt;` no se lee.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="labelStyle" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>CSS inline de la etiqueta del campo, incluida la del checkbox. El motor lo lee junto con `Formstyle` y toma el primero que esté seteado: **si el campo declara los dos, gana `Formstyle`**. Se acepta también como atributo del `&lt;label&gt;`, donde escribe la misma propiedad.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="encode" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Simbología del código de barras cuando el campo dibuja un código: el valor se pasa tal cual
          al generador. Si falta se usa `int25` (Interleaved 2 of 5). El valor en uso en el repo es
          "QR".
          **No confundir con el `encode` del `&lt;tipo&gt;`**, que es otra cosa: ahí define el
          algoritmo de encriptación y sólo se mira con `Type="password"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="search" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Si "true", el campo participa de la búsqueda aunque tenga opciones cargadas (se lo consulta al re-aplicar las condiciones de la consulta). Va en el `&lt;field&gt;`: escrito en el `&lt;helper&gt;` queda en el helper, donde no lo lee nadie.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="id_int" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Campo interno asociado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="id_cont" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Contenedor interno asociado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="invalid" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Motivo que se muestra cuando el campo no valida. Trabaja junto a `validar="true"`: si el
          valor del campo (o su último valor grabado) no es `true` ni `1`, el botón de procesar queda
          deshabilitado y este texto es el tooltip que explica por qué.
          Es un **atributo**: escrito como elemento (`&lt;invalid&gt;…&lt;/invalid&gt;`) el mensaje no
          se muestra nunca.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="required" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          Campo obligatorio. El form no graba si está vacío.
          **Sin `default` declarado a propósito**: el motor lo evalúa con dos criterios a la vez —
          hay lecturas que tratan la **ausencia** como obligatorio (comparan contra `'false'`) y otras
          que exigen el literal "true". Conviene ponerlo siempre explícito.
          Ojo: es un **atributo**; escrito como elemento (`&lt;required&gt;true&lt;/required&gt;`) no
          lo lee nadie y el campo no queda obligatorio.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="refresh" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Sólo actúa en campos de un contenedor embebido, y sólo con el literal "true": al cambiar el
          valor recarga la cabecera del formulario y dispara la grabación del contenedor de origen.
          **No es el mecanismo para refrescar campos dependientes** — eso lo dispara el campo de
          origen con `&lt;actualiza&gt;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="validRow" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Convierte al campo en validador de la fila: si su valor es `0`, la fila se descarta y no
          graba. **El motor sólo mira si el atributo está presente, no su valor**, así que
          `validRow="false"` lo activa igual y el `default="false"` de esta declaración es engañoso:
          para no validar hay que **omitir** el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="acumula" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          La celda **muestra el acumulado** de la columna hasta esa fila en lugar del valor propio de
          la fila (y recibe la clase de celda sumada). Exige el literal "true", y necesita que el
          campo lleve además `suma="true"`: sin la suma el acumulado no se calcula y la columna sale
          en `0`. Con `norepeat` no acumula.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="path" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Directorio de lectura/subida de los archivos de este campo (`file`, `files`, `dir`), que se
          concatena al del contenedor. En los adjuntos del PDF (`attach="true"`) puede además nombrar
          una columna: en ese caso ese tramo de la ruta sale del valor de la columna en cada fila.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="retroalimenta" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No tiene nada que ver con popups ni con devolverle valores al padre.** Convierte el
          resultado del `&lt;if&gt;` del campo en un **acumulado fila a fila** (un running total que
          arranca de nuevo en la fila 0) y desplaza la suma normal de la columna.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="clasefila" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          El **valor del campo en cada fila** se agrega como clase CSS del `&lt;tr&gt;`; si hay varios
          campos marcados, se concatenan. **Cualquier valor no vacío lo activa, incluido "false"**:
          para desactivarlo hay que omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="autocompletar" type="ModoAutocompletado" use="optional">
      <xs:annotation>
        <xs:documentation>
          Alias español de `autocomplete`, y **no es el atributo HTML del input**: es el modo del
          helper de ayuda del campo. Los valores con efecto son "true" (autocompletado sobre el
          input) y "full" (autocompletado + popup de la ayuda); `on`/`off` no prenden ni apagan nada.
          Sin helper de ayuda declarado no hace nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="forceUpdate" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Si "true", incluye el campo en el UPDATE aunque su valor no haya cambiado respecto al original.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="log" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Decide si los cambios de **este campo** entran a la auditoría. **Está al revés de lo que
          sugiere el nombre**: con el atributo ausente el campo **se audita** igual, y el único valor
          con efecto es `log="false"`, que lo excluye. La auditoría además tiene que estar prendida en
          el contenedor: con `&lt;histrix log="false"&gt;` no se audita ningún campo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="obj" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML referenciado como objeto embebido (grid hijo o ficha link).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="bloqueafila" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Con el literal "true", las filas en las que **el valor de este campo sea `1` o `true`**
          quedan bloqueadas: se marcan como no editables y se pintan grisadas. Con cualquier otro
          valor en la celda, la fila queda normal.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="showObjTabla" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Muestra dentro de la celda la grilla del contenedor embebido del campo (el que trae `obj`).
          Con esa grilla visible, el valor propio del campo se muestra sólo si además lleva
          `showValor`. Combinado con `lazyLoad="true"` difiere la carga.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="objdir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML referenciado por `obj`. Con barra inicial (`/carpeta`) **reemplaza** el subdirectorio vigente; sin barra se concatena a él.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="linkdir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML al que apunta `linkint`. Si falta, se usa el directorio del XML del contenedor.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="linkint" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML interno al que linkea el campo (genera link clickable). En un campo con `attach="true"` cumple una segunda función: lo que se adjunta al PDF no es un archivo del disco sino **el PDF generado por ese XML**.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="attach" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Al generar el PDF adjunta un archivo por cada renglón, usando **el valor del campo** como
          nombre. La ruta se arma con el directorio de datos, el `path` del contenedor y el `path` del
          campo — que puede nombrar una columna, en cuyo caso ese tramo sale del valor de la fila. Si
          el archivo no existe **no adjunta nada y no avisa**. Con `watermark` el nombre visible del
          adjunto se toma de otra columna, y con `linkint` en vez de buscar un archivo se genera el
          PDF de ese XML y se adjunta ése. Sólo actúa con el literal "true".
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="preventLoop" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Va en el campo **destino** de un `&lt;jseval&gt;`: cuando el cálculo le escribe un valor
          nuevo no se dispara su evento `change`, así que los `&lt;jseval&gt;` que dependen de él no
          se recalculan y la cascada se corta ahí. Sólo lo activa el literal "true".
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="valop" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Nada que ver con filtros opcionales, y sólo hace algo con el literal "true": las
          `&lt;options&gt;` del campo **dejan de aplicarse al armar el input** (el combo sale sin
          opciones) y el valor de cada fila se traduce por el texto de la opción al recorrer el
          resultado. Alcanza con un campo con `valop` para que el contenedor procese el resultado
          fila por fila.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="customTotal" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Con el literal "true" la celda de total del pie **no suma la columna**: el número sale de
          evaluar el `&lt;if&gt;` del campo sobre la fila de totales. Es la forma de armar un total
          que depende de otros totales (un porcentaje, un neto), y necesita que el campo tenga
          `&lt;if&gt;`: de ahí saca el valor.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="valoropcion" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Si "true", al grabar usa el VALUE de la opción seleccionada del combo, no el texto visible.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="checkType" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No valida tipos, y viene activo.** Sólo aplica a campos `tipo="date"`: prendido — que es
          lo que pasa cuando el atributo falta — convierte un valor de 8 dígitos `ddmmyyyy` en fecha.
          El único valor útil es `checkType="false"`, que desactiva esa conversión; el
          `default="false"` de esta declaración es engañoso. Nada de esto valida números.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="tipoOrden" type="OrdenTipo" use="optional" default="ASC">
      <xs:annotation>
        <xs:documentation>
          Dirección de orden del campo. `orderType` es el nombre inglés pero **no son alias
          simétricos**: el motor toma el primero que esté seteado, así que con los dos declarados gana
          `tipoOrden`. Y el reordenamiento en memoria de la tabla temporal reconoce **sólo `DESC` en
          mayúsculas**: con `tipoOrden="desc"` la consulta sale ordenada y la tabla temporal no.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="setOriginalValue" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Con el literal "true", al restaurar el campo se vuelve al **valor original del registro**
          aun cuando `valauto="true"` mandaría a conservar el `&lt;valor&gt;` declarado. Sin este
          atributo la restauración ya ocurre con `valauto` en "false", que es el comportamiento por
          omisión: sólo hace falta para ganarle a un `valauto="true"`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attributeGroup ref="UiFlags" />
    <xs:attributeGroup ref="idGrup" />
    <xs:attributeGroup ref="CalendarGrup" />
    <xs:attributeGroup ref="KanbanGrup" />
  </xs:complexType>

  <!-- =======================
       Grupo de campos
       ======================= -->
  <xs:complexType name="FieldGroupType">
    <xs:annotation>
      <xs:documentation>
        Agrupador visual/lógico de campos. Se renderiza en el orden de aparición.
        Los `&lt;field&gt;` sueltos (fuera de cualquier fieldGroup) se renderizan al final del form.

        Con `xml="..."` + `dir="..."` incluye un XML externo como fieldGroup (resuelto server-side).
        Útil para compartir campos entre formularios (ej. datos de agente de retención compartidos entre IG e IB).
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="if" type="xs:string">
        <xs:annotation><xs:documentation>Inclusión condicional del fieldGroup.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="field" type="FieldType">
        <xs:annotation><xs:documentation>Campo dentro del grupo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="campo" type="FieldType">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="validation" type="validationType">
        <xs:annotation>
          <xs:documentation>
            Validación de formulario declarada dentro del grupo. **Sólo se registra si el
            `&lt;if&gt;` del grupo dio verdadero**, y por eso es la forma de declarar una validación
            condicional: por ejemplo, exigir la cuenta contable únicamente si el módulo de
            contabilidad está activo.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="cierraproceso" type="cierraProcesoType">
        <xs:annotation>
          <xs:documentation>
            Proceso de cierre declarado dentro del grupo. Igual que la `&lt;validation&gt;` de acá:
            sólo se registra si el `&lt;if&gt;` del grupo dio verdadero.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="id" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Identificador del grupo (sirve para targeting CSS y referencias).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="valauto" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Aplica `valauto` a todos los campos del grupo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xml" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML externo a incluir como fieldGroup (resuelto server-side).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML externo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="label" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Etiqueta del grupo (header del fieldset).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attributeGroup ref="UiFlags" />
  </xs:complexType>

  <!-- =======================
       Ordenamiento
       ======================= -->
  <xs:complexType name="OrderType">
    <xs:annotation>
          <xs:documentation>
            Definición de orden de la consulta (`ORDER BY`). Cada `&lt;field&gt;` declara una columna y su dirección.
            Va dentro de la `&lt;table&gt;`/`&lt;tabla&gt;`: colgado directo del `&lt;form&gt;`/`&lt;consulta&gt;`
            o de un `&lt;fieldGroup&gt;` no se lee y el `ORDER BY` no se aplica.
          </xs:documentation>
        </xs:annotation>

    <xs:sequence>
      <xs:element name="if" type="ifType" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>Orden condicional: aplica el `&lt;field&gt;` que esté dentro de la rama true/false según la expresión.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="field" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Campo por el que ordenar. Si tiene `&lt;expression&gt;`, se ordena por esa expresión SQL.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:sequence>
          </xs:sequence>
          <xs:attribute name="id" type="xs:string" use="required">
            <xs:annotation><xs:documentation>`id` del campo por el que ordenar.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="tabla" type="xs:string" use="optional">
            <xs:annotation><xs:documentation>Tabla del campo (cuando ambiguo en JOINs).</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="table" type="xs:string" use="optional">
            <xs:annotation><xs:documentation>Alias inglés de `tabla`.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="tipoOrden" type="OrdenTipo" use="optional" default="ASC">
            <xs:annotation><xs:documentation>Dirección de orden. Alias inglés: `orderType`.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="orderType" type="OrdenTipo" use="optional">
            <xs:annotation><xs:documentation>Dirección de orden (inglés). Coexiste con `tipoOrden`; el motor lee ambos.</xs:documentation></xs:annotation>
          </xs:attribute>
        </xs:complexType>
      </xs:element>
      <xs:element name="campo" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`: el motor lo toma como fallback cuando el bloque de orden no tiene ningún `&lt;field&gt;`.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:attribute name="id" type="xs:string" use="required">
            <xs:annotation><xs:documentation>`id` del campo por el que ordenar.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="tabla" type="xs:string" use="optional">
            <xs:annotation><xs:documentation>Tabla del campo (cuando ambiguo en JOINs).</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="table" type="xs:string" use="optional">
            <xs:annotation><xs:documentation>Alias inglés de `tabla`.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="tipoOrden" type="OrdenTipo" use="optional">
            <xs:annotation><xs:documentation>Dirección de orden.</xs:documentation></xs:annotation>
          </xs:attribute>
          <xs:attribute name="orderType" type="OrdenTipo" use="optional">
            <xs:annotation><xs:documentation>Dirección de orden (inglés).</xs:documentation></xs:annotation>
          </xs:attribute>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="ordenaTemporal" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Aplica el orden sobre la tabla temporal del grid en lugar de sobre la consulta original.
          Es lo que hace que un grid de carga respete el orden declarado en vez de mostrar los
          renglones en el orden en que se fueron cargando.

          **Cuidado si hay más de un bloque de orden en la misma tabla**: el valor se asigna al
          procesar cada bloque, así que gana el último y puede apagar el de un bloque anterior.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="cierraProcesoType">
    <xs:annotation>
          <xs:documentation>
            Cierre de proceso: ejecuta otro XML como parte de la grabación del actual.
            Con `processFirst="true"` en el `&lt;histrix&gt;` del hijo, el hijo se ejecuta antes que el padre
            (útil para obtener numeradores en el hijo antes de que el padre grabe).

            Caso típico: `recibos` y `pagos` comparten `cajmovimientos_ing.xml` como cierraproceso,
            diferenciando con el parámetro `tesconie` ('I' o 'E').
          </xs:documentation>
        </xs:annotation>
    <xs:sequence>
      <xs:element name="paring" minOccurs="1" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Mapea un campo del padre a un campo del XML cierraproceso.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="destino" type="xs:string" use="required">
                <xs:annotation><xs:documentation>`id` del campo destino en el XML cierraproceso.</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Nombre del XML cierraproceso (sin .xml).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="condition" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Condición PHP que decide si el cierraproceso se ejecuta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML cierraproceso.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <!-- =======================
       Validación
       ======================= -->
  <xs:complexType name="fieldValidationsType">
    <xs:annotation>
          <xs:documentation>
            Validaciones **por campo**, en plural. Es un mecanismo distinto del `&lt;validation&gt;`
            singular: construye una validación por cada `&lt;field&gt;` del bloque, que compara el valor
            del campo contra el resultado de una `&lt;expression&gt;` SQL usando el `&lt;operator&gt;`.

            **El plural es obligatorio.** Con `&lt;validation&gt;` (singular) el bloque se ignora en
            silencio: el singular busca `&lt;condition&gt;` y `&lt;message&gt;` directos y no mira los
            `&lt;field&gt;`. No hay error en ningún log — la validación simplemente deja de existir.
          </xs:documentation>
        </xs:annotation>
    <xs:sequence>
      <xs:element name="field" minOccurs="1" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Campo a validar. El `id` es el campo del form cuyo valor se compara.</xs:documentation></xs:annotation>
        <xs:complexType>
          <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element name="expression" type="xs:string">
              <xs:annotation><xs:documentation>SELECT cuyo resultado se compara con el valor del campo. Soporta `'[__campo__]'` para interpolar otros campos del form.</xs:documentation></xs:annotation>
            </xs:element>
            <xs:element name="operator" type="xs:string">
              <xs:annotation><xs:documentation>Operador de comparación (`==`, `!=`, ...). Default `==`. Suele ir en CDATA cuando lleva `&lt;` o `&gt;`.</xs:documentation></xs:annotation>
            </xs:element>
            <xs:element name="message" type="xs:string">
              <xs:annotation><xs:documentation>Mensaje de error si la validación falla. Default "Valor invalido".</xs:documentation></xs:annotation>
            </xs:element>
            <xs:element name="value">
              <xs:annotation><xs:documentation>Valor literal contra el que comparar, como alternativa a `&lt;expression&gt;`. Con `eval="true"` el contenido se evalúa como PHP.</xs:documentation></xs:annotation>
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:string">
                    <xs:attribute name="eval" type="BoolEstricto" use="optional" default="false" />
                  </xs:extension>
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
          </xs:choice>
          <xs:attribute name="id" type="xs:string" use="required" />
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="validationType">
    <xs:annotation>
          <xs:documentation>
            Validación del formulario, en singular: `&lt;condition&gt;` + `&lt;message&gt;`. Si la
            condición no se cumple se muestra el mensaje y no se graba.

            **La condición es JavaScript que corre en el browser, no SQL ni PHP.** Se evalúa
            reemplazando los nombres de campo por el valor del input correspondiente del DOM, así que
            sólo ve campos que existan como input: con `oculto="true"` el campo no se renderiza y la
            validación **nunca dispara**; con `noshow="true"` el input sigue en el DOM y sí funciona.
            Los atributos `operador` y `valorde` que admite el tipo de la `&lt;condition&gt;` pertenecen
            a la `&lt;condicion&gt;` SQL de un campo: acá no se leen.

            No confundir con `&lt;validations&gt;` (plural), que es otro mecanismo — compara el valor de
            un campo contra el resultado de un SELECT. Y en singular **no hay alias español**: hay 20
            validaciones escritas con `&lt;condicion&gt;` que no existen.
          </xs:documentation>
        </xs:annotation>
    <xs:sequence>
      <xs:choice minOccurs="1" maxOccurs="1">
        <xs:element name="condition" type="CondicionType">
          <xs:annotation>
            <xs:documentation>Condición (expresión) que debe cumplirse para pasar la validación. Acepta atributo `operador` opcional cuando la expresión usa un comparador SQL.</xs:documentation>
          </xs:annotation>
        </xs:element>
      </xs:choice>
      <xs:element name="message" type="xs:string" minOccurs="1" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Mensaje a mostrar si la condición no se cumple.</xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="optional" type="BoolEstricto" use="optional" />
  </xs:complexType>

  <!-- =======================
       Movimiento
       ======================= -->
  <xs:complexType name="MovimientoType">
    <xs:annotation>
      <xs:documentation>
        Lista de XMLs relacionados que el motor procesa en cascada al grabar el padre.
        Útil para INSERT/UPDATE en múltiples tablas relacionadas (cabecera + detalles).
      </xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element name="relacion" type="RelacionType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Referencia a un XML hijo que se procesa como parte del movimiento.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="cabecera" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML cabecera (padre lógico del movimiento).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Directorio donde buscar el XML de `cabecera`. **Sólo afecta a `cabecera`**: las
          `&lt;relacion&gt;` hijas resuelven su propio XML por su cuenta y no heredan este valor.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <!-- =======================
       Relación
       ======================= -->
  <xs:complexType name="RelacionType">
    <xs:annotation>
          <xs:documentation>Referencia a otro XML que se procesa al grabar el actual.</xs:documentation>
        </xs:annotation>
    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Nombre del XML relacionado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML relacionado.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <!-- =======================
       Filtros
       ======================= -->
  <xs:complexType name="FilterType">
    <xs:annotation>
          <xs:documentation>
            Filtros adicionales mostrados en la barra superior del listado.
            Recordar: un filtro debe declararse en `&lt;filters&gt;` Y como `&lt;field&gt;` (puede ser `oculto="true"`).

            Sólo se leen los `&lt;field&gt;`/`&lt;campo&gt;` hijos: un `&lt;label&gt;` o un
            `&lt;tabla_ex&gt;` colgado directo del bloque no genera ningún filtro. Y si en la misma tabla
            se escriben los dos tags, `&lt;filters&gt;` y `&lt;filtros&gt;`, la segunda lectura **resetea
            el `auto`** de la primera.
          </xs:documentation>
        </xs:annotation>
    <xs:sequence>
      <xs:element name="field" type="FieldType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Campo de filtro (cada uno genera un input en la barra de filtros).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="campo" type="FieldType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`: el motor lee las dos grafías como filtro.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="if" type="xs:string" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>
            Condición de procesamiento del bloque entero: es texto PHP que se evalúa antes de leer
            los filtros y, si da falso, el bloque no se procesa. **No es el `&lt;if&gt;` de un campo**:
            no lleva `exp` ni ramas `&lt;true&gt;`/`&lt;false&gt;`, y su resultado no es un valor sino
            un sí/no sobre si el bloque existe.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="auto" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Con `auto="true"` los filtros se aplican solos al cambiar cualquier valor, sin botón
          "filtrar". La comparación es estricta contra el texto `true`: `1` no sirve.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:group name="FieldsOrGroups">
    <xs:choice>
      <xs:element name="field" type="FieldType" />
      <xs:element name="campo" type="FieldType" />
      <xs:element name="fieldGroup" type="FieldGroupType" />
    </xs:choice>
  </xs:group>

  <xs:complexType name="AttributeType">
    <xs:annotation>
          <xs:documentation>
            **No inyecta HTML: hace condicional un atributo de Histrix según el valor de otra columna.**
            El `id` es el nombre del atributo Histrix que se va a setear en este campo (`print`,
            `noshow`, `oculto`, `linkint`…) y **el texto del tag es el `id` de otro campo**, cuyo valor
            se copia en ese atributo. Ejemplo real:
            `&lt;attribute id="print"&gt;muestra_iva&lt;/attribute&gt;` — el campo se imprime o no según
            lo que traiga la columna `muestra_iva` en la fila.

            Si el campo nombrado no existe o viene sin valor, el atributo queda vacío (o sea, apagado).
            Para `data-*` o `aria-*` no sirve.
          </xs:documentation>
        </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="id" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Nombre del atributo HTML (ej. "data-tooltip", "aria-label").</xs:documentation></xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="parameterTipo">
    <xs:annotation>
          <xs:documentation>
            Parámetro para helpers/links (mapeo source→target).
            Histrix convierte fechas ISO (`yyyy-mm-dd`) a `dd/mm/yyyy` al pasarlas entre XMLs.
            Para fichas con `preFetch="true"`, el `target` debe coincidir con un campo `esClave="true"` de la ficha destino.
            Los nombres son `source` y `target`: **`id=` y `value=` no se leen**, un `&lt;parameter&gt;`
            escrito así no transfiere nada.
          </xs:documentation>
        </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="source" type="xs:string" use="required">
          <xs:annotation><xs:documentation>`id` del campo origen (en el XML padre).</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="target" type="xs:string" use="required">
          <xs:annotation><xs:documentation>`id` del campo destino (en el XML hijo/helper).</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="operator" type="OperadorCondicion" use="optional" default="=">
          <xs:annotation><xs:documentation>Operador SQL aplicado al pasar el valor. Default "=".</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="operador" type="OperadorCondicion" use="optional">
          <xs:annotation><xs:documentation>Alias español de `operator`.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="replace" type="BoolEstricto" use="optional">
          <xs:annotation>
            <xs:documentation>
              En vez de agregar una condición sobre el campo destino, **reemplaza el valor** del
              campo destino por el que viene del padre. Se usa cuando el hijo tiene que arrancar
              con un dato del padre en un campo editable, no filtrado por él.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <!-- =======================
       Tabla dentro del form
       ======================= -->
  <xs:complexType name="TableType">
    <xs:annotation>
      <xs:documentation>
        Tabla SQL fuente de datos (`&lt;table&gt;` o alias español `&lt;tabla&gt;`).
        Contiene los campos del form, opcionalmente grupos, orden, filtros, validaciones, movimientos, cierraproceso.
        El `id` es el nombre de la tabla en la BD.
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="movimientos" type="MovimientoType">
        <xs:annotation><xs:documentation>Relaciones a otros XMLs que se procesan al grabar (cascada de inserts/updates).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="ejecuta" type="xs:string">
        <xs:annotation><xs:documentation>SQL/PHP arbitrario a ejecutar. En `tipo="nosql"` Histrix reemplaza `'nombre_campo'` (entre comillas) por el valor del campo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="cierraproceso" type="cierraProcesoType">
        <xs:annotation><xs:documentation>XML hijo que se procesa al cerrar/grabar este. Con `processFirst="true"` el hijo se ejecuta antes que el padre.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="validation" type="validationType">
        <xs:annotation><xs:documentation>Validación previa a la grabación con `&lt;condition&gt;` + `&lt;message&gt;`: si la condición falla, se muestra el mensaje y se aborta. **La `&lt;condition&gt;` es JavaScript que se evalúa en el browser**, no SQL — pese a estar tipada igual que la `&lt;condicion&gt;` de un campo, acá no se leen `operador`, `valorde` ni `having`. Y como sólo ve los campos que existen como input en el DOM, un campo con `oculto="true"` no está y la condición nunca dispara (con `noshow="true"` sí, porque el input queda en el DOM). Para validar un campo contra una consulta SQL, ver `&lt;validations&gt;` (plural), que es otro mecanismo.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="validations" type="fieldValidationsType">
        <xs:annotation><xs:documentation>Validaciones por campo (`&lt;field&gt;` con `&lt;expression&gt;` + `&lt;operator&gt;` + `&lt;message&gt;`). **El plural es obligatorio**: en singular el bloque se ignora sin avisar.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="filters" type="FilterType">
        <xs:annotation><xs:documentation>Filtros adicionales (barra superior). Equivalente a `&lt;filtros&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="filtros" type="FilterType">
        <xs:annotation><xs:documentation>Alias español de `&lt;filters&gt;`, pero **no del todo equivalente**: el motor procesa primero `&lt;filtros&gt;` y después `&lt;filters&gt;`, y la segunda pasada reasigna el `auto` de la barra sin condición, así que el `auto` declarado en `&lt;filtros&gt;` **se pierde**. Los `&lt;field&gt;` se leen normal en las dos formas. Si se usa `auto`, escribir la barra como `&lt;filters&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="preconsultas" type="preconsultasType">
        <xs:annotation><xs:documentation>**No ejecuta SQL.** Es una lista de otros XMLs que se consultan antes de la consulta principal y cuyas filas quedan disponibles para ésta. La estructura real es `&lt;preconsultas&gt;&lt;xmlfile id="otro.xml"/&gt;&lt;/preconsultas&gt;`, un `&lt;xmlfile&gt;` por consulta previa (69 usos en el repo). Ojo que el contenido está tipado como libre: un `&lt;xmlfile&gt;` mal escrito no lo detecta la validación y el bloque queda inerte.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="importar" type="importarType">
        <xs:annotation><xs:documentation>Popup hijo para alimentar el grid actual.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="sql" type="xs:string">
        <xs:annotation><xs:documentation>SQL raw. Sólo se reemplaza `[__campo__]`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:group ref="FieldsOrGroups" />
      <xs:element name="group" type="groupType">
        <xs:annotation><xs:documentation>Campos por los que agrupar (genera `GROUP BY`). Requiere `tabla="X"` en cada `&lt;field&gt;` cuando la columna está en múltiples tablas del JOIN.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="order" type="OrderType">
        <xs:annotation><xs:documentation>Orden de la consulta (genera `ORDER BY`).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="orden" type="OrderType">
        <xs:annotation><xs:documentation>Alias español de `&lt;order&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="grafico" type="GraficoType">
        <xs:annotation><xs:documentation>Gráfico asociado a la tabla (líneas/barras/torta).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="status" type="KanbanStatusType">
        <xs:annotation><xs:documentation>Columna de un tablero `tipo="kanban"`: un valor del campo declarado en `statusField`. Se ignora en cualquier otro tipo de vista.</xs:documentation></xs:annotation>
      </xs:element>
      <!--
        El tablero NO declara un alias `<column>` a propósito, aunque el nombre se haya discutido:
        `<column>` ya existe en este schema como hijo de `<serie>` (la columna que aporta datos a una
        serie de un chart). Declararlo también acá deja dos elementos distintos con el mismo nombre,
        y la referencia generada colapsa los dos en una sola entrada. El tag del tablero es `<status>`,
        que además es el que hace juego con el atributo `statusField`.
      -->
      <!--
        `<filtro_ex>` (con su `<tabla_ex>` y `<filtra>`) NO se declara a propósito.
        Está deprecado de forma fatal: un XML que lo tenga como hijo de <table> **corta la request**
        e imprime un mensaje de "tag deprecated" — la pantalla no carga. Dejarlo sin declarar es
        intencional: el error de validación es la única señal automática de que ese XML está roto.
        Migración: un `<field>` en `<filters>` + `<helper type="combo">` sobre la tabla externa.
      -->
      <xs:element name="select" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>SELECT crudo que reemplaza la consulta que el motor armaría con los `&lt;field&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="selectCode" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            Bloque de código PHP que llena la grilla a mano, en lugar del SELECT que armaría el XML:
            se ejecuta al cargar los datos y tiene que dejar armado un arreglo con un elemento por
            renglón, y cada renglón como pares campo =&gt; valor. Si devuelve algo, ese contenido
            reemplaza por completo al resultado de la consulta.

            Dos trampas: **no corre si el contenedor tiene `dataSource`**, que tiene prioridad; y si
            el código falla, la grilla queda vacía sin ningún aviso en pantalla — el error va al log.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="onRowClick" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>JS a ejecutar al hacer clic en una fila de la grilla.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="helper" type="HelperTypeCplx">
        <xs:annotation>
          <xs:documentation>
            Helper de importación a nivel de tabla (`type="import"`): define de dónde se traen
            renglones para llenar la grilla. **Sólo se mira el primer `&lt;helper&gt;` hijo de la
            tabla**: si hay más de uno, los siguientes se ignoran en silencio.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Nombre de la tabla SQL base.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="label" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Etiqueta legible de la tabla. **En la práctica no se ve en ninguna parte**: el motor la toma
          como título inicial del contenedor y unas líneas después la pisa siempre con el título
          declarado en el `&lt;histrix&gt;` — y si el `&lt;histrix&gt;` no trae título, la pisa con
          vacío. Ni la pestaña, ni el PDF, ni el nombre del archivo exportado la usan, aunque hay
          cientos de XMLs que la escriben. El título va en el `&lt;title&gt;`/`&lt;titulo&gt;` del
          `&lt;histrix&gt;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="idCampoCond" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>`id` del campo que habilita la grabación de la fila: si su valor es falsy, el INSERT/UPDATE de esa fila no se ejecuta. Cuentan como falsy el vacío (o sólo espacios), `0`, `'0'`, `0.00`, `'0.00'`, `false` y `'false'`; cualquier otra cosa graba, incluido el literal `"true"`. **Va en el `&lt;table&gt;`**: escrito en el `&lt;form&gt;`/`&lt;consulta&gt;` se descarta en silencio (30 XMLs del repo lo tienen ahí y no hace nada).</xs:documentation></xs:annotation>
    </xs:attribute>
    <!-- Formas alternativas del mismo atributo (ver la descripción de cada una) -->
    <xs:attribute name="CondExp" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Condición de grabación por expresión, **complemento de `idCampoCond`** y no un reemplazo: el motor toma el último valor grabado del campo nombrado en `idCampoCond` (o `0` si viene vacío), le pega este texto a la derecha y evalúa el resultado como expresión PHP; si da falso, la fila no se graba. O sea que acá va sólo el operador y el término derecho — por ejemplo `&gt; 0` o `== 5`. Estando presente, reemplaza la evaluación truthy simple de `idCampoCond`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="idCampoCant" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>`id` de un campo cuyo valor es la **cantidad de veces que se repite la grabación** de la fila: con `idCampoCant="cant"` y `cant=3`, el movimiento inserta tres registros iguales en lugar de uno. Vacío o ausente equivale a 1, y si el campo contiene una grilla embebida se usa la cantidad de filas de esa grilla. **No valida nada**: no tiene relación con rechazar cantidades en cero.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          CSS inline que se emite como atributo `style` de la `&lt;table&gt;` HTML de la grilla, tanto
          en la vista web como en la mobile.
          **Ojo que reemplaza el default del motor**, que es `float:left;clear:left;`: si sólo
          querés agregar una regla conviene repetir esas dos, porque al pisarlas cambia el
          layout de la grilla. Y `styleTbody` —que este mismo XSD recomendaba antes para el
          `&lt;tbody&gt;`— **no existe en ninguna forma**: los 265 usos del repo no hacen nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="ajusteDebe" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>`id` del campo donde se imputa el ajuste al Debe. El valor se transfiere al contenedor, pero **hoy ningún consumidor del motor lo lee**: escribirlo no cambia nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="ajusteHaber" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>`id` del campo donde se imputa el ajuste al Haber. El valor se transfiere al contenedor, pero **hoy ningún consumidor del motor lo lee**: escribirlo no cambia nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="as" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Alias SQL de la tabla base. Necesario cuando la misma tabla entra más de una vez en la
          consulta (por ejemplo un JOIN a sí misma) para poder calificar las columnas sin ambigüedad.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="showCantidad" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Muestra la cantidad de renglones de la grilla. Ojo con la grafía: es exactamente
          `showCantidad` — `showCant` y `showcantidad` no se leen.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <!-- =======================
       Pre-consultas / Temp Table / Importar
       ======================= -->
  <xs:complexType name="preconsultasType">
    <xs:annotation>
      <xs:documentation>
        Bloque de pre-consultas/validaciones ejecutadas antes de la consulta principal.

        La forma canónica es `&lt;xmlfile id="mi_consulta"/&gt;` (69 usos en el repo): cada uno nombra
        otro XML que se ejecuta antes, y sus resultados quedan disponibles para la consulta de esta
        pantalla. Se usa para preparar datos —llenar una tabla temporal, recalcular un saldo— sin
        meter ese SQL en la consulta principal.

        El contenido está declarado permisivo a propósito, así que **acá el schema no valida nada**:
        un tag mal escrito adentro de este bloque no da error y simplemente no hace nada.
      </xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax" />
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="tempTableType" mixed="true">
    <xs:annotation>
      <xs:documentation>
        Definición de tabla temporal usada como fuente del grid (típicamente en flujos de importación).

        La forma canónica es una lista de `&lt;row&gt;`, cada uno con los `&lt;field&gt;` de un renglón
        (315 usos en el repo): con eso se precarga el grid con renglones fijos sin que exista una
        tabla real detrás. También se acepta SQL de creación de la temporal.

        El contenido está declarado permisivo a propósito, así que **acá el schema no valida nada**:
        un tag mal escrito adentro de este bloque no da error y simplemente no hace nada.
      </xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax" />
    </xs:sequence>
    <xs:attribute name="id" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de la tabla temporal.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="importarFieldType" mixed="true">
    <xs:annotation>
          <xs:documentation>
            Mapeo origen→destino dentro de `&lt;importar&gt;`. **El `id` es el campo origen y el texto
            del tag es el campo destino**; con el texto vacío se usa el mismo nombre para los dos.
            Ese texto es significativo, no un comentario: borrarlo o comentarlo rompe el mapeo.
          </xs:documentation>
        </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="id" type="xs:string" use="required">
          <xs:annotation><xs:documentation>`id` del campo del padre que recibe.</xs:documentation></xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="importarType" mixed="true">
    <xs:annotation>
          <xs:documentation>
            Configura un popup hijo que alimenta el grid/form actual con datos seleccionados.
            Cada `&lt;field&gt;` (o su equivalente `&lt;parameter&gt;`) mapea un campo del hijo con uno
            del padre.

            Cuidado: al volver del popup, el renglón se busca por los campos `esClave` del grid. Si un
            campo `esClave` del padre no se pasa al hijo y no vuelve via `&lt;detalle&gt;`, el matcheo
            falla y se crea una fila nueva (fantasma) en lugar de actualizar la existente.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="preparam" type="preparamType">
        <xs:annotation><xs:documentation>Pre-condición que se aplica al popup hijo antes de ejecutar su query.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="param" type="preparamType">
        <xs:annotation><xs:documentation>Parámetro pasado al popup hijo (legacy de `preparam`).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="field" type="importarFieldType">
        <xs:annotation><xs:documentation>Mapeo origen→destino entre hijo y padre. El `id` es el origen y **el texto del tag es el destino**; con el texto vacío se usa el mismo nombre en los dos lados. Ojo: ese texto es significativo, no un comentario.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="parameter" type="parameterTipo">
        <xs:annotation><xs:documentation>Forma equivalente a `&lt;field&gt;` con la sintaxis `source`/`target`. Las dos formas registran exactamente el mismo mapeo, así que **no hay diferencia funcional**: `&lt;parameter source="X" target="Y"/&gt;` equivale a `&lt;field id="X"&gt;Y&lt;/field&gt;`. Se procesa después de los `&lt;field&gt;`, así que ante un `source` repetido en las dos formas gana este. En el repo hay 460 usos de `&lt;field&gt;` y 7 de esta forma.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="xml" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML hijo que se abre como popup.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML hijo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="label" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto del botón que abre el popup.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="title" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Título del popup.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="titulo" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias español de `title`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="resume" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Se acepta por compatibilidad, pero **acá no hace nada**: `readImportTag` no lee este
          atributo y `Relationship/ImportRelationship` no lo conoce. El `resume` funcional es el
          del `&lt;histrix&gt;`, donde `Data/Container` lo consulta para descartar el SQL externo
          previo del contenedor referente al volver de un popup.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="ventint" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          Se acepta por compatibilidad, pero **el motor no lo lee** — igual que en el
          `&lt;helper&gt;`. `readImportTag` sólo procesa `xml`, `field`/`campo`, `parameter`,
          `icon`, `accesskey`, `boton`, `width`, `height`, `label`, `titulo`/`title` y `dir`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="icon" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de la clase de icono a mostrar en el botón (ej. `ui-icon-search`, `ui-icon-plus`).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="width" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Ancho del popup.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="height" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alto del popup.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="accesskey" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Tecla de acceso rápido del botón de importación (una letra). La menciona la doc de
          `ventint` y acá se declara: con esto el operador dispara la búsqueda sin ir al mouse.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="boton" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Texto del botón que abre la importación, en lugar del texto por defecto.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="preparamType">
    <xs:annotation>
      <xs:documentation>
        Condición pre-filtrado en consultas/importar. `field` es alias de `campo` (mismo significado).
      </xs:documentation>
    </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="campo" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>`id` del campo del XML hijo al que se le aplica el pre-filtro.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="field" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Alias inglés de `campo`.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="operador" type="OperadorCondicion" use="optional">
          <xs:annotation><xs:documentation>Operador SQL del pre-filtro.</xs:documentation></xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <!-- =======================
       Join / Relaciones
       ======================= -->
  <xs:complexType name="CondicionType">
    <xs:annotation>
          <xs:documentation>
            Condición SQL de un campo: operador + valor literal/expresión. Va al `WHERE`.

            El ruteo al `HAVING` lo decide el atributo `having` **del `&lt;expression&gt;` del campo**
            (o del propio `&lt;field&gt;`): escrito acá, sobre la `&lt;condicion&gt;`, no se lee — hay 7
            XMLs con `&lt;condicion having="true"&gt;` que no hacen nada.
            En `&lt;helper&gt;` de combo, las condiciones se aplican al SELECT del combo (NO soportan
            `'[__campo__]'` — usar `&lt;actualiza&gt;` para combo dinámico).

            Este mismo tipo se usa para la `&lt;condition&gt;` de un `&lt;validation&gt;`, que **no es
            SQL** sino una expresión JavaScript evaluada en el browser: ahí ni `operador` ni `valorde`
            se leen.
          </xs:documentation>
        </xs:annotation>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="operador" type="OperadorCondicion" use="optional" default="=">
          <xs:annotation><xs:documentation>Operador SQL: =, !=, &lt;, &gt;, &lt;=, &gt;=, like, in, between, is null, etc. Opcional: si se omite, el motor asume `=` (normaliza el operador vacío), por lo que omitirlo y escribir `operador="="` son equivalentes.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="valorde" type="xs:string" use="optional">
          <xs:annotation><xs:documentation>Toma el valor de otro campo del mismo form (`id`). Útil en JOINs y filtros dinámicos.</xs:documentation></xs:annotation>
        </xs:attribute>
        <xs:attribute name="logic" type="xs:string" use="optional">
          <xs:annotation>
            <xs:documentation>
              Cómo se encadena esta condición con las anteriores del mismo campo. Por defecto todas
              se suman con `AND`; con `logic="OR"` esta condición se une con `OR` a la de arriba.
              Es la única forma declarativa de armar un `(a = 1 OR a = 2)` sin escribir el SQL a mano.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="fixed" type="BoolEstricto" use="optional">
          <xs:annotation>
            <xs:documentation>
              Hace la condición **inmune al botón de limpiar filtros**: cuando el usuario resetea la
              barra de filtros, las condiciones normales se van y ésta queda. Sirve para las
              restricciones que son parte de la definición de la pantalla (una empresa, un estado,
              un tipo de comprobante) y que el usuario no debería poder sacar.
            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="JoinFieldType">
    <xs:annotation>
          <xs:documentation>
            Campo que define una condición ON del JOIN. La `&lt;condicion&gt;` con `valorde="campo"`
            referencia un campo de la tabla principal para armar el `ON tabla_join.x = tabla_principal.y`.
          </xs:documentation>
        </xs:annotation>

    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="condicion" type="CondicionType">
        <xs:annotation><xs:documentation>Condición del JOIN (operador + valor o referencia a otro campo).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="condition" type="CondicionType">
        <xs:annotation><xs:documentation>Alias inglés de `&lt;condicion&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="expression" type="ExpressionType">
        <xs:annotation><xs:documentation>Expresión SQL alternativa para el campo del JOIN.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="expresion" type="ExpressionType">
        <xs:annotation><xs:documentation>Alias español de `&lt;expression&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="label" type="LabelType">
        <xs:annotation><xs:documentation>Etiqueta del campo en pantalla (si se muestra).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="tipo" type="TipoType">
        <xs:annotation><xs:documentation>Tipo del campo del JOIN.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>

    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Nombre de la columna en la tabla del JOIN.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="oculto" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Si "true", oculta el campo en pantalla.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="noshow" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Alias inglés de `oculto`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tabla" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Tabla a la que pertenece el campo (cuando ambiguo).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="alias" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias SQL del campo.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="JoinTablaType">
    <xs:annotation>
      <xs:documentation>
        Tabla referenciada en el JOIN. Puede ser una tabla real o un `&lt;subquery&gt;` aliasado.
      </xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element name="field" type="JoinFieldType" minOccurs="1" maxOccurs="unbounded">
        <xs:annotation><xs:documentation>Campos de la tabla joineada con sus condiciones ON.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Nombre de la tabla joineada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="as" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias SQL de la tabla en el JOIN.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="SubqueryType">
    <xs:annotation>
      <xs:documentation>
        Subquery aliasada usada como tabla joineada. Estructura: `&lt;subquery as="ALIAS"&gt;&lt;sql&gt;SELECT ...&lt;/sql&gt;&lt;field .../&gt;...&lt;/subquery&gt;`.
        El SQL dentro de `&lt;sql&gt;` soporta `'[__campo__]'` para valores de campos del padre.
      </xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element name="sql" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation><xs:documentation>SQL del subquery. Acepta `'[__campo__]'` para parámetros.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="field" type="JoinFieldType">
          <xs:annotation><xs:documentation>Campos del subquery con sus condiciones ON.</xs:documentation></xs:annotation>
        </xs:element>
        <xs:element name="campo" type="JoinFieldType">
          <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`.</xs:documentation></xs:annotation>
        </xs:element>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="as" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Alias SQL obligatorio del subquery.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="JoinType">
    <xs:annotation>
          <xs:documentation>
            Relación con otra tabla (JOIN SQL). Soporta también JOIN a `&lt;subquery&gt;` cuando la tabla joineada es derivada (`&lt;subquery as="ALIAS"&gt;&lt;sql&gt;SELECT ...&lt;/sql&gt;&lt;/subquery&gt;`).

            **Un `&lt;if&gt;` dentro del `&lt;join&gt;` no se lee: el join se aplica siempre.** Para
            condicionarlo está `grupo`.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="1" maxOccurs="unbounded">
      <xs:element name="tabla" type="JoinTablaType">
        <xs:annotation><xs:documentation>Tabla joineada (alias español). Un `&lt;join&gt;` puede contener múltiples tablas joineadas en cadena.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="table" type="JoinTablaType">
        <xs:annotation><xs:documentation>Tabla joineada (alias inglés).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="subquery" type="SubqueryType">
        <xs:annotation><xs:documentation>JOIN a un subquery (tabla derivada). Requiere atributo `as`.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="tipo" type="JoinTipo" use="optional">
      <xs:annotation><xs:documentation>Tipo de JOIN: left | right | inner | full (case-insensitive).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="grupo" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Grupo lógico del join (usado para identificar conjuntos de joins relacionados).</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>


  <xs:complexType name="groupType">
    <xs:annotation>
          <xs:documentation>
            Campos por los cuales agrupar el SQL (`GROUP BY`).
            Los campos con `&lt;expression&gt;` que usan funciones de agregación (SUM/COUNT/MAX) quedan fuera del GROUP BY automáticamente.
            Va dentro de la `&lt;table&gt;`/`&lt;tabla&gt;`: dentro de un `&lt;fieldGroup&gt;` no se lee y
            el `GROUP BY` no se aplica.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="1" maxOccurs="unbounded">
      <xs:element name="field" type="FieldType">
        <xs:annotation><xs:documentation>Campo de agrupación. Requiere `tabla="X"` si la columna está en múltiples tablas del JOIN.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="campo" type="FieldType">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="if" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Condición de procesamiento del bloque entero: texto PHP que decide si el `GROUP BY` se
            aplica o no. No lleva `exp` ni ramas — no es el `&lt;if&gt;` de un campo.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
  </xs:complexType>

  <!-- =======================
       Formulario
       ======================= -->
  <xs:complexType name="FormType">
    <xs:annotation>
      <xs:documentation>
        Contenedor del modelo de edición/visualización (`&lt;form&gt;` o alias español `&lt;consulta&gt;`).
        Define la tabla base, joins, parámetros, y opcionalmente un bloque PDF custom.
      </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="pdf" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>
            Impresión PDF con código embebido: `&lt;header&gt;`, `&lt;code&gt;` y `&lt;footer&gt;` se
            ejecutan como código PHP con la variable `$pdf` disponible, que apunta al objeto que
            dibuja el PDF — de ahí salen los métodos que se pueden llamar (`Cell`, `MultiCell`,
            `Image`, `SetFont`, `SetXY`…).

            **Los atributos de este tag se descartan**: `PDForientacion` / `PDForientation`
            escritos acá no hacen nada (146 XMLs del repo lo intentan) — la orientación va en el
            `&lt;histrix&gt;`. Y el bloque tiene que colgar del `&lt;form&gt;`/`&lt;consulta&gt;`: un
            `&lt;code&gt;` suelto dentro de `&lt;table&gt;` no se lee.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:sequence>
            <xs:element name="header" minOccurs="0" maxOccurs="1" type="xs:string">
              <xs:annotation><xs:documentation>Código que se ejecuta al dibujar cada cabecera de página.</xs:documentation></xs:annotation>
            </xs:element>
            <xs:element name="code" minOccurs="0" maxOccurs="1">
              <xs:annotation><xs:documentation>Código del cuerpo del PDF, se ejecuta una vez al imprimir el contenedor.</xs:documentation></xs:annotation>
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:string" />
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
            <xs:element name="footer" minOccurs="0" maxOccurs="1" type="xs:string">
              <xs:annotation><xs:documentation>Código que se ejecuta al dibujar cada pie de página.</xs:documentation></xs:annotation>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="join" type="JoinType">
        <xs:annotation><xs:documentation>JOIN a otra tabla. El motor genera el SQL del JOIN a partir de los `&lt;field&gt;` con `&lt;condicion&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="table" type="TableType">
        <xs:annotation><xs:documentation>Tabla adicional declarada al nivel del `&lt;form&gt;`/`&lt;consulta&gt;`. **No es un JOIN implícito**: el motor arma un contenedor por cada `&lt;table&gt;` de este nivel y se queda **sólo con el último**, sin merge ni condición ON automática, así que la primera tabla queda pisada. Para relacionar tablas va `&lt;join&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="tabla" type="TableType">
        <xs:annotation><xs:documentation>Alias español de `&lt;table&gt;` a este nivel, con el mismo comportamiento: si se declara más de una, sobrevive únicamente la última.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="tempTable" type="tempTableType">
        <xs:annotation><xs:documentation>Tabla temporal que alimenta el grid (flujos de importación). **No lleva SQL**: el motor lee sólo dos cosas, los `&lt;field id="destino"&gt;origen&lt;/field&gt;`, que mapean el campo de origen al campo del form, y los `&lt;row&gt;` con esos mismos `&lt;field&gt;` adentro, que cargan filas fijas numeradas desde 0 (315 `&lt;row&gt;` en el repo). Un `CREATE TEMPORARY TABLE` escrito acá se ignora. Y va sólo al nivel del `&lt;form&gt;`/`&lt;consulta&gt;`: dentro de `&lt;table&gt;` no se lee.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="detalle" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML hijo que se abre como popup al hacer click en una fila (vista de detalle).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML referenciado por `detalle` (relativo al módulo).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="inline" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Muestra el detalle expandido bajo la fila en lugar de en un popup. **Cualquier valor no vacío lo activa, incluido `"false"`**: para que el detalle salga en popup hay que **omitir** el atributo. El `default="false"` declarado acá es engañoso — `inline="false"` no apaga nada, lo prende.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="single" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Con `"true"`, y sólo cuando hay `detalle` + `inline`, agrega la clase CSS `single` a la celda del detalle inline. **No hace nada más**: no es "registro único", no toca la paginación ni la navegación entre filas, y hoy ni el JS ni el CSS del proyecto consumen esa clase.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="multipleSelection" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Agrega la columna de checkboxes para seleccionar varias filas. **`multipleSelection="false"` no lo apaga**: unas partes del render exigen el literal `"true"` y otras se conforman con que el atributo tenga cualquier valor, así que con `"false"` la selección múltiple queda a medio armar. Para desactivarla hay que **omitir** el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="showCab" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Muestra arriba del detalle la cabecera de lo consultado (el form en modo lectura). **Escrito acá no funciona**: el motor lo renombra al transferirlo al contenedor y la propiedad resultante no la lee nadie. Hoy el único lugar donde surte efecto es el `&lt;histrix&gt;`, con `showCab="true"`. Es un defecto del motor, no del schema.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="detailText" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto que ocupa el panel de detalle **mientras no se eligió ninguna fila** (el "Seleccione una fila…"). Sólo aplica al detalle en panel, no al inline, y si se omite se usa el texto traducido por defecto.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="hasDetail" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Campo cuyo valor determina si la fila tiene detalle disponible (icono "+").</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <!-- =======================
       Grafico
       ======================= -->
  <xs:complexType name="KanbanStatusType">
    <xs:annotation>
      <xs:documentation>
        Una columna del tablero `tipo="kanban"`. Va como hijo directo de `&lt;table&gt;`, al lado de
        `&lt;filters&gt;` y `&lt;order&gt;`, y se declara una por cada valor del campo indicado en
        `statusField` que quiera verse. Las columnas se dibujan **en el orden de aparición**.

        Las filas cuyo estado no coincide con ninguna columna declarada no desaparecen: van a una
        columna "Sin clasificar" al final (ver `showOrphans` y `orphanLabel` en `&lt;histrix&gt;`).
      </xs:documentation>
    </xs:annotation>
    <xs:attribute name="value" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Valor del campo `statusField` que cae en esta columna. **Obligatorio**, y no puede repetirse entre columnas: las dos cosas se reportan como error de configuración en pantalla. Es también el único destino válido de un drop — arrastrar a la columna de huérfanos se rechaza con 400.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="label" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Título de la columna. Si no está, se muestra el `value`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="color" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Color CSS de la columna: pinta la línea superior del encabezado y el borde izquierdo de sus tarjetas. Un campo con `kanban="color"` lo pisa por tarjeta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="limit" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Límite de trabajo en curso (WIP). Es **sólo visual**: el contador pasa a mostrar `usadas/limit` y se pinta en alerta al pasarse, pero no impide mover tarjetas a la columna.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="confirmXml" use="optional">
      <xs:annotation>
        <xs:documentation>
          XML que hay que completar para entrar a esta columna. **Sin este atributo el
          movimiento se graba directo**, como en el resto de las columnas.

          Con él, el tablero **no graba**: al soltar la tarjeta abre este XML como
          modal pasándole la clave de la tarjeta y el estado destino —los
          `&lt;parameter&gt;` prellenan el form del lado del servidor, así que la
          pantalla abre con el estado nuevo ya puesto— y **ese XML es el que graba**.
          Puede ser un `update` simple o una ficha con `&lt;movimientos&gt;`, que es
          justamente lo que el UPDATE plano del campo no puede hacer.

          Al cerrarse el modal el tablero recarga y acomoda cada tarjeta según el
          estado que tiene en la base: si se confirmó queda en la columna nueva, y si
          se canceló vuelve sola a donde estaba.

          El endpoint del movimiento rechaza estas columnas con `400`, así que la
          confirmación no se puede saltear pegándole a la API.

          **Es la forma de dar drag a un estado que es consecuencia de un proceso**
          (presupuesto → facturado, orden pendiente → cumplida): en vez de pisar el
          campo, se dispara la pantalla que hace el trabajo completo.
        </xs:documentation>
      </xs:annotation>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value=".*\.xml" />
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
    <xs:attribute name="confirmDir" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio de `confirmXml`. **Obligatorio cuando hay `confirmXml`**: sin él el path no resuelve en varios contextos y el popup abre con "file not found" (la misma regla que el `dir` de los includes). Declararlo aunque apunte al directorio del propio XML.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="confirmTitle" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Título del modal de confirmación. Si no está, se usa el `label` de la columna.</xs:documentation></xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="GraficoType">
    <xs:annotation>
          <xs:documentation>
            Definición de un gráfico. Va dentro de la `&lt;table&gt;`/`&lt;tabla&gt;` de la consulta que
            le da los datos: suelto en la raíz del `&lt;histrix&gt;` no dibuja nada.

            Valores de `&lt;tipo&gt;`: `L` línea, `C` columnas, `SC` columnas también (es idéntico a `C`,
            no apila nada), `P` torta, `H` gráfico de cotizaciones (exige una serie llamada `_time`), más
            `heatmap`, `treemap` y `gauge`. **`B` no existe**: cualquier valor no reconocido se pasa
            crudo a la librería de gráficos, que no dibuja nada y deja el spinner girando para siempre.

            Mezclar tipos de serie en un mismo gráfico **sí se puede**: el `type` de cada
            `&lt;serie&gt;` gana sobre el `&lt;tipo&gt;` general, así que se pueden combinar columnas
            con una línea encima (por ejemplo importes por mes más la tendencia).
          </xs:documentation>
        </xs:annotation>

    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="ancho" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            **No hace nada: es código muerto.** Se lee del XML pero nadie lo usa para dimensionar el
            gráfico, que siempre toma el ancho del contenedor. Los 87 XMLs que lo escriben (muchos
            con `800px`) no cambian nada. Queda declarado para que esos XMLs no den error, pero no
            hay que agregarlo en código nuevo.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="alto" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Alto del gráfico. A diferencia de `&lt;ancho&gt;`, este sí se aplica: es la única forma de
            que un gráfico no salga con el alto por defecto.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="tipo" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Tipo de gráfico. Valores válidos: `L` línea, `C` columnas, `SC` columnas (idéntico a `C`),
            `P` torta, `H` cotizaciones (exige una serie llamada `_time`), `heatmap`, `treemap` y
            `gauge`. **`B` no existe** aunque muchos XMLs lo escriben: un valor no reconocido se pasa
            crudo a la librería de gráficos, que no dibuja nada y deja el spinner girando para siempre.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="etiquetas" type="xs:string">
        <xs:annotation><xs:documentation>Campo cuyo valor se usa como eje X (etiquetas de cada punto).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="serie">
        <xs:annotation>
          <xs:documentation>
            Campo de una serie de datos (eje Y). Repetir para múltiples series.
            El texto del tag es el nombre del campo; con `&lt;column&gt;` adentro se declaran varias
            columnas en una misma serie.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType mixed="true">
          <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element name="column" type="xs:string">
              <xs:annotation><xs:documentation>Columna que aporta datos a esta serie.</xs:documentation></xs:annotation>
            </xs:element>
          </xs:choice>
          <xs:attribute name="type" type="xs:string" use="optional">
            <xs:annotation>
              <xs:documentation>
                Tipo de esta serie en particular, con los mismos valores que `&lt;tipo&gt;`. Pisa al
                tipo general y es lo que permite mezclar columnas y líneas en un mismo gráfico.
              </xs:documentation>
            </xs:annotation>
          </xs:attribute>
        </xs:complexType>
      </xs:element>
      <xs:element name="objective" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Valor meta que se dibuja como referencia sobre el gráfico (la línea de objetivo).
            El valor puede quedar guardado por el usuario desde la propia pantalla, así que lo que se
            declara acá es el default.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="options" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            JSON crudo que se mergea sobre las opciones de la librería de gráficos, para llegar a
            cualquier setting que el motor no expone (formato de tooltip, colores, ejes secundarios).
            **No se valida**: un JSON mal formado rompe el gráfico entero sin mensaje de error.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="datos" type="xs:string">
        <xs:annotation><xs:documentation>Fuente alternativa de datos (cuando no se infiere del form).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="title" type="xs:string">
        <xs:annotation><xs:documentation>Título del gráfico.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="titulo" type="xs:string">
        <xs:annotation><xs:documentation>Alias español de `&lt;title&gt;`; el motor lee las dos grafías.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="subtitulo" type="xs:string">
        <xs:annotation><xs:documentation>Subtítulo del gráfico.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>

    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Identificador del gráfico.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="pareto" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Dibuja el gráfico como Pareto: las columnas ordenadas de mayor a menor más la curva de
          porcentaje acumulado. Sirve para el clásico "qué 20% explica el 80%".
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="average" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Agrega la línea de promedio de la serie sobre el gráfico. No confundirlo con el `average`
          de un `&lt;field&gt;`, que es otra cosa (promedia una columna de la grilla).
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <!-- =======================
       Widget
       ======================= -->
  <xs:complexType name="WidgetType">
    <xs:annotation>
          <xs:documentation>
            Widget embebido en el dashboard. **No es un iframe y no apunta afuera**: el contenido de
            `&lt;url&gt;` se pide por AJAX y su HTML se inyecta dentro del div del widget, y la ruta se
            reescribe a la API interna, así que lo que se referencia es una pantalla de la propia
            aplicación, no un sitio externo.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="title" type="xs:string">
        <xs:annotation><xs:documentation>Título visible del widget.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="url" type="xs:string">
        <xs:annotation><xs:documentation>Pantalla interna cuyo HTML se inyecta en el div del widget (se pide por AJAX, no es un iframe).</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="text" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Contenido HTML fijo del widget, para los casos en que no hay una pantalla que embeber:
            un texto, un aviso, un bloque de links. Alternativa a `&lt;url&gt;`.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="iframe" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            **Éste sí es un iframe de verdad**, y es la forma de mostrar algo externo a la aplicación
            (un tablero, un mapa, una página). Lo que la documentación suele atribuirle a
            `&lt;url&gt;` —apuntar afuera— en realidad se hace acá.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="id" type="xs:string" use="required">
      <xs:annotation><xs:documentation>Identificador del widget.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="width" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Ancho del widget (CSS).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="height" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alto del widget (CSS). Ojo: en la práctica el alto lo termina fijando `style`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          CSS inline del div del widget. **Es lo único que efectivamente le da alto**: sin `style`
          el widget se dimensiona por su contenido, y un widget que carga por AJAX arranca en cero.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>


  <!-- =======================
       Raíz histrix
       ======================= -->
  <xs:complexType name="HistrixType">
    <xs:annotation>
      <xs:documentation> Raíz del documento. Atributo "tipo" identifica el modo del documento
        (p.ej., "consulta"). </xs:documentation>
    </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="title" type="xs:string" minOccurs="1" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Título del documento/pantalla.</xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="titulo" type="xs:string" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Alias en español de title (tooltip/título).</xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="pie" minOccurs="1" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Pie del documento/pantalla (atributos para PDF).</xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string">
              <xs:attribute name="size" type="xs:string" use="optional" />
              <xs:attribute name="align" type="xs:string" use="optional" />
              <xs:attribute name="style" type="xs:string" use="optional" />
              <xs:attribute name="color" type="xs:string" use="optional">
                <xs:annotation><xs:documentation>Color del texto del pie.</xs:documentation></xs:annotation>
              </xs:attribute>
            </xs:extension>
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="obs" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Observaciones (puede estar vacío).</xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string" />
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="confirmacion" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Mensaje de confirmación.</xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string" />
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="eventos" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>
            Grafía castellana de `&lt;events&gt;`: contenedor de `&lt;event id="..."&gt;` con el
            JavaScript que se ejecuta en cada momento del ciclo de la pantalla. El motor lee las dos
            grafías; en código nuevo usar `&lt;events&gt;`.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:sequence>
            <xs:element name="event" minOccurs="0" maxOccurs="unbounded">
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:string">
                    <xs:attribute name="id" type="xs:string" use="required" />
                  </xs:extension>
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="events" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>Alias en inglés de "eventos".</xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:sequence>
            <xs:element name="event" minOccurs="1" maxOccurs="unbounded">
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:string">
                    <xs:attribute name="id" type="xs:string" use="required" />
                  </xs:extension>
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
            <xs:element name="events" minOccurs="0" maxOccurs="unbounded">
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:string">
                    <xs:attribute name="id" type="xs:string" use="required" />
                  </xs:extension>
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="customScript" minOccurs="0" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Script personalizado para la pantalla.</xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:simpleContent>
            <xs:extension base="xs:string" />
          </xs:simpleContent>
        </xs:complexType>
      </xs:element>
      <xs:element name="form" type="FormType" minOccurs="1" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Definición del formulario/consulta principal. Contiene `&lt;table&gt;`, opcionales `&lt;join&gt;`, `&lt;sql&gt;`, `&lt;tempTable&gt;`, `&lt;importar&gt;`.</xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="consulta" type="FormType" minOccurs="1" maxOccurs="1">
        <xs:annotation>
          <xs:documentation>Alias español de `&lt;form&gt;`. Misma estructura.</xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="widget" type="WidgetType" minOccurs="0" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>Widget embebido (iframe a URL externa o componente custom) para dashboards.</xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="scriptSrc" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Librería JavaScript externa que la pantalla necesita: el texto del tag es la ruta del
            archivo y se inyecta antes que el resto del JS del XML. El orden de carga es
            `&lt;scriptSrc&gt;` → el `.js` hermano del XML (si existe, se carga solo) →
            `&lt;customScript&gt;`, así que las funciones declaradas acá ya están disponibles en el
            `customScript`. Repetible: un tag por librería.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="txtresultados">
        <xs:annotation>
          <xs:documentation>
            Cambia el texto que acompaña al contador de resultados del listado: `&lt;prefijo&gt;` va
            antes del número y `&lt;sufijo&gt;` después (por ejemplo "Se encontraron" / "comprobantes").
            Sin este bloque se usa el texto por defecto del sistema.
          </xs:documentation>
        </xs:annotation>
        <xs:complexType>
          <xs:sequence>
            <xs:element name="prefijo" type="xs:string" minOccurs="0" maxOccurs="1">
              <xs:annotation><xs:documentation>Texto antes de la cantidad de resultados.</xs:documentation></xs:annotation>
            </xs:element>
            <xs:element name="sufijo" type="xs:string" minOccurs="0" maxOccurs="1">
              <xs:annotation><xs:documentation>Texto después de la cantidad de resultados.</xs:documentation></xs:annotation>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="prompt" type="xs:string">
        <xs:annotation>
          <xs:documentation>
            Instrucción para el resumen automático de la pantalla: el texto se le pasa al modelo de
            lenguaje junto con los datos del listado para que genere el análisis que se muestra al
            usuario. Acá se le dice qué mirar ("resumí los desvíos de stock por depósito") en lugar
            de dejarlo librado al resumen genérico.
          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attribute name="tipo" type="xs:string" use="required">
      <xs:annotation><xs:documentation>
        Modo del documento. **El valor es el nombre de la vista que lo renderiza**, así que la lista
        no es cerrada y un valor mal escrito no resuelve ninguna vista. Los que se usan de verdad:
        "consulta" (listado/grid), "ficha" (form de un registro), "fichaing" (form con grillas
        embebidas), "ing" (grid editable), "crud", "update", "abm" (alta/baja/mod), "abm-mini",
        "grid", "horizontalGrid", "liveGrid", "list", "card", "cabecera", "delete",
        "insert" (alta directa con SQL literal), "sql", "nosql" (ejecuta PHP/SQL sin mapeo),
        "chart" (gráfico), "arbol", "dashboard", "calendar", "map",
        "kanban" (tablero por estado: ver `statusField`, `drag` y `&lt;status&gt;`)
        y "ayuda" (popup de ayuda).
        "gantt" tiene vista en el motor pero **ningún XML lo usa**.
      </xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="inserta" type="BoolTrueFalse" default="true" use="optional">
      <xs:annotation><xs:documentation>Permite el alta de registros en ABM/consulta. Default true. Equivalente al botón "+". XMLs legacy usan `inserta="no"`, pero **las dos vistas no lo tratan igual**: la web contempla los dos valores ("false" y "no"), mientras que la vista mobile sólo mira "false". En los XMLs con `inserta="no"` el botón de alta queda oculto en la web y **visible en mobile**. Para que valga en las dos, usar "false". Ojo también con `insertaABM`: es la otra grafía del mismo permiso, y cuando están los dos `inserta` la pisa.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="multipleSelection" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Agrega la columna de checkboxes para seleccionar varias filas; al procesar, el motor junta
          las marcadas y las manda juntas. Sirve declarado acá **o** en el `&lt;form&gt;`: los dos
          caminos terminan en el mismo contenedor, y de ahí lo leen los consumidores.
          **Ojo con el valor**: una parte del render exige el literal `"true"` y otra alcanza con que
          el atributo esté presente, así que `multipleSelection="false"` deja el render a medias
          (columna y clase CSS sí, configuración de la grilla no). Usar `"true"` o no ponerlo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="deleteRow" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Habilita el botón eliminar en cada fila. **No es alias de `borra`**: `deleteRow` es el que decide el permiso de borrado que expone la API, y `borra="false"` no apaga el botón por fila de las grillas.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="saveRowButton" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Muestra botón "guardar" por fila en grids editables.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tableHeight" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Altura fija del contenedor de la tabla (`px`, `vh`, etc.). Va en `&lt;histrix&gt;`, NO en `&lt;table&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <!-- Tablero kanban: la configuración va en atributos del tag raíz, no en un tag envolvente. -->
    <xs:attribute name="statusField" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>
        Sólo para `tipo="kanban"`, donde es **obligatorio**: nombra el campo cuyo valor decide en qué
        columna cae cada tarjeta. Tiene que estar declarado como `&lt;field&gt;` en el XML (puede ser
        `noshow="true"`); si falta o no existe, el tablero no se dibuja y lo dice en pantalla.
      </xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="drag" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>
        Permite mover tarjetas entre columnas. **Apagado por defecto.** Sólo se concede si además el
        XML permite escribir: `sololectura` y `modifica="false"` lo desactivan, y el servidor lo
        vuelve a evaluar antes de grabar, así que un PUT a mano sobre un tablero de sólo lectura
        responde 403.

        El movimiento hace un UPDATE del campo de estado y **no dispara `&lt;movimientos&gt;`**. Para
        un estado que es consecuencia de un comprobante o de un proceso (presupuesto → facturado,
        orden pendiente → cumplida), dejarlo en `false`: el tablero grabaría el estado nuevo sin los
        movimientos que le corresponden.
      </xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="showOrphans" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>
        Muestra la columna con las tarjetas cuyo estado no coincide con ninguna `&lt;status&gt;`
        declarada. **Activa por defecto, a propósito**: apagarla hace que esas filas desaparezcan del
        tablero sin ningún aviso. Esa columna no acepta drops.
      </xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="orphanLabel" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Título de la columna de huérfanos. Default "Sin clasificar".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="borra" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Permiso de borrado a nivel ABM. **No es alias de `deleteRow`** y la precedencia es asimétrica: en el permiso de borrado que expone el schema de la API gana `deleteRow`, y `borra="false"` **no** apaga el botón de borrar de cada fila de las grillas. Acepta la variante legacy `borra="no"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="modifica" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Muestra el botón de modificar registros. Con la variante legacy `modifica="no"` pasa lo mismo que con `inserta="no"`: algunas ramas del ABM la toleran, pero la vista que dibuja el botón lee el atributo tal cual y con "no" **deja el botón visible**. Usar "false".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="imprime" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Muestra el botón imprimir/exportar a PDF. "false" lo oculta.
        **Sin `default` declarado a propósito**: el motor lo lee con dos criterios distintos, así que
        el valor efectivo depende de la vista. En unas ramas el botón aparece salvo que el valor sea
        exactamente "false"; en otras hace falta el literal "true" para que se active. Omitirlo NO es
        equivalente a ponerlo en "true". Y el valor se compara sin normalizar: `imprime="false "`
        (con un espacio al final) no es "false" y el botón se dibuja igual.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="preFetch" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>En ficha/fichaing, precarga los datos existentes del registro. NO usar en formularios de alta. El motor lo lee con más de un criterio, así que omitirlo no es idéntico a escribir "false". `autoprint="true"` lo fuerza a "true" (y de paso deja el formulario en sólo lectura). Escrito `prefetch` o `preetch` (28 usos en el repo) no lo lee nadie y la ficha no precarga nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="preloadData" type="BoolTrueFalse" default="true" use="optional">
      <xs:annotation><xs:documentation>Si "true", ejecuta la query y precarga los datos al renderizar; "false" en grillas embebidas con `lazyLoad` difiere la carga. El motor lo lee con más de un criterio, así que omitirlo no es idéntico a escribir "false".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="autoprint" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Imprime automáticamente al abrir el documento (sin click del usuario). **Trae dos efectos laterales que se leen como un bug**: al parsear el XML fuerza `preFetch="true"` y pone en **sólo lectura** el formulario y sus grillas embebidas. Es la causa de "puse autoprint y ahora no puedo editar".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="printer" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Dirige la impresión a una impresora concreta. **El valor no es el nombre del dispositivo sino un `idPrinter` de la tabla `HTXPRINTERS`**: el motor busca esa fila y de ahí saca `printerClass`, `systemName` (el nombre real en el sistema; si viene vacío usa el `idPrinter`), `outputFormat` y `printMode` — con `printMode = 'client'` la impresión se enruta al cliente de impresión instalado en la máquina del usuario. Dos consecuencias: si la fila **no existe** en la base del cliente no se imprime y **no hay mensaje de error**, así que el mismo XML funciona en un cliente y no en otro; y su sola presencia **cambia la salida de PDF a HTML** (iframe de preview + script del cliente de impresión), por eso la vista deja de declarar el contenido como PDF cuando está seteado — declararlo igual provocaría un "Failed to load PDF document" en Chrome. El valor viaja además en la URL de impresión (`&amp;printer=…`). Para elegirlas desde la UI existe `ayudas/htxprinters_qry.xml`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFfontSize" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Tamaño de fuente global del PDF (en puntos). Es la única grafía que se lee a nivel `&lt;histrix&gt;`; a nivel `&lt;field&gt;`/`&lt;label&gt;` la grafía viva es `PDFfontsize` (con `s` minúscula), que es otro atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFfont" type="xs:string" default="helvetica" use="optional">
      <xs:annotation><xs:documentation>Fuente del PDF (helvetica, times, courier...).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="copies" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Cantidad de copias del PDF a generar (multiplicar la salida).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="limit" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>`LIMIT` aplicado a la query principal. Tres cosas que no son obvias: `limit="0"` significa **sin límite** (está excluido explícitamente); la forma `"offset,count"` es válida y es la que usa la paginación; y **la forma `[__campo__]` no existe** — el valor se castea a entero, así que `[__campo__]` colapsa a 0 y el límite queda desactivado en silencio.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="export" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Muestra el botón exportar (XLS/CSV). "false" lo oculta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="form" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>**No suprime el render del formulario.** Sólo tiene efecto real en las vistas de árbol (`arbol`/`treeView`); en `tipo="consulta"` lo único que hace `form="false"` es desactivar el llenado del formulario al clickear una fila (click-en-la-fila → llena-el-form). En las demás vistas es inerte. Además el motor lo lee con más de un criterio, así que omitirlo no es idéntico a escribir "true".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="transaccional" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Envuelve la grabación en una transacción SQL explícita (BEGIN/COMMIT/ROLLBACK). El motor lo lee con más de un criterio, así que omitirlo no es idéntico a escribir "false": para activarlo va el literal "true". **Escrito con una sola `c` (`transacional`, 12 usos en el repo) no lo lee nadie y la grabación no va en transacción.**</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="process" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Habilita el procesamiento de `&lt;movimientos&gt;`/relaciones al grabar. **`procesa` y `procesar` no son sus aliases**: son tres atributos con lectores distintos, y poner uno esperando el efecto de otro no funciona (ver sus descripciones). El motor lo lee con más de un criterio, así que omitirlo no es idéntico a escribir "false".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="processFirst" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>En `&lt;cierraproceso&gt;`: si "true", ejecuta los movimientos del HIJO antes que los del padre. Útil para obtener numeradores antes de que el padre grabe.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="exportasuma" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**No interviene en la exportación.** Aplica a los XMLs que devuelven datos al contenedor referente (`llenoReferente` / `&lt;importar&gt;`): con "true" se devuelve **una sola fila** que acumula los campos marcados con `suma="true"`, en vez de las N filas del resultado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="llenoTemporal" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de la tabla temporal usada para alimentar grillas de importación. **Tiene doble semántica**: además de nombre funciona como flag — `llenoTemporal="false"` evita que la tabla temporal se vacíe al recargar, y el valor participa de la cadena de `preFetch` del schema.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="llenoreferente" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Es la misma propiedad que `llenoReferente`: escribir cualquiera de las dos grafías llega al mismo lugar. **El `default="false"` declarado acá es engañoso**, y además contradice a `llenoReferente`, que a propósito no declara ninguno: hay caminos del motor que tratan la ausencia como activo y otros que exigen el literal "true". Ver la descripción de `llenoReferente`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="resume" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>En `&lt;importar&gt;` con popup hijo. **No "mantiene el contexto": en su ámbito hace lo contrario** — antes de volver a subir la celda al padre **descarta el SQL que esa celda ya había generado**, justamente para que reabrir el popup no duplique movimientos.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="subtipo" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Subtipo del modo. Ej. `tipo="ing" subtipo="vertical"` renderiza el form de entrada como columna en lugar de fila.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dataSource" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de una clase PHP propia con un método estático `Select()` que recibe el contenedor de datos y provee las filas (o `Clase::MetodoCustom`, para varias vistas en una misma clase), bypaseando la generación de SQL del motor.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="confirma" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**Es la leyenda del botón de procesar, no un mensaje de confirmación**: el valor se usa como texto del botón (el mismo lugar que `btnconfirma`). Los valores del repo lo confirman: GRABAR, CONFIRMAR, ANULAR, FACTURAR. El mensaje del popup "¿Está seguro?" es el elemento `&lt;confirmacion&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tipoInsert" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Modificador que se inyecta en el `INSERT` generado — y también en los `UPDATE`. **Los valores válidos son "IGNORE" y "REPLACE", en mayúsculas.** Los tres que documentaba antes el XSD estaban mal: `insertSelect` no existe; `replace` en minúscula no matchea la comparación del motor y se emite tal cual, generando `INSERT replace into …` (error de sintaxis SQL); e `ignore` funciona de casualidad, porque la base ignora el case de la keyword.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDForientation" type="xs:string" default="P" use="optional">
      <xs:annotation><xs:documentation>Orientación del PDF: "P" (Portrait, default) | "L" (Landscape — rota contenido 90°). **Es la grafía que lee la generación del PDF**; la variante española `PDForientacion` sólo preselecciona el valor en el selector de impresión de la UI.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFpageSize" type="xs:string" default="A4" use="optional">
      <xs:annotation><xs:documentation>Tamaño de página PDF: "A4", "Letter", "Legal", o personalizado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFlines" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>**Ya vienen prendidas: el atributo sirve sólo para apagarlas con "false".** Y no son líneas separadoras entre filas: es el recuadro de cada celda (un rectángulo por columna), y en la vista de tarjetas el marco de la tarjeta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="paginar" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Activa paginación con N filas por página.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="width" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Ancho del contenedor, pero **sólo lo leen cuatro vistas, y con unidades distintas**: en `arbol`, `map` y `calendar` es un número sin unidad (el porcentaje del split), y en `chart` un entero en px al que el motor le agrega el `px`. En consulta/abm/ficha es **inerte** (93 XMLs del repo lo declaran ahí para nada), y `width="30%"` en un chart genera CSS inválido.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="path" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No tiene nada que ver con la resolución de XMLs** (de eso se ocupan `dir=` y el orden de búsqueda del motor, que no miran `path`): es el **directorio de subida y lectura** de los campos `file`, `files` y `dir`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="grid" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No es un modo de render: es la geometría del contenedor.** El valor son dos medidas separadas por coma — `"ancho,alto"` — que se vuelcan al CSS del contenedor; en `tipo="card"` es lo que dimensiona las tarjetas.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="log" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Registra las operaciones en HTXLOG (auditoría). **Requiere el literal "true"**: todos los lectores lo comparan exacto, y **no** existe la variante de pasarle el nombre de un logger custom (el puente de auditoría se resuelve por plugin). Lo que faltaba, y es lo importante: **todo XML abierto desde el menú arranca con `log="true"` implícito**, así que ahí declararlo no cambia nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="logReference" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Lista CSV de campos cuyo valor se incluye como observación en el log de auditoría.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="conBusqueda" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**No es una búsqueda global y no tiene relación con los campos `fullSearch`.** Es la barra Buscar/Limpiar de una **ficha**: marca el formulario como formulario de búsqueda, lo arranca vacío y hace que el campo clave se use para traer el registro por su clave.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="sololectura" type="BoolTrueFalse" use="optional">
      <xs:annotation><xs:documentation>Si "true", el formulario es de sólo lectura (no permite alta/modificación/baja).
        **Sin `default` declarado a propósito**: un camino del motor trata la ausencia como activo
        (sólo "false" lo desactiva) y otro exige el literal "true", o sea que omitirlo no equivale a
        ninguno de los dos valores en todos los caminos.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="noTabs" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Con "true" el contenedor se renderiza **inline en lugar de como pestaña**.

          **Va en el `&lt;histrix&gt;` del XML EMBEBIDO**, no en el del formulario padre: los dos
          únicos lectores lo consultan sobre el contenedor interno, no sobre el del padre. O sea que
          hay que ponerlo en el XML que abre el `&lt;helper type="object"&gt;`, no en el que lo
          contiene. Declararlo en el padre no hace nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFsincabecera" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Omite la cabecera del PDF (la primera página arranca sin header global). **Cualquier valor no vacío lo activa, incluido "false"**: con `PDFsincabecera="false"` la cabecera igual se suprime. Para que la cabecera salga hay que **omitir el atributo**.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFpageSizeY" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Alto custom de página PDF (mm). **Va siempre en pareja con `PDFpageSizeX`**: si falta el otro, el tamaño custom no se aplica. En `PDForientation="L"` se interpreta como ancho.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFpageSizeX" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Ancho custom de página PDF (mm). **Sólo se aplica si además está declarado `PDFpageSizeY`**: solo, no hace nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="procesa" type="BoolTrueFalse" default="true" use="optional">
      <xs:annotation><xs:documentation>**No es alias de `process` ni de `procesar`**: son tres atributos con lectores disjuntos. `procesa` es el que decide si el contenedor tiene proceso y si se dibuja el botón de procesar. El motor lo lee con más de un criterio, así que omitirlo no es idéntico a escribir "false".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="procesar" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>**No es una variante de `procesa` ni hace lo mismo.** `procesar="true"` dispara la grabación de los contenedores internos y marca el contenedor como procesable en el schema que expone la API. El botón de procesar y la detección de "este XML tiene proceso" las maneja `procesa`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="showCantidad" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Imprime al pie del PDF la línea `Registros: N` con la cantidad de filas. **Sólo aplica al PDF**: no hay equivalente en la vista HTML. Requiere el literal "true"; el default es no imprimirla, así que `showCantidad="false"` es redundante. El motor también lo acepta en `&lt;table&gt;`, aunque hoy ningún XML lo use ahí. Escrito `showCant` o `showcantidad` (34 usos en el repo) no lo lee nadie.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="filter" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Controla el **buscador general de la grilla** (el input de búsqueda que trae la tabla),
          **no la barra de `&lt;filters&gt;`**. Y el valor engaña: cualquier valor no vacío lo
          enciende, incluido `"false"` — lo único que lo apaga es **omitir el atributo**.
          El que sí controla la barra de filtros es `autoFilter`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="noForm" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          No dibuja la barra de botones de la toolbar y, en la vista mobile, tampoco la columna de
          borrar fila de los grids `ing`. **No suprime el tag `&lt;form&gt;` envolvente ni oculta el
          formulario**: para esconder el form de entrada está `hideForm`, que es otra cosa.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="hideForm" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          En `tipo="ing"`, deja la grilla sola y esconde el formulario de entrada detrás de un botón.
          No es sólo un `display:none`: el bloque del formulario nace oculto, el botón pasa a decir
          **"Agregar …"** y es el que lo despliega, y al confirmar el alta el diálogo se cierra y el
          formulario vuelve a ocultarse. Se compara contra el literal `"true"`: cualquier otro valor
          deja el formulario visible.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="dataTable_info" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Controla el pie informativo de la grilla ("Mostrando X de Y registros").</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="onDuplicateKey" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Activa `ON DUPLICATE KEY UPDATE` en el INSERT.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="forceReload" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Después de grabar o de borrar, vuelve a consultar la base (y a recargar la tabla temporal)
          en lugar de reusar los datos que ya tenía en memoria.
          **Excepción a `BoolTrueFalse`: el motor sólo mira si el atributo está presente, no su
          valor** — cualquier valor lo activa, incluido `"false"`. Para desactivarlo hay que quitar
          el atributo (o inutilizarlo renombrándolo, p.ej. `_forceReload`).
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDForientacion" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**No es un alias equivalente de `PDForientation`.** La generación del PDF lee `PDForientation`; `PDForientacion` sólo preselecciona el valor en el selector de impresión de la UI, así que por sí solo no rota nada.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dataTableReorder" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Habilita el reordenamiento de filas de la grilla por drag-and-drop ("true"/"false").</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="class" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Clase CSS del contenedor de la vista. **No es "adicional": reemplaza** la clase por
          defecto (`consulta`, `ParentingClass`, el `defaultClass` de la vista…) — todos los lectores
          la toman como default-override. Para agregar sin reemplazar existe `tableClass`, que sí
          concatena.
          `class` y `clase` son la misma cosa en el `&lt;histrix&gt;`: escribir cualquiera de las dos
          llega al mismo lugar.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="clase" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Igual que `class` (es la propiedad interna a la que `class` se mapea; escribir cualquiera
          de las dos llega al mismo lugar). El default cuando se omite lo pone cada vista:
          `ParentingClass` en un grid `ing`, y su clase por defecto propia en las vistas de
          mapa, ayuda, árbol y consulta.
          **No confundir con el `clase` del `&lt;field&gt;`**, que es la clase CSS del input.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="imprimetanda" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Agrega un botón que imprime **en tanda**: un solo PDF con una copia por cada fila de la
          tabla temporal, respetando el campo de condición de cada fila y el `pageBreak`. No
          "agrupa varias instancias" en una sola impresión.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pageBreak" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es lo mismo que `PDFpageBreak`**: `pageBreak` sólo existe en la impresión en tanda
          (`imprimetanda`) y decide si cada **fila** arranca en una página nueva.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFlineColor" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Color de las líneas/recuadros del PDF. **No es hexadecimal**: son los tres componentes RGB en decimal separados por coma, y el default es `220,220,220`. Un valor como `#cccccc` no se parsea: rompe la generación del PDF.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="unserializeParent" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          Al volver de un popup, deserializa los datos del padre desde la sesión (sincroniza estado).
          **Viene activo aunque no se declare**: el único valor que lo apaga es `"false"`, así que el
          `default="false"` declarado acá hace creer lo contrario de lo que pasa.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="preventDuplication" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No bloquea el doble submit del botón grabar.** Lo que evita es que se duplique la solapa
          de un grid `ing` embebido en un `fichaing`, y para lograrlo descarta los scripts de recarga
          de los contenedores relacionados. **Con el atributo ausente ya está activo**: escribir
          `"true"` no cambia nada y el valor útil es `"false"`. El propio motor tiene este camino
          marcado como código a corregir, así que conviene verificar el efecto antes de dejarlo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="editable" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          **Está invertido respecto del nombre**: `editable="true"` convierte la grilla en editable
          inline y por eso **oculta el botón Modificar**. `"false"` es lo mismo que no poner nada y
          no bloquea nada — para bloquear van `modifica="false"` o `sololectura`.
          (En un `&lt;field&gt;` el atributo homónimo significa otra cosa.)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="importTempTable" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre de la tabla temporal de importación que alimenta el grid.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="serverSide" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Activa el modo server-side de la grilla: paginación, búsqueda y orden se resuelven en el servidor vía AJAX en vez de traer todas las filas al navegador.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="importFile" type="BoolPresencia" use="optional">
      <xs:annotation>
        <xs:documentation>
          Activa el botón para importar datos desde archivo XLS/CSV. **El motor sólo mira si el
          atributo está presente, no su valor**: `importFile="false"` muestra el botón igual. Para
          que no aparezca hay que omitir el atributo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="style" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>CSS inline del contenedor.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="btnconfirma" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto custom del botón de confirmación.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="xmlImpresion" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del XML usado para la impresión (separado de la consulta).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tabindex" type="xs:integer" use="optional">
      <xs:annotation><xs:documentation>Tabindex base del formulario para navegación por teclado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="hasFieldset" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Envuelve los campos en un `&lt;fieldset&gt;` HTML (agrupado visual). **Cualquier valor no vacío lo activa, incluido `"false"`**: para no envolver hay que omitir el atributo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="sqlhardcoded" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No afecta la consulta sino la grabación**: el INSERT se arma con los valores crudos y sin
          lista de columnas, se deshabilitan los prepared statements y cambia el contexto en que se
          resuelve el código del INSERT. El motor anota el XML en su log de pendientes como algo a
          arreglar, así que es un modo a evitar en XMLs nuevos. Para SQL literal en la **consulta**
          va el elemento `&lt;sql&gt;`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="cancel" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          Muestra el botón que **limpia el formulario** (no cancela ninguna transacción).
          **Con `preFetch="true"` el botón no se dibuja**, aunque se declare `cancel="true"`.
          (Bug conocido: en `fichaing` devuelve `ReferenceError: xml is not defined`.)
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="sidePanel" type="BoolTrueFalse" use="optional">
      <xs:annotation>
        <xs:documentation>
          Panel lateral de las vistas `calendar`. **Está al revés**: viene activo por default y la
          comparación es estricta contra un booleano, así que **cualquier valor explícito lo apaga**,
          incluido `sidePanel="true"`. Para tenerlo hay que omitir el atributo; para esconderlo,
          escribir cualquier valor. Ojo que `calendarTasks` necesita el panel visible.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="noform" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Alias lowercase de `noForm`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarAspectRatio" type="xs:decimal" default="1" use="optional">
      <xs:annotation><xs:documentation>Relación ancho/alto con la que se dibuja el calendario.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="tableClass" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Clases CSS que se **agregan** a la tabla de la grilla. Es el complemento de `class`/`clase`, que en cambio **reemplazan** la clase por defecto de la vista.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="createButtonLabel" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Texto del botón "Nuevo" del listado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="evento" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es un evento JS**: se resuelve server-side y filtra **qué evento de grabación dispara
          este movimiento**. Si el evento en curso no coincide con el valor, el contenedor se saltea
          entero y no graba nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarTasks" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Fuente de tareas del panel lateral del calendario. **No puede ser un campo**: el valor se
          usa crudo como **nombre de un XML** (se busca en el directorio del XML actual) y sus filas
          son las tareas. Requiere el panel lateral visible — ver `sidePanel`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarDefaultView" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Vista inicial del calendario; omitido equivale a `"month"`. El valor se inyecta **crudo** en
          la configuración del calendario, así que un nombre inexistente no lo rechaza el schema pero
          rompe el render del lado del cliente. Los nombres válidos en la versión instalada son
          `month`, `basicWeek`, `basicDay`, `agendaWeek`, `agendaDay`, `listDay`, `listWeek`,
          `listMonth` y `listYear` — las vistas de lista ya vienen incluidas, sin agregar nada.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="calendarOptions" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Opciones extra del calendario: el contenido se concatena **como JavaScript crudo** dentro
          de su configuración. No hay validación ni escapado, así que un error de sintaxis acá rompe
          el render del calendario entero.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="close" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Si "true", el popup se cierra automáticamente tras procesar.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="before" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          Selector de **fase** del movimiento: con `"true"` ese contenedor se graba en la pasada
          previa; con cualquier otro valor, o ausente, en la pasada posterior. No tiene nada que ver
          con eventos de plugins.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="accordion" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Convierte las **solapas internas** (los campos con contenedor embebido) en un acordeón de paneles colapsables. **Los `&lt;fieldGroup&gt;` no participan.**</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="exportHeader" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Controla si la primera fila del XLS exportado lleva los nombres de columnas.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="grabaFinal" type="BoolEstricto" default="false" use="optional">
      <xs:annotation>
        <xs:documentation>
          **Va en el `&lt;histrix&gt;` del XML de movimiento/relación**, no en un `&lt;table&gt;`.
          Con "true", ese contenedor se saltea en la pasada normal de movimientos y se graba al
          final de todo. No es específico de UPDATE. La fase exacta la termina de elegir
          `postGrabacion`, que se evalúa junto con él. Alias lowercase aceptado: `grabafinal`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="htmlTemplate" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Template HTML alternativo para fichas con layout custom.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dirImpresion" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Directorio del XML de impresión (combinado con `xmlImpresion`).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="mapPolyline" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Polyline que conecta los puntos del mapa. **Viene dibujada aunque no se declare el atributo**: el único valor con efecto es `"false"`, que la saca.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="zoom" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Zoom inicial (mapa/gráfico). Alias corto de `mapZoom`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="fontSize" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Tamaño de fuente de la **planilla exportada** (default 10). No toca el HTML del formulario ni el PDF.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFcardBottomMargin" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          En PDFs tipo etiqueta/tarjeta: **pese al nombre se usa como offset superior** del contenido
          de la tarjeta y como separación entre filas de tarjetas (mm). No tiene efecto en la primera
          tarjeta ni en la primera de cada página.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="exportFormat" type="xs:string" default="Xls" use="optional">
      <xs:annotation><xs:documentation>Formato con el que sale el botón exportar: "Xls" (default) | "Csv" | "Xml". El valor se resuelve por nombre, así que hay que respetar las mayúsculas del ejemplo.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="delimiter" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>**Sólo precarga** el separador en el input del diálogo de exportación; no es el separador efectivo del CSV. El carácter que se escribe lo fija `delimitador`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="delimitador" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Separador de columnas **efectivo** de la exportación CSV (default ","). **No es un alias secundario de `delimiter`**: `delimiter` sólo precarga el input del diálogo y éste es el que manda.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="extension" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Extensión que se propone en el diálogo de exportación (default "csv"). **Sólo precarga el input**: cuando la exportación se pide por API gana la extensión propia del formato. El motor acepta además la grafía con typo `extenssion` como alias.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="updateTempTable" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es el nombre de una tabla: es un booleano y viene activo.** Con el atributo ausente
          los datos del grid se copian a las tablas temporales; el único valor con efecto es
          `"false"`, que desactiva ese volcado.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="postGrabacion" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>En `&lt;histrix tipo="nosql"&gt;`: ejecuta el PHP DESPUÉS de la grabación, no antes.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dsn" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del DSN alternativo (definido en `custom_dsn` de config.xml). Permite que este XML consulte otra BD distinta de la principal.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="reloadForm" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          Sólo aplica a los **detalles inline** (el XML embebido, distinto del padre) y sólo cuando
          `llenoReferente` no está en `"true"`. Y `"false"` no significa "no recargar": el bloque
          inline **se vacía** en lugar de recargarse.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="llenoReferente" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Si "true", al recibir un parámetro referente precarga sus datos en el form.
        **Sin `default` declarado a propósito**: hay un camino que trata la ausencia como activo
        (sólo "false" lo desactiva) y otros dos que exigen el literal "true", así que omitirlo no
        equivale a ninguno de los dos valores.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="replaceInto" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Genera `REPLACE INTO` en lugar de `INSERT INTO` (MySQL).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFconpie" type="BoolPresencia" use="optional">
      <xs:annotation><xs:documentation>Renderiza el pie de página del PDF (toma el contenido del `&lt;pie&gt;` del XML). **Cualquier valor no vacío lo activa, incluido "false"**: con `PDFconpie="false"` el pie se imprime igual. Para que no salga hay que **omitir el atributo**.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="bottomMargin" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es un margen sino la coordenada Y del pie del PDF** (mm): positiva se mide desde el
          borde superior, negativa desde el borde inferior. Además corre el punto donde se dispara el
          salto de página. `bottomMargin="20"` imprime el pie a 20 mm del borde **superior**.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <!-- Atributos confirmados leídos por el motor (src/) — agregados a posteriori -->
    <xs:attribute name="autoupdate" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Refresco automático periódico de la vista. **No es un booleano sino el intervalo**: el
          valor se compara contra cero, así que `autoupdate="true"` no hace nada — hay que poner un
          número. Y el polling sólo se arma en gráficos de tipo `gauge`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="buildTree" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Construye una estructura jerárquica a partir de los resultados. Requiere campos con `arbol="padre"` y `arbol="hijo"`, **en minúscula**: escrito `Arbol` hay consumidores que no lo leen y el árbol sale vacío.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="template" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre/ruta de un template alternativo de impresión/render para este `&lt;histrix&gt;`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="XMLroot" type="xs:string" default="data" use="optional">
      <xs:annotation><xs:documentation>Nombre del tag raíz cuando se exporta a XML (`tipo="exportXML"` o similar). Default: "data".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="XMLrow" type="xs:string" default="row" use="optional">
      <xs:annotation><xs:documentation>Nombre del tag por fila al exportar a XML. Default: "row".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="innerForms" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Modo de render de formularios internos en ABMs. Ej. "list" muestra los hijos como lista en lugar de tabs.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="showLink" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Agrega en el ABM un botón que abre la URL de la vista en otra pestaña. **No aparece en la grilla** ni agrega una columna con el link.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="checkDupOnUpdate" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No valida duplicados**: el valor se pasa a un parámetro que nadie consume. Su único
          efecto real es forzar el camino **sin prepared statement** en el UPDATE.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFheaderBorder" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Recuadro redondeado de la **cabecera del documento** (logo, título, fecha) más el separador de la fecha. **No afecta los títulos de columna de la tabla.** Viene activo; "false" lo saca.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFheaderTotals" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Si "true", repite la fila de totales en el header de cada página del PDF.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFpageBreak" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation><xs:documentation>Permite/desactiva el salto de página automático del PDF; "false" lo desactiva. **No es lo mismo que `pageBreak`**, que sólo existe en la impresión en tanda y decide si cada fila arranca en página nueva.</xs:documentation></xs:annotation>
    </xs:attribute>
    <!-- Variantes en minúscula: ojo, no todas son equivalentes a su par camelCase -->
    <xs:attribute name="grabafinal" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Alias lowercase de `grabaFinal`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="llenotemporal" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias lowercase de `llenoTemporal`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="dirimpresion" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias lowercase de `dirImpresion`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="impresion" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Alias lowercase de `xmlImpresion`: XML de impresión asociado.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="graba" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Opt-**in** del contenedor de **cabecera** para que la cabecera haga su propio INSERT: sólo
          `"true"` hace algo. `graba="false"` no impide ninguna grabación, es un no-op.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="maxshowresult" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          Tope de filas que la consulta acepta mostrar. **No pagina ni recorta**: si el total supera
          el número **no se renderiza ninguna fila** y el cuerpo de la tabla se reemplaza por una
          línea con "N Registros Encontrados". Sirve para obligar a filtrar antes de listar.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="insertaABM" type="BoolDefaultActivo" default="true" use="optional">
      <xs:annotation>
        <xs:documentation>
          **No es independiente de `inserta`**: los dos escriben el mismo interruptor de alta y, si
          están los dos, **gana `inserta`** porque se aplica después. Poner `insertaABM="true"` junto
          a `inserta="false"` no habilita el alta.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="pageNumber" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          `id` del campo que aporta el número de página del PDF. **No es un booleano ni un
          número**: el motor toma el valor de ese campo (o el último valor, si hay varias filas)
          como número de la primera página y sigue numerando desde ahí.
          Ej: `pageNumber="numerator"` numera con el campo `numerator`.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="calculateButton" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Agrega un botón de recálculo a la barra de acciones. **No es un booleano**: el valor es la etiqueta visible del botón. Ej: `calculateButton="Buscar"`.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="sinFecha" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Suprime la fecha en la cabecera del PDF.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="sinNumero" type="BoolEstricto" default="false" use="optional">
      <xs:annotation><xs:documentation>Suprime el número de comprobante en la cabecera del PDF.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="privacy" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Activa la **seguridad por registro**: al insertar se guarda quién es el dueño de la fila, y
          después las consultas de esa pantalla filtran por eso. Requiere el literal "true".
          Es el atributo que hace que dos usuarios de la misma pantalla vean sólo lo suyo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="showProcessButton" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Fuerza el botón de procesar en un contenedor embebido. **Va en el `&lt;histrix&gt;` del XML embebido**, no en el del padre.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="selectModifier" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Texto que se inyecta **entre `SELECT` y la lista de campos**, para forzar el plan del
          optimizador: típicamente `STRAIGHT_JOIN` o un hint entre comentarios. Va crudo al SQL, así
          que un valor inválido rompe la consulta.
          Usarlo sólo después de haber medido el plan de ejecución; no es una optimización a ciegas.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="modificar" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Tercera grafía viva junto a `modifica`: con "true" habilita el botón de modificar. Conviene usar `modifica`, que es la que contemplan todos los caminos del render.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="ancho" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Misma cosa que `width`: se leen juntos y gana el que esté presente. Aplica al ancho del contenedor en las vistas de árbol, mapa, calendario y consulta.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="postGridData" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>**Va en el `&lt;histrix&gt;` del XML embebido**: con "true" ese contenedor interno postea sus datos junto con el formulario padre, como si fuera una grilla. Sirve para que un embebido que no es grid participe de la grabación del padre.</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="printViewer" type="xs:string" use="optional">
      <xs:annotation><xs:documentation>Nombre del conversor con el que se previsualiza la impresión en el navegador, para formatos que no son PDF. Valores en uso: `Zpl2pdf` (etiquetas Zebra) y `Esc2html` (impresoras de tickets).</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="automail" type="BoolEstricto" use="optional">
      <xs:annotation><xs:documentation>Con "true" y junto a `autoprint`, además de imprimir dispara el envío del PDF por mail. Requiere el literal "true".</xs:documentation></xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFheigthRel" type="xs:decimal" use="optional">
      <xs:annotation>
        <xs:documentation>
          Divisor del interlineado de las celdas del PDF: cuanto más alto, más comprimidas quedan las
          filas. Default 1.2 en el cuerpo de la grilla y 2 en la línea de cantidad de registros.
          **El nombre lleva el typo `heigth` en el motor** y hay que escribirlo así.
          Es el atributo que se busca cuando `PDFlineHeight` no da el resultado esperado: ése fija un
          alto absoluto en mm, éste es el multiplicador.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFprintPageTotals" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Totales por página del PDF. **Trampa:** vienen activos por defecto y la comparación es
          estricta contra un booleano, así que **declarar el atributo con cualquier valor —incluido
          "true"— los desactiva**. Para tenerlos hay que omitirlo.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="xmldir" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Directorio del XML, usado para armar la clave de programa de las notificaciones.
          Va **en el `&lt;histrix&gt;`**: sólo el tag raíz copia todos sus atributos al contenedor.
          Puesto en el `&lt;form&gt;` no llega a ninguna propiedad y no hace nada, porque ahí el
          parser sólo toma una lista blanca de atributos.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="dataTablePaginate" type="BoolJsLiteral" use="optional">
      <xs:annotation>
        <xs:documentation>
          Enciende el paginador de la grilla: aparecen los botones de página y el selector de
          cuántos renglones mostrar (arranca en 10, con opciones 10/25/50/100). Viene apagado, así
          que por defecto la grilla dibuja todos los renglones y se recorre con la barra de scroll
          vertical. Trampa: el valor se copia tal cual al script de la grilla, sólo se acepta true o
          false — cualquier otro texto rompe el armado de la grilla. En grillas con
          serverSide="true" que declaran paginar=, la paginación la decide paginar y este atributo
          queda sin efecto.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="dataTableScrollCollapse" type="BoolJsLiteral" use="optional">
      <xs:annotation>
        <xs:documentation>
          Cuando la grilla tiene menos renglones de los que entran en su alto, el marco con scroll
          se encoge hasta la altura real de los datos en vez de dejar el hueco vacío abajo. Viene
          apagado. Trabaja de la mano del alto de la grilla (tableHeight): si no hay un alto que
          sobre, no se nota diferencia. Sólo se acepta true o false, porque el valor se copia tal
          cual al script de la grilla.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="dataTableScroller" type="BoolJsLiteral" use="optional">
      <xs:annotation>
        <xs:documentation>
          Cambia la paginación por scroll virtual: la grilla mantiene el alto fijo y va pidiendo y
          dibujando sólo los renglones que caen en la ventana visible mientras se scrollea, lo que
          la vuelve usable con muchísimos registros. Viene apagado. Trampa grande: se aplica
          únicamente en grillas con serverSide="true"; en una grilla común el atributo se ignora por
          completo. Sólo se acepta true o false.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="swap" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Agrega una columna angosta a la izquierda de la grilla con una manija para arrastrar
          renglones y cambiarles el orden; al soltar, el cambio se graba solo, sin apretar Grabar.
          Trampa central: no se guarda ningún número de orden — el motor intercambia el contenido de
          los renglones entre registros dejando los campos clave quietos, o sea que reescribe con
          UPDATE los registros involucrados (el registro que tenía la clave 1 queda con los datos
          del que estaba abajo). Todos los movimientos van en una sola transacción. Sólo true
          activa.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="ganttScale" type="GanttEscala" use="optional">
      <xs:annotation>
        <xs:documentation>
          Escala de tiempo con la que abre el diagrama de Gantt: horas, días, semanas o meses. Si no
          se indica, abre en días. Después el usuario puede acercar o alejar con el zoom, siempre
          dentro de los límites que fijan ganttMinScale y ganttMaxScale.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="ganttMinScale" type="GanttEscala" use="optional">
      <xs:annotation>
        <xs:documentation>
          Escala más fina a la que el usuario puede acercar el zoom del diagrama de Gantt. Trampa:
          si no se declara queda en días, más restrictivo que el mínimo natural de la vista, así que
          el zoom a horas está bloqueado hasta que se ponga hours de forma explícita.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="ganttMaxScale" type="GanttEscala" use="optional">
      <xs:annotation>
        <xs:documentation>
          Escala más gruesa a la que el usuario puede alejar el zoom del diagrama de Gantt. Si no se
          indica queda en meses, que además es lo máximo que maneja la vista, así que en la práctica
          sólo sirve para restringir más (por ejemplo, poner weeks para que no se pueda alejar hasta
          meses).
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="ganttItemsPerPage" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          Cuántas filas muestra cada página del diagrama de Gantt; el resto se llega con los botones
          de página. Si no se indica, 20. Tiene que ser un número: el valor se copia tal cual al
          script de la vista, así que un texto la rompe.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="noLimit" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Saca el tope de un solo registro que el motor le agrega por defecto a sus UPDATE y DELETE,
          de manera que la operación alcanza a todos los renglones que cumplan la condición. Usar
          con mucho cuidado: si la condición no identifica un único registro, se modifican o borran
          varios de una vez y sin aviso. Además, una vez que se aplica deja en cero el tope
          declarado con limit= para lo que resta de la operación. Sólo true activa.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="sqlKillable" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Hoy no produce ningún efecto: el motor tiene la lectura del atributo, pero el bloque que
          la usaba está desactivado, así que declararlo o no genera exactamente el mismo SQL. Estaba
          pensado para evitar que la consulta salga marcada con el identificador de la instancia
          (marca que servía para reconocerla en el servidor y cancelarla si quedaba colgada). No
          conviene declararlo en XMLs nuevos.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="sqlClausule" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Texto que se inserta tal cual inmediatamente después del SELECT y antes de la lista de
          campos; el uso típico es meter DISTINCT. Viene vacío. Trampas: no agrega separador, hay
          que dejarle el espacio final (por ejemplo "DISTINCT ") o queda pegado al primer campo y el
          SQL sale inválido; y no se aplica a la consulta de conteo que usa paginar=, así que el
          total de registros puede no coincidir con las filas que realmente se ven. Cae en el mismo
          lugar que selectModifier, que es el atributo de uso corriente para esto.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="treeGrid" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Muestra el árbol como grilla: una primera columna con los nodos y, a la derecha, una
          columna por cada campo visible del XML (con su etiqueta y su ancho). Sin este atributo el
          árbol muestra únicamente los nodos. Trampas: las columnas se arman sólo con los campos que
          no estén marcados como ocultos, y en árboles que llevan un campo con suma="true" —que se
          construyen completos de entrada— las columnas de datos se pierden y queda sólo la del
          nodo. Sólo true activa.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="displayFilterButton" type="BoolDefaultActivo" use="optional" default="true">
      <xs:annotation>
        <xs:documentation>
          Con false esconde el renglón de la barra de filtros donde viven el botón Buscar, el de
          limpiar filtros y los botones de toolbar declarados para la barra de filtros. Cualquier
          otro valor —o no poner el atributo— los deja visibles. Sólo tiene sentido si la búsqueda
          se dispara de otra forma (por ejemplo con autoFilter="true"), porque si no el usuario se
          queda sin manera de ejecutar el filtro. Trampa: el ocultado se genera con un estilo mal
          cerrado, así que además de esconderse puede comerse parte del contenido de ese renglón.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="topMargin" type="xs:decimal" use="optional">
      <xs:annotation>
        <xs:documentation>
          Se declara como margen superior de la impresión PDF y el motor lo lee y lo guarda (si no
          se pone, vale 0), pero ninguna rutina de armado del PDF llega a usarlo: hoy no produce
          ningún efecto visible. Si lo que buscás es correr hacia abajo el arranque del contenido en
          las hojas que se abren por corte de página, el atributo que sí funciona es
          PDFheaderMargin; para el margen de abajo está bottomMargin, que sí se aplica.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="PDFheaderMargin" type="xs:decimal" use="optional">
      <xs:annotation>
        <xs:documentation>
          Distancia en milímetros, medida desde el borde superior de la hoja, a la que vuelve el
          contenido cada vez que la impresión PDF abre una hoja nueva por corte de página. Por
          defecto son 27 mm, que es lo que ocupa la cabecera estándar (logo, datos de la empresa,
          título, fecha): si la cabecera de la base es más alta, de la segunda hoja en adelante el
          contenido se le encima y hay que subir este número. No cambia nada en la primera hoja,
          donde el contenido arranca justo debajo de la cabecera ya medida.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="directprint" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Nombre de la impresora configurada a la que se manda, sin pasar por el visor de PDF, el
          comprobante que el programa imprime automáticamente después de grabar. Cualquier valor no
          vacío enciende el modo directo, pero NO es un booleano: el valor se usa tal cual como
          nombre de impresora, así que poner "true" hace que se busque una impresora llamada "true"
          (con el usuario en modo editor eso corta con un error de impresora inexistente). Escrito
          en el XML de un movimiento la impresión se resuelve entera en el servidor; escrito en el
          programa principal el navegador dispara el pedido en segundo plano en lugar de abrir la
          ventana con el PDF. Va todo en minúsculas.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="defaultPrinter" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Impresora que queda preseleccionada en el combo de impresoras del panel de opciones de
          impresión del programa. El combo se arma con las impresoras configuradas en la base más la
          opción PDF, que es la primera y corresponde al valor vacío, así que el texto tiene que
          coincidir exactamente con el nombre de una impresora configurada; si no coincide con
          ninguna no se preselecciona nada y queda PDF. Es sólo un valor por defecto: el usuario
          puede cambiarlo antes de imprimir.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="printCondition" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Nombre de un campo del propio programa que decide si se hace o no la impresión automática
          posterior a la grabación. La trampa está en la comparación: la impresión se saltea
          únicamente cuando ese campo vale el texto exacto "false"; con 0, con vacío, con "no" o con
          cualquier otro valor imprime igual. Sirve con un campo calculado que devuelva "true" o
          "false" según el tipo de comprobante.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="notificationText" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Reemplaza el texto del aviso que reciben los usuarios suscriptos al programa cuando
          alguien da de alta, modifica, borra o procesa. Si no se pone, el aviso dice la acción más
          el título del programa ("Insertó Facturas", "Modificó Facturas"). El texto aparece en el
          globo de notificación, en el panel de notificaciones y en el aviso push del celular. Ojo
          que el aviso de altas, modificaciones y bajas sólo se emite si el programa tiene la
          auditoría activada y si hay usuarios suscriptos.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="notificationLink" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Nombre del XML al que apunta el botón de lupa ("Ver") del aviso de notificación: al
          tocarlo se abre el historial de auditoría de ese evento para ese programa. Si no se pone,
          se usa el XML del programa que generó el aviso, que es lo habitual; sirve cuando el aviso
          lo dispara un XML de grabación pero el historial que le interesa al usuario es el de otro
          programa.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="notificationDir" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Subdirectorio del XML vinculado al aviso de notificación; si no se pone, se usa el
          directorio del programa que generó el aviso. El dato viaja dentro de la notificación y
          llega al cliente, pero la interfaz actual no lo usa para armar el botón del aviso (lo arma
          sólo con el nombre del XML), así que hoy no cambia nada visible: queda disponible para
          clientes propios que consuman las notificaciones por API.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="xlsHeader" type="BoolJsLiteral" use="optional">
      <xs:annotation>
        <xs:documentation>
          Decide si la exportación a Excel arranca con un bloque de cabecera: logo, nombre de la
          empresa, CUIT/teléfono/web, línea separadora y el título del reporte con la fecha, y los
          datos empiezan más abajo. Tiene tres estados: "true" la fuerza, "false" la saca y, si no
          se declara el atributo, se respeta la configuración general de la base (que viene
          apagada). También deja pre-marcado el interruptor de cabecera del diálogo de exportación,
          que el usuario puede cambiar para esa exportación puntual. Sólo afecta a Excel: CSV, PDF y
          XML lo ignoran.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="redirect" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          El motor lo publica tal cual en el esquema que devuelve la API del programa, junto con el
          título, el tipo, los permisos y la paginación, para que un cliente propio decida a dónde
          llevar al usuario. El resto del motor no lo interpreta: en la interfaz web clásica no
          produce ningún efecto, y si no se declara el esquema lo informa vacío. No tiene relación
          con el parámetro de redirección del login, que es otra cosa y viaja por la URL.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="tableBorder" type="xs:integer" use="optional">
      <xs:annotation>
        <xs:documentation>
          Se pasa tal cual al atributo border de la tabla HTML de la grilla, así que con "1" te
          dibuja el borde de la tabla y de las celdas. Trampa: el border escrito en el tag table del
          XML no lo lee el motor; el que manda es este, arriba en histrix.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="styleTable" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Estilo CSS inline que se aplica a la tabla HTML de la grilla. Trampa importante: cuando no
          se declara, el motor le pone float:left;clear:left;, así que en el momento en que lo
          definís pisás ese default y la grilla deja de flotar a la izquierda y de cortar el renglón
          anterior; si solo querés agregar algo, repetí el float y el clear. En la vista mobile no
          hay default: ahí queda solo lo que escribas.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="titulo_div" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Título alternativo del contenedor: reemplaza al título declarado a la hora de nombrar el
          PDF que se imprime, de titular ese PDF y de describir la operación en la auditoría. Se le
          sacan las etiquetas HTML antes de usarlo. Trampa: normalmente lo manda el navegador con el
          título de la pestaña que abrió el usuario, y ese valor pisa lo que venga escrito en el
          XML.
          **Ningún XML del repo lo usa**: es una función viva del motor que nadie descubrió, así
          que no hay ningún uso real contra el que contrastar este comportamiento.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="showcantidad" type="BoolEstricto" use="optional">
      <xs:annotation>
        <xs:documentation>
          Variante en minúsculas de `showCantidad`, que **acá sí funciona**: la búsqueda de atributos del
          `&lt;histrix&gt;` ignora mayúsculas. En `&lt;table&gt;`, en cambio, se acepta sólo
          `showCantidad`. En código nuevo escribir `showCantidad` en los dos lugares.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="detailDir" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Directorio donde vive el XML de detalle: el que se abre al expandir una fila, y también el
          detalle de las vistas de calendario y de gantt. Sin esto se busca en el directorio del XML
          actual. Si el nombre declarado en `detalle` ya trae ruta propia, esa ruta gana.

          El mismo valor se puede escribir como `dir` en el `&lt;consulta&gt;`/`&lt;form&gt;`, y si
          están los dos, el del `&lt;consulta&gt;`/`&lt;form&gt;` pisa a éste.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="email" type="xs:string" use="optional">
      <xs:annotation>
        <xs:documentation>
          Al grabar, además de disparar la impresión declarada en `impresion`, abre el envío de ese PDF
          por mail. **Alcanza con que el atributo esté presente: el valor no se usa para nada** (por
          convención se repite el nombre del XML de impresión, pero es indistinto).

          Sin `impresion` no hace nada, porque el envío viaja pegado a la llamada de impresión.
        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>

  <xs:element name="histrix" type="HistrixType">
    <xs:annotation>
      <xs:documentation>
        Raíz del documento Histrix. Define un formulario, consulta, ficha, grid, gráfico u otra vista
        según el atributo `tipo`. Contiene un `&lt;form&gt;` (o alias `&lt;consulta&gt;`) con la definición de
        datos, y opcionalmente `&lt;grafico&gt;`, `&lt;widget&gt;`, `&lt;eventos&gt;`, `&lt;customScript&gt;`, etc.
      </xs:documentation>
    </xs:annotation>
  </xs:element>

  <xs:element name="abm" type="HistrixType">
    <xs:annotation>
      <xs:documentation>Alias legacy de `&lt;histrix&gt;` (mismo contenido). Conservado por compatibilidad.</xs:documentation>
    </xs:annotation>
  </xs:element>

  <!-- =======================
       Raíz para XML parciales que se incluyen vía <fieldGroup xml="..." dir="...">
       ======================= -->
  <xs:complexType name="IncludeType">
    <xs:annotation>
          <xs:documentation>
            Fragmento XML incluido por otro documento (típicamente vía `&lt;fieldGroup xml="..." dir="..."&gt;`).
            Comparte la estructura de un fieldGroup pero como documento independiente: lista de campos
            o grupos que el motor inyecta en el sitio donde se incluye.
            El nombre del elemento raíz **no se chequea**: el motor toma los hijos del documento sin
            mirar cómo se llama la raíz, así que `&lt;include&gt;` es una convención, no un requisito.
          </xs:documentation>
        </xs:annotation>
    <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element name="field" type="FieldType">
        <xs:annotation><xs:documentation>Campo incluido.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="campo" type="FieldType">
        <xs:annotation><xs:documentation>Alias español de `&lt;field&gt;`.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="fieldGroup" type="FieldGroupType">
        <xs:annotation><xs:documentation>Sub-grupo incluido.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="if" type="xs:string">
        <xs:annotation><xs:documentation>Inclusión condicional: texto PHP que decide si el contenido del include se procesa.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="validation" type="validationType">
        <xs:annotation><xs:documentation>Validación incluida junto con los campos.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="cierraproceso" type="cierraProcesoType">
        <xs:annotation><xs:documentation>Cierre de proceso incluido junto con los campos.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="importar" type="importarType">
        <xs:annotation><xs:documentation>Bloque de importación incluido junto con los campos.</xs:documentation></xs:annotation>
      </xs:element>
      <xs:element name="helper" type="HelperTypeCplx">
        <xs:annotation><xs:documentation>Helper incluido junto con los campos.</xs:documentation></xs:annotation>
      </xs:element>
    </xs:choice>
  </xs:complexType>

  <xs:element name="include" type="IncludeType">
    <xs:annotation>
      <xs:documentation>Raíz para XMLs parciales incluidos por otro documento.</xs:documentation>
    </xs:annotation>
  </xs:element>

</xs:schema>