Saltar al contenido principal

Formato de plantillas

Para desarrolladores

Una plantilla del editor visual se guarda como un documento JSON: filas, columnas y módulos, cada uno con sus propiedades. Es el documento que la API de plantillas devuelve y recibe completo en config, y el que un asistente de IA lee y edita, conectado por MCP o con la plantilla copiada desde el editor.

La especificación de esta página es la misma que entrega la API, así que puede leerla aquí o pedirla desde su integración:

Para ver un documento completo real, GET /v1/template-catalog/{category}/{name} devuelve una plantilla de la biblioteca sin copiarla.

El recorrido completo por la API está en Plantillas por API, y los ejemplos de cada tipo de módulo en Ejemplos del formato de plantillas. La especificación técnica está en español y nombra los campos, los valores y los códigos tal como los escribe y los devuelve la plataforma.

El formato del documento de plantilla​

El documento JSON que guarda una plantilla del editor de correo de la plataforma y a partir del cual se genera el HTML del correo. Junto con este texto, el formato se entrega como datos: field_types (los campos de cada nodo y el tipo que toma cada uno) y defaults (el valor con que empieza cada campo de una fila, una columna o un módulo nuevos).

Esta parte aplica a la API de plantillas.

La API de plantillas lee y escribe el documento completo, en config, y entrega junto con este texto un skeleton: un documento completo mínimo para empezar.

Partir de un documento que existe​

Un campo que el documento omite no es un error: el correo se genera con el valor de respaldo del motor. Ese valor no siempre coincide con el valor por defecto del editor, así que la plantilla se guarda, pero se ve peor (se pierde un margen, cambia un color). Modifique un documento que ya existe en vez de escribir uno desde cero, y envíelo de vuelta completo, como se leyó, con sus cambios.

  1. Lea el documento: el config de una plantilla, una plantilla de la biblioteca o el skeleton.
  2. Cambie lo que necesite: un campo, una fila, una columna, un módulo. Deje el resto como estaba.
  3. Valide el resultado antes de guardarlo.

Los campos y los valores por defecto de un tipo de módulo también se entregan por separado, con un módulo de ejemplo de ese tipo.

Estructura​

{ "page": {
"title": "…", optional, the <title> of the HTML
"body": { … }, page properties: font, colors, width
"rows": [ top to bottom
{ "uuid", "type", "container", "content",
"columns": [ left to right
{ "uuid", "grid-columns", "style",
"modules": [ top to bottom
{ "uuid", "type", "descriptor": { … } } ] } ] } ] } }

Las anotaciones del bloque están en inglés: el título es opcional y es el título del HTML, el cuerpo lleva las propiedades de la página (fuente, colores, ancho), las filas y los módulos van de arriba abajo, y las columnas de izquierda a derecha.

  • page.body: su type es fidelizador-page-properties. content.style.font-family y content.style.color son la fuente y el color del texto de la página, content.computedStyle.linkColor el color de sus enlaces y messageWidth el ancho de cada fila (lo normal es 600px). container.style.background-color es el color alrededor del mensaje.
  • Una fila es una franja del correo. container.style pinta el ancho completo (un color o una imagen de fondo), content.style el ancho del mensaje. content.computedStyle.rowColStackOnMobile apila sus columnas en un teléfono.
  • Una columna ocupa grid-columns de las 12 unidades de la fila. Las unidades de las columnas de una fila suman 12: [12], [6, 6], [4, 8], [3, 3, 3, 3].
  • Un módulo es un bloque de contenido. Su type es fidelizador-modules-<kind>.

Ids​

Cada fila, columna y módulo lleva un uuid, único en el documento. Una edición se dirige a un nodo por él, así que dos nodos con el mismo id pueden hacer que una edición caiga en el que no corresponde. El validador rechaza un id ausente o repetido.

  • Conserve el uuid de cada nodo que ya existe.
  • Un nodo nuevo recibe un id nuevo. Al copiar un nodo, dele un id nuevo a la copia y a cada nodo dentro de ella.
  • Un documento de HTML crudo no tiene filas y por lo tanto no tiene ids.

Módulos​

Cada módulo guarda su contenido en una clave del descriptor y su espaciado en descriptor.style (padding-*). descriptor.computedStyle.hideContentOnMobile / hideContentOnDesktop lo ocultan en uno de los dos lados. descriptor.mobileStyle reemplaza padding-*, font-size y text-align en un teléfono. Los valores por defecto de cada campo están en defaults, bajo modules.<kind>.descriptor.

