# Rails sostenible (V): helpers, CSS y minimizar JavaScript

*Quinta entrega de la serie **[Rails sostenible](/search?tag=rails-sostenible)**, sobre el libro de David Bryant Copeland. Estamos en el ecuador. Tiempo de lectura estimado: 14 minutos.*

Seguimos en la capa de presentación que empezamos en [la entrega anterior](/post/rails-sostenible-iv-rutas-y-plantillas-html). Copeland dedica cuatro capítulos al front-end —helpers, CSS y dos enteros a JavaScript— y aquí los junto porque comparten una misma filosofía: **el front-end es donde más fácil se cuela el carrying cost**, y la disciplina consiste en no dejarlo entrar.

## Helpers: para qué son y para qué no

Los helpers de Rails son métodos disponibles en las vistas. La pregunta sostenible no es *cómo* usarlos, sino *para qué*. Copeland traza una línea clara:

- **Sí:** exponer estado global de UI (¿está logueado el usuario? ¿en qué sección estamos? ¿qué clases CSS lleva el body?) y generar markup repetitivo.
- **No:** lógica de dominio. Un helper no es el sitio para calcular el precio de un pedido ni para decidir reglas de negocio. Eso vive en el seam que [montamos en la tercera entrega](/post/rails-sostenible-iii-logica-de-negocio-fuera-de-active-record).

Confundir helpers con dominio es un clásico: empiezas con un `format_price(order)` inocente y acabas con reglas de negocio escondidas en `app/helpers`, el último sitio donde alguien las buscará.

### Defínelos en los menos sitios posibles

Aquí Copeland desmonta una ilusión de Rails. Por defecto, Rails genera un módulo de helper por cada controlador (`CustomersHelper`, `OrdersHelper`...) y eso *sugiere* que están separados por contexto. Pero es mentira: **todos los helpers son globales** en las vistas, vivan en el módulo que vivan. Esa falsa separación solo añade ruido. Su recomendación es concentrarlos en `ApplicationHelper` o en muy pocos módulos con nombres honestos, asumiendo su naturaleza global en vez de fingir un namespacing que no existe.

### Usa las APIs de Rails para generar markup

Cuando un helper construye HTML, nada de interpolar strings a mano (que además abre la puerta a inyecciones). Usa los generadores de Rails, que escapan el contenido por ti:

```ruby
module ApplicationHelper
  def nav_link(text, path, active:)
    link_to text, path, class: ["nav__link", ("nav__link--active" if active)]
  end

  def status_badge(status)
    tag.span(status.humanize, class: "badge badge--#{status}")
  end
end
```

`link_to`, `tag`, `content_tag` y compañía generan markup seguro y son testeables. Que lleva al último punto del capítulo: **los helpers deben testearse**. Son código Ruby normal; un helper sin test es lógica de presentación sin red.

### El recelo hacia presenters y decorators

Mucha gente, al sacar lógica de las vistas, recurre a presenters, decorators o view models (Draper y similares). Copeland los mira con recelo, no porque sean malos en sí, sino por su **carrying cost**: añaden una capa, una gema, un patrón que cada persona nueva debe aprender, y a menudo resuelven un problema que unos helpers bien testeados ya resolvían. Su consejo: empieza con helpers; solo escala a objetos de presentación cuando el dolor sea real y demostrable, no preventivo.

![Programador trabajando frente a varias pantallas](fig-01.webp)

## CSS: adopta un sistema, no improvises

El capítulo de CSS se resume en tres movimientos:

1. **Adopta un design system.** Define una vez tu escala de espaciado, tu paleta de colores, tu tipografía y tus tamaños. No decidas `margin: 13px` a ojo en cada plantilla. Un sistema de diseño hace que la interfaz sea coherente *y* que añadir pantallas nuevas sea rápido, porque ya tienes el vocabulario.
2. **Adopta una estrategia CSS y sé consistente.** Da igual si eliges utility-first (Tailwind), BEM, o lo que sea: lo insostenible es **mezclar** sin criterio. La consistencia es lo que permite que cualquiera entienda tu CSS.
3. **Crea una guía de estilo viva.** Una página dentro de la propia app que muestre tus componentes y tokens renderizados de verdad. Documentación que no se desincroniza porque *es* el código.

> El CSS no se vuelve insostenible por ser mucho, sino por no tener reglas. Mil líneas con sistema son mantenibles; cien sin sistema son un campo de minas.

Este blog es un caso de manual de lo que Copeland predica: Tailwind para utilidades y BEM (`.bloque__elemento--modificador`) para los componentes propios, con la regla explícita de **no mezclar** ambos en el mismo elemento. Tener esa estrategia escrita evita el caos.

## Minimiza el JavaScript

Llegamos al tema más afilado del libro. Copeland sostiene que **el JavaScript es un pasivo serio**, y no por ideología anti-JS, sino por carrying cost puro:

