Files
agenticSystem/docs/css-js-conventions.md
Jordan Diaz 301cef4d69 Forzar máximo 2 steps en plan: 1 coder + 1 reviewer opcional
El planner generaba 3+ steps para tareas simples causando que el
coder repitiera acciones en cada step (creaba el módulo varias veces).
Ahora el engine fusiona los steps en 1 coder con descripción combinada.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 23:47:05 +00:00

7.5 KiB

CSS & JavaScript Conventions

Estructura del módulo

  • Genera HTML + CSS + JS (o Vue 3 si es necesario)
  • Define una clase raíz en kebab-case: product-card, hero-section, etc.
  • Todo el CSS y JS scopeado bajo esa clase raíz

CSS

Tailwind First

Usar TailwindCSS como método principal. Solo CSS custom cuando Tailwind no cubra el estilo o se necesiten estados complejos/transiciones específicas.

<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow-md">
  <h2 class="text-2xl font-bold text-gray-900">Title</h2>
</div>

BEM para CSS Custom

Cuando se necesite CSS personalizado, siempre scopeado bajo la clase raíz con BEM:

.hero-section { }
.hero-section__title { }
.hero-section__image { }
.hero-section--dark { }

Nunca usar clases globales sin prefijo de módulo.

CSS Variables del tema

var(--main-color)        /* Color de marca primario */
var(--main-color-light)  /* Variante clara */
var(--main-color-dark)   /* Variante oscura */

Estilos inline con fallbacks

Patrón para colores configurables por el usuario:

<div style="background-color: {{ colordefondo ? colordefondo : 'transparent' }}">
<p style="color: {{ colordeltexto ? colordeltexto : '#111827' }}">

Clases utilitarias de Acai

Clase Descripción
transition3s Transición suave 0.3s
click-a-child Hace el padre clickeable via primer <a> hijo
line-clamp2 / line-clamp3 / line-clamp5 Truncar texto a N líneas
filter-white Filtro CSS para hacer imágenes/iconos blancos
lazyload Lazy loading (usar con data-src)
text-shadow Sombra de texto para legibilidad sobre imágenes
wysiwyg Wrapper para contenido de texto enriquecido
bg-main-color / bg-main-color-light / bg-main-color-dark Fondos con color primario
text-main-color / text-main-color-light / text-main-color-dark Texto con color primario

JavaScript

Module Scripts (script.js)

JavaScript scopeado al módulo usando section_id:

This is the default and expected place for module JavaScript. Do NOT embed <script> tags directly inside index-base.tpl for normal module behavior. Keep index-base.tpl for HTML/Twig markup and put interactive logic in script.js. script.js is a static file: do NOT use Twig syntax inside it. Do NOT write {{ section_id }}, {{ variable }}, {% if ... %}, or builder attributes inside script.js.

If JavaScript needs dynamic values, pass them from index-base.tpl through data-* attributes:

<section
  class="buscador-apartados"
  data-section-id="{{ section_id }}"
  data-hook-endpoint="/hooks/buscadorapartados_hjd8s/">
</section>
document.querySelectorAll('.buscador-apartados').forEach((section) => {
  const sectionId = section.dataset.sectionId;
  const hookEndpoint = section.dataset.hookEndpoint;
  const buttons = section.querySelectorAll('.btn');
  // ...
});

CmsApi (Client-Side)

CmsApi.hook('/hooks/module_id/', { action: 'getData', id: 123 }, function(response) {
  console.log(response);
});

If you are calling a hook that belongs to the current module, the endpoint must use the real module id:

// Module folder: template/estandar/modulos/buscadorapartados_hjd8s/
CmsApi.hook('/hooks/buscadorapartados_hjd8s/', { termino: 'vela' }, function(response) {
  console.log(response);
});

Do not try to build this endpoint with Twig inside script.js. Put the final endpoint in a data-hook-endpoint attribute in index-base.tpl if needed.

Module Styles (style.css)

