# Templates

> Compón un árbol de documento con TSX, y calcula la estructura con el mismo JavaScript que escribes en todas partes.

Source: https://docxcelerate.com/es/docs/essentials/templates/

Una plantilla nombra un árbol de documento para que un proyecto pueda apuntar a
él. Nada de lo que hay en ella se renderiza cuando se escribe — evaluar el JSX
solo construye elementos, y eso es lo que permite a un componente decidir qué es
más tarde, con los datos en la mano.

## La forma

```tsx
import { Document, Section, template } from "docxcelerate/template";
import { Greeting, NextSteps } from "./nodes/index.ts";
import type { DocumentData } from "./types.ts";

export const documentTemplate = template<DocumentData>(
  <Document id="tenancy-renewal" title="Tenancy Renewal">
    <Section id="opening" title="Opening">
      <Greeting />
    </Section>
    <Section id="closing" title="Closing">
      <NextSteps />
    </Section>
  </Document>,
);
```

Lo que conecta ese JSX con Docxcelerate en lugar de con React es
`tsconfig.json`:

```json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "docxcelerate/template"
  }
}
```

Los workspaces generados ya lo traen, así que no hay nada que añadir al principio
de cada archivo. Si estás incorporando Docxcelerate a un proyecto que apunta
`jsxImportSource` a otro sitio, un comentario
`/** @jsxImportSource docxcelerate/template */` en la primera línea lo
sobrescribe para ese archivo.

## Estructura calculada

Una plantilla se escribe en el ámbito del módulo, donde todavía no hay datos, así
que la estructura que depende de los datos va en un componente, no en la
plantilla:

```tsx
export const Enclosures: Section = () => {
  const [state] = useState((data: DocumentData) => ({ items: data.enclosures }));

  return (
    <Section id="enclosures" title="Enclosed">
      {state.items.map((item) => <Paragraph>{item.label}</Paragraph>)}
    </Section>
  );
};
```

Cualquier hijo falsy se omite, así que `{condition && <Node />}` se lee como en
cualquier otro sitio. Los nodos sin id toman uno de donde están, y eso impide que
un `map` exija nombres que no tienes.

