# Paragraph

> Prosa, escrita a partir de tus datos o resuelta desde prompts en el momento de la petición.

Source: https://docxcelerate.com/es/docs/nodes/paragraph/

The workhorse. A static paragraph holds the text as its children; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode` — which is inferred from what you supply, never declared. Supplying both text and a prompt on one element is an error rather than a precedence rule.

- **Helpers:** `Paragraph`, `useSetPrompts`, `useSetPlaceholders`
- **Clase de nodo:** `paragraph`
- **Categoría:** Texto
- **Se resuelve:** Both
- **Hijos:** None. Paragraphs are leaves.

| Opción | Tipo | Qué hace |
| --- | --- | --- |
| `id` | `string` | Stable address for the node. Engines target it and build artifacts diff on it, so treat a rename as a breaking change. Optional: a node without one takes an id from where it sits, which is what keeps branches and loops from forcing you to invent names. Two nodes claiming one id is an error rather than a race. |
| `children` | `string` | Static only. The text, interpolated the way any JSX children are. A node given its own text is static, whatever prompts a hook set around it. |
| `text` | `string` | Static only. The same thing as children, for when a computed string reads better as a prop than as a body. |
| `align` | `"left" \| "center" \| "right" \| "justify"` | How the lines sit in the text column. Say it here when the alignment is what the paragraph *is* — a date ranged right, a standfirst centred. Leave it out and let a `variant` carry it when the alignment is what the theme thinks that kind of block looks like. A node that states both wins over its block, the same way a cell wins over its column. |
| `placeholder` | `string` | Dynamic only. What previews show in place of generated content. Also settable with `useSetPlaceholders`. Optional, but a document that reads badly without one cannot be reviewed. |
| `generalPrompt` _obligatorio_ | `string` | Dynamic only. What this node should say. |
| `infoPrompt` | `string` | Dynamic only. Context the model should have but should not restate. |
| `negativePrompt` | `string` | Dynamic only. What to avoid — claims, tones, or facts it must not invent. |
| `systemPrompt` | `string` | Dynamic only. Role and voice, applied ahead of the other prompts. |
| `examplePrompt` | `string` | Dynamic only. What a good answer looks like, written out as finished text. Shown last, because it is what the answer gets measured against. |
| `derivers` | `DeriverInvocation[]` | Values the engine computes before the node resolves, written to `derived.*` and readable from a template token. Built with `derive()`. These survive publishing and run per document — use them for anything computed from request data. `useDeriver` runs one during the build instead. |

## Escribir uno

El texto son los hijos del elemento, interpolados como cualesquiera hijos de
JSX. No hay ningún lenguaje de plantillas, así que el formato, las bifurcaciones
y los plurales son TypeScript corriente, hechos en el inicializador de estado,
que es donde un componente piensa:

```tsx
import { Paragraph, useState } from "docxcelerate/template";
import type { MemberData } from "../types.ts";

export const Greeting: Paragraph = () => {
  const [state] = useState((data: MemberData) => ({ name: data.memberName }));

  return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
};
```

Un componente puede ser `async` — pero uno que hace peticiones es uno que puede
fallar a mitad de la compilación, así que es preferible poner antes el valor en
tus datos. Si haces `await`, todo hook debe llamarse antes del primero.

## El presupuesto de tokens

`useAvailableTokens()` es el presupuesto que asignó esta compilación, `2000`
salvo que definas `availableTokens` en las opciones de compilación. Los nodos
estáticos pueden ignorarlo. Los dinámicos deberían gastarlo:

```tsx
const availableTokens = useAvailableTokens();

useSetPrompts({
  generalPrompt: `Explain the change. At most ${Math.floor(availableTokens / 4)} words.`,
});
```

## Bifurcaciones

Un documento que dice una de tres cosas es un componente con tres desenlaces.
Dale a cada rama su propio id:

```tsx
if (state.settled) {
  return <Paragraph id="balance-settled">Nothing outstanding.</Paragraph>;
}

