Concepts

    Templates and the form schema

    A template is a schema, not a document. Understanding it is most of understanding DocDealer.

    A template is not a Word file with holes in it. It is a schema with three parts, and the same schema drives the UI form, the AI generation and the API surface — which is why any client produces the same validated document.

    Rendering diagram…
    One schema, three consumers

    The three parts

    Prop

    Type

    Objects and fields

    formSchema holds objects and fields. An object is a repeating group — "the parties to this deed", "the properties being transferred" — and a field is a single value inside it.

    That structure is exactly why formData keys look the way they do:

    formData
    {
      "comparecientes._count": 2,
      "comparecientes.0.nombre": "Ana Torres",
      "comparecientes.1.nombre": "Luis Gil"
    }

    objectKey._count says how many entries the repeating group has; objectKey.index.fieldKey addresses one value inside one entry. There is no nesting beyond that — the shape is deliberately flat so it survives a JSON round-trip through any client.

    Read the real keys rather than guessing: GET /api/templates/{templateId} returns formSchema with objects and fields, and each field's fieldKey (falling back to its id) is what goes in the key.

    Field types

    TypeValue shape
    stringText.
    numberNumeric.
    dateISO date string.
    checkboxBoolean.
    selectorOne of a declared set of allowed values.
    file_attachmentAn object wrapper — an uploaded file, optionally with an AI analysis of it.
    geoaddressAn object wrapper — a structured address with coordinates.
    connector_toolAn object wrapper — a value fetched from an organization connector.

    The first five are scalars: pass the value directly. The last three are object wrappers, because one field holds several derived values at once — an analysed ID document yields a name, a number and a date, and a geocoded address yields road, house number, postcode, city and coordinates.

    Sections and example content

    The template's content is divided into sections, and each section carries example content: real prose from real documents of that kind. Generation shows the model the examples plus the relevant form values and asks for one section. It is imitation against a known shape, not open-ended writing — which is what makes it fast and cheap. See Generation.

    A section can also be marked to skip AI entirely and emit its example content verbatim when conditions hold. That is a <when result="static"> inside the case, covered in Rules.

    Response language

    Every AI prompt appends a "respond in X" directive taken from the template, never from the reader's UI language. The chain is template.locale → the organization's default locale → English. So a Spanish template generates Spanish prose for an English-speaking operator, which is almost always what you want for a legal document.

    The authoring format: a catalogue of examples

    Everything above describes the schema. This is what it looks like on disk.

    A template serializes to one .html file: YAML frontmatter carrying the metadata and the form schema, then the HTML body carrying the sections — each with its own <instructions>, its <when> rules, an optional <default> and its <case> reference examples. There is no separate rules block: a rule is a child of the thing it describes.

    Each snippet below is a literal extract from a template that parses and validates — a test asserts both, so an example here cannot document syntax the validator would reject.

    47 examples. Every snippet is cut verbatim from a template that parses and validates, so it is safe to copy. Agents get the same catalogue over MCP from get_template_examples.

    Frontmatter

    The YAML head of the file. Only these keys survive an export → import cycle.

    Template metadata

    frontmatter-metadatafrontmatter

    Title, description, tags and the four instruction fields.

    title: Contrato de arrendamiento
    description: Plantilla mínima que muestra la forma del fichero y los campos escalares.
    tags:
      - Ejemplo
      - Arrendamiento
    validateDocumentInstructions: Comprueba que la renta mensual y la fianza son coherentes entre sí.
    generateSectionInstructions: Usa un registro formal. Nunca inventes datos que no estén en el formulario.
    titleGenerationInstructions: Usa el nombre del arrendatario y la fecha del contrato.
    agentInstructions: |-
      Pregunta primero por el inmueble y después por cada parte. Si el arrendatario
      es una sociedad, pide también el CIF.
    form:
    • These are the ONLY metadata keys that round-trip. `icon`, `formatConfig` and `locale` are not written to the file — setting them here does nothing and they are lost on the next export.
    • The three `*Instructions` fields are appended to the server's own AI prompts. `agentInstructions` is different: it is handed to the external agent interviewing the user and never enters a prompt.

    agentInstructions

    frontmatter-agent-instructionsfrontmatter

    Guidance for the MCP agent gathering data from the user.

    agentInstructions: |-
      Pregunta primero por el inmueble y después por cada parte. Si el arrendatario
      es una sociedad, pide también el CIF.
    form:
    • Returned by `get_document_generation_skill({ templateId })`, so an agent reads it before it starts asking questions.
    • Write it as instructions to a colleague taking the client call — what to ask first, what to double-check — not as instructions to a writing model.

    Form objects

    One object per entity. If it repeats, it is an object.

    Singleton object

    object-singletonfrontmatter

    Document-wide data: one instance, always.

        - id: obj_contrato
          label: DATOS DEL CONTRATO
          objectKey: contrato
          description: Datos que valen para todo el documento.
          minimumCount: 1
          maximumCount: 1
    • `maximumCount: 1` is what makes it a singleton. Use one for data that applies to the whole document.

    Repeatable object

    object-repeatablefrontmatter

    An entity the user can add any number of: parties, properties, line items.

        - id: obj_parte
          label: PARTES
          objectKey: parte
          description: Cada interviniente en el contrato.
          minimumCount: 1
          maximumCount: null
    • `maximumCount: null` means unlimited. Its form data is keyed `parte.0.nombre`, `parte.1.nombre`, … with a `parte._count`.
    • If it repeats, it is an object — never a field holding a list.

    Object validation rule

    object-validation-rulefrontmatter

    A prose rule checked by the AI across every entry of an object.

          validationRules:
            - id: val_vendedor_nif
              rule: Dos vendedores no pueden compartir el mismo NIF.
    • Written inline on the object. The parser injects `targetObjectId` from its position — never write that key yourself.
    • Evaluated by AI, not by a regex, which is what lets it compare entries to each other.

    Form fields

    The eight input types and their per-type properties.

    string field

    field-stringfrontmatter

    Free text, single line or multiline.

        - id: field_observaciones
          label: Observaciones
          type: string
          fieldKey: observaciones
          objectId: obj_parte
          required: false
          description: Notas libres sobre esta parte.
          multiline: true
          rows: 4
    • `multiline: true` renders a textarea; `rows` sizes it. Also takes `defaultValue`, `sampleValue`, `placeholder`, `maxLength`.

    number field

    field-numberfrontmatter

    A numeric value with optional bounds and a unit.

        - id: field_renta
          label: Renta mensual
          type: number
          fieldKey: renta
          objectId: obj_contrato
          required: true
          description: Importe en euros.
          min: 0
          max: 100000
          units: 
    • `units` is display only — the stored value is the bare number.

    date field

    field-datefrontmatter

    An ISO date, optionally defaulting to today.

        - id: field_fecha
          label: Fecha del contrato
          type: date
          fieldKey: fecha
          objectId: obj_contrato
          required: true
          description: Fecha de firma.
          defaultValue: today
    • `defaultValue: today` resolves at document creation. Also takes `minDate` / `maxDate`.

    selector field

    field-selectorfrontmatter

    A closed set of allowed values.

        - id: field_duracion
          label: Duración
          type: selector
          fieldKey: duracion
          objectId: obj_contrato
          required: true
          description: Plazo pactado.
          options:
            - Un año
            - Tres años
            - Cinco años
    • A condition testing this field must use one of the declared options — the validator checks it.
    • Add `multiple: true` for a multi-select; the value is then an array.

    checkbox field

    field-checkboxfrontmatter

    A boolean, with an optional default.

        - id: field_prorroga
          label: Prórroga automática
          type: checkbox
          fieldKey: prorroga
          objectId: obj_contrato
          required: false
          description: Marque si el contrato se prorroga sin preaviso.
          defaultValue: true
    • In a body condition the value is the string `"true"` / `"false"`, e.g. `op="is" value="false"`.

    file_attachment field

    field-file-attachmentfrontmatter

    An uploaded file the AI extracts structured data from.

        - id: field_dni
          label: DNI del poderdante
          type: file_attachment
          fieldKey: dni
          objectId: obj_poderdante
          required: true
          description: Adjunte anverso y reverso.
          kind: DNI
          multiple: true
          acceptedTypes: application/pdf,image/*
          analysisInstructions: |-
            Es un DNI español. Extrae número, nombre, apellidos, fecha de nacimiento
            y domicilio.
          needAcceptance: true
          schema:
            type: object
            properties:
              numero:
                type: string
              nombre:
                type: string
              fecha_nacimiento:
                type: string
                format: date
            required:
              - numero
              - nombre
    • `analysisInstructions` tells the analyzer what to pull out; `schema` (JSON Schema) constrains the result and is validated on save.
    • `needAcceptance: true` makes a human confirm the extraction before it counts as filled.
    • Never set a `file_attachment` value yourself — upload with `create_attachment_upload_url`, then `analyze_attachments`.
    • `allowedTools` is obsolete and ignored: attachment analysis calls no tools. To derive a value from an address, put `geocode_search` in the consuming section's `tools`.

    geoaddress field

    field-geoaddressfrontmatter

    An address, stored as a structured object with coordinates.

        - id: field_domicilio
          label: Domicilio
          type: geoaddress
          fieldKey: domicilio
          objectId: obj_poderdante
          required: true
          description: Dirección completa; se geocodifica al guardarla.
          placeholder: Calle, número, población
    • Pass the object returned by `geocode_address`, not a plain string.

    connector_tool field

    field-connector-toolfrontmatter

    A value fetched from one of the organization's connectors.

        - id: field_titularidad
          label: Titularidad registral
          type: connector_tool
          fieldKey: titularidad
          objectId: obj_inmueble
          required: false
          description: Se consulta en el registro conectado a la organización.
          connectorId: 7c2c1c2e-1f4a-4a1b-9f3e-0c9a1d2b3c4d
          toolName: buscar_finca
          multiple: false
    • `connectorId` and `toolName` are set once by the author. When the caller supplies the live connector list, the validator checks the reference still resolves.
    • Distinct from a section `tools` entry: this fetches a FORM value, that lets a section call a tool while writing.

    Field and object rules

    Inline visibility and validation, written on the field or object they govern.

    show_if visibility rule

    field-visibility-show-iffrontmatter

    Show a field only when a condition holds.

          visibilityRules:
            - id: vr_iban
              mode: show_if
              effect: hide
              logicOperator: and
              conditions:
                - fieldId: field_forma_pago
                  operator: equals
                  value: Transferencia
    • Inline on the field. The parser injects `targetFieldId` from its position — do not write it.
    • `required` is only enforced while the field is visible.

    hide_if rule with effect: disable

    field-visibility-hide-iffrontmatter

    Grey a field out rather than removing it, when any of several conditions holds.

          visibilityRules:
            - id: vr_vencimiento
              mode: hide_if
              effect: disable
              logicOperator: or
              conditions:
                - fieldId: field_forma_pago
                  operator: not_equals
                  value: Aplazado
                - fieldId: field_precio
                  operator: is_empty
    • `effect: disable` keeps the field visible but uneditable — prefer it when the reader benefits from knowing the field exists.
    • `logicOperator: or` means any condition suffices; `and` requires all. There is no nesting.

    always rule

    field-visibility-alwaysfrontmatter

    Unconditionally hide a field — an internal note the client never sees.

          visibilityRules:
            - id: vr_nota_interna
              mode: always
              effect: hide
              logicOperator: and
              conditions: []
    • With `mode: always` the conditions are not evaluated — but the `conditions` key and `logicOperator` must still be present. An `always` rule with them missing is a validation error.

    Field validation rule

    field-validation-rulefrontmatter

    A prose constraint on one field, checked by AI.

          validationRules:
            - id: val_iban
              rule: Debe ser un IBAN español válido de 24 caracteres.

    Form-level validation rule

    form-validation-rulefrontmatter

    A rule about the form as a whole, targeting no single field.

      validationRules:
        - id: val_form_global
          rule: El precio total debe ser coherente con el número de fincas transmitidas.
    • Sits on `form`, not on a field or object — that is what makes it form-level.
    • The validator emits an informational warning ("no target (form-level rule)") for these. That is expected, not a defect in your template.

    Sections

    The unit of generation. `uses` is the load-bearing attribute.

    uses

    section-usesbody

    Declares which form objects a section's generation can see.

    <section id="partes" name="PARTES" uses="obj_parte,obj_contrato">
      <case id="partes_general">
        <description>
    Cuando interviene una persona física.
        </description>
        <p><strong>{nombre | ANA TORRES GIL}</strong>, mayor de edad, con domicilio en Barcelona.</p>
      </case>
    </section>
    • Forgetting `uses` is the single most common template bug: the AI simply never receives that data, and nothing errors at generation time. Once any section declares it, a section without it is a validation error.
    • Comma-separated object ids, no spaces.

    <default>

    section-defaultbody

    The text a section starts with, before anything is generated.

    <section id="diligencia" name="DILIGENCIA DE CIERRE" uses="obj_acta" editable="false">
      <default>
        <p>Y yo, el Notario, doy fe de cuanto antecede.</p>
      </default>
      <case id="diligencia_general">
        <description>
    Fórmula de cierre.
        </description>
        <p>Y yo, el Notario, doy fe de cuanto antecede y de haber identificado a los comparecientes.</p>
      </case>
    </section>
    • At most one per section, and it must come first inside the `<section>` — before any `<case>`.
    • This section also shows `editable="false"`, which locks the generated text in the editor.
    • This is the ONLY thing that puts text in a section on document creation. `<case>` content never does — a case is reference material.
    • It takes no attributes: there is one per section and nothing can reference it.

    Section visibility

    section-when-hidebody

    Drop a whole section when the form says it does not apply.

    <section id="clausula_no_residente" name="CLÁUSULA DE NO RESIDENTE" uses="obj_vendedor">
      <when result="hide">
        <condition field="field_no_residente" op="is" value="false"></condition>
      </when>
    • `result` is `hide` | `disable` | `optional` | `mandatory`. A section may carry several `<when>`s; the most restrictive match wins.
    • `result="static"` is NOT legal here — that belongs on a `<case>`. The validator rejects it.
    • Never write "skip this section if…" in `<instructions>`: the model does not decide visibility, this does.

    optional and mandatory results

    section-when-optionalbody

    Make a section skippable, or required, depending on the form.

    <section id="advertencia_aplazado" name="ADVERTENCIA DE APLAZAMIENTO" uses="obj_operacion">
      <when result="optional">
        <condition field="field_forma_pago" op="is-not" value="Aplazado"></condition>
      </when>
      <when result="mandatory">
        <condition field="field_vencimiento" op="not-empty"></condition>
      </when>
      <case
    • `optional` may be generated and is skipped without complaint; `mandatory` must be generated. Set `visibility` on the section to change what happens when NO rule matches.

    disable result, and the empty operator

    section-when-disablebody

    Keep a section in the document but do not generate it.

    <section id="nota_registral" name="NOTA REGISTRAL" uses="obj_finca,obj_operacion">
      <when result="disable">
        <condition field="field_referencia_catastral" op="empty"></condition>
      </when>
    • `disable` leaves the section present but ungenerated — use it when the reader should see that the section exists and does not apply.
    • `empty` / `not-empty` take no `value`.

    Hiding a section on entry count

    section-count-visibilitybody

    Drop a section that only makes sense with more than one entry.

    <section id="pluralidad_fincas" name="PLURALIDAD DE FINCAS" uses="obj_finca">
      <instructions>
    Enumera las fincas por su referencia catastral.
      </instructions>
      <when result="hide">
        <condition object="obj_finca" op="lte" value="1"></condition>
      </when>
    • Count operators need `object=`, never `field=`. A count threshold is a number, written as an attribute string.

    visibility, temperature, editable

    section-attributesbody

    Per-section generation settings.

    <section id="valoracion" name="VALORACIÓN" uses="obj_acta" visibility="optional" temperature="0.2" tools="connector:7c2c1c2e-1f4a-4a1b-9f3e-0c9a1d2b3c4d:precio_referencia">
    • `visibility` is the fallback when no `<when>` matches — `mandatory` (the default), `optional`, `disable` or `hide`.
    • `editable="false"` locks the generated text in the editor. `model` and `temperature` override the generation defaults.

    Cases

    Reference variants the AI picks between, or emits verbatim.

    <description>

    case-descriptionbody

    Prose that tells the AI when to pick this variant.

      <case id="partes_general">
        <description>
    Cuando interviene una persona física.
        </description>
        <p><strong>{nombre | ANA TORRES GIL}</strong>, mayor de edad, con domicilio en Barcelona.</p>
      </case>
    • A case with only a `<description>` is AI-selected and AI-rewritten — the content is reference material, not literal output.
    • `<description>` and `<instructions>` are ELEMENTS, not attributes, and hold no formatting marks.
    • A `<case>` takes exactly one attribute, `id`.

    <when result="static">

    case-staticbody

    Emit this case verbatim, with no AI rewrite, when the condition holds.

      <case id="precio_transferencia">
        <description>
    Pago mediante transferencia bancaria.
        </description>
        <when result="static">
          <condition field="field_forma_pago" op="is" value="Transferencia"></condition>
        </when>
        <p>El precio se abona mediante transferencia bancaria a la cuenta designada por la parte vendedora.</p>
      </case>
    • The presence of the `<when>` is the only thing that makes a case static — there is no separate flag.
    • Write a `<description>` too, even on a static case. Staticness is decided per DOCUMENT: if no static rule in the section matches, the whole section falls through to AI generation and every case is offered to the model by its description.
    • A case holds at most one `<when>`.

    match="all"

    case-static-match-allbody

    Require every condition instead of any one.

        <when result="static" match="all">
          <condition field="field_forma_pago" op="is" value="Cheque bancario"></condition>
          <condition field="field_precio" op="gt" value="1000"></condition>
        </when>
    • Only the literal `match="all"` means "and". Anything else — including `match="any"` — means "or".
    • Do not write `match="any"`: it is the default and is never serialized, so it disappears on the next save.

    Conditions

    The test grammar shared by every rule in the body.

    Field condition

    condition-isbody

    The common case: a field equals a value.

    <condition field="field_forma_pago" op="is" value="Transferencia">
    • Operators: `is`, `is-not`, `contains`, `not-contains`, `gt`, `lt`, `empty`, `not-empty`.
    • `field` names the field ID, not its label or key.
    • Against a `selector`, the value must be one of the declared options.

    Object count condition

    condition-object-countbody

    Test how many entries a repeating object has.

    <condition object="obj_vendedor" op="gte" value="3">
    • Use `object=` rather than `field=`, and the count operators `gte`, `lte`, `eq`.
    • The count operators work ONLY with `object=`. Writing `field="x" op="gte"` silently drops the condition — it is not an error, and the rule then behaves as if the condition were not there.

    Condition on an analysis result

    condition-pathbody

    Branch on something the AI extracted from an uploaded file.

    <condition field="field_pasaporte" path="nacionalidad" op="is" value="Española">
    • `path` is a dot path into the `file_attachment` field's analysis result, so document structure can follow the contents of an attachment.
    • Ignored unless the field is a `file_attachment`.

    The remaining operators

    condition-operators-referencebody

    One case per operator that has no other example here.

    <section id="operadores" name="OPERADORES RESTANTES" uses="obj_operacion,obj_vendedor,obj_finca">
      <instructions>
    Sección de referencia que reúne los operadores que no aparecen arriba.
      </instructions>
      <case id="op_contains">
        <description>
    El nombre del vendedor contiene una forma societaria.
        </description>
        <when result="static">
          <condition field="field_nombre_vendedor" op="contains" value="S.L."></condition>
        </when>
        <p>La parte vendedora es una sociedad mercantil debidamente constituida.</p>
      </case>
      <case id="op_not_contains">
        <description>
    El nombre del vendedor no contiene forma societaria.
        </description>
        <when result="static">
          <condition field="field_nombre_vendedor" op="not-contains" value="S.L."></condition>
        </when>
        <p>La parte vendedora interviene en su propio nombre y derecho.</p>
      </case>
      <case id="op_lt">
        <description>
    Operaciones de importe reducido.
        </description>
        <when result="static">
          <condition field="field_precio" op="lt" value="60000"></condition>
        </when>
        <p>La operación no supera el umbral de comunicación reforzada.</p>
      </case>
      <case id="op_count_gte">
        <description>
    Tres o más vendedores.
        </description>
        <when result="static">
          <condition object="obj_vendedor" op="gte" value="3"></condition>
        </when>
        <p>Los vendedores comparecen conjuntamente y por partes iguales.</p>
      </case>
      <case id="op_count_eq">
        <description>
    Exactamente un vendedor.
        </description>
        <when result="static">
          <condition object="obj_vendedor" op="eq" value="1"></condition>
        </when>
        <p>La parte vendedora está formada por un único titular.</p>
      </case>
    </section>
    • Substring operators (`contains`, `not-contains`) test text; `gt` / `lt` compare numbers; `gte` / `lte` / `eq` count object entries.
    • This section exists to exercise the grammar. In a real template you would not group unrelated variants like this.

    Content

    What may appear inside a section, and placeholders.

    Static text outside a section

    content-static-textbody

    Text that always appears verbatim, with no AI involvement.

    <p><strong>CONTRATO DE ARRENDAMIENTO</strong></p>
    • Plain HTML outside any `<section>`. Do NOT wrap always-present, unconditional text in a section.
    • A section exists to let the AI choose between variants, or to be governed by visibility rules. If neither applies, it should not be a section.

    {label | sample} placeholder

    content-placeholder-authoringbody

    Mark a dynamic value inside reference content.

    <p>En Barcelona, a {fecha | tres de julio de dos mil veinticinco}.</p>
    • The part after `|` is a realistic sample so the model can see the expected shape. Never let a sample reach the finished document.
    • When the value is missing at generation time the model emits a placeholder chip instead.

    Placeholder chip

    content-placeholder-chipbody

    The node the generator emits for a value it does not have.

    <span data-type="placeholder" data-name="BASE IMPONIBLE" data-description="Importe sobre el que se liquida el impuesto" data-field="escritura.base_imponible">BASE IMPONIBLE</span>
    • `data-field` carries the canonical `objectKey.fieldKey`; filling the chip then writes that form field. Omit it and the value goes to document memory instead.
    • `data-description` explains what is missing — it is what the person filling the gap reads.

    Headings, lists, tables, page breaks

    content-rich-blocksbody

    The block content a section may hold beyond paragraphs.

        <h2>Exposición</h2>
        <p>Comparece <span data-bookmark="OTORGANTE"><strong>{otorgante | ANA TORRES GIL}</strong></span>, cuyos datos constan al inicio.</p>
        <ul>
          <li>Primera manifestación.</li>
          <li>Segunda manifestación.</li>
        </ul>
        <ol>
          <li>Primer acuerdo.</li>
          <li>Segundo acuerdo.</li>
        </ol>
    • Allowed inside a section: paragraph, heading (h1–h4), bullet list, ordered list, blockquote, horizontal rule, table, image, page break.
    • Bold is `<strong>`. Separate paragraphs with `<p>`.

    Page break

    content-page-breakbody

    Force the exported DOCX onto a new page.

    <div data-page-break="true">
        </div>
    • Never fake one with empty paragraphs — they reflow when the content above changes length.

    Bookmarks and field references

    Never hardcode a page, folio or cross-reference — these resolve on export.

    Bookmark

    reference-bookmarkbody

    Anchor a fragment so other parts of the document can point at it.

    <span data-bookmark="OTORGANTE"><strong>{otorgante | ANA TORRES GIL}</strong></span>
    • The name is yours to choose; it is what every `data-bookmark-name` refers to.

    REF — repeat bookmarked text

    reference-refbody

    Insert the text of a bookmark instead of retyping it.

    <span data-field-ref="true" data-field-type="REF" data-bookmark-name="OTORGANTE"></span>
    • Resolved on DOCX export. Retyping the text by hand is how two copies drift.

    PAGEREF and FOLIOREF

    reference-pagerefbody

    The page or folio a bookmark falls on.

    <p>Consta en la página <span data-field-ref="true" data-field-type="PAGEREF" data-bookmark-name="PROTOCOLO"></span> y en el folio <span data-field-ref="true" data-field-type="FOLIOREF" data-bookmark-name="PROTOCOLO"></span>.</p>

    NUMPAGES, NUMFOLIOS and their _TEXT forms

    reference-countsbody

    Totals for the whole document. These need no bookmark.

    <p>La presente escritura consta de <span data-field-ref="true" data-field-type="NUMPAGES"></span> páginas (<span data-field-ref="true" data-field-type="NUMPAGES_TEXT"></span>) y <span data-field-ref="true" data-field-type="NUMFOLIOS"></span> folios (<span data-field-ref="true" data-field-type="NUMFOLIOS_TEXT"></span>).</p>
    • A `*_TEXT` variant renders the number in words — "tres" rather than "3".
    • A phrase like "extendida en tres folios" must use `NUMFOLIOS_TEXT`, never literal text.

    PREV_PAGES and PREV_FOLIOS

    reference-prevbody

    How many pages or folios precede this point. No bookmark needed.

    <p>Extendida en las <span data-field-ref="true" data-field-type="PREV_PAGES"></span> páginas anteriores (<span data-field-ref="true" data-field-type="PREV_PAGES_TEXT"></span>) y en los <span data-field-ref="true" data-field-type="PREV_FOLIOS"></span> folios anteriores (<span data-field-ref="true" data-field-type="PREV_FOLIOS_TEXT"></span>).</p>
    • A phrase like "los cuatro folios anteriores" must use `PREV_FOLIOS_TEXT`.

    REMAINING_PAGES

    reference-remainingbody

    How much of the document follows a bookmark.

    <p>Restan <span data-field-ref="true" data-field-type="REMAINING_PAGES" data-bookmark-name="PROTOCOLO"></span> páginas (<span data-field-ref="true" data-field-type="REMAINING_PAGES_TEXT" data-bookmark-name="PROTOCOLO"></span>).</p>

    PAGES_BEFORE and FOLIOS_BEFORE

    reference-beforebody

    How much of the document precedes a bookmark.

    <p>Antes del marcador hay <span data-field-ref="true" data-field-type="PAGES_BEFORE" data-bookmark-name="OTORGANTE"></span> páginas (<span data-field-ref="true" data-field-type="PAGES_BEFORE_TEXT" data-bookmark-name="OTORGANTE"></span>) y <span data-field-ref="true" data-field-type="FOLIOS_BEFORE" data-bookmark-name="OTORGANTE"></span> folios (<span data-field-ref="true" data-field-type="FOLIOS_BEFORE_TEXT" data-bookmark-name="OTORGANTE"></span>).</p>
    • Measured to a bookmark, unlike `PREV_*`, which measures to the current position.

    PAGES_BETWEEN and FOLIOS_BETWEEN

    reference-betweenbody

    Counts measured between two bookmarks.

    <p>Entre ambos marcadores median <span data-field-ref="true" data-field-type="PAGES_BETWEEN" data-bookmark-name="PROTOCOLO" data-bookmark-name-end="OTORGANTE"></span> páginas (<span data-field-ref="true" data-field-type="PAGES_BETWEEN_TEXT" data-bookmark-name="PROTOCOLO" data-bookmark-name-end="OTORGANTE"></span>) y <span data-field-ref="true" data-field-type="FOLIOS_BETWEEN" data-bookmark-name="PROTOCOLO" data-bookmark-name-end="OTORGANTE"></span> folios (<span data-field-ref="true" data-field-type="FOLIOS_BETWEEN_TEXT" data-bookmark-name="PROTOCOLO" data-bookmark-name-end="OTORGANTE"></span>).</p>
    • The `*_BETWEEN` types are the only ones that take a second bookmark, `data-bookmark-name-end`.

    Section tools

    Extra AI tools a section may call while generating.

    tools

    section-toolsbody

    Let a section call an extra AI tool while it generates.

    <section id="localizacion" name="LOCALIZACIÓN" uses="obj_acta" tools="geocode_search">
    • `math` and `update_memory` are always available and must NOT be listed. `geocode_search` is opt-in, which is what this attribute is for.
    • An unknown token is a validation error. Call `list_section_tools` to see what this organization can use — connector tokens differ per organization.
    • A section reads the document's shared memory but does not write the form: values that map to a field are emitted as placeholders instead.

    Connector tool token

    section-tools-connectorfrontmatter

    Give a section access to one tool on an organization connector.

    tools="connector:7c2c1c2e-1f4a-4a1b-9f3e-0c9a1d2b3c4d:precio_referencia"
    • The token is `connector:<connectorId>:<toolName>`. Get real ones from `list_section_tools`.
    • A token whose connector has been deleted or disabled is dropped at generation time; the validator warns about it when it can see the organization's connector list.