Ir al contenido

Rechazos y correcciones de páginas personalizadas

Qué es esto

El catálogo completo de rechazos y fallos para las páginas personalizadas de ComStack: cada error que puede devolver upload-custom-page, cada fallo en la etapa de publicación que afecta a las páginas personalizadas y los avisos de lint. Cada entrada indica el disparador exacto, el texto real del mensaje y la solución, redactados para que un desarrollador o agente pueda autocorregirse solo con el mensaje. El flujo ideal está documentado en Crear una página personalizada.

Cómo funciona

Los fallos en las páginas personalizadas aparecen en dos etapas, además de un canal que nunca bloquea:

  1. Momento de carga. upload-custom-page valida de forma síncrona y rechaza toda la llamada con un error de parámetro. No se almacena nada al rechazar; una fila de auditoría registra el intento con un código de error como marker_extraction_failed o blocked_href_scheme.
  2. Momento de publicación. publish (la prueba de simulación) rechaza generar un token de confirmación para estados de borrador ambiguos. publish-confirm se ejecuta de forma asíncrona, por lo que los fallos posteriores —el control de Lighthouse, la compilación del sitio— aparecen al consultar publish-status como status: "failed" con el texto del error.
  3. Lint — nunca bloquea. Los hallazgos de lint aparecen en la respuesta de carga como lint_findings con severity: "warning". Son solo informativos y nunca bloquean una carga o publicación. Se incluyen porque predicen fallos de rendimiento de Lighthouse, los cuales sí bloquean cuando se ejecuta el control.

Los mensajes a continuación se citan tal como los emite la implementación, con valores de ejemplo representando las partes variables.

Cuándo usar esto

  • upload-custom-page devolvió un error y quieres la solución precisa, no una suposición.
  • publish se negó a generar un token de confirmación, o publish-status informa failed.
  • Un borrador que esperabas publicar no aparece en el manifiesto de publicación.
  • Estás decidiendo si los lint_findings necesitan corrección antes de publicar.

Rechazos en la carga

Límites de título y descripción

Disparador: title de menos de 10 o más de 60 caracteres; description de menos de 50 o más de 160.

title must be between 10 and 60 characters
description must be between 50 and 160 characters

Solución: cuenta los caracteres, espacios incluidos, y reescribe dentro de los límites. En sitios multilingües, los títulos y descripciones traducidos automáticamente que exceden el límite se recortan a 60/160 caracteres con un al final; deja margen si puedes.

Desajuste de project_name

Disparador: project_name no coincide con el nombre real del proyecto (la comparación no distingue entre mayúsculas y minúsculas, tras eliminar espacios en blanco).

project_name does not match project k2xPq9wEfGhRtY3LmN8b: expected 'Acme Robotics', got 'Acme'

Solución: copia el nombre literalmente desde get-project-state. La respuesta es una medida de seguridad para que una llamada destructiva nunca se ejecute contra el proyecto incorrecto.

Idioma no incluido en las configuraciones regionales del proyecto

Disparador: language nombra una configuración regional para la cual el proyecto no está configurado.

language 'fr' is not in project.locales [en, es]. Add it to modules.custom_pages.locales first, or omit `language` to use the default_locale ('en').

Solución: el mensaje es autocorrectivo: añade la configuración regional al módulo de páginas personalizadas o elimina el argumento language. Recuerda que language es la configuración regional fuente de la página, no una solicitud de traducción (la publicación traduce automáticamente), y que se ignora en las actualizaciones: una página existente mantiene su idioma.

Validación de slug

Disparador: cualquier segmento de slug separado por / que no esté en minúsculas (kebab-case), o un / solo.

Slug must be lowercase kebab-case. Use lowercase letters, digits, and hyphens. Use '/' to nest path segments. Use 'index' for the locale root.

Solución: ejemplos válidos: index, guides/buying, blog/buying-property-spain-2026. La raíz de la configuración regional es el slug literal index, nunca /.

Un segundo rechazo de slug, más raro: cuando el agente en vivo del proyecto se coloca en una página de destino, ese slug queda reservado:

Slug 'talk' is reserved by settings/site.live_agent_slug while live_agent_placement is 'destination_page'.

