{
  "docs": {
    "gettingStarted": {
      "hero": {
        "badge": "Primeros Pasos",
        "title": "Documentación",
        "subtitle": "Todo lo que necesitas para construir aplicaciones web reactivas con No.JS"
      },
      "introduction": {
        "title": "Introducción",
        "text": "No.JS es un framework reactivo HTML-first. Construye aplicaciones web dinámicas y basadas en datos usando únicamente atributos HTML — sin paso de compilación, sin DOM virtual, sin JSX.",
        "callout": "Cero dependencias · Funciona en todos los navegadores modernos · Sin paso de compilación"
      },
      "installation": {
        "title": "Instalación",
        "cdnSubtitle": "CDN (recomendado)",
        "selfHostedSubtitle": "Auto-alojado",
        "selfHostedText": "Descarga dist/iife/no.js e inclúyelo con una etiqueta <script>. Es un solo archivo.",
        "npmSubtitle": "npm / ESM",
        "npmText": "Al usar npm, debes llamar a NoJS.init() manualmente después de que el DOM esté listo. El script CDN lo hace automáticamente."
      },
      "quickStart": {
        "title": "Inicio Rápido",
        "text": "Crea un archivo index.html: incluye el script, agrega algunos atributos y listo. Sin app.mount(), sin createApp(), sin NgModule. Simplemente funciona."
      },
      "howItWorks": {
        "title": "Cómo Funciona",
        "text": "En DOMContentLoaded, No.JS recorre el DOM buscando elementos con atributos conocidos. Cada atributo se mapea a una directiva que se ejecuta por prioridad.",
        "card1Title": "1. Analizar",
        "card1Desc": "Recorre el DOM buscando elementos con atributos conocidos.",
        "card2Title": "2. Resolver",
        "card2Desc": "Cada atributo se mapea a una directiva ejecutada por prioridad (la obtención de datos primero, luego condicionales, luego renderizado).",
        "card3Title": "3. Reaccionar",
        "card3Desc": "Todos los datos viven en contextos reactivos (respaldados por Proxy). Cuando los datos cambian, cada elemento enlazado se actualiza automáticamente.",
        "card4Title": "4. Ámbito",
        "card4Desc": "Los contextos heredan de los elementos padre, como el ámbito léxico. Un bind dentro de un bucle each puede acceder tanto al elemento del bucle como a los datos ancestros."
      },
      "coreConcepts": {
        "title": "Conceptos Clave",
        "reactiveContextSubtitle": "Contexto Reactivo",
        "reactiveContextText": "Cada elemento puede tener un contexto — un objeto de datos reactivo. Los contextos se crean con state, get, store, etc. Los elementos hijos heredan el contexto de su padre automáticamente.",
        "directivePrioritySubtitle": "Prioridad de Directivas",
        "tableCol1": "Prioridad",
        "tableCol2": "Directivas",
        "tableCol3": "Descripción",
        "tableRow1": "Inicializar estado local/global",
        "tableRow4": "Estructural (agregar/eliminar DOM)",
        "tableRow5": "Renderizado (actualizar DOM existente)",
        "tableRow7": "Efectos secundarios",
        "expressionSubtitle": "Sintaxis de Expresiones",
        "expressionText": "La mayoría de los valores de directivas aceptan expresiones JavaScript evaluadas contra el contexto actual:",
        "tableRow1b": "Obtener datos, error boundaries, namespace i18n",
        "tableRow2b": "Valores derivados y watchers de efectos secundarios",
        "tableRow5b": "Referencias a elementos",
        "tableRow15": "Configuración de drag and drop (Ahora en NoJS Elements)"
      }
    },
    "cheatsheet": {
      "hero": {
        "badge": "Referencia de API",
        "title": "Hoja de Referencia de Directivas",
        "subtitle": "Referencia completa de cada directiva de No.JS"
      },
      "data": {
        "title": "Datos",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "base": "Establece la URL base de la API para los descendientes",
        "get": "Obtener datos (GET)",
        "post": "Enviar datos (POST)",
        "put": "Actualizar datos (PUT)",
        "patch": "Actualización parcial (PATCH)",
        "delete": "Eliminar datos (DELETE)",
        "as": "Nombre para los datos obtenidos en el contexto",
        "body": "Cuerpo de la solicitud",
        "headers": "Encabezados de la solicitud",
        "params": "Parámetros de consulta",
        "cached": "Cachear respuestas (memory/local/session)",
        "into": "Escribir la respuesta en un store global nombrado",
        "debounce": "Debounce para re-obtenciones reactivas de URL (ms)",
        "retry": "Número de reintentos por elemento",
        "refresh": "Intervalo de polling en ms",
        "success": "ID de plantilla a mostrar en caso de éxito",
        "then": "Expresión a ejecutar en caso de éxito",
        "redirect": "Ruta a la que navegar después del éxito",
        "confirm": "Mensaje de confirmación antes de la solicitud",
        "skeleton": "Mostrar/ocultar un placeholder durante la carga",
        "retryDelay": "Retraso entre reintentos en ms"
      },
      "state": {
        "title": "Estado",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "state": "Crear estado reactivo local",
        "store": "Definir/acceder a un store global",
        "computed": "Valor derivado reactivo",
        "watch": "Reaccionar a cambios de valor",
        "persist": "Atributo de la directiva state — persiste el estado en almacenamiento",
        "model": "Enlace bidireccional para inputs",
        "persistKey": "Clave de almacenamiento para persistencia",
        "persistFields": "Campos separados por coma para persistir",
        "persistSchema": "Validar claves restauradas contra el estado inicial"
      },
      "rendering": {
        "title": "Renderizado",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "bind": "Establecer contenido de texto",
        "bindHtml": "Establecer innerHTML (sanitizado)",
        "bindStar": "Enlazar cualquier atributo",
        "if": "Renderizado condicional",
        "elseIf": "Condicional encadenado",
        "then": "Plantilla para verdadero",
        "else": "Plantilla para falso",
        "show": "Alternar visibilidad (CSS)",
        "hide": "Inverso de show",
        "switch": "Renderizado switch/case",
        "case": "Coincidencia de caso",
        "default": "Caso por defecto"
      },
      "loops": {
        "title": "Bucles",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "foreach": "Directiva de bucle primaria",
        "each": "Alias de foreach",
        "for": "Alias de foreach",
        "from": "Array de origen (DEPRECATED — usa la sintaxis \"item in array\")",
        "template": "Plantilla a clonar",
        "index": "Nombre de variable de índice",
        "key": "Clave única para diffing",
        "filter": "Expresión de filtro",
        "sort": "Propiedad para ordenar (solo el nombre, sin prefijo de variable de elemento)",
        "limit": "Máximo de elementos",
        "offset": "Omitir elementos"
      },
      "events": {
        "title": "Eventos",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "onClick": "Manejador de click",
        "onSubmit": "Manejador de submit",
        "onInput": "Manejador de input",
        "onKeydown": "Manejador de tecla",
        "onInit": "Se ejecuta inmediatamente durante la inicialización",
        "onMounted": "Ciclo de vida: montado",
        "onUnmounted": "Ciclo de vida: desmontado",
        "throttle": "Limitar ejecución del manejador (ms)",
        "self": "Solo se ejecuta si el objetivo del evento es el elemento mismo",
        "backspace": "Modificador de tecla para la tecla Retroceso",
        "onUpdated": "Lifecycle: mutación del DOM observada",
        "onError": "Lifecycle: error en el subárbol"
      },
      "styling": {
        "title": "Estilos",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "classStar": "Alternar clase CSS",
        "classMap": "Clase desde objeto",
        "styleStar": "Establecer estilo inline",
        "styleMap": "Estilo desde objeto"
      },
      "forms": {
        "title": "Formularios",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "validate": "Habilitar validación de formulario/campo (Ahora en NoJS Elements)",
        "error": "Plantilla de error para el campo",
        "success": "Plantilla de éxito",
        "loading": "Plantilla de carga",
        "confirm": "Diálogo de confirmación",
        "redirect": "Redirigir en caso de éxito"
      },
      "routing": {
        "title": "Enrutamiento",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "route": "Definir ruta o enlace",
        "routeView": "Salida de ruta",
        "routeViewNamed": "Salida de ruta nombrada",
        "outlet": "Apuntar a una salida nombrada",
        "routeActive": "Clase de enlace activo",
        "guard": "Condición de guarda de ruta",
        "routeActiveExact": "Clase activa de coincidencia exacta para enlaces de ruta",
        "redirect": "Ruta de redirección cuando falla la guarda",
        "lazyPriority": "Cargar plantilla remota antes que todas las demás (Fase 0)",
        "lazyOnDemand": "Obtener plantilla de ruta solo en la primera visita (solo plantillas de ruta)",
        "routerForward": "Navegar hacia adelante en el historial",
        "routerOn": "Suscribirse a cambios de ruta",
        "routeWildcard": "Ruta wildcard catch-all 404",
        "routerCurrent": "Objeto de ruta actual",
        "routeMatched": "<code>true</code> si una ruta explícita coincidió, <code>false</code> para wildcard/fallback",
        "i18nNs": "Auto-derivar namespace i18n del nombre de archivo de ruta",
        "routeViewSrc": "Outlet de enrutamiento basado en archivos",
        "routeIndex": "Nombre de archivo para la raíz <code>/</code> (por defecto <code>\"index\"</code>)",
        "routeExt": "Extensión de archivo (por defecto <code>\".tpl\"</code>, respaldo <code>\".html\"</code>)",
        "transitionVT": "Preset View Transition API en route-view (slide, fade, scale, none)"
      },
      "animation": {
        "title": "Animación",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "animate": "Animación de entrada",
        "animateEnter": "Animación de entrada",
        "animateLeave": "Animación de salida",
        "animateDuration": "Duración en ms",
        "animateStagger": "Retraso escalonado",
        "transition": "Transición CSS (basada en clases, para elementos regulares)",
        "transitionVT": "Preset View Transition API en route-view (slide, fade, scale, none)"
      },
      "dnd": {
        "title": "Drag and Drop (Ahora en NoJS Elements)",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "drag": "Hacer elemento arrastrable",
        "dragType": "Identificador de tipo de datos",
        "dragEffect": "Efecto permitido (move/copy/link/all)",
        "dragHandle": "Restringir arrastre al selector de handle",
        "dragDisabled": "Deshabilitar arrastre condicionalmente",
        "dragClass": "Clase añadida durante el arrastre",
        "dragGroup": "Alcance del arrastre a un grupo nombrado",
        "drop": "Hacer elemento una zona de soltar",
        "dropAccept": "Tipo(s) de arrastre aceptados",
        "dropEffect": "Efecto de retroalimentación visual",
        "dropClass": "Clase añadida al arrastrar sobre",
        "dropDisabled": "Deshabilitar soltar condicionalmente",
        "dropMax": "Máximo de elementos en la zona de soltar",
        "dropSort": "Habilitar ordenamiento posicional",
        "dropPlaceholder": "Plantilla placeholder durante el arrastre",
        "dropSettleClass": "Clase CSS personalizada para la animación de asentamiento",
        "dropEmptyClass": "Clase CSS personalizada para el estado vacío en drag-list",
        "dragList": "Lista ordenable vinculada a un array de estado",
        "dragListKey": "Clave única para cada elemento",
        "dragListItem": "Selector de plantilla del elemento",
        "dragListCopy": "Copiar en lugar de mover en la transferencia",
        "dragListRemove": "Eliminar elementos del origen en la transferencia",
        "dragMultiple": "Lazo / selección múltiple en hijos",
        "dragMultipleClass": "Clase añadida a los elementos seleccionados",
        "dropRejectClass": "Clase añadida al rechazar arrastre"
      },
      "i18n": {
        "title": "i18n",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "t": "Traducir clave",
        "tStar": "Parámetro de traducción",
        "tHtml": "Renderizar traducción como HTML sanitizado"
      },
      "misc": {
        "title": "Varios",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "ref": "Referencia de elemento con nombre",
        "call": "Disparar llamada a API",
        "trigger": "Emitir evento personalizado",
        "use": "Instanciar plantilla",
        "src": "Plantilla remota (ver también: lazy)",
        "loading": "Marcador de posición mostrado mientras la plantilla remota carga; se elimina al llegar",
        "include": "Clonar sincrónicamente una plantilla inline en la posición actual",
        "errorBoundary": "Límite de error",
        "var": "Nombre de variable del template"
      },
      "headManagement": {
        "title": "Head Management",
        "col1": "Directiva",
        "col2": "Ejemplo",
        "col3": "Descripción",
        "pageTitle": "Establecer document.title reactivamente",
        "pageDescription": "Establecer <meta name=\"description\">",
        "pageCanonical": "Establecer <link rel=\"canonical\">",
        "pageJsonld": "Establecer <script type=\"application/ld+json\">"
      }
    },
    "stateManagement": {
      "hero": {
        "badge": "Guías",
        "title": "Gestión de Estado",
        "subtitle": "Estado local, stores globales, propiedades computadas y watchers"
      },
      "state": {
        "title": "state — Estado Local",
        "text": "Crea un contexto reactivo con ámbito en el elemento y sus hijos.",
        "preview": "Vista previa",
        "helloLabel": "Hola,",
        "countLabel": "Conteo:",
        "resetBtn": "Restablecer"
      },
      "store": {
        "title": "store — Store Global",
        "text": "Un store reactivo global accesible desde cualquier lugar. Ideal para estado de autenticación, tema, datos compartidos."
      },
      "configStores": {
        "title": "Pre-inicialización de Stores vía config()",
        "text": "Puedes pre-crear stores con datos iniciales dentro de NoJS.config(). Cada clave en el objeto stores se convierte en un store global nombrado, accesible inmediatamente vía $store.nombre.",
        "callout": "Si un store ya existe (ej. creado mediante un atributo store), config() no lo sobrescribirá — el store existente se preserva."
      },
      "into": {
        "title": "into — Escribir Resultados de Fetch en un Store",
        "text": "El atributo into en cualquier directiva HTTP escribe la respuesta directamente en un store global nombrado.",
        "callout": "El store no necesita estar predefinido — into lo creará si no existe."
      },
      "computed": {
        "title": "computed — Estado Derivado",
        "text": "Valores que se recalculan automáticamente cuando las dependencias cambian.",
        "preview": "Vista previa",
        "priceLabel": "Precio:",
        "qtyLabel": "Cant:",
        "subtotalLabel": "Subtotal:",
        "totalLabel": "Total:"
      },
      "watch": {
        "title": "watch — Efectos Secundarios",
        "text": "Ejecuta una acción cada vez que un valor cambia."
      },
      "persistence": {
        "title": "Persistencia de Estado",
        "text": "Persiste el estado entre recargas de página usando localStorage o sessionStorage."
      },
      "notify": {
        "title": "NoJS.notify() — Actualizar Store",
        "text": "Cuando JavaScript externo muta una store vía NoJS.store, llame a NoJS.notify() para actualizar todos los bindings del DOM.",
        "callout": "Solo es necesario al mutar NoJS.store desde JavaScript puro — fuera de expresiones HTML. Las mutaciones dentro de on:click o bind se manejan automáticamente."
      },
      "persistSchema": {
        "title": "persist-schema — Validación de Schema",
        "text": "Al restaurar estado persistido, persist-schema valida que las claves restauradas coincidan con la forma del estado inicial. Las claves no presentes en el estado inicial se descartan. Esto previene que datos obsoletos o corruptos rompan tu app tras cambios en el schema."
      },
      "scoping": {
        "title": "Alcance de Contexto & Shadowing",
        "text": "Los contextos heredan de elementos padre como alcance léxico. Un state hijo puede sobrescribir una propiedad del padre — el hijo ve su propio valor, mientras los hermanos ven el del padre.",
        "callout": "El shadowing es intencional y sigue las reglas de alcance de JavaScript. Si necesitas estado compartido entre hermanos, usa un store."
      }
    },
    "dataBinding": {
      "hero": {
        "badge": "Guías",
        "title": "Enlace de Datos",
        "subtitle": "Enlace de datos unidireccional y bidireccional con bind y model"
      },
      "bind": {
        "title": "bind — Contenido de Texto",
        "text": "Reemplaza el textContent del elemento con la expresión evaluada."
      },
      "bindHtml": {
        "title": "bind-html — HTML Interno",
        "text": "Renderiza la expresión evaluada como HTML. Sanitizado por defecto.",
        "callout": "⚠️ Usa sanitización estructural integrada basada en DOMParser: elimina etiquetas <script>, bloquea manejadores de eventos on* y elimina URIs javascript:."
      },
      "bindAttr": {
        "title": "bind-* — Enlace de Atributos",
        "text": "Enlaza cualquier atributo HTML dinámicamente."
      },
      "model": {
        "title": "model — Enlace Bidireccional",
        "text": "Para inputs de formulario, model crea enlace de datos bidireccional automático.",
        "preview": "Vista previa",
        "nameLabel": "Nombre",
        "placeholder": "Escribe tu nombre...",
        "checkbox": "Acepto",
        "agreedLabel": "Aceptado:",
        "helloPrefix": "Hola,"
      },
      "radioSelect": {
        "title": "Radio Buttons & Multi-Select",
        "text": "model funciona con todos los tipos de input. Para radio buttons, vincula el mismo nombre de model a un grupo — el valor se actualiza al value del radio seleccionado. Para multi-select, el valor del model se convierte en un array de opciones seleccionadas."
      },
      "commonMistakes": {
        "title": "Errores Comunes",
        "text": "Evita estas trampas comunes de data binding:",
        "mistake1": "Usar bind-html sin entender la sanitización — todo contenido es sanitizado por defecto, pero ten en cuenta qué se elimina.",
        "mistake2": "Olvidar que bind reemplaza textContent — cualquier elemento hijo dentro de un bind es eliminado.",
        "mistake3": "Usar model en elementos que no son formulario — model solo funciona en input, textarea y select."
      }
    },
    "events": {
      "hero": {
        "badge": "Guías",
        "title": "Manejo de Eventos",
        "subtitle": "Enlaza eventos del DOM directamente en HTML con la sintaxis on:event"
      },
      "handlers": {
        "title": "on:* — Manejadores de Eventos",
        "text": "Enlaza cualquier evento del DOM directamente en HTML. Accede al estado y las variables de contexto directamente en la expresión del manejador.",
        "preview": "Vista previa",
        "inputPlaceholder": "Escribe algo...",
        "youTyped": "Escribiste:",
        "countLabel": "Conteo:"
      },
      "modifiers": {
        "title": "Modificadores de Eventos",
        "text": "Los modificadores permiten controlar el comportamiento del evento directamente en el atributo:"
      },
      "eventAndEl": {
        "title": "$event y $el",
        "text": "$event es el evento nativo del DOM. $el se refiere al elemento actual."
      },
      "lifecycle": {
        "title": "Hooks de Ciclo de Vida",
        "col1": "Hook",
        "col2": "Cuándo",
        "onInit": "La directiva se procesa por primera vez",
        "onMounted": "El elemento se inserta en el DOM visible",
        "onUpdated": "Mutación del DOM observada (via MutationObserver)",
        "onUnmounted": "El elemento se elimina del DOM",
        "onError": "Error en el subárbol de este elemento"
      },
      "keyModifiers": {
        "title": "Modificadores de Tecla",
        "text": "Usa modificadores de tecla en eventos de teclado para filtrar por teclas específicas. Combina con Ctrl, Alt, Shift o Meta para atajos.",
        "col1": "Modificador",
        "col2": "Tecla",
        "enter": "Enter / Return",
        "escape": "Escape",
        "space": "Espacio",
        "tab": "Tab",
        "backspace": "Backspace",
        "arrowUp": "Flecha Arriba",
        "arrowDown": "Flecha Abajo",
        "arrowLeft": "Flecha Izquierda",
        "arrowRight": "Flecha Derecha",
        "ctrl": "Ctrl (Control)",
        "alt": "Alt (Option en Mac)",
        "shift": "Shift",
        "meta": "Meta (Cmd en Mac, Win en Windows)",
        "delete": "Delete"
      }
    },
    "conditionals": {
      "hero": {
        "badge": "Guías",
        "title": "Condicionales",
        "subtitle": "Controla el renderizado con if, show, hide y switch"
      },
      "ifThenElse": {
        "title": "if / then / else",
        "text": "Renderiza elementos o plantillas condicionalmente basándose en expresiones.",
        "preview": "Vista previa",
        "checkbox": "Conectado",
        "welcome": "✅ ¡Bienvenido de nuevo!",
        "login": "Por favor inicia sesión."
      },
      "elseIf": {
        "title": "else-if — Condicionales Encadenados"
      },
      "showHide": {
        "title": "show / hide",
        "text": "Alterna display: none sin agregar/eliminar elementos del DOM. Mejor para elementos que se alternan frecuentemente.",
        "comparisonTitle": "if vs show",
        "colIf": "if",
        "colShow": "show",
        "mechanism": "Mecanismo",
        "mechanismIf": "Agrega/elimina elementos del DOM",
        "mechanismShow": "Alterna display CSS",
        "bestFor": "Mejor para",
        "bestForIf": "Contenido alternado raramente",
        "bestForShow": "Contenido alternado frecuentemente",
        "preservesState": "Preserva estado",
        "preservesIf": "No (re-crea)",
        "preservesShow": "Sí"
      },
      "switchCase": {
        "title": "switch / case",
        "text": "Renderiza una de varias plantillas basándose en un valor.",
        "inlineSubtitle": "Contenido Inline (sin plantillas)",
        "multiValueSubtitle": "Caso con Múltiples Valores"
      }
    },
    "loops": {
      "hero": {
        "badge": "Guías",
        "title": "Directivas de Iteración",
        "subtitle": "Itera sobre arrays con foreach, each y for"
      },
      "foreach": {
        "title": "foreach — Iterar Sobre Arrays",
        "text": "La directiva de iteración primaria. Usa foreach=\"item in array\" para iterar sobre arrays. each y for son aliases con comportamiento idéntico.",
        "col1": "Atributo",
        "col2": "Descripción",
        "foreach": "Expresión: item in array",
        "from": "Array de origen (DEPRECATED — usa la sintaxis item in array)",
        "index": "Nombre de variable para el índice (por defecto: $index)",
        "key": "Expresión de clave única para identificación y seguimiento de elementos",
        "else": "ID de plantilla a renderizar cuando el array está vacío",
        "filter": "Expresión para filtrar elementos",
        "sort": "Ruta de propiedad para ordenar (prefijo - para descendente)",
        "limit": "Número máximo de elementos a renderizar",
        "offset": "Número de elementos a omitir",
        "preview": "Vista previa",
        "template": "ID de plantilla externa (opcional — los hijos se convierten en la plantilla si se omite)"
      },
      "fullExample": {
        "title": "Ejemplo completo con todos los atributos",
        "text": "Un ejemplo completo mostrando foreach con filtrado, ordenación, paginación y un fallback para estado vacío."
      },
      "aliases": {
        "title": "Aliases: each y for",
        "text": "each y for son aliases de foreach — comparten el mismo handler y soportan todos los mismos atributos."
      },
      "inline": {
        "title": "Plantilla Inline de Hijos",
        "text": "Cuando no se especifica el atributo template, los hijos del elemento se convierten en la plantilla de repetición."
      },
      "deprecated": {
        "title": "Deprecated: Atributo from",
        "text": "El atributo from aún funciona pero está deprecated. Usa la sintaxis \"item in array\" en su lugar. Usar from emitirá una advertencia en la consola."
      },
      "contextVars": {
        "title": "Variables de Contexto del Bucle",
        "col1": "Variable",
        "col2": "Descripción",
        "index": "Índice actual (base 0)",
        "count": "Número total de elementos",
        "first": "true si es el primer elemento",
        "last": "true si es el último elemento",
        "even": "true si el índice es par",
        "odd": "true si el índice es impar"
      },
      "nested": {
        "title": "Bucles Anidados",
        "text": "Los bucles hijos (foreach, each o for) pueden acceder a las variables del ámbito padre."
      },
      "reactivity": {
        "title": "Reactividad",
        "text": "Las directivas de loop son totalmente reactivas. Cuando el array de origen cambia (elementos añadidos, eliminados o reordenados), el DOM se actualiza automáticamente. Usa el atributo key para diffing eficiente — sin él, la lista completa se re-renderiza en cada cambio."
      },
      "objectIteration": {
        "title": "Iteración de Objetos",
        "text": "Para iterar sobre las entradas de un objeto, usa el filtro keys o values para convertirlo en array primero.",
        "callout": "La iteración directa de objetos no es soportada — siempre convierte a array con keys, values u Object.entries() en una expresión computed."
      }
    },
    "templates": {
      "hero": {
        "badge": "Guías",
        "title": "Plantillas",
        "subtitle": "Fragmentos HTML reutilizables con variables, slots y carga remota"
      },
      "basic": {
        "title": "Plantilla Básica",
        "text": "Las plantillas son fragmentos HTML reutilizables que nunca se renderizan directamente. Se clonan cuando son referenciadas por directivas como then, else, template, loading, error, etc."
      },
      "var": {
        "title": "Variables de Plantilla (var)",
        "text": "Las plantillas pueden declarar qué variable esperan del contexto que las invoca."
      },
      "slots": {
        "title": "Slots de Plantilla",
        "text": "Permiten que las plantillas acepten contenido proyectado."
      },
      "remote": {
        "title": "Plantillas Remotas (src)",
        "text": "Carga plantillas desde archivos HTML externos."
      },
      "recursive": {
        "subtitle": "Carga Recursiva",
        "text": "Las plantillas remotas se cargan recursivamente — si una plantilla remota contiene elementos <template src=\"...\"> en sí misma, esos se resuelven automáticamente también:"
      },
      "remoteRoutes": {
        "subtitle": "Plantillas Remotas en Rutas",
        "text": "Las plantillas remotas dentro del contenido de una ruta también se resuelven automáticamente antes de que la ruta se renderice. Consulta Enrutamiento para más detalles."
      },
      "lazy": {
        "title": "Carga Diferida (lazy)",
        "text": "Controla cuándo se obtienen las plantillas remotas usando el atributo lazy en elementos <template src=\"...\">. NoJS carga las plantillas en fases para optimizar el tiempo de primer renderizado.",
        "col1": "Valor",
        "col2": "Comportamiento",
        "absent": "(ausente)",
        "absentDesc": "Auto-priorización por defecto: las plantillas de inclusión de contenido y la plantilla de ruta actual se cargan antes del primer renderizado; otras plantillas de ruta se precargan en segundo plano después del primer renderizado.",
        "priorityDesc": "Forzar la carga antes que todo lo demás — incluso antes de las inclusiones de contenido regulares. Útil para plantillas de diseño compartidas críticas.",
        "ondemandDesc": "Solo válido en plantillas de ruta. Nunca se precarga — se obtiene de forma diferida la primera vez que el usuario navega a esa ruta. Ideal para páginas pesadas o raramente visitadas."
      },
      "phases": {
        "subtitle": "Fases de Carga",
        "text": "Las plantillas se resuelven en cuatro fases ordenadas: la Fase 0 obtiene las plantillas lazy=\"priority\" primero; la Fase 1 obtiene todas las demás plantillas que no son de ruta más la plantilla de ruta activa (bloqueando antes del primer renderizado); la Fase 2 precarga las plantillas de ruta restantes en segundo plano después del primer renderizado; y la carga bajo demanda obtiene las plantillas de ruta lazy=\"ondemand\" solo cuando el usuario navega a ellas por primera vez."
      },
      "loading": {
        "title": "Marcador de Posición de Carga (loading)",
        "text1": "Muestra una plantilla de marcador de posición mientras se obtiene una plantilla remota. El marcador se inserta sincrónicamente — antes de cualquier solicitud de red — y se elimina automáticamente una vez que llega el contenido real. Funciona tanto para inclusiones de contenido estático como para plantillas anidadas dentro de páginas de ruta.",
        "text2": "Se aceptan tanto IDs simples como la sintaxis #id. La plantilla de marcador se clona cada vez, por lo que puede reutilizarse en múltiples plantillas remotas:"
      },
      "include": {
        "title": "Inclusión de Plantilla Inline (include)",
        "text1": "Clona una plantilla inline en la posición actual sincrónicamente, antes de cualquier obtención de datos. Útil para inyectar marcado reutilizable (por ejemplo, conjuntos de iconos, fragmentos comunes) sin hacer una solicitud de red.",
        "text2": "include y loading tienen propósitos diferentes: include clona contenido inline permanentemente; loading inserta un marcador temporal que desaparece una vez que la plantilla remota termina de cargarse."
      }
    },
    "dataFetching": {
      "hero": {
        "badge": "Guías",
        "title": "Obtención de Datos",
        "subtitle": "Solicitudes HTTP declarativas — solo agrega atributos a elementos HTML"
      },
      "baseUrl": {
        "title": "URL Base",
        "text1": "Se establece una vez en cualquier elemento ancestro. Todos los get, post, etc. descendientes resuelven URLs relativas contra ella.",
        "text2": "Sobreescribir para secciones específicas:",
        "text3": "Las URLs absolutas omiten la resolución de base:"
      },
      "config": {
        "title": "Configuración Programática"
      },
      "headers": {
        "title": "Encabezados Por Solicitud"
      },
      "get": {
        "title": "get — Obtener y Renderizar Datos",
        "attributesTitle": "Atributos",
        "col1": "Atributo",
        "col2": "Tipo",
        "col3": "Descripción",
        "get": "URL a obtener (solicitud GET)",
        "as": "Nombre para asignar la respuesta en el contexto. Por defecto: \"data\"",
        "loading": "ID de plantilla a mostrar durante la carga (ej. \"#skeleton\")",
        "error": "ID de plantilla a mostrar en caso de error",
        "empty": "ID de plantilla a mostrar cuando la respuesta es un array vacío/null",
        "refresh": "Intervalo de auto-actualización en ms (polling)",
        "cached": "Cachear respuestas. cached = memory, cached=\"local\" = localStorage, cached=\"session\" = sessionStorage",
        "into": "Escribir la respuesta en un store global nombrado",
        "debounce": "Debounce en ms (útil con URLs reactivas)",
        "headers": "Cadena JSON de encabezados adicionales",
        "params": "Expresión que resuelve a un objeto de parámetros de consulta"
      },
      "fullExample": {
        "title": "Ejemplo Completo"
      },
      "reactiveUrls": {
        "title": "URLs Reactivas",
        "text": "Las URLs que hacen referencia a variables de estado se re-obtienen automáticamente cuando esos valores cambian."
      },
      "mutations": {
        "title": "post, put, patch, delete — Solicitudes de Mutación",
        "text": "Se usan en formularios o se disparan vía call.",
        "formSubmissionTitle": "Envío de Formulario",
        "putPatchDeleteTitle": "PUT / PATCH / DELETE"
      },
      "mutationAttrs": {
        "title": "Atributos de Mutación",
        "col1": "Atributo",
        "col2": "Descripción",
        "method": "URL para la solicitud",
        "body": "Cuerpo de la solicitud (cadena JSON con interpolación). Para formularios, serializa los campos automáticamente",
        "success": "ID de plantilla a renderizar en caso de éxito. Recibe la respuesta como var",
        "error": "ID de plantilla a renderizar en caso de error. Recibe el error como var",
        "loading": "ID de plantilla a mostrar durante la solicitud",
        "confirm": "Mostrar diálogo confirm() del navegador antes de enviar",
        "redirect": "URL a la que navegar en caso de éxito (ruta SPA)",
        "then": "Expresión a ejecutar en caso de éxito (ej. \"users.push(result)\")",
        "into": "Escribir la respuesta en un store global nombrado",
        "cached": "Cachear respuestas (memory/local/session). Nota: el cacheo solo aplica a solicitudes GET."
      },
      "lifecycle": {
        "title": "Ciclo de Vida de Solicitud"
      },
      "liveDemo": {
        "title": "Demo en Vivo — Obtención de API",
        "label": "Resultado"
      }
    },
    "routing": {
      "hero": {
        "badge": "Guías",
        "title": "Enrutamiento",
        "subtitle": "Navegación SPA completa del lado del cliente sin recargas de página"
      },
      "definition": {
        "title": "Definición de Ruta"
      },
      "params": {
        "title": "Parámetros y Query de Ruta"
      },
      "context": {
        "title": "$route — Contexto de Ruta",
        "col1": "Propiedad",
        "col2": "Descripción",
        "path": "Ruta actual (ej. \"/users/42\")",
        "params": "Parámetros de ruta (ej. { id: \"42\" })",
        "query": "Parámetros de cadena de consulta (ej. { q: \"hello\" })",
        "hash": "Hash de la URL (ej. \"#section\")",
        "matched": "Si una ruta explícita coincidió (true) o si se está renderizando un wildcard/fallback (false)"
      },
      "activeStyle": {
        "title": "Estilo de Ruta Activa"
      },
      "guards": {
        "title": "Guardias de Ruta"
      },
      "programmatic": {
        "title": "Navegación Programática",
        "callout": "$router.push() y $router.replace() devuelven Promises — la navegación (incluyendo la carga de plantillas remotas) es completamente asíncrona. En manejadores on:click el valor de retorno se ignora, pero en scripts se puede usar await:"
      },
      "nested": {
        "title": "Rutas Anidadas"
      },
      "remoteTemplates": {
        "title": "Plantillas Remotas en Rutas",
        "text1": "Las plantillas de ruta pueden incluir <template src=\"...\"> para cargar contenido de archivos externos. Se resuelven automáticamente antes de que la ruta se renderice:",
        "text2": "Las plantillas remotas anidadas (una plantilla remota que a su vez contiene más <template src>) se cargan recursivamente."
      },
      "fileBased": {
        "title": "Enrutamiento Basado en Archivos",
        "text": "En lugar de declarar cada plantilla de ruta manualmente, apunta tu salida <code>route-view</code> a una carpeta. No.JS resolverá automáticamente las rutas a archivos de plantilla dentro de esa carpeta.",
        "howItWorks": "Cómo funciona",
        "list1": "Añade <code>route-view</code> a tu elemento de salida — el enrutamiento basado en archivos está habilitado por defecto (config <code>router.templates: \"pages\"</code>). Sobrescribe por salida con <code>src=\"folder/\"</code>.",
        "list2": "Cuando un usuario navega a <code>/analytics</code>, No.JS lo resuelve a <code>pages/analytics.tpl</code>",
        "list3": "La plantilla se obtiene, almacena en caché y renderiza — automáticamente",
        "attributesTitle": "Atributos",
        "colAttr": "Atributo",
        "colDefault": "Predeterminado",
        "colDesc": "Descripción",
        "srcDesc": "Carpeta base para la resolución de plantillas (sobrescritura por salida; config: <code>router.templates</code>)",
        "routeIndexDesc": "Nombre de archivo para la ruta raíz <code>/</code>",
        "extDesc": "Extensión de archivo añadida a los segmentos de ruta (respaldo: <code>\".html\"</code>)",
        "i18nNsDesc": "Cuando está presente, auto-deriva el namespace i18n del nombre de archivo",
        "callout": "<strong>Config por defecto:</strong> El valor predeterminado de <code>router.templates</code> es <code>\"pages\"</code>, así que el enrutamiento basado en archivos funciona de inmediato — solo añade <code>route-view</code> a tu salida. Sobrescribe con <code>NoJS.config({ router: { templates: 'views' } })</code> o por salida vía <code>src=\"./custom/\"</code>.",
        "exampleTitle": "Ejemplo — Dashboard SaaS",
        "exampleText": "Eso es todo — <strong>dos líneas</strong> para una SPA completa con seis rutas.",
        "mixingTitle": "Mezclando Rutas Explícitas y Basadas en Archivos",
        "mixingText": "Las declaraciones explícitas <code>&lt;template route=\"...\"&gt;</code> <strong>siempre tienen prioridad</strong>. Esto te permite combinar ambos enfoques — usa enrutamiento basado en archivos para páginas simples y plantillas explícitas para rutas que necesitan guards, parámetros o salidas con nombre:",
        "autoI18nTitle": "Namespace i18n Automático",
        "autoI18nText": "Cuando el elemento <code>route-view</code> tiene un atributo <code>i18n-ns</code> (incluso sin valor), No.JS carga automáticamente el namespace i18n que coincide con el nombre del archivo:",
        "autoI18nText2": "Esto reemplaza la necesidad de añadir <code>i18n-ns=\"...\"</code> en cada plantilla de ruta individualmente."
      },
      "lazyLoading": {
        "title": "Carga Diferida de Plantillas",
        "text": "El atributo lazy en <template src=\"...\"> controla cuándo se obtiene una plantilla remota en relación al primer renderizado. Úselo para priorizar plantillas críticas y diferir páginas pesadas o raramente visitadas.",
        "col1": "Valor",
        "col2": "Fase",
        "col3": "Comportamiento",
        "absent": "(ausente)",
        "absentPhase": "1 o 2",
        "absentDesc": "Auto: las plantillas que no son de ruta y la plantilla de ruta activa se cargan antes del primer renderizado (Fase 1); otras plantillas de ruta se precargan en segundo plano después del primer renderizado (Fase 2).",
        "priorityPhase": "0",
        "priorityDesc": "Cargar antes que todo lo demás — incluso antes de las inclusiones de contenido regulares. Usar para plantillas de diseño compartidas críticas.",
        "ondemandPhase": "bajo demanda",
        "ondemandDesc": "Solo válido en plantillas de ruta. Nunca se precarga — se obtiene la primera vez que el usuario navega a esa ruta. Ideal para páginas pesadas o raramente visitadas."
      },
      "anchor": {
        "title": "Enlaces Ancla",
        "text1": "Al usar useHash: true, el hash de la URL (#) se usa para el enrutamiento (ej. #/docs). Esto normalmente entra en conflicto con los enlaces ancla estándar como <a href=\"#section\"> — pero No.JS lo maneja automáticamente en ambos modos (hash e history).",
        "text2": "Los enlaces ancla que apuntan a un id de elemento en la página son interceptados por el enrutador: el elemento destino se desplaza a la vista suavemente, y el enlace clickeado recibe una clase active. La ruta en sí no se ve afectada.",
        "howItWorks": "Cómo funciona:",
        "list1": "Al hacer clic en <a href=\"#introduction\"> se desplaza a <div id=\"introduction\"> con comportamiento suave",
        "list2": "La clase .active se alterna en el enlace clickeado (y se elimina de los hermanos)",
        "list3": "La ruta actual se preserva — no ocurre navegación",
        "list4": "Los enlaces con un atributo route siempre se tratan como navegación de ruta, no como anclas",
        "tip": "Consejo: Estiliza el enlace ancla activo con .active en tu CSS — el enrutador gestiona la clase por ti."
      },
      "namedOutlets": {
        "title": "Salidas Nombradas (route-view)",
        "text": "Múltiples salidas route-view pueden coexistir en la misma página. Dale a cada salida un nombre mediante el valor del atributo, y apunta las plantillas de ruta a salidas específicas usando el atributo outlet.",
        "callout": "Las salidas sin plantilla coincidente para la ruta actual siempre se limpian al navegar."
      },
      "catchAll": {
        "title": "404 / Rutas Catch-All",
        "text": "Usa <code>route=\"*\"</code> para definir una plantilla <strong>wildcard catch-all</strong> que se renderiza cuando ninguna ruta explícita coincide con la ruta actual. El wildcard siempre se evalúa al final, sin importar el orden en el DOM.",
        "text2": "Las rutas explícitas <strong>siempre tienen prioridad</strong> — el wildcard solo se activa cuando <code>matchRoute()</code> no encuentra coincidencia.",
        "fallbackTitle": "Fallback 404 Automático",
        "fallbackText": "Si no defines una plantilla <code>route=\"*\"</code>, No.JS muestra automáticamente una página 404 mínima integrada cuando ninguna ruta coincide. Esto asegura que los usuarios siempre vean algo significativo en lugar de una salida vacía.",
        "fallbackTip": "El fallback integrado es intencionalmente mínimo y sin estilos. Define tu propia plantilla <code>route=\"*\"</code> para aplicaciones en producción.",
        "namedTitle": "Wildcards en Salidas Nombradas",
        "namedText": "Cada salida nombrada puede tener su propio fallback wildcard. Cuando ninguna ruta coincide para una salida, el framework resuelve los fallbacks en este orden:",
        "namedList1": "<strong>Wildcard local</strong> — <code>&lt;template route=\"*\" outlet=\"{name}\"&gt;</code> para esa salida específica",
        "namedList2": "<strong>Wildcard global</strong> — <code>&lt;template route=\"*\"&gt;</code> (el wildcard de la salida por defecto), usado solo para salidas no predeterminadas",
        "namedList3": "<strong>404 integrado</strong> — la página de fallback mínima del framework",
        "namedText2": "Si la barra lateral no tiene wildcard local, recurre al <code>route=\"*\"</code> global. Si ninguno existe, se usa el 404 integrado.",
        "matchedTitle": "$route.matched",
        "matchedText": "El booleano <code>$route.matched</code> te indica si la ruta actual encontró una ruta explícita (<code>true</code>) o un wildcard/fallback (<code>false</code>). Úsalo para renderizado condicional dentro de tus plantillas:",
        "matchedText2": "<code>$route.matched</code> se establece <strong>antes</strong> de que la plantilla se renderice, por lo que siempre está disponible durante el procesamiento.",
        "remoteTitle": "Plantilla 404 Remota",
        "remoteText": "Las rutas wildcard soportan todos los mismos atributos que las plantillas de ruta regulares, incluyendo <code>src</code> para carga remota:",
        "remoteText2": "La plantilla remota se obtiene, almacena en caché y renderiza igual que cualquier otra plantilla de ruta — y tiene acceso completo a <code>$route.path</code>, <code>$route.matched</code>, y todas las demás funcionalidades del framework.",
        "fileBasedTitle": "404 en Enrutamiento Basado en Archivos",
        "fileBasedText": "Al usar enrutamiento basado en archivos, navegar a una ruta cuyo archivo <code>.tpl</code> no existe en el servidor (HTTP 404 u otro error) activa automáticamente la cadena de fallback wildcard.",
        "fileBasedText2": "La respuesta HTTP fallida <strong>no</strong> se almacena en caché — las navegaciones posteriores a otras rutas no se ven afectadas."
      },
      "headAttributes": {
        "title": "Atributos Head en Rutas",
        "text": "Los templates de ruta pueden declarar <code>page-title</code> y <code>page-description</code> directamente en la etiqueta <code>&lt;template&gt;</code>. Cuando la ruta se activa, las etiquetas <code>&lt;head&gt;</code> correspondientes se actualizan automáticamente.",
        "text2": "Se admiten tanto cadenas estáticas como expresiones dinámicas:",
        "colAttr": "Atributo",
        "colDesc": "Descripción",
        "pageTitleDesc": "Establece <code>document.title</code> cuando la ruta está activa",
        "pageDescriptionDesc": "Establece el contenido de <code>&lt;meta name=\"description\"&gt;</code> cuando la ruta está activa",
        "callout": "Para gestión completa del head (URLs canónicas, JSON-LD, Open Graph), consulta la guía de <a href=\"/docs/head-management\">Gestión del Head</a>."
      },
      "focusBehavior": {
        "title": "Accesibilidad — Gestión de Foco",
        "text": "Después de cada navegación, No.JS mueve el foco a un elemento predecible dentro del contenido de la nueva ruta. Esto garantiza que los lectores de pantalla anuncien el cambio de página y los usuarios de teclado aterricen en un punto útil.",
        "text2": "Cuando focusBehavior es 'auto' (por defecto), el framework recorre una lista de prioridad:",
        "priority1": "<code>[autofocus]</code> — un elemento con el atributo <code>autofocus</code> dentro del template de ruta",
        "priority2": "<code>h1</code> — el primer <code>&lt;h1&gt;</code> dentro del contenido de la ruta",
        "priority3": "<code>[tabindex=\"-1\"]</code> — un contenedor enfocable manualmente",
        "priority4": "El propio elemento route-view (último recurso)",
        "defaultTitle": "Comportamiento por Defecto",
        "defaultText": "La gestión de foco está <strong>habilitada por defecto</strong> — no necesitas configurar nada. La estrategia <code>'auto'</code> funciona de inmediato.",
        "timingTitle": "Temporización",
        "timingText": "El foco se mueve <strong>después</strong> de que el template de ruta esté completamente renderizado y todos los templates remotos dentro de él estén resueltos. Esto garantiza que el elemento destino exista en el DOM.",
        "sideEffectsTitle": "Efectos Secundarios",
        "sideEffectsText": "El elemento enfocado recibe <code>tabindex=\"-1\"</code> si no tiene un tabindex, y la página se desplaza para hacerlo visible mediante <code>scrollIntoView({ block: 'nearest' })</code>.",
        "futureTitle": "Valores Futuros",
        "futureText": "Actualmente solo se admite <code>'auto'</code>. Versiones futuras pueden agregar <code>'none'</code> (desactivar) y <code>'target'</code> (enfocar un selector específico).",
        "ariaLiveTitle": "Región ARIA Live",
        "ariaLiveText": "Para lectores de pantalla que no responden a cambios de foco, agrega <code>aria-live=\"polite\"</code> a tu <code>route-view</code>. El navegador anunciará el nuevo contenido cuando aparezca:"
      },
      "viewTransitions": {
        "title": "View Transitions",
        "text": "El atributo <code>transition</code> en <code>route-view</code> ahora usa la <strong>View Transition API</strong> por defecto. Solo elige un nombre de preset y los cambios de ruta se animan nativamente por el navegador — sin CSS manual.",
        "presetsTitle": "Presets Integrados",
        "presetsText": "No.JS incluye cuatro presets de transición integrados:",
        "colPreset": "Preset",
        "colEffect": "Efecto",
        "presetSlide": "Slide direccional — el contenido se desliza izquierda/derecha según la dirección de navegación (forward/backward)",
        "presetFade": "Crossfade — el contenido anterior se desvanece mientras el nuevo aparece",
        "presetScale": "Scale zoom — el contenido anterior se reduce mientras el nuevo se amplía",
        "presetNone": "Cambio instantáneo — sin animación, el contenido se reemplaza inmediatamente",
        "configTitle": "Configuración",
        "configText": "La View Transition API está <strong>habilitada por defecto</strong> vía <code>router.viewTransition: true</code>. Configúrala como <code>false</code> para volver al sistema legacy de transiciones basado en clases CSS.",
        "customCssTitle": "CSS Personalizado",
        "customCssText": "Para animaciones personalizadas, apunta a los pseudo-elementos <code>::view-transition-old(route-content)</code> y <code>::view-transition-new(route-content)</code>. El <code>view-transition-name: route-content</code> se establece automáticamente en cualquier outlet con atributo <code>transition</code>.",
        "howItWorksTitle": "Cómo Funciona",
        "howItWorksText": "Cuando ocurre un cambio de ruta con un preset <code>transition</code>:",
        "howStep1": "El router detecta la dirección de navegación (<strong>forward</strong> o <strong>backward</strong>) desde la pila de historial",
        "howStep2": "<code>document.startViewTransition()</code> es invocado, capturando el estado actual del outlet",
        "howStep3": "El nuevo contenido de la ruta se renderiza dentro del outlet",
        "howStep4": "El navegador anima entre los snapshots antiguo y nuevo usando las reglas CSS del preset",
        "deprecationTitle": "Migración desde Transiciones Basadas en Clases",
        "deprecationText": "El sistema antiguo de transiciones basado en clases (<code>*-enter</code>, <code>*-enter-active</code>, <code>*-leave</code>, etc.) en <code>route-view</code> está <strong>deprecated</strong>. Aún funciona cuando <code>router.viewTransition</code> está configurado como <code>false</code>, pero la View Transition API es el enfoque recomendado.",
        "deprecationCallout": "<strong>La migración es prácticamente automática.</strong> Si ya usas <code>transition=\"fade\"</code> en un <code>route-view</code>, ahora usa la View Transition API por defecto — sin cambios de código necesarios. Tu CSS personalizado <code>.fade-enter</code> / <code>.fade-leave</code> simplemente se ignora (a menos que configures <code>viewTransition: false</code>)."
      }
    },
    "formsValidation": {
      "hero": {
        "badge": "Guías",
        "title": "Formularios y Validación",
        "subtitle": "Envío de formularios declarativo con reglas de validación integradas y personalizadas"
      },
      "submission": {
        "title": "Envío Declarativo de Formularios"
      },
      "rules": {
        "title": "Reglas de Validación"
      },
      "perRuleErrors": {
        "title": "Mensajes de Error por Regla",
        "text": "Use atributos error-{regla} para establecer un mensaje personalizado para una regla específica, o error como respaldo genérico."
      },
      "errorTemplates": {
        "title": "Plantillas de Error",
        "text": "Apunte un atributo error a un <template> usando el prefijo # para renderizar UI de error enriquecida. Dentro de la plantilla, $error contiene el mensaje y $rule el nombre de la regla que falló."
      },
      "errorClass": {
        "title": "Clase CSS de Error",
        "text": "Use error-class en el formulario o en campos individuales para alternar una clase CSS cuando un campo es inválido y ha sido tocado."
      },
      "formContext": {
        "title": "$form — Contexto del Formulario",
        "text": "Dentro de cualquier <form> con el atributo validate, $form proporciona:",
        "col1": "Propiedad",
        "col2": "Tipo",
        "col3": "Descripción",
        "valid": "true si todos los campos pasan la validación",
        "dirty": "true si algún campo ha sido modificado",
        "touched": "true si algún campo ha recibido y perdido el foco",
        "submitting": "true mientras la solicitud está en curso",
        "pending": "true mientras los validadores asíncronos están en ejecución",
        "errors": "Mapa de nombres de campo → mensajes de error",
        "values": "Valores actuales del formulario",
        "firstError": "Mensaje de error del primer campo inválido (orden del DOM)",
        "errorCount": "Número de campos que actualmente fallan la validación",
        "fields": "Objeto de estado por campo (valid, error, dirty, touched)",
        "reset": "Restablecer el formulario a los valores iniciales, limpiar errores y clases"
      },
      "formFields": {
        "title": "$form.fields — Estado por Campo",
        "text": "$form.fields expone el estado individual de cada campo indexado por nombre.",
        "asTitle": "Alias de Campo con as",
        "asText": "Use el atributo as para exponer el estado de un campo bajo un nombre personalizado en el contexto."
      },
      "validateOn": {
        "title": "Disparadores de Validación (validate-on)",
        "text": "Por defecto, la validación se ejecuta en input y focusout. Use validate-on para cambiar cuándo aparece el feedback visual.",
        "note": "Internamente, los datos de $form siempre se mantienen actualizados independientemente de validate-on. El disparador solo controla cuándo se muestra el feedback visual."
      },
      "validateIf": {
        "title": "Validación Condicional (validate-if)",
        "text": "Omita la validación de un campo basándose en una condición. Cuando validate-if evalúa a false, el campo se trata como válido."
      },
      "autoDisable": {
        "title": "Deshabilitar Botones de Envío Automáticamente",
        "text": "Los botones de envío se deshabilitan automáticamente cuando el formulario es inválido. Los botones con type=\"button\" no se ven afectados."
      },
      "customValidators": {
        "title": "Validadores Personalizados"
      },
      "liveDemo": {
        "title": "Demo en Vivo — Formulario de Registro",
        "label": "Resultado",
        "usernameLabel": "Usuario",
        "usernamePlaceholder": "Elige un nombre de usuario",
        "emailLabel": "Correo electrónico",
        "emailPlaceholder": "tu@ejemplo.com",
        "ageLabel": "Edad",
        "agePlaceholder": "Tu edad",
        "roleLabel": "Rol",
        "roleDefault": "Selecciona un rol",
        "roleDev": "Desarrollador",
        "roleDesign": "Diseñador",
        "roleMgr": "Gerente",
        "termsLabel": "Acepto los términos",
        "registerButton": "Registrarse",
        "successMessage": "¡Registro completo! Bienvenido."
      }
    },
    "styling": {
      "hero": {
        "badge": "Guías",
        "title": "Estilos Dinámicos",
        "subtitle": "Alterna clases CSS y estilos inline reactivamente"
      },
      "classToggle": {
        "title": "class-* — Alternar Clases",
        "text": "Añade o elimina clases CSS reactivamente basándose en expresiones. Usa class-{name} para toggles individuales o class-map para múltiples clases.",
        "multiObject": "Múltiples Clases desde Objeto",
        "multiObjectText": "Usa class-map con una expresión de objeto para alternar múltiples clases. Las claves son nombres de clases, los valores son expresiones booleanas.",
        "fromArray": "Desde Array"
      },
      "inlineStyles": {
        "title": "style-* — Estilos Inline",
        "text": "Establece propiedades CSS inline reactivamente usando atributos style-{property}. Los nombres de propiedades usan kebab-case (ej: style-font-size, style-background-color).",
        "fromObject": "Desde Objeto",
        "fromObjectText": "Usa style-map con una expresión de objeto para establecer múltiples estilos inline a la vez."
      },
      "liveDemo": {
        "title": "Demo en Vivo — Estilos Dinámicos",
        "label": "Resultado",
        "toggleButton": "Alternar Activo"
      },
      "classStatic": {
        "title": "Interacción con class Estática",
        "text": "Las directivas class-* funcionan junto con atributos class estáticos. No.JS solo alterna las clases gestionadas por la directiva — las clases estáticas nunca se eliminan.",
        "callout": "Puedes combinar class=\"card\" con class-active=\"isActive\" de forma segura. La clase card siempre está presente; active se alterna."
      },
      "cssCustomProperties": {
        "title": "CSS Custom Properties",
        "text": "Usa style-* con CSS custom properties (variables) para estilos reactivos con temas. Los nombres de propiedades siguen la misma convención kebab-case.",
        "callout": "Las CSS custom properties definidas via style-* tienen alcance en el elemento y son heredadas por sus hijos — igual que las CSS custom properties regulares."
      }
    },
    "animations": {
      "hero": {
        "badge": "Guías",
        "title": "Animaciones y Transiciones",
        "subtitle": "Animaciones declarativas de entrada/salida y transiciones CSS"
      },
      "enterLeave": {
        "title": "animate — Animaciones de Entrada/Salida",
        "attrsTitle": "Atributos de Animación",
        "col1": "Atributo",
        "col2": "Descripción",
        "row1": "Clase de animación CSS añadida cuando el elemento entra",
        "row2": "Clase de animación CSS añadida cuando el elemento sale",
        "row3": "Duración en milisegundos pasada a animationDuration y usada como timeout de respaldo. Si se omite, el respaldo se ejecuta en el siguiente tick del event-loop (0 ms)",
        "row4": "Retraso en milisegundos entre cada elemento en un bucle (each / foreach)",
        "fallbackCallout": "Timeout de respaldo — No.JS escucha animationend / transitionend para limpiar clases y disparar re-renderizados tras las animaciones de salida. Si el evento nunca se dispara (p. ej. CSS ausente, elemento desconectado) un setTimeout actúa como red de seguridad para que el pipeline nunca se bloquee permanentemente. Si se omite animate-duration, el timeout es 0 ms — se ejecuta en el siguiente tick del event-loop sin espera artificial. Pasar un animate-duration=\"300\" explícito establece tanto animation-duration en el elemento como el timeout de seguridad en 300 ms."
      },
      "transition": {
        "title": "transition — Clases de Transición CSS",
        "viewTransitionNote": "<strong>Nota:</strong> Las transiciones de ruta en <code>route-view</code> ahora usan la <a href=\"/docs/routing#view-transitions\">View Transition API</a> por defecto. El atributo <code>transition</code> basado en clases descrito a continuación aún se aplica a <strong>elementos regulares</strong> (ej: <code>if</code>, <code>show</code>).",
        "text1": "Sigue una convención similar al sistema de transiciones de Vue.",
        "text2": "No.JS agrega/elimina clases durante la transición:",
        "col1": "Clase",
        "col2": "Cuándo",
        "row1": "Estado inicial de entrada",
        "row2": "Estado activo de entrada",
        "row3": "Estado final de entrada",
        "row4": "Estado inicial de salida",
        "row5": "Estado activo de salida",
        "row6": "Estado final de salida"
      },
      "loopAnimations": {
        "title": "Animaciones en Bucles",
        "text": "Todas las directivas de bucle (foreach, each, for) soportan animaciones de entrada/salida y escalonamiento."
      },
      "builtIn": {
        "title": "Nombres de Animación Integrados",
        "text": "No.JS incluye estas animaciones CSS:"
      },
      "liveDemo": {
        "title": "Demo en Vivo — Animación con Alternado",
        "label": "Resultado",
        "toggleButton": "Alternar",
        "demoText": "¡Hola, Mundo Animado! ✨"
      },
      "a11y": {
        "title": "Accesibilidad — Movimiento Reducido",
        "text": "No.JS respeta automáticamente la media query prefers-reduced-motion. Cuando el usuario prefiere movimiento reducido, todas las animaciones CSS se deshabilitan mediante una regla CSS integrada que establece animation-duration y transition-duration en 0.01ms.",
        "callout": "Esto se aplica globalmente — no se necesita configuración por elemento. Los usuarios que prefieren movimiento reducido ven cambios de estado instantáneos sin animación."
      }
    },
    "dnd": {
      "hero": {
        "badge": "Guías",
        "title": "Drag and Drop",
        "subtitle": "Arrastrar, soltar, listas ordenables y selección múltiple declarativos — cero JavaScript"
      },
      "drag": {
        "title": "drag — Hacer un Elemento Arrastrable",
        "preview": "Vista previa",
        "demoText": "Arrastra frutas a la cesta:",
        "fruitsLabel": "Frutas",
        "basketLabel": "Cesta",
        "col1": "Atributo",
        "col2": "Tipo",
        "col3": "Defecto",
        "col4": "Descripción",
        "dragDesc": "El valor siendo arrastrado",
        "dragTypeDesc": "Tipo nombrado — solo responden las zonas <code>drop-accept</code> coincidentes",
        "dragEffectDesc": "Se asigna a <code>dataTransfer.effectAllowed</code>",
        "dragHandleDesc": "Restringe el área de agarre a un elemento hijo",
        "dragImageDesc": "Elemento fantasma personalizado para arrastre",
        "dragImageOffsetDesc": "Desplazamiento en píxeles para imagen de arrastre personalizada",
        "dragDisabledDesc": "Cuando es verdadero, desactiva el arrastre",
        "dragClassDesc": "Clase añadida durante el arrastre",
        "dragGhostClassDesc": "Clase añadida al elemento de imagen de arrastre",
        "dragGroupDesc": "Nombre del grupo para selección múltiple"
      },
      "drop": {
        "title": "drop — Definir una Zona de Soltar",
        "preview": "Vista previa",
        "demoText": "Arrastra elementos entre zonas:",
        "zoneALabel": "Zona A",
        "zoneBLabel": "Zona B",
        "col1": "Atributo",
        "col2": "Tipo",
        "col3": "Defecto",
        "col4": "Descripción",
        "dropDesc": "Expresión ejecutada al soltar",
        "dropAcceptDesc": "Tipo(s) <code>drag-type</code> aceptados. Use <code>\"*\"</code> para cualquiera",
        "dropEffectDesc": "Se asigna a <code>dataTransfer.dropEffect</code>",
        "dropClassDesc": "Clase añadida cuando un elemento válido sobrevuela",
        "dropRejectClassDesc": "Clase añadida cuando el elemento es rechazado (tipo incorrecto o máximo excedido)",
        "dropDisabledDesc": "Cuando es verdadero, desactiva la soltura",
        "dropMaxDesc": "Máximo de elementos que acepta la zona",
        "dropSortDesc": "Habilita reordenamiento por posición",
        "dropPlaceholderDesc": "Muestra placeholder en el punto de inserción",
        "dropPlaceholderClassDesc": "Clase para el placeholder",
        "dropSettleClassDesc": "Clase CSS personalizada para la animación de asentamiento",
        "dropEmptyClassDesc": "Clase CSS personalizada para el estado vacío en la zona de soltura"
      },
      "dragList": {
        "title": "drag-list — Lista Ordenable",
        "text": "Una directiva de nivel superior que combina <code>drag</code> y <code>drop</code> en una lista ordenable vinculada a un array de estado.",
        "preview": "Vista previa",
        "todoLabel": "Por Hacer",
        "doneLabel": "Hecho",
        "col1": "Atributo",
        "col2": "Tipo",
        "col3": "Defecto",
        "col4": "Descripción",
        "dragListDesc": "Ruta al array en el estado",
        "templateDesc": "Plantilla para cada elemento",
        "dragListKeyDesc": "Clave única por elemento para identidad estable",
        "dragListItemDesc": "Nombre de la variable del loop en la plantilla",
        "dropSortDesc": "Dirección del layout",
        "dropAcceptDesc": "Tipos aceptados (por defecto: misma lista)",
        "dragListCopyDesc": "Copiar elementos en lugar de mover",
        "dragListRemoveDesc": "Eliminar elementos al arrastrar fuera",
        "dragDisabledDesc": "Desactiva el arrastre desde esta lista",
        "dropDisabledDesc": "Desactiva la soltura en esta lista",
        "dropMaxDesc": "Máximo de elementos permitidos",
        "dropSettleClassDesc": "Clase CSS personalizada para la animación de asentamiento",
        "dropEmptyClassDesc": "Clase CSS personalizada para el estado vacío en drag-list",
        "dropPlaceholderDesc": "Muestra un placeholder donde se soltará el elemento"
      },
      "dragListEvents": {
        "title": "Eventos de Drag-List",
        "col1": "Evento",
        "col3": "Descripción",
        "reorderDesc": "Elemento reordenado dentro de la misma lista",
        "receiveDesc": "Elemento recibido de otra lista",
        "removeDesc": "Elemento eliminado (arrastrado fuera)",
        "preview": "Vista previa",
        "inboxLabel": "Bandeja de Entrada",
        "archiveLabel": "Archivo",
        "eventLogLabel": "Registro de eventos:"
      },
      "dragMultiple": {
        "title": "drag-multiple — Selección Múltiple",
        "text": "Habilita clic para seleccionar en elementos hijos, luego arrastra todos los elementos seleccionados a la vez.",
        "preview": "Vista previa",
        "demoText": "Haz clic en los elementos para seleccionar, <kbd>Ctrl</kbd>+clic para múltiples, luego arrastra a la zona de soltura:",
        "availableLabel": "Disponibles",
        "collectedLabel": "Recolectados",
        "col1": "Atributo",
        "col2": "Defecto",
        "col3": "Descripción",
        "dragMultipleDesc": "Habilita clic para seleccionar",
        "dragMultipleClassDesc": "Clase añadida a los elementos seleccionados",
        "dragGroupDesc": "Nombre del grupo — todos los elementos seleccionados se mueven juntos",
        "selectionTitle": "Comportamiento de selección:",
        "selCol1": "Acción",
        "selCol2": "Resultado",
        "selClick": "Clic",
        "selClickResult": "Selecciona un solo elemento (reemplaza anterior)",
        "selCtrlClick": "Ctrl/Cmd + Clic",
        "selCtrlClickResult": "Añade a la selección",
        "selEscape": "Escape",
        "selEscapeResult": "Limpia todas las selecciones",
        "selDrag": "Arrastrar un elemento seleccionado",
        "selDragResult": "<code>$drag</code> se convierte en un array de todos los elementos seleccionados"
      },
      "implicitVars": {
        "title": "Variables Implícitas",
        "text": "Estas variables están disponibles dentro de expresiones <code>drop</code> y handlers <code>on:drop</code>:",
        "col1": "Variable",
        "col2": "Tipo",
        "col3": "Descripción",
        "dragDesc": "El valor arrastrado. Array si es selección múltiple",
        "dragTypeDesc": "El <code>drag-type</code> del elemento",
        "dragEffectDesc": "El <code>drag-effect</code>",
        "dropIndexDesc": "Índice de inserción dentro de la zona de soltura",
        "sourceDesc": "<code>{ list, index, el }</code> — información de origen",
        "targetDesc": "<code>{ list, index, el }</code> — información de destino",
        "preview": "Vista previa",
        "demoText": "Cada elemento tiene un <code>drag-type</code> diferente:",
        "dropHere": "Suelta aquí para inspeccionar"
      },
      "cssClasses": {
        "title": "Clases CSS",
        "text": "Inyectados automáticamente por No.JS:",
        "col1": "Clase",
        "col2": "Cuando se aplica",
        "draggingDesc": "En el elemento de origen durante el arrastre",
        "dragOverDesc": "En la zona de soltura mientras un elemento válido sobrevuela",
        "dropRejectDesc": "En la zona de soltura cuando el elemento es rechazado (tipo incorrecto o máximo excedido)",
        "dropPlaceholderDesc": "En el placeholder de inserción",
        "selectedDesc": "En los elementos con selección múltiple",
        "dropSettleDesc": "Breve animación de asentamiento al soltar",
        "dragListEmptyDesc": "En una <code>drag-list</code> cuando no tiene elementos",
        "preview": "Vista previa",
        "demoText": "Arrastra un elemento y observa las clases aplicarse:",
        "dropZoneHint": "Zona de soltura (máx 2) — observa <code>.nojs-drag-over</code> / <code>.nojs-drop-reject</code>"
      },
      "a11y": {
        "title": "Accesibilidad",
        "text": "No.JS añade automáticamente atributos ARIA y soporte de teclado:",
        "col1": "Característica",
        "col2": "Detalles",
        "draggableDesc": "Establecido en las fuentes de arrastre",
        "ariaGrabbedDesc": "Refleja el estado de arrastre (<code>true</code>/<code>false</code>)",
        "ariaDropeffectDesc": "Establecido en las zonas de soltura",
        "roleListboxDesc": "En contenedores <code>drag-list</code>",
        "roleOptionDesc": "En elementos de <code>drag-list</code>",
        "tabindexDesc": "Para acceso por teclado",
        "keyboardTitle": "Atajos de teclado:",
        "keyCol1": "Tecla",
        "keyCol2": "Acción",
        "spaceDesc": "Agarrar el elemento enfocado",
        "escapeDesc": "Cancelar el arrastre",
        "arrowDesc": "Navegar entre elementos durante el arrastre",
        "enterDesc": "Soltar en la posición actual",
        "preview": "Vista previa",
        "demoText": "Usa <kbd>Tab</kbd> para enfocar, <kbd>Espacio</kbd> para agarrar, <kbd>↑↓</kbd> para mover, <kbd>Enter</kbd> para soltar:"
      }
    },
    "filters": {
      "hero": {
        "badge": "Guías",
        "title": "Filtros y Pipes",
        "subtitle": "Transforma valores en expresiones bind usando la sintaxis de pipe |"
      },
      "text": {
        "title": "Filtros de Texto"
      },
      "number": {
        "title": "Filtros de Número"
      },
      "array": {
        "title": "Filtros de Array"
      },
      "date": {
        "title": "Filtros de Fecha"
      },
      "utility": {
        "title": "Filtros de Utilidad"
      },
      "object": {
        "title": "Filtros de Objeto"
      },
      "chaining": {
        "title": "Encadenar Filtros"
      },
      "custom": {
        "title": "Filtros Personalizados"
      },
      "liveDemo": {
        "title": "Demo en Vivo — Filtros",
        "label": "Resultado",
        "uppercaseLabel": "Mayúsculas:",
        "slugifyLabel": "Slugify:"
      },
      "referenceTable": {
        "title": "Referencia Completa de Filtros",
        "text": "Los 32 filtros integrados de un vistazo:",
        "col1": "Filtro",
        "col2": "Categoría",
        "col3": "Descripción",
        "uppercase": "Convierte texto a MAYÚSCULAS",
        "lowercase": "Convierte texto a minúsculas",
        "capitalize": "Capitaliza la primera letra de cada palabra",
        "truncate": "Trunca texto a N caracteres con puntos suspensivos",
        "slugify": "Convierte a slug compatible con URL",
        "trim": "Elimina espacios en blanco iniciales/finales",
        "encodeUri": "Codifica un componente URI",
        "currency": "Formatea número como moneda",
        "number": "Formatea número con agrupación por localidad",
        "percent": "Formatea número como porcentaje",
        "ordinal": "Añade sufijo ordinal (1º, 2º, 3º)",
        "filesize": "Formatea bytes a tamaño legible",
        "reverse": "Invierte array o string",
        "unique": "Elimina valores duplicados",
        "pluck": "Extrae una propiedad de cada elemento",
        "where": "Filtra array por valor de propiedad",
        "sortBy": "Ordena array por propiedad",
        "first": "Retorna el primer elemento de un array",
        "last": "Retorna el último elemento de un array",
        "date": "Formatea objeto Date",
        "datetime": "Formatea Date con hora",
        "relative": "Tiempo transcurrido desde la fecha (ej: 'hace 5 minutos')",
        "fromNow": "Tiempo hasta una fecha futura (ej: 'en 3 horas')",
        "json": "Serializa valor como string JSON",
        "default": "Retorna fallback si el valor es null/undefined",
        "keys": "Retorna las claves del objeto como array",
        "values": "Retorna los valores del objeto como array",
        "count": "Retorna la longitud de un array",
        "join": "Une elementos del array con un separador",
        "stripHtml": "Elimina las etiquetas HTML de un string",
        "nl2br": "Convierte saltos de línea en etiquetas <br>",
        "debug": "Muestra el valor en consola y lo retorna"
      }
    },
    "i18n": {
      "hero": {
        "badge": "Guías",
        "title": "Internacionalización (i18n)",
        "subtitle": "Soporte multiidioma con traducciones, pluralización y formato según la localidad"
      },
      "setup": {
        "title": "Configuración"
      },
      "externalFiles": {
        "title": "Archivos de Localización Externos",
        "text": "En lugar de incluir todas las traducciones directamente en JavaScript, puedes cargarlas desde archivos JSON externos. Esto es ideal para aplicaciones grandes con muchos idiomas o cuando las traducciones son gestionadas por una herramienta separada.",
        "flatSubtitle": "Modo Plano (un archivo por idioma)",
        "flatText": "Estructura:",
        "nsSubtitle": "Modo Namespace (dividir por funcionalidad)",
        "nsText": "Divide las traducciones por funcionalidad para code-splitting y carga bajo demanda:",
        "nsRouteSubtitle": "Namespace por Ruta",
        "nsRouteText": "Usa i18n-ns en una plantilla de ruta para cargar un namespace bajo demanda cuando se navega a la ruta:",
        "nsElementSubtitle": "Namespace en Cualquier Elemento",
        "nsElementText": "Usa i18n-ns en cualquier elemento para cargar un namespace antes de que sus hijos sean procesados:",
        "cachingSubtitle": "Caché",
        "cachingText": "Los archivos JSON obtenidos se almacenan en caché en memoria por defecto. Establece cache: false durante el desarrollo:"
      },
      "usage": {
        "title": "Uso"
      },
      "formatting": {
        "title": "Formato de Números y Fechas"
      },
      "liveDemo": {
        "title": "Demo en Vivo — Selector de Idioma",
        "label": "Resultado",
        "localeLabel": "Idioma:"
      },
      "fallback": {
        "title": "Comportamiento de Fallback",
        "text": "Cuando una clave de traducción no existe en la localidad actual, No.JS recurre a la fallbackLocale (por defecto: igual a defaultLocale). Si la clave no existe en ambas, se muestra la ruta de la clave.",
        "callout": "Establece fallbackLocale explícitamente para asegurar que los usuarios siempre vean texto significativo, incluso para localidades parcialmente traducidas."
      },
      "detection": {
        "title": "Detección de Localidad del Navegador",
        "text": "Habilita la detección automática de localidad desde navigator.language del navegador con detectBrowser: true. La localidad detectada se combina con tus localidades disponibles.",
        "callout": "Cuando se combina con persist: true, la localidad detectada se guarda para que las visitas posteriores usen el mismo idioma sin re-detección."
      }
    },
    "actionsRefs": {
      "hero": {
        "badge": "Referencia de API",
        "title": "Acciones y Refs",
        "subtitle": "Dispara llamadas a API, emite eventos personalizados y referencia elementos del DOM"
      },
      "call": {
        "title": "call — Disparar Solicitudes de API desde Cualquier Elemento"
      },
      "trigger": {
        "title": "trigger — Emitir Eventos Personalizados"
      },
      "ref": {
        "title": "ref — Referencias con Nombre",
        "text": "Accede a elementos del DOM sin querySelector:"
      },
      "refsMap": {
        "title": "$refs — Mapa de Refs",
        "text": "Todos los elementos con ref son accesibles vía $refs en el ámbito actual:"
      },
      "triggerAttrs": {
        "title": "Atributos del Trigger",
        "text": "Personaliza el comportamiento del trigger con atributos adicionales:",
        "col1": "Atributo",
        "col2": "Descripción",
        "triggerData": "Payload de datos adjunto a la propiedad detail del CustomEvent"
      }
    },
    "customDirectives": {
      "hero": {
        "badge": "Referencia de API",
        "title": "Directivas Personalizadas",
        "subtitle": "Extiende No.JS con tus propios comportamientos basados en atributos"
      },
      "directive": {
        "title": "NoJS.directive()"
      },
      "usage": {
        "title": "Uso"
      },
      "webComponents": {
        "title": "Compatibilidad con Web Components",
        "text": "Las directivas de No.JS funcionan en elementos personalizados:"
      },
      "componentPatterns": {
        "title": "Patrones Tipo Componente con Plantillas"
      },
      "priority": {
        "title": "Niveles de Prioridad",
        "text": "La prioridad determina cuándo se ejecuta tu directiva en relación a las directivas integradas. Los números más bajos se ejecutan primero.",
        "col1": "Rango",
        "col2": "Cuándo",
        "range0": "Antes de todo (inicialización de estado)",
        "range1": "Después del estado, con obtención de datos",
        "range10": "Con directivas estructurales (if, each)",
        "range20": "Con directivas de renderizado (bind, on:*)",
        "range30": "Después de todo (validación, efectos secundarios)"
      },
      "disposal": {
        "title": "Disposal & Cleanup",
        "text": "Las directivas personalizadas deben limpiar después de sí mismas. Usa el callback _onDispose para eliminar event listeners, limpiar timers y cancelar watchers.",
        "callout": "Las directivas registradas después de init (via plugins) no son congeladas — pero no pueden sobrescribir directivas integradas."
      }
    },
    "errorHandling": {
      "hero": {
        "badge": "Referencia de API",
        "title": "Manejo de Errores",
        "subtitle": "Plantillas de error por elemento, lógica de reintentos y manejadores de error globales"
      },
      "perElement": {
        "title": "Manejo de Errores Por Elemento"
      },
      "globalHandler": {
        "title": "Manejador de Errores Global"
      },
      "errorBoundary": {
        "title": "error-boundary — Capturar Errores en el Subárbol"
      },
      "retry": {
        "title": "Comportamiento de Retry",
        "text": "Las solicitudes HTTP fallidas (errores 5xx y fallos de red) se reintentan automáticamente. Configura el número de reintentos por elemento con el atributo retry, y el retraso entre reintentos con retry-delay.",
        "callout": "Los reintentos solo aplican a errores de servidor (5xx) y fallos de red. Los errores de cliente (4xx) no se reintentan."
      },
      "boundaryEvents": {
        "title": "Eventos de Error Boundary",
        "text": "Cuando un error boundary captura un error, dispara un CustomEvent nojs:error en el elemento boundary. Escucha con on:error para registrar errores o mostrar notificaciones.",
        "callout": "El objeto $event.detail contiene: message (string), source (element) y error (objeto Error original)."
      },
      "expressionErrors": {
        "title": "Errores de Expresión",
        "text": "Cuando una expresión falla al evaluar (ej: acceder a una propiedad de undefined), No.JS captura el error, registra un warning via _warn() y retorna undefined. Una expresión rota nunca derrumba la página completa."
      }
    },
    "configuration": {
      "hero": {
        "badge": "Referencia de API",
        "title": "Configuración y Seguridad",
        "subtitle": "Configuración global, interceptores de solicitudes y mejores prácticas de seguridad"
      },
      "globalSettings": {
        "title": "Configuración Global"
      },
      "configStores": {
        "title": "Pre-inicialización de Stores",
        "text": "Usa la clave stores en NoJS.config() para pre-crear stores globales nombrados con datos iniciales. Cada entrada se convierte en un store reactivo accesible vía $store.nombre en cualquier parte de tu HTML.",
        "callout": "Los stores creados vía config() no sobrescribirán stores que ya existen — la primera definición prevalece."
      },
      "configOptions": {
        "title": "Detalle de Opciones de Configuración",
        "sanitizeTitle": "sanitize",
        "sanitizeType": "Tipo: boolean | Por defecto: true",
        "sanitizeText": "Controla si el contenido HTML renderizado vía bind-html se sanitiza a través de un sanitizador estructural basado en DOMParser. Cuando está habilitado, todas las etiquetas y atributos potencialmente peligrosos (ej., <script>, onerror) se eliminan antes de la inserción en el DOM.",
        "devtoolsTitle": "devtools",
        "devtoolsType": "Tipo: boolean | Por defecto: false",
        "devtoolsText": "Habilita el panel de devtools de No.JS, accesible vía window.__NOJS_DEVTOOLS__. Cuando está activo, expone el estado reactivo, las directivas registradas, las rutas activas y los árboles de componentes para inspección en la consola del navegador.",
        "templatesCacheTitle": "templates.cache",
        "templatesCacheType": "Tipo: boolean | Por defecto: true",
        "templatesCacheText1": "Controla si el contenido HTML de archivos .tpl obtenidos remotamente se almacena en un Map en memoria después de la primera solicitud. En navegaciones repetidas a la misma ruta, se usa directamente el HTML en caché y no se realiza ninguna solicitud HTTP. La caché vive durante la sesión de la página (sin TTL — los assets de plantillas son estáticos).",
        "templatesCacheText2": "Establece false durante el desarrollo local si deseas que los cambios en archivos .tpl se reflejen sin una recarga completa de la página.",
        "loadPathTitle": "i18n.loadPath",
        "loadPathType": "Tipo: string | null | Por defecto: null",
        "loadPathText": "Plantilla de URL para cargar archivos JSON de localización vía fetch. Usa {locale} y opcionalmente {ns} como marcadores de posición. Cuando es null, las traducciones deben proporcionarse inline vía NoJS.i18n({ locales }).",
        "nsTitle": "i18n.ns",
        "nsType": "Tipo: string[] | Por defecto: []",
        "nsText": "Array de identificadores de namespace para precargar en init(). Cada namespace corresponde a un archivo JSON separado por idioma. Namespaces adicionales pueden cargarse bajo demanda vía la directiva i18n-ns o el atributo de ruta.",
        "cacheTitle": "i18n.cache",
        "cacheType": "Tipo: boolean | Por defecto: true",
        "cacheText": "Controla si los archivos JSON de localización obtenidos se almacenan en un Map en memoria después de la primera solicitud. Establece false durante el desarrollo para recarga en caliente de archivos de traducción."
      },
      "apiProperties": {
        "title": "Propiedades de API",
        "baseApiUrlTitle": "NoJS.baseApiUrl",
        "baseApiUrlText": "Getter/setter para la URL base de la API utilizada por todas las directivas de fetch y las llamadas NoJS.http. Puede leerse o reasignarse en tiempo de ejecución.",
        "versionTitle": "NoJS.version",
        "versionText": "Propiedad de solo lectura que devuelve la cadena de versión actual del framework No.JS."
      },
      "interceptors": {
        "title": "Interceptores de Solicitudes"
      },
      "security": {
        "title": "Seguridad",
        "xssTitle": "Protección XSS",
        "xssList1": "bind siempre establece textContent, nunca innerHTML — seguro por defecto.",
        "xssList2": "bind-html sanitiza el contenido usando un sanitizador estructural integrado basado en DOMParser (elimina etiquetas <script>, bloquea manejadores de eventos on*, elimina URIs javascript:).",
        "xssList3": "Las expresiones de plantilla se evalúan mediante un parser sandboxed personalizado — no se usa eval() ni Function(), y las propiedades peligrosas como __proto__ y constructor están bloqueadas.",
        "csrfTitle": "Protección CSRF",
        "cspSecTitle": "Content Security Policy",
        "cspSecText1": "No.JS utiliza un parser de expresiones personalizado totalmente compatible con CSP — no se usa eval() ni el constructor Function(). No se requiere la directiva unsafe-eval en tu Content Security Policy."
      }
    },
    "plugins": {
      "hero": {
        "badge": "Referencia de API",
        "title": "Plugins",
        "subtitle": "Extiende No.JS con paquetes reutilizables — analíticas, autenticación, feature flags y más"
      },
      "use": {
        "title": "NoJS.use()",
        "text": "Registra un plugin antes o después de NoJS.init(). Si la app ya está inicializada, el hook init del plugin se ejecuta inmediatamente.",
        "objectFormTitle": "Forma de Objeto",
        "objectFormText": "La forma estándar de definir un plugin. Proporciona un nombre, una función install y hooks de ciclo de vida opcionales.",
        "functionTitle": "Atajo de Función",
        "functionText": "Para plugins simples, pasa una función con nombre. El nombre de la función se convierte en el nombre del plugin.",
        "functionCallout": "Las funciones anónimas y las funciones flecha son rechazadas — el plugin debe tener un nombre.",
        "optionsTitle": "Opciones",
        "optionsText": "El segundo argumento de NoJS.use() se pasa a la función install del plugin.",
        "optionsTrustedNote": "La opción trusted es especial — consulta Interceptores de Confianza más abajo."
      },
      "interface": {
        "title": "Interfaz del Plugin",
        "thProperty": "Propiedad",
        "thType": "Tipo",
        "thRequired": "Requerido",
        "thDescription": "Descripción",
        "yes": "Sí",
        "no": "No",
        "nameDesc": "Identificador único. Los nombres duplicados son rechazados.",
        "versionDesc": "Cadena semver para depuración.",
        "capabilitiesDesc": "Capacidades declaradas (registradas en modo debug).",
        "installDesc": "Se llama sincrónicamente por NoJS.use().",
        "initDesc": "Se llama después de que NoJS.init() se completa (el DOM está listo).",
        "disposeDesc": "Se llama durante NoJS.dispose() para limpieza.",
        "lifecycleTitle": "Ciclo de Vida",
        "lifecycleInstall": "install se ejecuta inmediatamente y de forma sincrónica. Úsalo para registrar interceptores, globales, directivas, escuchadores de eventos y stores.",
        "lifecycleInit": "init se ejecuta después de que el DOM es procesado y el router está activo. Úsalo para trabajo que dependa de elementos renderizados o del estado de la ruta.",
        "lifecycleDispose": "dispose se ejecuta durante la destrucción de la app. Úsalo para cerrar conexiones WebSocket, limpiar intervalos, enviar analíticas pendientes, etc.",
        "duplicateTitle": "Detección de Duplicados",
        "duplicateText": "Un nombre de plugin solo puede registrarse una vez. Si NoJS.use() se llama de nuevo con el mismo nombre pero un objeto diferente, se registra una advertencia y la llamada se ignora.",
        "freezingTitle": "Congelación del Registro de Directivas",
        "freezingText": "Las directivas principales (state, bind, get, on:*, etc.) se congelan después de que el framework carga. Los plugins pueden registrar nuevas directivas pero no pueden sobrescribir las integradas."
      },
      "globals": {
        "title": "NoJS.global()",
        "text": "Inyecta una variable reactiva accesible como $name en cualquier expresión de plantilla.",
        "namingTitle": "Convenciones de Nombres",
        "namingPrefix": "Los nombres globales se acceden con un prefijo $ en las plantillas: NoJS.global('foo', ...) se convierte en $foo.",
        "namingNamespace": "Usa el nombre de tu plugin como namespace para evitar colisiones: $analytics, $auth, $featureFlags.",
        "reservedTitle": "Nombres Reservados",
        "reservedText": "Los siguientes nombres no pueden usarse con NoJS.global():",
        "reservedProto": "Los vectores de contaminación de prototipos (__proto__, constructor, prototype) también están bloqueados.",
        "reactivityTitle": "Reactividad",
        "reactivityText": "Los valores de objeto pasados a NoJS.global() se envuelven automáticamente en un contexto reactivo. Las mutaciones disparan actualizaciones del DOM igual que los cambios de store o state.",
        "ownershipTitle": "Seguimiento de Propiedad",
        "ownershipText": "Cuando un plugin registra un global durante su fase install, se convierte en el propietario. Si un plugin diferente sobrescribe ese global, se registra una advertencia."
      },
      "sentinels": {
        "title": "Centinelas de Interceptores",
        "text": "Los interceptores de solicitud pueden devolver objetos especiales con clave de Symbols centinela para controlar el pipeline de fetch.",
        "cancelTitle": "NoJS.CANCEL — Abortar la Solicitud",
        "cancelText": "Devuelve un objeto con [NoJS.CANCEL]: true para evitar que la solicitud sea enviada. El fetch lanza un AbortError.",
        "respondTitle": "NoJS.RESPOND — Servir una Respuesta en Caché",
        "respondText": "Devuelve un objeto con [NoJS.RESPOND]: data para cortocircuitar la solicitud y devolver data directamente como respuesta. No se realiza ninguna solicitud HTTP.",
        "replaceTitle": "NoJS.REPLACE — Reemplazar Datos de Respuesta",
        "replaceText": "Devuelve un objeto con [NoJS.REPLACE]: data desde un interceptor de respuesta para reemplazar el cuerpo de respuesta parseado con datos personalizados.",
        "summaryTitle": "Resumen",
        "thSentinel": "Centinela",
        "thUsedIn": "Usado En",
        "thEffect": "Efecto",
        "cancelUsedIn": "Interceptor de solicitud",
        "respondUsedIn": "Interceptor de solicitud",
        "replaceUsedIn": "Interceptor de respuesta",
        "cancelEffect": "Aborta la solicitud (lanza AbortError)",
        "respondEffect": "Devuelve datos directamente, omite la llamada HTTP",
        "replaceEffect": "Reemplaza el cuerpo de respuesta parseado"
      },
      "trusted": {
        "title": "Interceptores de Confianza",
        "text": "Por defecto, los interceptores reciben copias redactadas de solicitudes y respuestas — los encabezados sensibles y parámetros de URL se eliminan o reemplazan con [REDACTED].",
        "fullAccess": "Los plugins de autenticación que necesitan acceso a los encabezados reales pueden instalarse con { trusted: true }.",
        "callout": "Se registra una advertencia en consola cuando un plugin se instala con acceso de confianza. Solo otorga trusted a plugins que controles o hayas auditado.",
        "redactedTitle": "Encabezados Redactados",
        "requestLabel": "Encabezados de solicitud eliminados antes de pasar a interceptores no confiables:",
        "responseLabel": "Encabezados de respuesta eliminados del proxy de respuesta:"
      },
      "dispose": {
        "title": "NoJS.dispose()",
        "text": "Destruye toda la aplicación: dispone los plugins, limpia los globales y elimina los interceptores.",
        "orderTitle": "Orden de Destrucción",
        "orderStep1": "Los plugins se destruyen en orden inverso de instalación (el último instalado se destruye primero).",
        "orderStep2": "La función dispose de cada plugin tiene un tiempo límite de 3 segundos. Si lo excede, se registra un error y la destrucción continúa con el siguiente plugin.",
        "orderStep3": "Después de que todos los plugins son destruidos, los globales e interceptores se limpian.",
        "asyncTitle": "Dispose Asíncrono",
        "asyncText": "Las funciones dispose de los plugins pueden ser asíncronas. El framework espera cada una (sujeta al tiempo límite de 3 segundos).",
        "callout": "Los plugins no pueden instalarse durante la destrucción. Las llamadas a NoJS.use() mientras dispose() se ejecuta son ignoradas con una advertencia."
      },
      "security": {
        "title": "Directrices de Seguridad",
        "text": "Al crear plugins, sigue estas prácticas para mantener tu aplicación segura.",
        "namespaceTitle": "Nombra Todo con Namespace",
        "namespaceText": "Prefija globales, stores y nombres de eventos con el nombre de tu plugin para evitar colisiones.",
        "evalTitle": "Nunca Uses eval ni Function",
        "evalText": "Los valores pasados a NoJS.global() se verifican en busca de referencias peligrosas. eval y Function están bloqueados.",
        "cleanupTitle": "Limpia en dispose",
        "cleanupText": "Siempre proporciona un hook dispose que limpie intervalos, cierre conexiones y elimine escuchadores de eventos.",
        "overwriteTitle": "Evita Sobrescribir Globales Ajenos",
        "overwriteText": "Si tu plugin detecta que un global ya es propiedad de otro plugin, considera usar un nombre diferente en lugar de sobrescribir silenciosamente.",
        "validateTitle": "Valida las Opciones",
        "validateText": "Verifica las opciones requeridas en install y advierte temprano."
      },
      "example": {
        "title": "Ejemplo Completo",
        "text": "Un plugin de analíticas completo que demuestra el ciclo de vida del plugin, globales, interceptores y destrucción."
      }
    },
    "headManagement": {
      "hero": {
        "badge": "Guías",
        "title": "Gestión del Head",
        "subtitle": "Gestiona el título del documento, meta description, URL canónica y JSON-LD desde cualquier elemento"
      },
      "intro": {
        "text": "No.JS te permite controlar los metadatos del <head> de forma declarativa desde cualquier parte de tu markup. Usa elementos ocultos con atributos page-title, page-description, page-canonical y page-jsonld — se evalúan de forma reactiva y actualizan el head del documento automáticamente.",
        "routingTip": "Para gestión del head a nivel de ruta, también puedes usar <code>page-title</code> y <code>page-description</code> directamente en etiquetas <code>&lt;template route=\"...\"&gt;</code>. Consulta <a href=\"/docs/routing#route-head-attributes\">Atributos Head en Rutas</a>."
      },
      "placement": {
        "title": "Ubicación",
        "text": "Coloca los elementos de gestión del head dentro de cualquier scope de state. Deben tener el atributo hidden — nunca se renderizan visualmente.",
        "reactive": "Todos los valores son reactivos — cuando el state subyacente cambia, el head del documento se actualiza inmediatamente."
      },
      "pageTitle": {
        "title": "page-title",
        "text": "Establece document.title con el valor de la expresión evaluada.",
        "expression": "El valor es una expresión estándar de No.JS evaluada contra el contexto del elemento."
      },
      "pageDescription": {
        "title": "page-description",
        "text": "Establece el atributo content de la etiqueta <meta name=\"description\">. Si la etiqueta no existe, se crea.",
        "duplicate": "Solo un page-description debería estar activo a la vez. Si hay múltiples presentes, el último procesado gana."
      },
      "pageCanonical": {
        "title": "page-canonical",
        "text": "Establece el atributo href de la etiqueta <link rel=\"canonical\">. Si la etiqueta no existe, se crea.",
        "existing": "Si ya existe un enlace canónico en tu HTML, el valor del atributo se actualiza en su lugar."
      },
      "pageJsonld": {
        "title": "page-jsonld",
        "text": "Inyecta un bloque <script type=\"application/ld+json\"> en el head del documento. El texto interno del elemento se usa como payload JSON-LD.",
        "scriptNote": "El contenido se inserta tal cual — asegúrate de que sea JSON válido. Usa interpolación <code>{variable}</code> para inyectar valores dinámicos.",
        "marker": "La etiqueta script generada se marca con <code>data-nojs-jsonld</code> para poder identificarla y limpiarla al desmontar.",
        "fullExampleTitle": "Ejemplo Completo de Página de Producto"
      },
      "notes": {
        "title": "Notas y Casos Especiales",
        "competingTitle": "Múltiples Directivas Compitiendo",
        "competingText": "Si múltiples elementos establecen el mismo atributo head (ej. dos elementos page-title), el último procesado gana. Cuando un elemento se desmonta, su contribución al head se revierte al valor anterior.",
        "cleanupTitle": "Limpieza al Desmontar",
        "cleanupText": "Cuando un elemento con una directiva de head se elimina del DOM (ej. dentro de un bloque if o un cambio de ruta), su contribución al head se limpia automáticamente.",
        "captureTitle": "Captura de Template JSON-LD",
        "captureText": "El innerHTML del elemento page-jsonld se captura en el momento del procesamiento. Si necesitas JSON-LD reactivo, coloca el elemento dentro de un scope de state y usa interpolación {expresión}."
      }
    }
  }
}