return <Paragraph id="balance-arrears">A balance remains.</Paragraph>;
```

Los ids distintos son lo que permite al documento resuelto registrar qué rama le
tocó a este destinatario, y son obligatorios en cuanto la bifurcación se publica:
un motor almacena ambas ramas, y un id direcciona exactamente un nodo.

Cuando los desenlaces solo se diferencian en la redacción, calcular la cadena y
devolver un solo nodo conserva un único id, que es la costumbre más antigua y
sigue siendo buena:

```tsx
const [state] = useState((data: MemberData) => ({
  line: data.settled ? "Nothing outstanding." : "A balance remains.",
}));

return <Paragraph id="balance">{state.line}</Paragraph>;
```

## Párrafos dinámicos

Un párrafo dinámico no tiene texto propio; lleva prompts y un marcador de
posición, fijados con `useSetPrompts` y `useSetPlaceholders` o dados como props.
Las compilaciones de vista previa lo resuelven al marcador y lo etiquetan; las
compilaciones en el momento de la petición envían los prompts. Solo
`generalPrompt` es obligatorio — la cuarta variante de abajo muestra para qué
sirven los otros tres.

## Variantes

### Static

Data in, a line of text out.

Source: `src/nodes/paragraph/static.node.tsx`

```tsx
import { Paragraph, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";

/**
 * The smallest useful node: data taken into state, and a line built from it.
 * `useState` is where data enters a component, and the only place it does.
 */
export const Greeting: Paragraph = () => {
  const [state] = useState((data: SampleData) => ({ name: data.memberName }));

  return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
};
```

**En qué se resuelve**

```json
{
  "id": "greeting",
  "kind": "paragraph",
  "mode": "static",
  "text": "Dear Adaeze Nkemelu,"
}
```

### Static, with branching

One node, several outcomes — and the id stays put.

Source: `src/nodes/paragraph/conditional.node.tsx`

```tsx
import { Paragraph, useState } from "docxcelerate/template";
import { money, type SampleData } from "../sample-data.ts";

/**
 * Branching is an ordinary `if`. The component decides what it is before it
 * says anything, and each arm returns the node that arm means — one id per
 * outcome, so a reader of the resolved document can tell which one they got.
 */
export const PriceChange: Paragraph = () => {
  const [state] = useState((data: SampleData) => {
    const delta = data.newPrice - data.lastPrice;

    return {
      plan: data.plan,
      renewsOn: data.renewsOn,
      lastPrice: data.lastPrice,
      newPrice: data.newPrice,
      delta,
      percent: Math.abs((delta / data.lastPrice) * 100).toFixed(1),
    };
  });

  if (state.delta === 0) {
    return (
      <Paragraph id="price-held">
        Your {state.plan} membership renews at {money(state.newPrice)} a year — the same
        price you paid last year.
      </Paragraph>
    );
  }

  return (
    <Paragraph id="price-change">
      Your {state.plan} membership is {state.delta > 0 ? "rising" : "falling"} by{" "}
      {state.percent}%, from {money(state.lastPrice)} to {money(state.newPrice)} a year.
      That is {money(state.newPrice / 12)} a month from {state.renewsOn}.
    </Paragraph>
  );
};
```

**En qué se resuelve**

```json
{
  "id": "price-change",
  "kind": "paragraph",
  "mode": "static",
  "text": "Your Peak Anytime membership is rising by 5.1%, from £468 to £492 a year. That is £41 a month from 1 October 2026."
}
```

### Dynamic

A prompt and a placeholder. Previews show the placeholder, labelled.

Source: `src/nodes/paragraph/dynamic.node.tsx`

```tsx
import { Paragraph, useSetPlaceholders, useSetPrompts, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";

/**
 * The minimum a dynamic paragraph needs: one prompt, and a placeholder so the
 * preview still reads as a letter. Setting prompts is what makes the node
 * dynamic — nothing declares a mode.
 */
export const NextSteps: Paragraph = () => {
  const [state] = useState((data: SampleData) => ({
    name: data.memberName,
    renewsOn: data.renewsOn,
  }));

  useSetPrompts({
    generalPrompt:
      `In two sentences, tell ${state.name} that their membership renews ` +
      `automatically on ${state.renewsOn} and how to change plan before then.`,
  });

  useSetPlaceholders(
    `Your membership renews automatically on ${state.renewsOn}. ` +
      `Nothing is needed from you unless you want to change plan.`,
  );

  return <Paragraph id="next-steps" />;
};
```

**En qué se resuelve**

```json
{
  "id": "next-steps",
  "kind": "paragraph",
  "mode": "dynamic",
  "text": "Your membership renews automatically on 1 October 2026. Nothing is needed from you unless you want to change plan."
}
```

**Qué se le pide al endpoint**

- `general` — In two sentences, tell Adaeze Nkemelu that their membership renews automatically on 1 October 2026 and how to change plan before then.

### Dynamic, all four prompts

System, general, info and negative, each doing one job.

Source: `src/nodes/paragraph/prompted.node.tsx`

```tsx
import {
  Paragraph,
  useAvailableTokens,
  useSetPlaceholders,
  useSetPrompts,
  useState,
} from "docxcelerate/template";
import { money, type SampleData } from "../sample-data.ts";

/**
 * All four slots: general says what to write, info supplies facts without
 * asking for them back, negative fences off the failure modes, system fixes
 * the voice. `useAvailableTokens` is the budget the build allotted this node.
 */
export const Apology: Paragraph = () => {
  const availableTokens = useAvailableTokens();
  const [state] = useState((data: SampleData) => ({
    name: data.memberName,
    centreName: data.centreName,
    plan: data.plan,
    newPrice: data.newPrice,
  }));

  useSetPrompts({
    systemPrompt:
      `You write for a public leisure centre. Plain British English, second ` +
      `person, no marketing language.`,
    generalPrompt: `Apologise to ${state.name} for the main pool closure and say what ` +
      `is still open. At most ${Math.floor(availableTokens / 4)} words.`,
    infoPrompt: `The main pool at ${state.centreName} is resurfacing until 12 October. ` +
      `The teaching pool, gym and classes are unaffected. Members on ` +
      `${state.plan} paying ${money(state.newPrice)} a year get two guest passes ` +
      `as compensation.`,
    negativePrompt:
      `Do not promise a refund, do not give a reopening date beyond 12 October, ` +
      `and do not restate the price.`,
  });

  useSetPlaceholders(
    `The main pool is closed for resurfacing until 12 October. ` +
      `The teaching pool and all land-based classes are running as normal.`,
  );

  return <Paragraph id="pool-closure" />;
};
```

**En qué se resuelve**

```json
{
  "id": "pool-closure",
  "kind": "paragraph",
  "mode": "dynamic",
  "text": "The main pool is closed for resurfacing until 12 October. The teaching pool and all land-based classes are running as normal."
}
```

**Qué se le pide al endpoint**

- `system` — You write for a public leisure centre. Plain British English, second person, no marketing language.
- `general` — Apologise to Adaeze Nkemelu for the main pool closure and say what is still open. At most 500 words.
- `info` — The main pool at Riverside Leisure Centre is resurfacing until 12 October. The teaching pool, gym and classes are unaffected. Members on Peak Anytime paying £492 a year get two guest passes as compensation.
- `negative` — Do not promise a refund, do not give a reopening date beyond 12 October, and do not restate the price.

## Notas

- Ambos modos se resuelven a `kind: "paragraph"`. Un renderizador lee `mode` si
  es que le importa; nunca se bifurca según cómo se escribió el nodo.
- Un nodo con texto propio es estático, sean cuales sean los prompts que un hook
  compartido haya fijado a su alrededor. Aportar ambas cosas en un mismo elemento
  es un error.
- Una cadena vacía devuelve un párrafo vacío, no la ausencia de nodo. Para
  eliminar el nodo, no devuelvas nada — `false`, `null` y `undefined` se omiten.
- Los renderizadores escapan el texto. Un párrafo no puede colar marcado en la
  página, y no es el lugar para el formato.
