Anti-Cliché: antes tardaba más arreglando que escribiendo
Llevo un par de años usando Cursor. Empecé en el Tab. Cuando pasé a agentes, una parte del rato se me iba en revisar y corregir lo que generaban. Armé Anti-Cliché.
Llevo un par de años usando Cursor. Al principio quería una cosa: desarrollar más rápido, sobre todo con el Tab. Te completa la línea y sigues tú.
No confiaba demasiado en el chat. Las pocas veces que lo usaba, metía errores o traía patrones que no eran de este proyecto. Seguí escribiendo el código yo. El Tab, de copiloto.
Los meses pasaron. Los modelos mejoraron, las herramientas también, y el mercado te empuja a los agentes. No solo porque funcionan mejor. Sino porque las expectativas se fueron para arriba: cuánto código produces, cuánto planificas, cuánto avanzas en el mismo rato.
En algún momento ya no alcanzaba con autocompletar unas líneas. Había que dejarle tareas enteras. Más autonomía. Eso empecé a hacer.
Había un problema. Aunque los modelos mejoraban, y aunque ya existían rules, skills y el contexto del proyecto, el código que devolvían seguía teniendo cosas raras.
Hace unos meses, en un code review, me encontré con esto:
function preventDefault(event: { preventDefault: () => void }) {
event.preventDefault();
}
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
preventDefault(event);
createUser(formData);
}
const form = (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit">Crear cuenta</button>
</form>
);
return <section>{form}</section>;A simple vista parecía bien (Knip, typecheck, ESLint). Cuando veías qué hacía, no hacía falta: una función de una línea alrededor de preventDefault, el formulario en un const.
Ese ejemplo me hizo mirar con más cuidado lo que escribía mi propio agente. No era un caso suelto. Decisiones que funcionan, pero no respetan las convenciones, meten complejidad de más, o no son lo que yo hubiera escrito.
Ahí apareció una pregunta incómoda. ¿Estaba siendo más eficiente, o solo había cambiado el tiempo de escribir por tiempo de revisar y corregir lo que genera la IA? Una parte del rato se me iba en eso: revisar, corregir, volver a explicar el contexto, y preguntarme si valía la pena delegar esa tarea.
Llegué a plantearme volver al Tab. Escribir más a mano. Tener el control otra vez. Antes de cerrar eso: quizás el problema no era el agente. Quizás lo estaba usando mal.
Ahí empecé a investigar cómo trabajar con agentes de verdad: contexto, rules, skills, el conocimiento del proyecto. No solo código que funciona. Código que tiene sentido aquí. Cada vez que no quería ver el mismo patrón, lo pasaba a una regla. Eso no vive en un comentario de PR. Vive en Anti-Cliché. Esta nota es el porqué. Lo que el agente tiene que cargar es la skill.
El cansancio no era de velocidad
El problema no es que el agente exista. El problema es el promedio que suelta cuando nadie le pone un borde. Claude, Cursor, los modelos de Anthropic: da lo mismo la marca. Ya son el trabajo.
Ese promedio no es solo una función absurda. En arquitectura: una clase UserService solo para ir a buscar datos. En estructura: un <Box> que solo pone las cosas en fila, un index.ts que reexporta todo el directorio, "use client" copiado por costumbre. Y sobre todo en la interfaz. El degradado violeta, la tipografía Inter, tres tarjetas con icono, el vidrio esmerilado, el botón con sparkles. También el sello más obvio: listas numeradas que no son pasos, section_title con guion bajo en cada título, una raya entre cada bloque, letra de código en texto que no es código. Lo ves y ya sabes qué modelo lo armó.
Knip te dice qué archivo ya nadie usa. Aquí el archivo se usa. Entra al proyecto. Pasa TypeScript. Pasa el linter. Esas herramientas dicen si está bien formado, no si sirve. Y sigue siendo el default de 2024 pegado en un proyecto de 2026.
Un useMemo alrededor de un texto. Un try/catch que imprime el error en consola y devuelve null. Un objeto STYLES para no escribir la clase junto al HTML. Nada de eso rompe el build. Todo eso es el dialecto.
Me cansé de explicar lo mismo en cada review. “Esto no se extrae.” “Esto ya lo hace el formulario.” “Esto no necesita un envoltorio.” “Esto parece una landing de Cursor.” El modelo no se ofende. El modelo tampoco recuerda el review anterior, salvo que se lo dejes escrito en un sitio que vuelva a leer.
Qué es un cliché de IA (y qué no)
Un cliché no es “código que no me gusta”. Es un patrón que el modelo elige porque apareció mil veces en el entrenamiento, no porque resuelva el problema de este archivo.
La función de preventDefault es el ejemplo más claro que tengo. El modelo aprendió que un submit pide e.preventDefault() para que el navegador no recargue. Después aprendió “extrae lo repetido”. Juntó las dos lecciones y fabricó una función de una línea. El resultado parece cuidado. En la práctica no aporta nada que no estuviera ya en el submit.
El principio de Anti-Cliché cabe en una frase: todo patrón prohibido sustituye forma por contenido, o complejidad por valor. El arreglo no es “escribe más limpio”. Es ser específico, respetar lo que ya existe en el proyecto, y usar lo que la plataforma ya hace (React 19, App Router, Tailwind en el HTML).
Lo mismo pasa en la interfaz: degradado violeta, blob 3D, botón “Generate” con sparkles, texto gris sobre blanco porque “se ve limpio”. Listas numeradas que no son pasos. Guiones bajos. Rayas de más. Letra de código donde no hay código. Y en la prosa: delve, tapestry, landscape, pivotal, la mandíbula que se tensa, la luz que se derrama. Distintos sitios, mismo mecanismo.
No estoy peleando con React. Estoy peleando con el reflejo de envolver un llamado nativo y ponerle un nombre de sistema.
Constantes para todo, incluido el JSX
El const form no vino solo. El modelo extrae para sentirse organizado, no para nombrar un concepto. El mismo reflejo, un paso más atrás, es el archivo de constantes.
Empieza inocente: CARD_PADDING = 'p-6'. Sigue con un objeto STYLES que es una hoja de estilos paralela. wrapper, header, body, footer — cada clave es una clase de Tailwind que ya se describe sola. Ahora, para saber cómo se ve, tienes que saltar a otro archivo. El editor no sigue la clave. Y a la tercera semana ya no recuerdas qué es STYLES.hint.
const STYLES = {
wrapper: 'flex flex-col gap-4 rounded-lg border p-6',
title: 'text-lg font-semibold',
hint: 'text-sm text-muted-foreground',
};
<div className={STYLES.wrapper}>
<h2 className={STYLES.title}>{title}</h2>
<p className={STYLES.hint}>{hint}</p>
</div>Eso no añade significado. Renombra. Una clase que no cambia se escribe junto al HTML. cn() existe para lo que cambia según el caso, no para pegar dos textos fijos. CVA existe para variantes reales, no para esconder p-6.
Si se comporta como un componente —tiene HTML, un rol, va a crecer o a repetirse— se declara como componente. Con function. No con const Icon = () =>. No con un JSX guardado en una variable que después se pega.
El criterio es corto. Clase que no varía: junto al HTML. Número o texto de dominio que se usa en tres sitios: constante con nombre de dominio. Pedazo de interfaz con cara y nombre: function. Si extraer solo pone un alias, no extraigas. El modelo cree que const es higiene. En la página, es un componente a medias.
Lo que la skill hace de verdad
Anti-Cliché no es la anécdota del review. Es el protocolo que el agente corre antes de escribir. Referencias por tema: arquitectura, componentes, servidor, Tailwind, interfaz, prosa.
Empecé con rules en el editor. Después un documento más largo. Después recortes, porque las rules kilométricas se diluyen. Terminé en una skill que no es perfecta. Pero el primer borrador llega sin el helper vacío, sin el JSX en un const, sin la interfaz que se reconoce a metros.
Eso no la deja cerrada. Cada cliché nuevo en un diff (lo que cambió en el archivo) entra. Si el promedio se mueve del violeta al cream + serif, la skill también se mueve. El criterio no cambia: si nadie lo eligió, no entra.
La skill no dice “escribe bonito”. Dice la pregunta. ¿Esto ya existe en el proyecto? ¿Se usa más de una vez? ¿Esta constante añade significado o solo nombra un texto? ¿"use client" está porque el browser lo necesita, o por costumbre? El resto (React 19, el index.ts que reexporta todo, Result, sparkles, delve) está en el archivo que el agente carga. Esta nota no sustituye ese texto.
Ninguna de esas reglas es original como idea. El valor está en tenerlas juntas, antes de tocar el proyecto, no en un comentario de PR que ya llegó tarde.
Convertirlo en un gate
Una skill que solo se lee cuando alguien se acuerda no sirve. El mismo criterio que Git Auditor y Knip: si te importa, que corra sin que lo pidas.
El texto canónico está publicado. Se instala así:
npx skills add enderpuentes/ai-agent-skills --skill anti-cliche-agentEl puntero en este sitio es la ficha Anti-Cliché. Esta nota no sustituye ese texto. Si hay conflicto entre el artículo y la skill, gana la skill.
En la práctica, en mis proyectos, Anti-Cliché queda en el repo (archivos ts, tsx y md), el agente la carga cuando toca interfaz o review, y la pasada humana deja de repetir “esto es un envoltorio vacío”. No reemplaza a TypeScript. No reemplaza a Knip. Corta el dialecto antes de que entre al cambio.
Lo que no hace
No formatea. No discute indentación. No te obliga a un framework. Si el proyecto ya usa un patrón, la skill dice respetarlo — inventar un paralelo “más limpio” también es cliché.
Tampoco es un detector automático en CI (el chequeo que corre al subir cambios). Es instrucción para el modelo, no un programa que inspecciona el código. Si quieres que el build falle, eso es ESLint o un test. Yo quería que el primer borrador no llegue con el dialecto.
Cierre
Antes tardaba más arreglando que escribiendo. Ahora no. Anti-Cliché existe porque me cansé de devolver ese promedio a mano, y porque el catálogo no se termina: cada cliché nuevo entra.
Sin ese borde, vuelvo al Tab. Con él, el agente escribe.
Si el agente va a escribir más rápido que tú, alguien tiene que nombrarle los clichés. Yo lo dejé por escrito: esta nota para el porqué, la skill para el protocolo. Carga la segunda. Cuando aparezca el siguiente cliché, también entra ahí.