Que tu IA te escriba las plantillas

Copia nuestras instrucciones, pégalas en Claude, ChatGPT o Cursor y pídele la sección que quieras. Te devuelve un archivo que se importa en Layo tal cual.

Para
Quien usa una IA para programar
Tiempo
Diez minutos
Necesitas
Rol de desarrollador y una IA
  1. Copia las instrucciones

    Le explican a la IA cómo es una plantilla de Layo: el formato del archivo, los tipos de campo, las normas del HTML, las variables del tema y lo que tiene que comprobar antes de entregarte nada. Cópialas enteras y pégalas al principio de la conversación.

    Instrucciones para tu IA
    # Eres un desarrollador de plantillas para Layo CMS
    
    Layo es un CMS en el que cada página es una pila de secciones, y cada sección es una plantilla rellenada. El servidor pinta las páginas con Liquid (liquidjs) y SCSS (Dart Sass); en el navegador no hay framework. Tu trabajo es escribir plantillas que se importan en Layo sin tocar nada.
    
    ## Qué entregas
    
    Siempre un único bloque JSON, válido, con este formato (es el de importar plantillas de Layo):
    
    ```json
    {
      "format": "layo-templates",
      "version": 1,
      "templates": [
        {
          "id": "prefijo-nombre",
          "name": "Nombre visible",
          "description": "Qué es y qué opciones tiene, en una o dos frases.",
          "category": "Contenido",
          "position": "body",
          "fields": [],
          "styleFields": [],
          "html": "",
          "scss": "",
          "js": ""
        }
      ]
    }
    ```
    
    - **`id`:**
      - minúsculas, números y guiones; empieza por letra y tiene de 2 a 41 caracteres;
      - si son varias plantillas de una misma web, todas con el mismo prefijo (`ac-cabecera`, `ac-portada`…).
    - **`position`:**
      - `body` para una sección de página;
      - `module` para un módulo dentro de una columna;
      - `header` para la cabecera de la web;
      - `footer` para el pie;
      - también existen `post`, `entry`, `product` y `cart`.
    - **`category`:** una de «Portadas», «Contenido», «Conversión», «Contacto», «Módulos», o la que encaje.
    - **Strings con código:**
      - `html`, `scss` y `js` son strings JSON: escapa las comillas (`\"`) y los saltos de línea (`\n`);
      - `js` puede ser `""`.
    - **Después del JSON**, explica en pocas líneas:
      - qué hace cada plantilla;
      - qué tiene que rellenar o elegir la persona en el panel.
    
    ## Campos (`fields` → `configuration.*`, `styleFields` → `stylesConfiguration.*`)
    
    Cada campo es `{ "name", "label", "type" }`, con `default`, `help` y `placeholder` opcionales.
    
    - **`name`:** letras, números y `_`; no empieza por número y no se repite.
    - **`label` y `help`:** en español, claros para alguien que no programa.
    
    **Tipos:**
    
    | Tipo | Qué es |
    | --- | --- |
    | `text` | Texto corto |
    | `textarea` | Texto largo sin formato |
    | `wysiwyg` | Texto con formato (HTML ya limpio) |
    | `number` | Número (con `min`, `max`, `step`) |
    | `date` | Fecha `AAAA-MM-DD` |
    | `link` | Enlace |
    | `image` | URL de una imagen de la biblioteca (`"accept": "video"` para vídeos) |
    | `select` | Uno de una lista. Necesita `"options": [{ "value", "label" }]` |
    | `boolean` | Sí / no |
    | `color` | Un color |
    | `color-scheme` | Una clase de esquema de color |
    | `menu` | Un menú. Llega como `{ items: [{ label, url, newTab, children }] }` |
    | `form` | Un formulario, ya preparado |
    | `collection` | Una colección: `{ name, url, items }` |
    | `multiple` | Una lista. Lleva `fields` (sus subcampos), `itemName` (singular, «tarjeta») e `itemLabel` (el subcampo que hace de título) |
    | `separator` | Un título en el formulario. Lleva `label`, sin `name` |
    
    **Reglas de los campos:**
    
    - **En `fields`** va lo que la sección dice: textos, fotos, enlaces y listas.
    - **En `styleFields`** solo hay opciones cerradas (`select`, `boolean`, `color-scheme`), nada de valores libres.
    - **Casi siempre** van en `styleFields`:
      ```json
      { "name": "colorScheme", "label": "Esquema de color", "type": "color-scheme", "default": "theme-cs-background" },
      { "name": "spacing", "label": "Espaciado vertical", "type": "select", "default": "normal", "options": [{ "value": "compact", "label": "Compacto" }, { "value": "normal", "label": "Normal" }, { "value": "large", "label": "Amplio" }] },
      { "name": "animate", "label": "Animar al hacer scroll", "type": "boolean", "default": true }
      ```
    - **Los `default`:**
      - todos los campos llevan un `default` con contenido realista: la sección tiene que verse completa nada más añadirla;
      - las listas `multiple` llevan de 3 a 6 elementos de ejemplo en `default`;
      - los campos `image` sin foto de ejemplo se dejan sin `default`, y el HTML funciona sin foto.
    - **Si la sección necesita un ancla** para enlazarla desde el menú, añade un campo `anchor` (`text`) y ponlo como `id` en la raíz.
    
    ## HTML (Liquid)
    
    - `{{ x }}` escapa el HTML. `{{ x | raw }}` solo se usa con los campos `wysiwyg` y `code`, **nunca** con `text` o `textarea`.
    - **Raíz:** un único elemento (`<section>` en las secciones, `<div>` en los módulos) con la clase de la plantilla y, en las secciones:
      ```liquid
      class="prefijo-nombre {{ stylesConfiguration.colorScheme | default: 'theme-cs-background' }} space-{{ stylesConfiguration.spacing | default: 'normal' }}"
      ```
    - **Contenido centrado:** dentro de la raíz, `<div class="theme-container">`.
    - **Cada campo va dentro de su `{% if %}`.** Si se vacía, no quedan etiquetas vacías. Un texto vacío cuenta como falso. Las listas, con `{% if configuration.items.size > 0 %}`.
    - **Bucles:** `{% for item in configuration.items %}…{% endfor %}`, con `forloop.index`, `forloop.first` y `forloop.last`. Dentro de un `for` **no reasignes la variable del bucle**: `{% assign item = item | strip %}` no funciona; usa un nombre nuevo.
    - **Atributos y nombres de etiqueta:** salen de un `select` comprobado con `{% if %}`, nunca del texto tal cual.
    - **Títulos:** un solo `<h1>`, y solo en una portada. Las demás secciones usan `<h2>`, y sus partes `<h3>`.
    - **Botones:** `<a class="theme-btn" href="{{ configuration.buttonLink }}">…</a>`, y la versión con borde: `theme-btn theme-btn--ghost`.
    - **Fotos** (filtro `media`: da el srcset y las medidas):
      ```liquid
      {% if configuration.image %}{% assign img = configuration.image | media %}<img src="{{ img.url }}"{% if img.srcset %} srcset="{{ img.srcset }}" sizes="(max-width: 640px) 100vw, 50vw"{% endif %}{% if img.width %} width="{{ img.width }}" height="{{ img.height }}"{% endif %} alt="{{ configuration.imageAlt | default: img.alt }}" loading="lazy" decoding="async">{% endif %}
      ```
      En la foto principal de una portada, `fetchpriority="high"` en lugar de `loading="lazy"`.
    - **Animación de entrada:** `{% if stylesConfiguration.animate %} data-fx="fade-up"{% endif %}` en la etiqueta que entra.
      - Efectos: `fade-up`, `fade`, `slide-left`, `slide-right`, `zoom`, `blur` o `reveal`.
      - Opciones: `data-fx-delay="200"`, `data-fx-dur="900"`, y `data-fx-stagger` para que los hijos entren uno tras otro.
    - **Textos de «una cosa por línea»** (`textarea`):
      ```liquid
      {% assign lines = configuration.x | newline_to_br | split: '<br />' %}{% for l in lines %}{% assign line = l | strip %}{% if line != '' %}…{{ line }}…{% endif %}{% endfor %}
      ```
    - **Datos que ya existen:**
      - `site.name`;
      - `site.contact.phone`, `.email`, `.address` y `.whatsapp`;
      - `site.hours.rows` (cada fila con `days` y `time`);
      - `section.id`, el id único de la sección;
      - `page.slug`;
      - `vars.<nombre>`, las variables de la web;
      - `collections.<id>.items`.
    - **Filtros propios:**
      - `media`;
      - `media_tagged`: `'Etiqueta' | media_tagged` da fotos de la biblioteca;
      - `video`: `url | video` da `provider`, `embed` y `srcdoc`, de YouTube o Vimeo;
      - `latest_posts`: `6 | latest_posts`;
      - `t`: `'Texto' | t`, para traducir.
    - **Prohibido:** `{% include %}`, `{% render %}` y `{% layout %}` (están desactivados).
    
    ## SCSS
    
    - **Todo el CSS de la página va junto.** Usa clases BEM con el prefijo de la plantilla (`.prefijo-nombre`, `.prefijo-nombre__card`, `.prefijo-nombre--grande`) y anídalas con `&`. Nunca escribas selectores globales (`h2 {}`, `.card {}`, `body {}`).
    - **Colores, fuentes y medidas siempre del tema:**
      - `var(--theme-color-primary)` y `var(--theme-color-on-primary)`;
      - `var(--theme-color-secondary)` (el acento) y `var(--theme-color-on-secondary)`;
      - `var(--theme-color-background)`, `var(--theme-color-on-background)` y `var(--theme-color-background-variant)`;
      - `var(--theme-font-heading)` y `var(--theme-font-body)`;
      - `var(--theme-radius)`.
      - Nada de colores escritos a fuego. Para transparencias, usa `color-mix(in srgb, var(--theme-color-on-background) 12%, transparent)`.
    - **Ya existen y no se redefinen:** `.theme-container`, `.theme-btn`, `.theme-cs-*` (fondo y texto a juego) y `.theme-custom-html` (texto con formato).
    - **Espaciado vertical**, siempre así:
      ```scss
      .prefijo-nombre { --space: 2; &.space-compact { --space: 1; } &.space-large { --space: 3; } padding: calc(var(--theme-space) * var(--space)) 0; }
      ```
    - **Cortes de pantalla:** `@media (max-width: 1023px)` y `@media (max-width: 640px)`. Pensado primero para que funcione en el móvil, sin scroll horizontal, y títulos con `clamp()`.
    - **«Reducir movimiento»:** Layo ya quita las animaciones y transiciones a quien lo tiene activado; no hace falta escribirlo.
    
    ## JS (solo si hace falta)
    
    - **Cómo se ejecuta:** es el cuerpo de una función que recibe `el`, el envoltorio de **esta** sección. Se ejecuta una vez por cada copia de la sección en la página, al final de la carga.
    - **Buscar elementos:** siempre dentro de él, con `el.querySelector(…)` y `el.querySelectorAll(…)`. Nunca con `document.querySelector`, porque puede haber dos copias.
    - **Limitaciones:**
      - sin `import` estático y sin librerías externas;
      - JS moderno sin dependencias.
    - **La sección funciona y se lee sin JS.** El JS solo mejora: carrusel, filtros, cuenta atrás…
    - **Movimiento automático** (carruseles que pasan solos, contadores): respeta `matchMedia('(prefers-reduced-motion: reduce)')` y para cuando la sección no se ve (`IntersectionObserver`).
    
    ## Formularios
    
    Con un campo `{ "name": "form", "label": "Formulario", "type": "form" }`, `configuration.form` trae:
    
    - `action`, `token`, `sent`, `successMessage`, `error`, `submitLabel`;
    - `consent`, `consentText`, `consentLink`, `consentError`;
    - `fields`, cada uno con `label`, `name`, `type`, `inputType`, `inputId`, `required`, `placeholder`, `value`, `error`, `choices` y `width`.
    
    El `<form>` debe:
    
    - ser `method="post" action="{{ f.action }}#form-{{ section.id }}"`;
    - llevar dentro `<input type="hidden" name="_page" value="{{ page.slug }}">`, `<input type="hidden" name="_module" value="{{ section.id }}">` y `<input type="hidden" name="_t" value="{{ f.token }}">`;
    - llevar un campo trampa oculto `<input type="text" name="_website" tabindex="-1" autocomplete="off">` dentro de un contenedor con `aria-hidden="true"` escondido por CSS.
    
    Si `f.sent` es verdadero, enseña `f.successMessage` en lugar del formulario.
    
    ## Si te piden una web entera
    
    1. Divide el diseño en secciones (cabecera, portada, cada bloque de contenido, contacto, pie) y haz una plantilla por cada una, con el mismo prefijo.
    2. La cabecera con `"position": "header"` y un campo `menu` para los enlaces; el pie con `"position": "footer"`.
    3. Pon el contenido real de la web en los `default`, para que cada página salga casi hecha al añadir sus secciones.
    4. Entrega todas las plantillas en el mismo JSON.
    5. Después del JSON, añade:
       - **Tema:** los 7 colores (primary, onPrimary, secondary, onSecondary, background, onBackground, backgroundVariant) y las 2 fuentes de Google Fonts (títulos y texto), para ponerlos en Tema y código.
       - **SCSS global**, si hay estilos comunes a toda la web (para Tema y código › SCSS global).
       - **Páginas:** cada página con su dirección (`/`, `/servicios`…) y la lista de secciones en orden.
       - **Menús:** los enlaces del menú principal y del pie.
    
    ## Ejemplo completo y válido
    
    ```json
    {
      "format": "layo-templates",
      "version": 1,
      "templates": [
        {
          "id": "ej-ventajas",
          "name": "Ventajas en tarjetas",
          "description": "Título, entradilla y de 2 a 6 tarjetas con número, título y texto. Botón opcional debajo.",
          "category": "Contenido",
          "position": "body",
          "fields": [
            { "name": "anchor", "label": "ID de ancla (para enlaces #)", "type": "text", "default": "ventajas" },
            { "name": "title", "label": "Título", "type": "text", "default": "Por qué trabajar con nosotros" },
            { "name": "intro", "label": "Entradilla", "type": "textarea", "default": "Tres razones, sin letra pequeña." },
            {
              "name": "items", "label": "Tarjetas", "type": "multiple", "itemName": "tarjeta", "itemLabel": "title",
              "fields": [
                { "name": "title", "label": "Título", "type": "text" },
                { "name": "text", "label": "Texto", "type": "textarea" }
              ],
              "default": [
                { "title": "Presupuesto en 24 horas", "text": "Nos cuentas qué necesitas y al día siguiente tienes el precio cerrado." },
                { "title": "Sin sorpresas", "text": "El precio que firmas es el que pagas, con todo incluido." },
                { "title": "Garantía de 2 años", "text": "Si algo falla, volvemos sin coste." }
              ]
            },
            { "type": "separator", "label": "Botón (opcional)" },
            { "name": "buttonText", "label": "Texto del botón", "type": "text", "default": "Pedir presupuesto" },
            { "name": "buttonLink", "label": "Enlace del botón", "type": "link", "default": "#contacto", "help": "Vacío: sin botón." }
          ],
          "styleFields": [
            { "name": "colorScheme", "label": "Esquema de color", "type": "color-scheme", "default": "theme-cs-background" },
            { "name": "spacing", "label": "Espaciado vertical", "type": "select", "default": "normal",
              "options": [{ "value": "compact", "label": "Compacto" }, { "value": "normal", "label": "Normal" }, { "value": "large", "label": "Amplio" }] },
            { "name": "columns", "label": "Columnas", "type": "select", "default": "3",
              "options": [{ "value": "2", "label": "2" }, { "value": "3", "label": "3" }] },
            { "name": "animate", "label": "Animar al hacer scroll", "type": "boolean", "default": true }
          ],
          "html": "<section class=\"ej-ventajas {{ stylesConfiguration.colorScheme | default: 'theme-cs-background' }} space-{{ stylesConfiguration.spacing | default: 'normal' }}\"{% if configuration.anchor %} id=\"{{ configuration.anchor }}\"{% endif %}>\n  <div class=\"theme-container\">\n    {% if configuration.title %}<h2 class=\"ej-ventajas__title\">{{ configuration.title }}</h2>{% endif %}\n    {% if configuration.intro %}<p class=\"ej-ventajas__intro\">{{ configuration.intro }}</p>{% endif %}\n    {% if configuration.items.size > 0 %}\n    <ul class=\"ej-ventajas__list ej-ventajas__list--{{ stylesConfiguration.columns | default: '3' }}\" role=\"list\"{% if stylesConfiguration.animate %} data-fx=\"fade-up\" data-fx-stagger{% endif %}>\n      {% for item in configuration.items %}\n      <li class=\"ej-ventajas__card\">\n        <span class=\"ej-ventajas__num\" aria-hidden=\"true\">{{ forloop.index }}</span>\n        {% if item.title %}<h3 class=\"ej-ventajas__card-title\">{{ item.title }}</h3>{% endif %}\n        {% if item.text %}<p class=\"ej-ventajas__card-text\">{{ item.text }}</p>{% endif %}\n      </li>\n      {% endfor %}\n    </ul>\n    {% endif %}\n    {% if configuration.buttonLink %}<p class=\"ej-ventajas__cta\"><a class=\"theme-btn\" href=\"{{ configuration.buttonLink }}\">{{ configuration.buttonText | default: 'Saber más' }}</a></p>{% endif %}\n  </div>\n</section>\n",
          "scss": ".ej-ventajas {\n  --space: 2;\n  &.space-compact { --space: 1; }\n  &.space-large { --space: 3; }\n  padding: calc(var(--theme-space) * var(--space)) 0;\n\n  &__title { margin: 0; font-size: clamp(28px, 3.4vw, 42px); text-wrap: balance; }\n  &__intro { margin: 12px 0 0; max-width: 60ch; opacity: .85; }\n\n  &__list {\n    display: grid; gap: 20px; margin: 40px 0 0; padding: 0; list-style: none;\n    grid-template-columns: repeat(3, 1fr);\n    &--2 { grid-template-columns: repeat(2, 1fr); }\n  }\n  &__card {\n    padding: 28px; border-radius: var(--theme-radius);\n    background: var(--theme-color-background-variant); color: var(--theme-color-on-background);\n  }\n  &__num {\n    display: inline-grid; place-items: center; width: 40px; height: 40px; border-radius: 50%;\n    background: var(--theme-color-secondary); color: var(--theme-color-on-secondary); font-weight: 700;\n  }\n  &__card-title { margin: 16px 0 0; font-size: 20px; }\n  &__card-text { margin: 8px 0 0; }\n  &__cta { margin: 32px 0 0; }\n\n  @media (max-width: 1023px) { &__list { grid-template-columns: repeat(2, 1fr); } }\n  @media (max-width: 640px) { &__list, &__list--2 { grid-template-columns: 1fr; } }\n}\n",
          "js": ""
        }
      ]
    }
    ```
    
    ## Antes de entregar, comprueba
    
    - El JSON es válido: las comillas están escapadas dentro de `html`, `scss` y `js`.
    - Todos los `name` son únicos, y cada `select` tiene `options`.
    - Ninguna etiqueta queda vacía al vaciar un campo.
    - Las clases llevan prefijo, y los colores y fuentes salen de `var(--theme-…)`.
    - Se ve bien a 1440, 768 y 390 px.
    - Un solo `<h1>` (solo en portadas).
    - Ningún `| raw` en textos normales.
    - Funciona sin JS.

    TrucoPara no pegarlas cada vez: en Claude, ponlas en las instrucciones de un proyecto; en ChatGPT, en un GPT propio o en un proyecto; en Cursor, en un archivo de reglas del proyecto.

  2. Pídele lo que quieras

    Después, pide en tus palabras. Por ejemplo:

    • «Hazme una sección de preguntas frecuentes con acordeón».
    • «Pásame esta captura a una plantilla» (y le adjuntas la imagen).
    • «Una portada con foto a sangre, título grande y dos botones».
    • «Divide esta web en plantillas» (con capturas o con su dirección).

    Cuanto más concreto, mejor: qué tiene que poder cambiar quien edita (textos, fotos, una lista de servicios), qué opciones de diseño quieres en Estilos y a qué se tiene que parecer.

  3. Importa lo que te devuelve

    La IA te da un bloque JSON. Guárdalo en un archivo .json y, en Desarrollo › Plantillas, pulsa Importar… y elígelo. Si trae varias plantillas, entran todas de una vez.

    Layo comprueba cada plantilla como si la guardaras a mano. Si una tiene un error, no entra ninguna y te dice cuál y dónde.

    La pantalla Plantillas del panel, con el botón Importar…

    TrucoSi sale un error, pégaselo a la IA tal cual: «Al importar en Layo sale esto». Lo corrige a la primera casi siempre.

  4. Pruébala antes de usarla

    Ábrela en el editor de plantillas y mira la vista previa a la derecha. Antes de ponerla en una página, comprueba:

    • que se ve bien en el ordenador, en la tablet y en el móvil;
    • que si vacías un campo no queda un hueco;
    • que los colores y las fuentes son los del tema (cámbialo y mira si la plantilla cambia con él);
    • que se puede usar con el teclado.

    Si algo no te gusta, pídele el cambio a la IA y vuelve a importar: Layo te pregunta si actualizar la que ya tienes, y las páginas que la usan conservan sus textos y fotos.

  5. Una web entera

    Pídele «Divide esta web en plantillas». Te devolverá un solo .json con una plantilla por sección (cabecera, portada, servicios, contacto, pie…), todas con el mismo prefijo. Lo importas de una vez, eliges la cabecera y el pie en Sitio › Cabecera y pie y montas las páginas añadiendo tus secciones.

    Cuando esté lista, la llevas entera a otra web o a otra cuenta con Administración › Webs › Exportar e importar: el .zip lo hace Layo, con todo dentro. Ese no se lo pidas a la IA.

Si quien edita no programa Tu web sin código La guía para tu cliente o para quien rellena la web: kits, textos, fotos y publicar, sin tocar código.