Solución: como indica el texto de corrección del error, elige un slug diferente o mueve la ubicación del agente en vivo fuera de destination_page para liberarlo.

Fallos de extracción de marcadores

Disparador: cualquier marcador de traducción no válido en body_source. La carga se rechaza por completo y se enumeran todos los problemas a la vez:

Marker extraction failed (2 errors):
1. offset=118 line=4 col=7 key='Hero.Title' — Invalid marker key 'Hero.Title'. Keys must match /^[a-z][a-z0-9_.]*[a-z0-9]$/ (lowercase, digits, underscore, dot).
2. offset=240 line=7 col=5 — <T> marker is missing required `key` attribute.

Los mensajes individuales:

DisparadorMensaje exacto
La clave no cumple el patrón: mayúscula, dígito inicial, . o _ final, o un solo carácter (el mínimo es dos)Invalid marker key 'Hero.Title'. Keys must match /^[a-z][a-z0-9_.]*[a-z0-9]$/ (lowercase, digits, underscore, dot).
<T> sin atributo key<T> marker is missing required `key` attribute.
key={expr} en lugar de un literal de cadena<T key={...}> dynamic key expressions are not allowed. Use a static string literal.
default={expr} en lugar de un literal de cadena<T default={...}> dynamic defaults are not allowed. Use a static string literal.
Sin atributo default y sin texto interno<T key='hero.cta'> is missing a default. Provide a static `default` attribute or static inner text.
{expression} dentro de un elemento <T><T>{expression}</T> dynamic inner content is not allowed. Use a static default attribute or static text.
Un fragmento dentro de un elemento <T><T> markers may not contain fragments. Use a static default attribute or static text.
<T> anidado dentro de <T>Nested <T> markers are not allowed. A marker cannot contain another marker.
El cuerpo se dirige al analizador JSX pero no es JSX válidoJSX/Astro parse error: Unexpected token (8:20). The body_source must be parseable JSX/Astro embedded inside a fragment.
Elemento data-t-key que contiene elementos hijosMarker 'hero.title' contains nested HTML elements. Marker elements must wrap plain text only.
Elemento data-t-key con texto vacíoMarker 'hero.title' has empty text content. The element's text becomes the English default; it must be non-empty.
data-t-key anidado dentro de otro elemento data-t-keyNested data-t-key markers are not allowed. 'inner.key' is inside marker 'outer.key'.
Misma clave usada dos veces con diferentes valores predeterminadosDuplicate marker key 'hero.cta' with conflicting default. Existing default: 'Talk to us'. New default: 'Contact us'. Use one default per key.