TipoClave del contenidoQué se fija
headingheadingtext (HTML en línea), title (h1…h6), style.color, style.font-size
paragraphparagraphhtml (<p> con formato en línea y enlaces), style.color
listlisthtml (<ul> u <ol>), tag, style.color
imageimagesrc, alt, href (enlace opcional), width
buttonbuttonlabel, href, style.background-color, style.color
dividerdividerstyle.border-top (1px solid #EDEDED)
spacerspacerstyle.height
menumenuItemsListitems[]: text y link.href
socialiconsListicons[]: name e image.{src, href, alt}
iconsiconsListicons[]: image, alt, href, text
videovideosrc (la página del video), thumbSrc (su imagen)
htmlhtmlhtml: marcado que pasa tal cual
tabletablecontent.headers[].cells[].html, content.rows[].cells[].html
countdowncountdownuntil (instante ISO 8601), labels, colores

text es un alias heredado de paragraph y empty no muestra nada; ninguno de los dos se ofrece para contenido nuevo. Los arreglos (items, icons, cells) son valores, no se combinan: fijar uno reemplaza el arreglo completo del valor por defecto.

Tipos de campo​

field_types enumera los campos de cada nodo y el tipo que toma cada uno. Un campo que no está ahí es desconocido: el motor nunca lo lee. Los tipos que conviene conocer antes de escribir un valor:

TipoAceptaEjemplo
objeto / arregloexactamente eso; un texto donde va un objeto es un error, nunca un atajo"descriptor": {"paragraph": {…}}, no "descriptor": "Hola"
colorhexadecimal (#RGB, #RRGGBB, con alfa), rgb()/rgba(), hsl(), transparent, un nombre de color CSS#1F2933
lengthun número con su unidad (px, %, em, rem, pt), 0, auto; hasta cuatro para padding y border-radius. line-height puede ir sin unidad10px, 0 8px, 150%
sizeun número, o un número de píxeles o un porcentaje como texto600, "600px"
urlhttps://, http://, mailto:, tel:, un enlace especial fide:, una ruta relativa a la raízhttps://example.com/oferta
imagehttps:// o una ruta relativa a la raíz de la galería/mail-templates/logo.png
htmlHTML, nunca Markdown: <strong>, <a href>, <br><p>Hola <strong>Ana</strong></p>
dateISO 86012030-12-31T23:59:00-03:00
grid-columnsun número entero de 1 a 126

Los números son finitos y tienen como máximo 15 dígitos. Un valor CSS es solo el valor: sin ;, sin una segunda declaración y sin comillas alrededor.

Reglas de calidad​

El validador informa estas reglas. Cada hallazgo nombra el puntero JSON del valor y cómo arreglarlo, y su nivel viene en inglés:

  • error — el motor fallaría, el correo llevaría algo inseguro o la estructura es ambigua. El documento se rechaza.
  • warning — el correo se genera, pero algún destinatario lo recibe roto.
  • suggestion — probablemente un error de tipeo que el motor ignora sin avisar.
NivelCódigoRegla
errorinvalid_document, invalid_structureLa forma descrita arriba: page, rows, columns y modules están donde corresponde
errorinvalid_typeUn campo tiene el tipo que declara field_types: un objeto, un arreglo, un texto, un número o un booleano
errorinvalid_numberUn número es finito y lo bastante corto para que el motor lo lea
errormissing_id, duplicate_idCada fila, columna y módulo tiene su propio uuid
errorempty_rowUna fila tiene al menos una columna
errorinvalid_gridCada columna tiene grid-columns, un número entero de 1 a 12
errorunknown_module_typeCada módulo es de un tipo que el motor sabe mostrar
errorunsafe_linkNingún enlace ni imagen ejecuta código: javascript:, vbscript: o un enlace data:
errorunsafe_htmlSin <script>, <iframe>, <object>, <embed>, <form>, <meta>, <base>, <style>, <link>, manejadores de eventos (onclick=) ni scripts en un estilo en línea
errorunbalanced_htmlCada <td> que el HTML abre, lo cierra
errorunsafe_css_valueUn valor CSS no lleva ", ;, <, >, {, } ni barra invertida: un valor por clave
errorunrenderableEl motor muestra el documento. Se informa cuando falla y ninguna regla de arriba explica por qué
warninginvalid_color, invalid_lengthUn color o una longitud es uno que un cliente de correo entiende
warninginvalid_link, placeholder_linkUn enlace es una URL completa: no www.…, no un # de relleno, no un href vacío
warninginvalid_image_srcUna imagen es una URL https:// o una ruta de la galería; http:, data: y las rutas relativas no se muestran
warninginvalid_enumUn campo con un conjunto cerrado de valores toma uno de ellos (title es h1…h6, en minúsculas)
warningempty_contentUn bloque tiene su contenido: un botón su texto, un texto sus palabras, un menú sus ítems
warningmalformed_html, markdown_in_htmlLas etiquetas se cierran en el orden en que se abren, y el texto es HTML y no Markdown
warninggrid_not_twelveLos grid-columns de una fila suman 12
warninginvalid_date, countdown_expiredUna cuenta atrás cuenta hasta un momento válido en el futuro
warningunusual_widthmessageWidth está entre 320 y 900 píxeles
warningempty_documentLa página tiene filas
warningimage_without_src, image_without_altUna imagen muestra algo, y dice qué muestra
warningbutton_without_linkUn botón enlaza a alguna parte
warningtext_without_colorUn texto declara su propio color. El HTML declara esquemas de color claro y oscuro, así que un texto sin color se pinta claro en modo oscuro y puede desaparecer
warningclick_here_without_linkUn texto que pide un clic lleva el enlace
warningunresolved_special_link, missing_special_linkLos enlaces especiales obligatorios son fide:unsubscribe, fide:complaint y fide:browser-view, y un correo lleva los tres, normalmente en el pie
warninggmail_clipEl HTML se queda por debajo de 102 400 bytes, donde Gmail recorta el mensaje y oculta el resto
suggestionunknown_keyUn campo que ningún lector conoce, en cualquier nivel, propiedades CSS incluidas. El motor lo ignora sin avisar; suele ser un error de tipeo
suggestionshort_typeUn documento completo escribe el tipo del módulo con su prefijo fidelizador-modules-

Las imágenes vienen de la galería de la plataforma o de la biblioteca de la cuenta, por ruta relativa a la raíz (/mail-templates/…) o por la URL completa que trae un documento guardado. Los enlaces especiales van en href.

La vista esencial​

Esta parte aplica al conector MCP y a la plantilla que el editor copia para un chat de IA. La API de plantillas no acepta la vista esencial.

El conector MCP trabaja sobre una forma más corta del mismo documento: su vista esencial. Tiene la misma forma, solo con lo que difiere de los valores por defecto, así que la mayoría de sus bytes dicen algo.

  • La plataforma convierte una vista de vuelta en el documento completo: repone cada valor por defecto y le da un uuid a cada fila, columna y módulo que no lo tenga, así que un nodo que se agrega a una vista no necesita id. Si la vista viene de un documento sin cambios, el documento que resulta genera el mismo HTML byte a byte.
  • En una vista, el type de un módulo puede ser el <kind> solo, sin el prefijo fidelizador-modules-.
  • En una vista, null en un campo que tiene valor por defecto significa "ausente": el documento guardado no lo traía, y el motor aplica su propio valor de respaldo. No toque esos null salvo que quiera fijar el campo.
  • Los ejemplos del formato son vistas esenciales: un esqueleto y uno por tipo de módulo.

Documento mínimo para empezar (skeleton)​

{
"page": {
"title": "Esqueleto",
"body": {
"type": "fidelizador-page-properties",
"container": {
"style": {
"background-color": "#F4F5F7"
}
},
"content": {
"style": {
"font-family": "Arial, 'Helvetica Neue', Helvetica, sans-serif",
"color": "#1F2933"
},
"computedStyle": {
"linkColor": "#0C6CC7",
"messageBackgroundColor": "#FFFFFF",
"messageWidth": "600px"
}
}
},
"rows": [
{
"container": {
"style": {
"background-color": "transparent",
"background-image": "none",
"background-position": "top left",
"background-repeat": "no-repeat"
}
},
"content": {
"computedStyle": {
"hideContentOnDesktop": false,
"hideContentOnMobile": false,
"rowColStackOnMobile": true,
"rowReverseColStackOnMobile": false,
"verticalAlign": "top"
},
"style": {
"background-color": "transparent",
"background-image": "none",
"background-position": "top left",
"background-repeat": "no-repeat",
"color": "#000000",
"width": "600px"
}
},
"empty": false,
"locked": false,
"synced": false,
"uuid": "d8821e6a-d48b-5af9-8dea-6ae8d5d55b68",
"type": "one-column-empty",
"columns": [
{
"style": {
"background-color": "transparent",
"border-top": "0px solid transparent",
"border-right": "0px solid transparent",
"border-bottom": "0px solid transparent",
"border-left": "0px solid transparent",
"padding-top": "5px",
"padding-right": "0px",
"padding-bottom": "5px",
"padding-left": "0px"
},
"uuid": "d198ba03-ec45-5365-9ee1-08330e1ea761",
"grid-columns": 12,
"modules": [
{
"uuid": "ae5863fd-e860-51a7-983d-9e048f600188",
"type": "fidelizador-modules-heading",
"descriptor": {
"heading": {
"title": "h1",
"style": {
"color": "#1F2933",
"font-family": "inherit",
"font-size": "24px",
"font-weight": "700",
"line-height": "120%",
"letter-spacing": "0px",
"text-align": "left",
"direction": "",
"link-color": "#1C93CA"
},
"text": "Título del correo"
},
"style": {
"padding-top": "10px",
"padding-right": "10px",
"padding-bottom": "10px",
"padding-left": "10px"
},
"computedStyle": {
"hideContentOnMobile": false,
"hideContentOnDesktop": false
}
}
},
{
"uuid": "79fd3fb4-c967-5b85-8d93-70a7c78b15b2",
"type": "fidelizador-modules-paragraph",
"descriptor": {
"paragraph": {
"style": {
"color": "#1F2933",
"font-family": "inherit",
"font-size": "16px",
"font-weight": "400",
"line-height": "150%",
"letter-spacing": "0px",
"text-align": "left",
"direction": ""
},
"computedStyle": {
"linkColor": "#1C93CA"
},
"html": "<p>Un párrafo que presenta el contenido.</p>"
},
"style": {
"padding-top": "10px",
"padding-right": "10px",
"padding-bottom": "10px",
"padding-left": "10px"
},
"computedStyle": {
"hideContentOnMobile": false,
"hideContentOnDesktop": false
}
}
},
{
"uuid": "dc0af782-89e0-5501-9e7f-544d968a6e88",
"type": "fidelizador-modules-button",
"descriptor": {
"button": {
"href": "https://example.com",
"target": "_blank",
"style": {
"background-color": "#0c6cc7",
"border-radius": "5px",
"color": "#ffffff",
"font-family": "inherit",
"font-size": "16px",
"font-weight": "700",
"line-height": "120%",
"letter-spacing": "0px",
"width": "100%",
"padding-top": "13px",
"padding-right": "30px",
"padding-bottom": "13px",
"padding-left": "30px",
"border-top": "0px solid #000000",
"border-right": "0px solid #000000",
"border-bottom": "0px solid #000000",
"border-left": "0px solid #000000"
},
"label": "Ver más"
},
"style": {
"text-align": "center",
"padding-top": "10px",
"padding-right": "10px",
"padding-bottom": "10px",
"padding-left": "10px"
},
"computedStyle": {
"width": 120,
"height": 42,
"hideContentOnMobile": false,
"hideContentOnDesktop": false
}
}
}
]
}
]
},
{
"container": {
"style": {
"background-color": "transparent",
"background-image": "none",
"background-position": "top left",
"background-repeat": "no-repeat"
}
},
"content": {
"computedStyle": {
"hideContentOnDesktop": false,
"hideContentOnMobile": false,
"rowColStackOnMobile": true,
"rowReverseColStackOnMobile": false,
"verticalAlign": "top"
},
"style": {
"background-color": "transparent",
"background-image": "none",
"background-position": "top left",
"background-repeat": "no-repeat",
"color": "#000000",
"width": "600px"
}
},
"empty": false,
"locked": false,
"synced": false,
"uuid": "d509b948-a515-51b6-b77c-bb062b6fa63e",
"type": "one-column-empty",
"columns": [
{
"style": {
"background-color": "transparent",
"border-top": "0px solid transparent",
"border-right": "0px solid transparent",
"border-bottom": "0px solid transparent",
"border-left": "0px solid transparent",
"padding-top": "5px",
"padding-right": "0px",
"padding-bottom": "5px",
"padding-left": "0px"
},
"uuid": "82963943-8e72-5123-b5bb-1b90cfb095bb",
"grid-columns": 12,
"modules": [
{
"uuid": "9c4e0d79-0a35-56ee-8783-b39ce54d9a76",
"type": "fidelizador-modules-paragraph",
"descriptor": {
"paragraph": {
"style": {
"color": "#6B7280",
"font-family": "inherit",
"font-size": "12px",
"font-weight": "400",
"line-height": "150%",
"letter-spacing": "0px",
"text-align": "center",
"direction": ""
},
"computedStyle": {
"linkColor": "#1C93CA"
},
"html": "<p>Recibes este correo porque te suscribiste. <a href=\"fide:browser-view\">Ver en el navegador</a> · <a href=\"fide:unsubscribe\">Darte de baja</a> · <a href=\"fide:complaint\">Reportar abuso</a></p>"
},
"style": {
"padding-top": "10px",
"padding-right": "10px",
"padding-bottom": "10px",
"padding-left": "10px"
},
"computedStyle": {
"hideContentOnMobile": false,
"hideContentOnDesktop": false
}
}
}
]
}
]
}
]
}
}

Cómo trabajar con un chat de IA​

Hay dos formas de que un asistente de IA edite una plantilla:

Cargar una plantilla pegada no la guarda. Revise el resultado en el editor y guarde cuando quiera; un deshacer vuelve a la plantilla anterior.