> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plantillas personalizadas

> Anula cómo se renderiza cualquier bloque del Aftersell Cart con tu propio JSX: qué reemplaza una plantilla, qué está en alcance, cómo darle estilo y dónde encontrar las props de cada bloque.

Una **plantilla personalizada** te permite anular cómo se renderiza un bloque individual. En lugar de la UI integrada del bloque, el carrito renderiza tu propio JSX, usando los mismos datos que el bloque usaría normalmente. Es una capacidad transversal más que un bloque en sí: la mayoría de los bloques la exponen desde su pestaña **Code**.

Esta página cubre lo que aplica a **todos** los bloques. Para las props que un bloque específico te entrega, ve a [la referencia del propio bloque](#props-for-each-block).

<div id="custom-template-vs-custom-code-block">
  ## Plantilla personalizada vs. bloque Custom code
</div>

Suenan parecido pero hacen cosas diferentes:

* Una **plantilla personalizada** *reemplaza el renderizado de un bloque existente* con tu propio marcado, y te entrega los datos propios de ese bloque (el título y el conteo de artículos del Header, los totales del Summary, etcétera). No agrega nada nuevo; rediseña un bloque.
* El bloque **[Custom code](/es/aftersell/cart/custom-code-blocks)** *agrega un bloque nuevo* de HTML o React arbitrario en cualquier parte del carrito.

Recurre a una plantilla personalizada cuando el bloque integrado está casi bien pero necesitas un layout o marcado diferente. Recurre a un bloque Custom code cuando quieras agregar algo que los bloques integrados no cubren.

<div id="using-a-custom-template">
  ## Usar una plantilla personalizada
</div>

1. Selecciona un bloque en el editor y abre su pestaña **Code**.
2. Edita la plantilla predeterminada. Las plantillas personalizadas son **solo JSX** (la elección entre HTML o JSX es exclusiva del bloque Custom code).
3. Haz clic en **Compile**. Compilar elimina los tipos y transpila el JSX, así que detecta errores de **sintaxis**. Los errores de tipos no detienen la compilación: el editor los marca inline mientras escribes, con el mismo IntelliSense que autocompleta las props del bloque.
4. Activa la plantilla para que el carrito la use en lugar del renderizado integrado.
5. **Reset to default** restaura la plantilla original del bloque en cualquier momento.

<div id="writing-a-template-with-ai">
  ## Escribir una plantilla con IA
</div>

La pestaña Code incluye un botón **Copy AI prompt** (icono de varita ✦). Al hacer clic, copia a tu portapapeles un brief autónomo que puedes pegar directamente en una sesión de chat con IA (Claude, ChatGPT o similar).

El prompt incluye todo lo que la IA necesita para escribir una plantilla válida para ese bloque específico:

* Las reglas de compilación (expresión única, sin `export default`, sin imports)
* Las props exactas que recibe el bloque, coincidiendo con lo que muestra el IntelliSense del editor
* La firma de función bloqueada que el editor impone
* Reglas específicas del bloque (formatos de dinero, qué handlers conectar, requisitos de accesibilidad)
* Una sección para completar donde pegas tu plantilla actual y describes el cambio que quieres

Después de copiar, abre una sesión de IA, pega el prompt, completa los dos espacios en blanco al final (tu plantilla actual y el cambio que quieres) y envíalo. La IA devuelve una plantilla completa que puedes pegar de vuelta en el editor y compilar.

<Tip>
  Pega tu plantilla existente en la sección para completar en lugar de dejarla en blanco. La IA la usa como punto de partida, así que cualquier personalización que ya hayas hecho se conserva en lugar de ser reemplazada por la predeterminada.
</Tip>

<Note>
  El prompt es específico de cada bloque. El botón **Copy AI prompt** solo aparece en bloques que admiten plantillas personalizadas.
</Note>

<Tip>
  La plantilla predeterminada de la que partes es una **copia funcional del marcado integrado del bloque**, así que siempre tienes una referencia correcta y que renderiza para modificar, en lugar de una página en blanco. Recurre a **Reset to default** cuando quieras recuperar esa referencia.

  No siempre es una coincidencia byte a byte. La plantilla predeterminada del Header también renderiza `logoUrl`, para el cual el marcado integrado no tiene ubicación, así que activar esa plantilla es la forma en que una imagen de encabezado subida aparece por primera vez.
</Tip>

<div id="what-your-template-replaces">
  ## Qué reemplaza tu plantilla
</div>

Una plantilla reemplaza el renderizado del bloque **por completo**. No queda ningún envoltorio alrededor de tu JSX, lo que tiene consecuencias que vale la pena conocer antes de empezar a borrar cosas:

| Pierdes                                                 | Qué significa                                                                                                                                                                                                                    |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| El elemento envoltorio del bloque                       | Nada envuelve tu marcado. Cualquier padding, alineación o layout que el bloque proporcionaba ahora te corresponde a ti.                                                                                                          |
| **Las configuraciones de la pestaña Design del bloque** | Las configuraciones de diseño se aplican como estilos inline en el envoltorio integrado, y ese envoltorio ya no existe. Los colores, espaciados y radios establecidos en la pestaña Design **dejan de aplicarse** a este bloque. |
| Las facilidades de accesibilidad integradas             | Los `aria-label`, el manejo del foco y los elementos semánticos solo existen si tu JSX los incluye.                                                                                                                              |

<Warning>
  **La pestaña Design es la que sorprende a la gente.** Mientras una plantilla personalizada está activa, los campos de la pestaña Design se deshabilitan y aparece un ícono de advertencia junto al encabezado "Design". Pasa el cursor sobre el ícono para ver el motivo. Dale estilo al bloque desde tu plantilla, ya sea [inline o con tu propio CSS](#styling-a-custom-template). Los campos se vuelven a habilitar en cuanto desactivas la plantilla personalizada.
</Warning>

Lo que conservas: la posición del bloque en el carrito, su interruptor de visibilidad, sus configuraciones (que siguen alimentando las props que recibes), el panel de [Custom CSS](/es/aftersell/cart/custom-css) del carrito y **el skeleton de carga integrado**.

Esto último sorprende a la gente. El bloque verifica si el carrito sigue cargando *antes* de llegar a tu plantilla, así que el skeleton integrado se renderiza durante la carga y tu plantilla solo se ejecuta una vez que el carrito está listo. No tienes que construir un estado de carga.

<div id="whats-available-inside-a-template">
  ## Qué está disponible dentro de una plantilla
</div>

Tu plantilla es un único componente de función. Se compila desde **TSX**, así que las anotaciones de tipo están permitidas y se eliminan en tiempo de compilación. Por eso las plantillas predeterminadas están escritas con ellas:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function Header(props: HeaderProps) {
  return <div>{/* … */}</div>;
}
```

**La línea de la firma y la llave de cierre están bloqueadas**: el editor no te deja editar ninguna de las dos, y al pasar el cursor muestra "Locked — this line can't be edited." Tú escribes el cuerpo entre ellas. **Reset to default** es lo único que puede reemplazarlas.

Qué más importa:

* **Tienes cinco hooks:** `useState`, `useEffect`, `useMemo`, `useRef` y `useCallback`. Más `Fragment`, para `<>…</>`.
* **No hay imports.** No puedes hacer `import` de nada, y no hay un objeto `React` en alcance, así que no hay `React.useReducer` ni `React.Children`. Si un hook no está en la lista de arriba, no está disponible.
* **Las props son de solo lectura.** Mutar una prop no servirá de nada. Para cambiar el carrito, usa las props de handler que el bloque te da (`onClose`, `increment`, `selectPlan`, etcétera) en lugar de escribir directamente en las props.
* **`window` es alcanzable**, así que una plantilla puede llamar al [Cart SDK](/es/aftersell/cart/sdk-overview) vía `window.aftersell.cart` cuando necesita algo que las props del bloque no cubren.

<div id="conventions-across-every-block">
  ## Convenciones en todos los bloques
</div>

Tres reglas se cumplen en todas partes, y conocerlas elimina la mayor parte de las conjeturas:

* **Las props `*Html` son texto enriquecido pre-sanitizado.** Renderízalas con `dangerouslySetInnerHTML`. Ya pasaron por el sanitizador del carrito, y los tokens del comerciante como `{{total_price}}` ya están resueltos.
* **Los precios que llegan como `string` ya están formateados** en el formato de dinero de la tienda. Los precios como `number` están en centavos. Un bloque te da uno u otro, y la tabla de cada bloque indica cuál.
* **`isLoading` siempre es `false` dentro de una plantilla.** El bloque renderiza su skeleton integrado y solo llama a tu plantilla una vez que el carrito se ha cargado, así que la prop se pasa por completitud más que para que ramifiques sobre ella.

<Note>
  Algunos bloques no devuelven nada en ciertos estados, así que tu plantilla nunca es llamada con datos vacíos. La plantilla de Rewards nunca ve un `milestones` vacío, y la plantilla de Subscription upgrade nunca ve un `view` nulo. La referencia de cada bloque indica dónde aplica esto, así que puedes omitir la rama del estado vacío.
</Note>

<div id="styling-a-custom-template">
  ## Dar estilo a una plantilla personalizada
</div>

La plantilla predeterminada de la que partes lleva los classnames del bloque. Cómo das estilo a tus ediciones depende de cuánto te alejes de ese punto de partida.

<div id="the-two-class-families">
  ### Las dos familias de clases
</div>

Cada elemento en una plantilla predeterminada lleva un classname emparejado, y hacen trabajos muy diferentes:

| Familia           | Qué hace                                                                                                              | ¿Escribir CSS contra ella?                                                                                  |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `cart-internal-*` | **Lleva el estilo integrado del bloque.** Cada regla en la hoja de estilos del carrito apunta a esta familia.         | No. Es la fontanería propia del carrito, y el editor de Custom CSS marca los selectores que apuntan a ella. |
| `cart-external-*` | **Un gancho sin estilo propio.** Nada en la hoja de estilos del carrito apunta a ella; existe para que tu CSS la use. | Sí. Esta es la forma soportada de rediseñar un bloque.                                                      |

Así que `cart-internal-header__title` es lo que hace que el título *se vea* como el título integrado, y `cart-external-header__title` es el asidero que debes usar cuando quieres cambiar cómo se ve.

<div id="small-changes-keep-both-classnames">
  ### Cambios pequeños: conserva ambos classnames
</div>

Si estás reordenando elementos, cambiando etiquetas o agregando algo dentro de la estructura existente, deja los classnames como están. Conservas gratis la apariencia integrada, y rediseñas mediante [Custom CSS](/es/aftersell/cart/custom-css) apuntando a los ganchos `cart-external-*`.

<div id="restructuring-drop-both-classnames">
  ### Reestructuración: elimina ambos classnames
</div>

Cuando estás cambiando la estructura del DOM en lugar de ajustarla, quita **ambas** familias de tu marcado y usa [tus propios classnames](#option-1-your-own-classnames-plus-custom-css) en su lugar. Hay una razón distinta para cada una.

**Elimina `cart-internal-*` porque el CSS integrado fue escrito para el DOM integrado.** Mantén esas clases en marcado reestructurado y heredarás reglas de layout que asumen elementos que ya no tienes: contenedores flex que esperan otros hijos, espaciado entre elementos que se movieron, posicionamiento relativo a algo que eliminaste. Esto suele manifestarse como que tu propio CSS "no funciona" cuando en realidad las reglas integradas son las que están ganando.

<Warning>
  **Elimina `cart-external-*` porque es un nombre compartido, no tuyo.** Esos classnames significan algo específico en el marcado integrado, y tu Custom CSS se escribe una sola vez para todo el carrito. Si una plantilla reestructurada los reutiliza, cualquier regla que escribas apuntará tanto a tu estructura como a la integrada.

  Eso sale mal en el momento en que desactivas la plantilla personalizada: el bloque vuelve a su marcado integrado, y tu CSS sigue apuntando a él, ahora dando estilo a un DOM para el que nunca fue escrito. Tu propio prefijo mantiene los dos limpiamente separados, de modo que desactivar una plantilla sea una reversión limpia.
</Warning>

Dos formas de dar estilo a lo que has construido:

<div id="option-1-your-own-classnames-plus-custom-css">
  #### Opción 1: tus propios classnames más Custom CSS
</div>

Lo mejor para cualquier cosa que vayas a mantener o reutilizar. Dale a tus clases un prefijo con el que nadie más colisione, normalmente el nombre de tu tienda o marca:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function Header(props) {
  return (
    <div className="northwind-custom-header">
      <div className="northwind-custom-header__title" dangerouslySetInnerHTML={{ __html: props.title }} />
      <button type="button" className="northwind-custom-header__close" onClick={props.onClose}>
        &times;
      </button>
    </div>
  );
}
```

Luego, en el editor del carrito, selecciona **Cart settings** en el panel izquierdo y abre la pestaña **Custom CSS** a la derecha:

```css theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
.northwind-custom-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px;
}

.northwind-custom-header__title {
  font-size: 18px;
  font-weight: 600;
}

.northwind-custom-header__close:hover {
  opacity: 0.6;
}
```

Un prefijo importa más de lo que parece. Sin uno, una clase como `.header` o `.title` corre el riesgo de colisionar con las clases propias del carrito, la plantilla de otra app o un bloque futuro.

<div id="option-2-inline-styles">
  #### Opción 2: estilos inline
</div>

Sin ida y vuelta al panel de CSS, y todo vive en un solo lugar:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
```

Bueno para andamiaje de layout y casos puntuales. Sus límites son los habituales: sin `:hover` ni otras pseudoclases, sin media queries y sin reutilización entre bloques. Recurre a la Opción 1 cuando quieras cualquiera de esas cosas.

<div id="picking-an-approach">
  ### Elegir un enfoque
</div>

| Situación                                             | Haz esto                                                                                                    |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Misma estructura, distinto texto u orden              | Conserva ambos classnames, rediseña vía Custom CSS sobre `cart-external-*`                                  |
| Nueva estructura, estilo que vas a mantener           | Tus propias clases con prefijo, ambas familias del carrito eliminadas                                       |
| Nueva estructura, unas pocas reglas rápidas de layout | Estilos inline, ambas familias del carrito eliminadas                                                       |
| Mucho código personalizado en varios bloques          | Tus propias clases con prefijo en todas partes, para que cualquier plantilla pueda desactivarse limpiamente |

<Note>
  El carrito se renderiza en un shadow root, así que la hoja de estilos de tu tema no puede alcanzar su interior. Los estilos de una plantilla personalizada tienen que venir del panel **Custom CSS** propio del carrito o de estilos inline, no de tu tema. Consulta [Custom CSS](/es/aftersell/cart/custom-css).
</Note>

<div id="when-a-template-fails">
  ## Cuando una plantilla falla
</div>

Una plantilla rota nunca rompe el carrito. El bloque no renderiza **nada** y todo lo que lo rodea sigue funcionando, lo cual es seguro pero fácil de pasar por alto: un espacio en blanco donde debería estar tu bloque es el síntoma.

| Fallo                           | Cuándo lo verás                  | Dónde se reporta                                                                                                          |
| ------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Error de tipos                  | Mientras escribes                | Un subrayado inline en el editor. **No** bloquea la compilación: el compilador elimina los tipos en lugar de verificarlos |
| Error de sintaxis               | Cuando haces clic en **Compile** | El editor, antes de que pueda llegar a tu tienda                                                                          |
| Un crash durante el renderizado | En la tienda, una vez en vivo    | `console.error('[aftersell-cart] module crashed: …')`                                                                     |

Como el bloque desaparece silenciosamente en lugar de mostrar un error visible, verifica siempre una plantilla en [vista previa](/es/aftersell/cart/previewing-carts) antes de publicar. Si un bloque ha desaparecido, abre primero la consola del navegador.

Dos cosas contra las que vale la pena protegerse, ya que ambas hacen crashear una plantilla que asume lo contrario:

* **Props anulables.** Muchas props son `null` en condiciones normales (`logoUrl` sin logo, `imageUrl` sin imagen, `variantTitle` en un producto de una sola variante). Verifícalas antes de usarlas.
* **Arrays que pueden estar vacíos.** `discountTags` y `discountCodes` son `[]` mucho más a menudo que no.

<div id="limitations">
  ## Limitaciones
</div>

* **Las plantillas personalizadas son anulaciones de visualización.** Para ejecutar lógica contra el carrito (suscribirte a eventos, agregar artículos, reaccionar a cambios), usa [Scripts personalizados](/es/aftersell/cart/custom-scripts) y el [Cart SDK](/es/aftersell/cart/sdk-overview).
* **Casi todos los bloques admiten una.** Las excepciones son el bloque **[Express payments](/es/aftersell/cart/express-payments-block)**, que aloja los propios botones de pago de Shopify, y el contenedor **[Cart items](/es/aftersell/cart/cart-items-block)** en sí, aunque la fila **Product** dentro de él sí admite una plantilla personalizada.
* **Una plantilla no puede cambiar lo que un bloque hace fundamentalmente.** Cambia cómo se presentan los datos del bloque, no los datos ni el comportamiento detrás de ellos.

<div id="props-for-each-block">
  ## Props de cada bloque
</div>

Cada bloque pasa sus propios datos. La tabla completa de props, con tipos y un ejemplo desarrollado, vive en la página de ese bloque:

| Bloque                                                                                | Props que recibe                                                                                                                   |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [Header](/es/aftersell/cart/header-block#custom-template)                             | `title`, `logoUrl`, `leftSection`, `rightSection`, `itemCount`, `onClose`, `isLoading`                                             |
| [Banner](/es/aftersell/cart/banner-block#custom-template)                             | `text`, `shouldUseTimer`, `isTimerExpiredAndShouldHide`, `isLoading`                                                               |
| [Rewards](/es/aftersell/cart/rewards-block#custom-template)                           | `milestones`, `rewardsMessageHtml`, `showIcons`, `isLoading`                                                                       |
| [Cart items · Product](/es/aftersell/cart/cart-items-block#custom-template)           | 25 props: contenido por línea, identificadores y controles de cantidad                                                             |
| [Subscription upgrade](/es/aftersell/cart/subscription-upgrade-block#custom-template) | `view`, `selectPlan`, `onChange`, `oneTimeValue` y más                                                                             |
| [Summary](/es/aftersell/cart/summary-block#custom-template)                           | `leftHtml`, `rightHtml`, `discountCodes`, `totalPrice`, `savings` y más                                                            |
| [Checkout button](/es/aftersell/cart/checkout-button-block#custom-template)           | `label`, `href`, `isLoading`                                                                                                       |
| [Discount code](/es/aftersell/cart/discount-code-block#custom-template)               | `discountCodeInput`, `placeholder`, `buttonText`, `isValidating`, `isInvalid`, `setDiscountCodeInput`, `handleSubmit`, `isLoading` |
| [Empty cart](/es/aftersell/cart/empty-cart-block#custom-template)                     | `text`, `cta`, `href`                                                                                                              |
| [Image](/es/aftersell/cart/image-block#custom-template)                               | `imageUrl`, `altText`, `maxHeight`, `fullWidth`                                                                                    |
| [Notes](/es/aftersell/cart/notes-block#custom-template)                               | `titleHtml`, `placeholder`, `noteInput`, `status`, `isExpanded`, `onNoteChange`, `onNoteBlur`, `onToggle` y más                    |
| [Product add-on](/es/aftersell/cart/product-add-on-block#custom-template)             | `addonTitleHtml`, `descriptionHtml`, `priceHtml`, `imageUrl`, `format`, `isEnabled`, `handleAdd`, `handleToggle` y más             |
| [Shipping protection](/es/aftersell/cart/shipping-protection-block#custom-template)   | `titleHtml`, `descriptionHtml`, `priceHtml`, `imageUrl`, `format`, `isEnabled`, `handleAdd`, `handleToggle` y más                  |
| [Upsells](/es/aftersell/cart/upsells-block#custom-template)                           | `title`, `addButtonText`, `layout`, `upsells`, `selectVariant`, `handleAdd` y los controles del carrusel                           |

El bloque [Custom code](/es/aftersell/cart/custom-code-blocks) es la única superficie que **agrega** marcado en lugar de reemplazar el renderizado de un bloque, así que sus props son diferentes: todo el carrito, más una acción de agregar al carrito. Consulta [Bloques de código personalizado → Props](/es/aftersell/cart/custom-code-blocks#props).