El mismo `.map()` sirve cuando la lista solo se conoce por petición. Aquí recorre
la colección, y al publicar llega al motor como un bucle — mira [los bucles se
publican como bucles](#los-bucles-se-publican-como-bucles) más abajo.

## Piezas reutilizables

Un componente es una función corriente, así que una envoltura de estilo de la
casa también lo es. Los componentes aceptan hijos como cualquier otra cosa:

```tsx
export const Letterhead: Section<{ children?: Yield }> = ({ children }) => (
  <Section id="letterhead" title="">
    <Image id="logo" src="assets/logo.png" alt="Ashcroft Housing" />
    {children}
  </Section>
);
```

Un hook compartido es la otra mitad de esto. Como `useSetPrompts` fija prompts en
lo que produzca el componente que lo llama, el estilo de la casa puede aplicarse
a un nodo que el hook ni siquiera posee:

```tsx
export function useHouseVoice() {
  useSetPrompts({
    systemPrompt: "You write for a housing association. Plain English, second person.",
  });
}
```

Cualquier componente que llame a `useHouseVoice()` recibe ese prompt de sistema
en el nodo que devuelve, y aún puede sobrescribirlo con una prop.

## Proyectos de documento

Un proyecto de documento ata una plantilla a su estilo y a sus datos de vista
previa mediante un único punto de entrada, `document.project.ts`:

```tsx
import { defineDocumentProject } from "docxcelerate/document";
import { documentTemplate } from "./document.tsx";
import { documentStyle } from "./document-style.ts";
import type { DocumentData } from "./types.ts";

export default defineDocumentProject<DocumentData>({
  id: "tenancy-renewal",
  name: "Tenancy Renewal",
  version: "1.0.0",
  template: documentTemplate,
  style: documentStyle,
  previewData: {
    residentName: "Avery Mitchell",
    propertyRef: "Flat 4, Ashcroft House",
  },
});
```

`previewData` es contra lo que resuelve la app de vista previa. Manténlo
realista — los nombres cortos y las ciudades de relleno ocultan problemas de
maquetación que los datos reales sí exponen.

## Qué cambia al publicar

Todo lo anterior da por hecho que tienes los datos. Cuando compilas con los datos
en la mano — una vista previa, un empaquetado local, un documento escrito sobre la
marcha — un `if` significa exactamente lo que dice, y un `.map()` recorre la lista
que se le dio.

Publicar en [un motor](/es/docs/generation/endpoint/) es distinto. El artefacto se
compila **una vez**, contra valores de relleno para una petición que nadie ha
hecho todavía, y se guarda. Una decisión que dependa de los datos de la petición
no se puede zanjar durante esa compilación; tiene que viajar al motor y tomarse
por documento.

De ahí salen tres reglas, y la compilación hace cumplir las tres en lugar de dejar
que un documento equivocado llegue a quien lo recibe.

### Calcula por documento, no por compilación

Interpolar un valor funciona al publicar: se convierte en el token que el motor
sustituye.

```tsx
<Paragraph id="greeting">Hello {state.name},</Paragraph>
// publicado como: "Hello {{data.name}},"
```

Calcular sobre él no, porque el valor todavía no se conoce. Formatear una moneda,
sumar una lista, comparar una fecha — eso necesita un **deriver**, que el motor
ejecuta por documento:

```tsx
<Paragraph
  id="balance"
  derivers={[
    derive("currencyLabel", { output: "balanceLabel", inputs: [dataRef("balanceDue")] }),
  ]}
>
  Your balance is {"{{derived.balanceLabel}}"}.
</Paragraph>
```

`useFormat` y `useDeriver` calculan durante la compilación, que es lo correcto
siempre que el valor ya se conozca. Echa mano de un deriver cuando no.

### Los bucles se publican como bucles

Una bifurcación tiene dos ramas y las dos se pueden publicar. Un bucle tiene
tantas como entradas traiga la petición, y ese número no lo sabe nadie hasta que
se escribe un documento — así que lo que se guarda es el bucle en sí. Lo que se
escribe es el `.map()` de siempre:

```tsx
const [visits] = useState((data: TenancyData) => data.visits);

return visits.map((visit) => <Paragraph id="visit">Visit: {visit.label}</Paragraph>);
```

Con datos reales esto es el `.map()` de la biblioteca estándar: recorre la
colección de inmediato, así que las vistas previas enseñan la repetición en vez de
una descripción de ella. Al publicar no se puede recorrer, así que el sustituto
intercepta esa misma llamada, ejecuta el cuerpo una vez contra un sustituto de una
entrada, y el bucle llega intacto al motor.

Aquí nadie escribe un token `{{ctx…}}`. La entrada que recibió el cuerpo sabe la
ruta que representa, así que la referencia se escribe sola — y las pasadas se
nombran por posición en ambos caminos, de modo que un id significa lo mismo en una
vista previa y en el documento que recibe alguien.

Todo lo que tenga que mirar las entradas antes de poder escribir el cuerpo —
`.filter()`, `.sort()`, `.length`, un bucle `for` — no se puede responder al
publicar. Eso remite a un deriver, que se ejecuta donde están los datos.

### Las bifurcaciones multiplican

Publicar una bifurcación guarda las dos ramas, cada una bajo la condición que la
selecciona. Anidar bifurcaciones multiplica lo que carga un documento, así que hay
un límite — `branchLimit`, 32 por defecto — y pasarlo hace fallar la compilación en
vez de producir un artefacto calladamente enorme. Sube la decisión a un deriver que
dé un solo valor, o eleva el límite si el documento de verdad es así de
condicional.