style.css is also a static file. Do NOT use Twig syntax or builder attributes inside it. Do NOT write selectors or values that depend on {{ section_id }}.

Scope styles with the module root class instead of dynamic Twig ids.

Cuándo usar Vue 3

Usar Vue 3 CDN cuando la lógica requiera:

  • Doble binding / reactividad
  • Solicitudes asíncronas complejas
  • Componentes reutilizables
  • Gestión de estado local
  • Ciclos de vida

Para lógica simple, usar JavaScript vanilla.

Vue 3 Integration

<div id="app-{{ section_id }}">
  <p>${ message }</p>
  <button @click="increment">${ count }</button>
</div>

<script>
const { createApp, ref } = Vue;
createApp({
  delimiters: ['${', '}'],  // Evitar conflicto con Twig {{ }}
  setup() {
    const message = ref('Hello');
    const count = ref(0);
    const increment = () => count.value++;
    return { message, count, increment };
  }
}).mount('#app-{{ section_id }}');
</script>

Siempre usar '${' y '}' como delimitadores Vue para evitar conflicto con Twig.


Variables Globales Disponibles

Variable Descripción Ejemplo
section_id ID único por instancia del módulo <div id="{{section_id}}">
server.HTTP_HOST Dominio actual https://{{ server.HTTP_HOST }}/path
loop.index Índice de iteración (1-based) en c-for/for {{ loop.index }}
loop.index is odd True en iteraciones impares Layouts alternados
loop.index is even True en iteraciones pares Patrones zigzag
interno True dentro del editor CMS c-class="{'editor-mode': interno}"

Patrón section_id

Cada instancia de módulo recibe un section_id único. Usar para navigation anchor e IDs:

<div id="{{section_id}}"></div>
<section id="id_{{ section_id }}" class="relative">
  <!-- contenido del módulo -->
</section>

Componentes Nativos

<div class="c-tns-wrapper" data-responsive="sm:2, md:3, lg:4" data-speed="1000" data-nav="true">
  <ul class="c-tns-container">
    <li data-field-type="multiv2" data-field-label="Slides" class="px-2">
      <!-- contenido del slide -->
    </li>
  </ul>
</div>
Atributo Descripción Ejemplo
data-responsive Items por breakpoint "sm:2, md:3, lg:4"
data-autoplay-timeout Intervalo autoplay (ms) "5000"
data-mode Modo de transición "gallery" o "carousel"
data-speed Velocidad de transición (ms) "400"
data-nav Puntos de navegación "true"

Dots de navegación custom:

<div class="c-tns-nav-container absolute bottom-4 left-0 w-full flex justify-center items-end z-20">
  <div c-for="item in records"
       class="pointer-events-auto cursor-pointer rounded-full border-2 border-white w-4 h-4 mx-1 bg-black bg-opacity-50">
  </div>
</div>

Lightbox

<a href="{{ image[0].urlPath }}" class="glightbox" data-gallery="gallery1">
  <img src="{{ image[0].urlPath | imagec(400) }}" />
</a>

Breadcrumb

<breadCrumb class="bg-gray-200 p-3 rounded" c-prevlinks="null"></breadCrumb>

AOS (Animate On Scroll)

<div data-aos="fade-up" data-aos-duration="800">Contenido</div>

Valores comunes: fade-up, fade-down, fade-left, fade-right, zoom-in, zoom-in-up, fade-up-right, fade-up-left

Después de cambios dinámicos: AOS.refresh() en JavaScript.

Lazy Loading

<!-- Builder var con lazy loading -->
<img data-field-type="upload" data-field-label="Imagen" data-lazy="true" data-field-width="800" alt="">

<!-- Manual en templates -->
<img class="lazyload" data-src="{{ record.imagen[0].urlPath | imagec(800) }}" alt="">

Buenas prácticas

  • HTML/Twig semántico
  • Código limpio y organizado
  • Evitar dependencias externas innecesarias
  • Evitar estilos inline salvo casos justificados (colores dinámicos del usuario)
  • No usar clases globales sin prefijo de módulo