# Cómo estructurar un monorepo .NET + Next.js sin que se vuelva un desastre

> Dos cadenas de herramientas en un repositorio funcionan cuando cada una es dueña de su subárbol y solo un contrato HTTP cruza el límite. La estructura, las cuatro reglas y los tres patrones de falla más caros.

# Cómo estructurar un monorepo .NET + Next.js sin que se vuelva un desastre

Un monorepo políglota funciona cuando cada cadena de herramientas es dueña de su propio subárbol y nada cruza el límite salvo un contrato HTTP y un puñado de archivos de configuración. Se vuelve un desastre cuando alguien intenta hacer que .NET y Node compartan un sistema de compilación.

## La estructura

```
apps/
  marketing-site/     Next.js: sitio público, SEO, precios
  web-app/            React: área de cliente autenticada
  admin-panel/        React: operación interna
backend/
  api/
    src/              Solución .NET: Api, Application, Domain, Infrastructure
    tests/            xUnit
config/
  branding.json  product.json  pricing.json  features.json
docs/
scripts/
package.json          npm workspaces, solo en la raíz
```

Dos cadenas de herramientas, dos raíces. `npm` nunca mira dentro de `backend/`; `dotnet` nunca mira dentro de `apps/`. El directorio `config/` es lo único que ambos lados leen.

## Regla 1: un solo lockfile, en la raíz

npm workspaces pone un único `package-lock.json` en la raíz del repositorio y ninguno en las apps individuales. Esto no es cosmético. Los lockfiles por app producen tres resoluciones distintas de la misma dependencia transitiva, tres conjuntos de alertas de seguridad para un mismo aviso, y una actualización de dependencia que hay que aplicar tres veces.

Agregá los lockfiles por app al `.gitignore` para que un `npm install` despistado dentro de `apps/web-app` no pueda commitear uno por accidente. Si tu plataforma de despliegue construye una sola app de forma aislada, hacé que instale desde el lockfile de la raíz en vez de dejar que genere el suyo.

El fijado de versiones también va en la raíz, con `overrides` en el `package.json` raíz, para que una versión forzada aplique en todas partes de una sola vez.

## Regla 2: el límite entre los stacks es HTTP, y nada más

La tentación es un paquete de tipos compartidos, generado desde los records de C# e importado por TypeScript. Resistila hasta haber sentido el dolor que lo justifique, porque acopla los ciclos de despliegue de dos cosas que se despliegan de forma independiente.

Lo que funciona en la práctica: la API publica un documento OpenAPI, y cada frontend mantiene un tipo de respuesta pequeño, escrito a mano, cerca del código que llama al endpoint. Se duplican unas pocas declaraciones de interfaz. A cambio, los frontends no tienen dependencia de compilación con el backend, y una refactorización del backend no puede romper la compilación de un frontend.

El corolario es que ningún frontend importa a otro frontend. Tres apps que comparten un lenguaje visual van a querer compartir componentes, y en el momento en que `web-app` importa de `admin-panel`, ya no tenés tres apps. Tenés una app con tres puntos de entrada y un radio de daño compartido. O duplicás el componente, o lo promovés a un paquete real del workspace con su propio `package.json`.

## Regla 3: la configuración es dato, y vive en un solo lugar

El nombre del producto, los colores de marca, los nombres de planes y las claves de funcionalidades aparecen en el sitio de marketing, en la app de clientes, en el panel de administración, en la API y en la semilla de la base de datos. Cinco copias son cinco lugares para olvidarse de uno.

Ponelos en `config/*.json` en la raíz y hacé que todos los consumidores lean de ahí: los frontends importan el JSON directamente, la API lo lee al arrancar, el script de semilla lo genera a partir de él. Renombrar el producto pasa a ser un diff de una línea en vez de una búsqueda por todo el repositorio.

La prueba de que esto funciona: rebrandear el producto entero debería tocar exactamente un archivo.

## Regla 4: la CI corre por proyecto, no todo o nada

Un monorepo donde cada push corre la matriz completa, es decir tres compilaciones de frontend, `dotnet test` y toda la suite end-to-end, entrena al equipo a ignorar la CI, porque un error de tipeo en un título de marketing tarda doce minutos en mergearse.

Dividila por ruta:

- Cambios bajo `apps/*` → lint, chequeo de tipos y tests unitarios de esa app
- Cambios bajo `backend/` → `dotnet build` y `dotnet test`
- Cambios en `config/` o en cualquier cosa compartida → todo
- Tests end-to-end → en la rama principal y bajo demanda, no en cada pull request

La suite end-to-end es la que sí debe estar acotada. Necesita una base de datos, una API corriendo y frontends compilados; es lo más lento y lo más frágil que tenés. Correla donde una falla merezca la atención de una persona, no en cada push de borrador.

## Qué sale mal de verdad

Tres patrones de falla explican la mayor parte del dolor de un monorepo políglota.

El primero es un Dockerfile compartido. La API .NET necesita una imagen con el SDK y un publish multi-etapa. Los frontends necesitan Node, y en la mayoría de las plataformas no necesitan contenedor en absoluto. Un solo Dockerfile para ambos significa que cada cambio en el backend reconstruye las capas del frontend. Dale a la API su propio Dockerfile y dejá que los frontends se desplieguen como salida estática o serverless.

El segundo es un entorno local que exige todo. Si trabajar en el sitio de marketing implica levantar Postgres, la API y otros dos frontends, la gente va a evitar el monorepo. Cada app debería correr sola contra una URL de API configurada, con un único `docker compose up` que levante la base cuando de verdad haga falta el backend.

El tercero es una CI por rutas que dispara de menos en silencio. Las reglas de arriba solo son seguras si el disparador "compartido" está realmente completo. Cuando cambia `config/` y la CI corre una sola app, publicás un cambio de nombre en dos de tres frontends. Mantené la lista de rutas compartidas lo bastante corta como para auditarla, y ante la duda, corré todo.

## Por qué un monorepo, después de todo

La recompensa es el cambio atómico. Agregar un campo a la API, exponerlo en el panel de administración y mostrarlo en el sitio de marketing es un commit, una revisión, un despliegue, un revert. Repartido en cuatro repositorios, son cuatro pull requests en un orden obligatorio, y una mala tarde si hay que deshacerlas.

Eso se paga con disciplina de compilación. Las cuatro reglas de arriba son el pago. Si las salteás, obtenés el acoplamiento de un monolito junto con la complejidad de herramientas de los microservicios.