- El ecosistema se mueve a una velocidad brutal: el build tool que elegiste hoy estará deprecado en dos años.
- El tooling (bundlers, transpilers, node_modules de 500 MB) es complejo y frágil.
- Cada línea de JavaScript es lógica que duplica, en el cliente, algo que el servidor ya sabe hacer.

La conclusión no es "cero JavaScript", es **el mínimo JavaScript posible**. Y la estrategia para lograrlo tiene tres patas.

### 1. Abraza las vistas renderizadas en servidor

El HTML lo genera el servidor, como Rails lleva haciendo desde 2004. Sin SPA, sin API + frontend separado para una app que no lo necesita. El servidor es la fuente de verdad y manda HTML. Esto elimina de un plumazo toda una clase de complejidad: sincronización de estado cliente-servidor, hidratación, duplicación de lógica.

### 2. Ajusta Turbo para mejorar la experiencia

[Turbo](https://turbo.hotwired.dev/) (parte de Hotwire) te da navegación rápida tipo SPA **sin escribir JavaScript**: intercepta clics y formularios, hace la petición y reemplaza el HTML. Con Turbo Frames y Turbo Streams consigues actualizaciones parciales de página manteniendo el servidor como cerebro. Copeland lo recomienda como la forma por defecto de mejorar la experiencia: el 90% de la interactividad que la gente cree que necesita JavaScript se resuelve con Turbo y HTML.

### 3. Abraza la plataforma web para lo básico

Antes de instalar nada, mira lo que el navegador ya hace de serie. El capítulo once insiste en la **web platform**: elementos nativos que hace años requerían librerías y hoy son HTML puro.

```html
<!-- Modal sin una línea de JavaScript -->
<dialog id="confirmar">
  <p>¿Seguro que quieres cancelar el pedido?</p>
  <form method="dialog"><button>Cerrar</button></form>
</dialog>

<!-- Acordeón nativo -->
<details>
  <summary>Detalles del envío</summary>
  <p>Llega en 3-5 días laborables.</p>
</details>

<!-- Validación de formularios sin JS -->
<input type="email" required>
```

`<dialog>`, `<details>`, validación nativa de formularios, `<input type="date">`... cada cosa que delegas en el navegador es JavaScript que no escribes, no mantienes y no actualizas.

### Cuando necesites un framework, elige uno solo

A veces sí necesitas interactividad rica de verdad (un editor, un canvas, un dashboard reactivo). Copeland no lo niega, pero pone una condición: **elige un único framework y úsalo con disciplina**. Lo insostenible es el patchwork —un poco de Stimulus aquí, un componente React allá, jQuery heredado en aquella página—. Esa mezcla multiplica el carrying cost. Una herramienta, bien acotada, para los puntos donde de verdad aporta.

### Que los tests de sistema fallen si el JS se rompe

Un cierre importante: si tu app depende de JavaScript para funcionar, tus **system tests deben ejecutarse con un navegador real** (no solo `rack_test`) en esas rutas, de modo que si el JavaScript se rompe, el test falle. JavaScript sin tests que lo ejerciten de verdad es la receta del "funcionaba ayer". Lo retomaremos en [el post de testing](/post/rails-sostenible-vii-testing-y-ejemplo-end-to-end).

## Mi versión

Llevo años predicando lo de minimizar JavaScript y da gusto ver a alguien argumentarlo en términos de coste y no de moda. La pregunta que me hago siempre antes de meter JS es: *¿esto lo hace ya el navegador o Turbo?* Nueve de cada diez veces, sí. El `<dialog>` y el `<details>` nativos me han borrado toneladas de código de modales y acordeones que antes arrastraba con librerías.

Este blog, de hecho, es mi declaración de principios al respecto: cero frameworks frontend, CSS crítico inline, y el JavaScript justo. La web va rapidísima precisamente por lo que *no* tiene. Copeland pondría el carrying cost de un bundle de React de 300 KB en la columna de pasivos, y tendría razón.

Donde más me peleo con la realidad es con los presenters. Copeland prefiere helpers testeados, y lo entiendo, pero en vistas muy complejas a veces un objeto de presentación me da más claridad que un cajón de helpers globales. Mi regla: helpers para markup y estado de UI; si una vista necesita orquestar mucho dato derivado, un objeto *page* (como vimos con la "una ivar por acción") antes que un decorator mágico atado al modelo.

## Lo que viene

Cerramos aquí la capa de presentación. En el **próximo post** bajamos al sótano, a la capa que más me apasiona: **modelos y base de datos**. Veremos la distinción entre Active Record (acceso a datos) y Active Model (modelado de recursos), la diferencia entre modelo lógico y físico, cómo escribir migraciones correctas con constraints de verdad en la base de datos, y por qué las validaciones de Rails no garantizan la integridad de los datos (aunque sean estupendas para la UX).
