Saltar al contenido

Mantenibilidad: escalar el código y el equipo

Un proyecto grande lo mantiene un equipo, y lo que aguanta el paso del tiempo son las convenciones que una máquina verifica: TypeScript en su modo más estricto con astro check, lint y formato que imponen una gramática común, commits convencionales y git hooks que cierran el paso a lo que no cumple, y documentación viva que reparte el conocimiento para que el proyecto no dependa de quien lo escribió.

TypeScript estricto y la verdad verificada

La primera red de seguridad es el tipo. Astro trae configuraciones de TypeScript listas para extender, y a escala la elección correcta no es la intermedia sino la más severa: astro/tsconfigs/strictest. Cada regla que activa —no permitir any implícito, exigir manejar null, prohibir accesos sin comprobar— es un error que dejas de descubrir en producción para descubrirlo mientras escribes. En un proyecto pequeño el modo estricto se siente como fricción; en uno grande, con gente que no escribió el código tocándolo, es lo que evita que un cambio inocente rompa algo tres módulos más allá.

JSON
// tsconfig.json{  "extends": "astro/tsconfigs/strictest",  "compilerOptions": {    "verbatimModuleSyntax": true  }}

El editor comprueba tipos mientras escribes, pero eso no basta: hace falta un comando que verifique el proyecto entero, incluidos los ficheros .astro que un tsc normal no entiende. Ese comando es astro check, y su lugar natural es la integración continua, como una puerta que no deja pasar código que no tipa. Junto a él, astro sync regenera los tipos que Astro deriva de tu contenido y tu configuración —los esquemas de las colecciones, los módulos virtuales—, de modo que el autocompletado y la comprobación conocen la forma exacta de tus datos. Correr astro sync antes de astro check en la CI garantiza que se verifica contra tipos frescos, no contra una foto vieja.

Lint, formato y una gramática común

Cuando ocho personas escriben en el mismo repositorio, cada una trae sus costumbres: comillas, sangrías, orden de imports. Discutir eso en cada revisión es desgaste puro. La cura es delegarlo en herramientas que impongan una sola gramática y la apliquen solas. El formato lo fija un formateador —Prettier con prettier-plugin-astro, o Biome como alternativa integrada— que reescribe el código a una forma canónica sin que nadie opine. El lint lo pone un linter —ESLint con eslint-plugin-astro— que va más allá del estilo y caza patrones peligrosos: una variable sin usar, una promesa sin esperar, un acceso que puede fallar.

JSON
// package.json{  "scripts": {    "check": "astro sync && astro check",    "lint": "eslint .",    "format": "prettier --write .",    "test": "vitest run"  }}

La distinción vale la pena tenerla clara: el formateador se ocupa de cómo se ve el código —algo sin opinión moral, mecánico, que nunca debería debatirse— y el linter se ocupa de qué hace el código —reglas con criterio, que pueden atrapar errores reales—. Separarlos evita que peleen y deja que cada uno haga bien su parte. Con ambos configurados, las revisiones de código dejan de gastarse en comas y sangrías y se dedican a lo único que un humano aporta y una máquina no: si el diseño es acertado.

Convenciones que la máquina impone

Tener los comandos no basta si alguien puede saltárselos. El eslabón que los vuelve obligatorios son los git hooks: ganchos que se disparan en momentos del flujo de git y pueden abortarlo. Un gestor como Lefthook —o Husky— los declara en un fichero versionado, de modo que todo el equipo comparte los mismos guardianes sin instalarlos a mano. En el pre-commit corres formato y lint solo sobre lo que cambió; en el commit-msg, validas que el mensaje siga una convención.

YAML
# lefthook.ymlpre-commit:  parallel: true  commands:    format: { run: prettier --check {staged_files} }    lint: { run: eslint {staged_files} }commit-msg:  commands:    conventional: { run: commitlint --edit {1} }

Esa convención de mensajes son los commits convencionales: un prefijo que declara la naturaleza del cambio, un ámbito opcional y una descripción. No es burocracia; es metadato legible por máquina. De él se derivan solos el registro de cambios y el número de versión, y en un monorepo esa automatización la remata una herramienta como Changesets, que agrupa los cambios de cada paquete, calcula qué versión le toca según la semántica de sus commits y publica solo lo que cambió.

Shell
feat(checkout): permite pagar con un solo clicfix(ui): el foco del boton respeta prefers-reduced-motionchore(deps): sube astro a la 7.1

El hook es la primera línea, rápida y local, que atrapa lo trivial antes de que salga de tu máquina; la CI es la línea de fondo, que vuelve a correrlo todo —porque un hook se puede saltar— y añade lo que tarda demasiado para el commit: la comprobación completa de tipos, los tests, el build de todas las apps.

YAML
# .github/workflows/ci.ymlname: cion: [push, pull_request]jobs:  calidad:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4      - uses: pnpm/action-setup@v4      - run: pnpm install --frozen-lockfile      - run: pnpm run lint      - run: pnpm run check   # astro sync mas astro check      - run: pnpm run test      - run: turbo run build  # respeta el grafo y cachea

Solo lo que pasa las dos líneas llega a la rama principal. Esa doble puerta es lo que mantiene sana una base de código que muchas manos tocan a diario, y en un monorepo la última orden delega en Turborepo para construir únicamente lo que cambió.

Documentar y escalar el equipo

Al final, un proyecto grande no lo mantiene un código: lo mantiene un equipo, y un equipo escala solo si el conocimiento no vive en una cabeza. La documentación que aguanta no es un manual que nadie actualiza, sino piezas vivas y ubicadas: un README por paquete que dice qué hace y cómo se usa; registros de decisión —los ADR— que capturan por qué se eligió algo, para que quien llegue no repita el debate ni deshaga la razón; y una ruta de muestrario que renderiza cada primitiva del design system en sus variantes, sirviendo a la vez de catálogo y de prueba visual.

El reparto de propiedad se declara también en el repositorio. Un fichero CODEOWNERS mapea zonas del árbol a las personas o equipos responsables, de modo que un cambio en el paquete de UI pide revisión a quien lo cuida y uno en checkout, a quien lo conoce. Es la organización por dominio del primer capítulo elevada a las personas: cada feature tiene dueño, y la estructura del código y la del equipo se reflejan la una en la otra.

TXT
# .github/CODEOWNERS/packages/ui/            @equipo-design-system/apps/web/               @equipo-web/src/features/checkout/  @equipo-pagos