Soluciones:

  • Claves: comienzan con una letra minúscula, terminan con una letra o dígito, solo puntos y guiones bajos en el medio, mínimo dos caracteres. hero.title es válido; Hero, 1cta, nav. y x son rechazados.
  • El error de análisis JSX suele significar un bloque <style> o <script> en línea en la ruta Astro/JSX: sus llaves { no son JSX válido. La ruta a ese analizador es un escaneo de texto plano, no consciente del análisis: un cuerpo que comienza con frontmatter ---, o que contiene la secuencia <T seguida de espacio en blanco, / o > en cualquier lugar —incluso dentro de una cadena de JavaScript o un comentario HTML— se analiza como JSX. Por lo tanto, un cuerpo HTML cuyo script en línea contenga un <T literal también provoca este error; elimina la secuencia o divídela ('<' + 'T '). De lo contrario: crea la página en el formulario HTML (marcadores data-t-key) o mueve los estilos y scripts fuera del cuerpo. El (line:col) en el mensaje apunta al primer carácter infractor.
  • Una clave solo puede repetirse con el mismo valor predeterminado. Elimina duplicados o renombra una de las claves.
  • Los marcadores envuelven solo texto plano; reestructura el marcado anidado en varios marcadores hermanos.
  • La redacción “English default” es literal en el mensaje; estrictamente es el valor predeterminado de la configuración regional fuente (lo que sea que resuelva tu language).

Esquemas href bloqueados

Disparador: cualquier atributo href o xlink:href cuyo valor se resuelva en javascript:, data: o vbscript:. Los valores se normalizan antes de la comprobación (se decodifican entidades HTML, se eliminan espacios en blanco y caracteres de control), por lo que se detectan formas ofuscadas como java&#x73;cript: o una tabulación dentro del esquema. Los atributos src no se comprueban: las imágenes data: en línea siguen funcionando. El intento se registra en la pista de auditoría con el código de error blocked_href_scheme.

body_source contains 1 blocked href scheme — javascript:, data: and vbscript: hrefs are rejected (XSS hard boundary; the body ships to the browser as authored):
1. offset=412 scheme=javascript: href="javascript:alert(1)"
Use https:, mailto:, tel:, relative or #anchor URLs instead. See transformento://docs/custom-pages/authoring.

Solución: como dice el mensaje. Para el comportamiento de clic, adjunta un oyente desde un script diferido en lugar de una URL javascript:.

Herramienta incorrecta para el tipo de página

Disparador: el slug se resuelve en una página de conocimiento (markdown) existente. El intento rechazado se registra en la pista de auditoría con el código de error wrong_tool_knowledge_page.

'pricing' (slug 'pricing') is a KNOWLEDGE (markdown) page, not a custom page. upload-custom-page would overwrite its body with opaque body_source. Edit it with update-page, or pick a different slug for the custom page.

Solución: exactamente como se indica: edita esa página con update-page o elige un slug diferente. La protección es simétrica: update-page en una página personalizada también se rechaza con:

'solutions-voice' is a CUSTOM page (metadata.type="custom"). Edit it with upload-custom-page (body_source + <T key=…> markers), not update-page. update-page edits knowledge (markdown) pages only.

En caso de duda, get-page-content devuelve page_type"custom" o "knowledge"— para que puedas elegir el editor correcto antes de escribir. Consulta Tipos de página.

is_faq está prohibido

Disparador: pasar is_faq: true. La herramienta no tiene ese parámetro y se detecta si se intenta introducir en los argumentos sin procesar.

is_faq=true is forbidden on custom-page docs (template.forbids_is_faq=true). FAQ JSON-LD is a knowledge-page concern.

Solución: coloca el contenido de preguntas frecuentes en una página de conocimiento. Las páginas personalizadas nunca emiten datos estructurados de FAQ.

Cuerpo por encima del límite estricto de 2 MB

Disparador: body_source mayor de 2 MB (2,097,152 bytes de UTF-8).

body_source is 2483911 bytes — above the 2 MB hard cap. Move large inline assets out of body_source (Cloudinary / static).

Solución: aloja imágenes, vídeos y fuentes con upload-media (o cualquier CDN) y haz referencia a ellos por URL. El aviso de lint body-too-large se activa antes, a los 500 KB; trátalo como una señal de advertencia.

Validación de media_variants

Disparador: un mapa media_variants mal formado. Cuatro rechazos distintos:

  • Forma general incorrecta:
media_variants must be an object map of { "<key>": { "<locale>": "<absolute URL>" } }.
  • Una clave sin un marcador coincidente en el cuerpo:
media_variants key 'hero.shot' has no matching data-t-media marker in the page body. Add data-t-media="hero.shot" to the <img|video|source> element, or remove the override.
  • Un mapa de configuración regional que no es un objeto:
media_variants['hero.shot'] must be a { "<locale>": "<absolute URL>" } object.
  • Una URL relativa o no http:
media_variants['hero.shot']['es'] must be an absolute http(s) URL (got /img/hero-es.png).

Solución: cada clave en media_variants debe coincidir con un atributo data-t-media en el cuerpo, y cada URL debe ser http(s) absoluta. El mapa es opcional; las configuraciones regionales sin una entrada renderizan el src en línea del elemento.

Otra validación de metadatos

  • og_image debe ser una URL http(s) absoluta.
  • published_date y updated_date deben ser fechas ISO 8601 (YYYY-MM-DD, sufijo de tiempo opcional).
  • schema_org_type debe ser uno de WebPage, Article, AboutPage, ContactPage, CollectionPage, ProfilePage.

Estos aparecen como problemas de validación con nombre de campo y los valores permitidos listados; corrige el campo nombrado y vuelve a intentar.

Fallos en la etapa de publicación

duplicate_draft_target

Disparador: en publish (simulación), dos o más borradores pendientes apuntan a la misma URL (slug, language), normalmente un borrador sobrante más uno nuevo. No se genera ningún token de confirmación.

duplicate_draft_target: 1 (slug, language) target(s) have more than one pending draft. No confirmation token was minted. 'solutions/voice' (en) → solutions-voice, k9XbQ2mEphd71GfTzALw. Discard the extra drafts (discard-draft) or change a slug, then publish again.

Los datos de error estructurados enumeran cada colisión y marcan exactamente un borrador como newest: true.

Solución: mantén el borrador marcado como más reciente y usa discard-draft en los otros —o cambia un slug si ambas páginas deben existir— y luego ejecuta publish de nuevo.

Borrador que falta en el manifiesto: zero-diff

Disparador: no es un error. Un borrador cuyo contenido es idéntico a la página en vivo se excluye del manifiesto de publicación y se elimina, no se promociona al publicar. Editar una página en vivo con contenido idéntico byte a byte ni siquiera crea un borrador; la respuesta de carga informa "action": "no_change".

Solución: nada que corregir. Si esperabas cambios, compara lo que subiste con la página en vivo; probablemente volviste a subir la versión actual.

Token de confirmación caducado

Disparador: más de 5 minutos entre publish y publish-confirm.

Confirmation token has expired (5-minute TTL). Call publish again to get a fresh token, then call publish-confirm immediately.

Solución: como se indica. Los tokens también son de un solo uso y están vinculados al usuario, al proyecto y al manifiesto exacto; si el conjunto de borradores cambió después de la simulación, el token se invalida y debes ejecutar publish de nuevo.

slug_in_use al volver a subir una página publicada

Disparador: un problema conocido en la actualización. upload-custom-page encuentra una página existente por el ID de documento que deriva del slug. La primera carga crea la página en ese ID y la primera publicación la mantiene; las cargas siguen funcionando. Pero la primera vez que editas y vuelves a publicar una página personalizada en vivo, su ID de documento rota; la siguiente carga para ese slug no encuentra nada en el ID derivado, toma la ruta de creación y activa la protección de colisión de slug contra la página en vivo rotada:

Slug 'solutions/voice' (language 'en') is already used by another page (path: 'k9XbQ2mEphd71GfTzALw', visibility: live). Two pages with the same slug + language would deploy to the same URL.

La solución sugerida por el error —update-pageno funciona para páginas personalizadas; las rechaza (ver “Herramienta incorrecta para el tipo de página” arriba).

Solución actual: llama a delete-page con la path nombrada en el error (el documento en vivo rotado), vuelve a ejecutar la misma llamada upload-custom-page y luego publica. El sitio desplegado sigue sirviendo la página existente hasta que se completa la publicación, por lo que los visitantes no ven interrupciones, pero vuelve a subir inmediatamente después de la eliminación, ya que la eliminación elimina la página almacenada de inmediato. En resumen: espera que cada carga funcione hasta que una página se haya vuelto a publicar una vez; la carga después de esa republicación genera este error hasta que la plataforma cierre esta brecha.

Fallo del control de Lighthouse — y force

Disparador: las páginas personalizadas se auditan antes de la publicación contra un estándar de 95 en las cuatro categorías (Rendimiento, Accesibilidad, Mejores prácticas, SEO) cuando se ejecuta el control de calidad de la plataforma. Cualquier categoría por debajo de 95 en cualquier página personalizada auditada hace fallar la publicación: publish-status informa status: "failed" con:

Lighthouse pre-publish gate failed: https://www.acme-robotics.com/solutions/voice scored below 95 (perf=82, a11y=98, bp=100, seo=100)

Varias URLs fallidas se unen con |. Cada URL fallida también lleva una lista de remediation: las auditorías específicas que fallan como auditId: title — description, nombrando exactamente qué corregir.

Solución: construye para obtener ≥95; ese es el contrato. Las reglas de lint en el momento de la carga se asignan a los fallos de rendimiento más comunes: scripts ansiosos, imágenes no diferidas, hojas de estilo que bloquean la renderización, fuentes sin font-display: swap, imágenes no optimizadas. Corrige, vuelve a subir, publica de nuevo.

force: publish-confirm acepta force: true, lo que permite pasar una puntuación por debajo del estándar. Nunca es silencioso: los hallazgos registran Lighthouse gate forced past failing scores via force:true. Audit warning will be recorded., el registro de publicación se marca como forzado y se escribe una entrada de auditoría. Úsalo para emergencias reales, luego corrige la página.

Fallo de sustitución en tiempo de compilación

Disparador: raro; un cuerpo almacenado que ya no se analiza cuando se insertan las traducciones en la compilación del sitio (en la práctica, solo es posible si el contenido se modificó fuera de la herramienta). La compilación falla estrepitosamente y la publicación falla con ella:

custom-page substitute: body_source did not parse as JSX/Astro. Re-extract markers and re-upload.

Solución: vuelve a ejecutar upload-custom-page con el cuerpo completo (la extracción en el momento de la carga enumerará los problemas de análisis reales) y luego publica de nuevo.

Hallazgos de lint — advertencias, nunca bloqueadores

Devueltos en la respuesta de carga como lint_findings, cada uno con la forma { rule, severity, line, column, message, fix_hint }. Cada hallazgo tiene severity: "warning"; nada aquí bloquea una carga o publicación. Cada regla predice un fallo de rendimiento de Lighthouse, que bloquea cuando se ejecuta el control, así que corrígelos de todos modos.

ReglaDisparadorSolución
body-too-largebody_source mayor de 500 KB (límite suave; el rechazo estricto es a los 2 MB)Mueve activos en línea a URLs de upload-media / CDN
no-eager-script<script> sin defer o async (JSON-LD, plantillas y type="module" están exentos)Añade defer (preferido) o async
img-not-lazy<img> sin loading="lazy" — suprimido cuando data-hero aparece dentro de los 4 KB de marcado anterioresAñade loading="lazy", o marca el contenedor de la imagen hero/LCP con data-hero
blocking-stylesheet<link rel="stylesheet"> sin atributo media y no precargadoUsa media="print" onload="this.media='all'", o un patrón de precarga y aplicación
no-font-display-swapBloque @font-face sin font-display: swapAñade font-display: swap; al bloque
cloudinary-not-autoUna URL de Cloudinary sin f_auto y/o q_auto (el mensaje nombra cuál falta)Inserta f_auto,q_auto/ después de /upload/ — la plataforma nunca reescribe tus URLs

Ejemplo

Un ciclo de autocorrección. Primer intento:

{
"project_id": "k2xPq9wEfGhRtY3LmN8b",
"project_name": "Acme Robotics",
"slug": "about",
"title": "About Acme Robotics",
"description": "Who builds Acme's multilingual voice agents — the team, the mission, and how to reach us.",
"body_source": "<section>\n <h1 data-t-key=\"Hero\">We teach robots to listen</h1>\n</section>"
}

Rechazado: la clave comienza con una letra mayúscula:

Marker extraction failed (1 error):
1. offset=12 key='Hero' — Invalid marker key 'Hero'. Keys must match /^[a-z][a-z0-9_.]*[a-z0-9]$/ (lowercase, digits, underscore, dot).

Cambia data-t-key="Hero" a data-t-key="about.hero" y vuelve a subir el cuerpo completo:

{
"ok": true,
"page": { "success": true, "path": "about", "visibility": "draft", "draft_url": "<edit-mode preview link>", "next_step": "<review the draft, then publish>" },
"markers_extracted_count": 1,
"lint_findings": [],
"docs": {
"authoring_guide": "transformento://docs/custom-pages/authoring",
"troubleshooting": "transformento://docs/custom-pages/troubleshooting"
}
}

Errores comunes

Los cinco más frecuentes, una línea cada uno:

  1. title must be between 10 and 60 characters — cuenta los caracteres, espacios incluidos; misma idea para la descripción de 50–160.
  2. language 'fr' is not in project.locales [en, es]. … — añade la configuración regional al módulo de páginas personalizadas u omite language.
  3. Invalid marker key '…' — inicio en minúscula, final alfanumérico, puntos y guiones bajos dentro, mínimo dos caracteres.
  4. body_source contains 1 blocked href scheme … — no uses javascript:/data:/vbscript: en hrefs; usa https:, mailto:, tel:, relativo o #anchor.
  5. Confirmation token has expired (5-minute TTL). … — ejecuta publish de nuevo y confirma inmediatamente.

Relacionado

Última actualización: