# Reglas del proyecto

## Idioma del código — regla dura, sin excepciones

**Todo identificador se escribe en inglés.** Sin excepciones y sin pedir permiso para aplicarla.

Cubre:

- Variables locales, parámetros y constantes de módulo
- Funciones y métodos, públicos y privados
- Clases, modelos, campos, columnas y tablas
- Módulos, paquetes y nombres de archivo
- Claves de diccionarios que viajan como props o como JSON de la API
- Nombres de tests y de fixtures
- Ramas de git
- **Las claves del catálogo de traducción** (`frontend/locales/*.json`)

Ejemplos de lo que **no** se acepta:

```python
lote = Batch.objects.get(id=batch_id)  # NO
dominios = Domain.objects.filter(...)  # NO


def _cupo_del_dia(lote): ...  # NO


MAX_POR_CUENTA = 200  # NO
```

```python
batch = Batch.objects.get(id=batch_id)  # sí
domains = Domain.objects.filter(...)  # sí


def _daily_quota(batch): ...  # sí


MAX_PER_ACCOUNT = 200  # sí
```

Esto vale para Python, TypeScript y TSX por igual.

## Los textos que ve el usuario van en inglés, y en el catálogo

**El idioma fuente del producto es el inglés.** Lo que ve el usuario se escribe en inglés y vive
en `frontend/locales/en.json`; el español rioplatense (voseo: «podés», «revisá», «tenés») es una
traducción más, en `frontend/locales/es-ar.json` (el archivo va en minúscula).

Las tres reglas:

1. **Ningún texto visible se escribe suelto en un `.tsx`.** Va como clave del catálogo y se
   resuelve con `t()`. Un literal en una pantalla es un texto que sólo existe en un idioma.
2. **El backend no manda texto a la interfaz.** Manda un `code` estable y los datos que la
   oración necesite; el catálogo del cliente arma la oración. Un aviso, un error de formulario y
   un paso del recorrido viajan como código, no como prosa.
3. **La API sí manda `message`, y en inglés.** Es contrato: quien integra lo lee en su log. La
   interfaz web lo ignora y traduce por `code`.

`LANGUAGE_CODE` es `en`. No vuelve a ser `es-ar`.

## Lo que sí va en español

- **Los comentarios y docstrings**, que explican **por qué** algo es así y nunca qué hace la línea
  de abajo.
- La documentación de `specs/`, la de `.agents/` y los mensajes de commit.
- Las traducciones de `frontend/locales/es-ar.json`, que son el producto hablando en español.

## La regla no admite excepciones

**No queda nada en español fuera de lo que la sección anterior autoriza.** La migración cubrió
todo, incluido lo que costaba más:

- Las claves del JSON `summary` de un lote, que estaban **escritas en la base**. Las reescribió
  `apps/jobs/migrations/0002_summary_keys_in_english`, en las dos direcciones.
- Las claves de props y del JSON de la API, junto con `contracts/openapi.yaml`.
- Lo que viaja en la dirección: filtros, claves de orden, `?days=`, `?created=`.
- Los nombres de las diez restricciones de base, cada uno con su migración.
- Las claves de sesión y los ids del DOM.
- Los encabezados del CSV de cobertura (`EXPORT_HEADER`), que son claves de datos: quedan en
  inglés y **congelados**, porque un script que procesa el archivo no puede depender de quién lo
  exportó. La columna de etiqueta legible, que sí es prosa, se traduce.

Que algo esté guardado en la base o publicado en un contrato explica **por qué cuesta más**, no
por qué se deja. Lo que corresponde es la migración, no la excepción.

Si mañana aparece una clave en español, es un descuido y se corrige: no hay una lista de casos
tolerados.

## La tarjeta estándar

**Toda tarjeta del producto es un `SectionCard`** (`frontend/components/SectionCard.tsx`). Es la
contracara de `Section`: una agrupa sin dibujar caja y la otra dibuja la caja, y por eso mismo **no
se anidan**.

Dos reglas duras, que no se vuelven a decidir al escribir cada pantalla:

1. **Toda tarjeta lleva título, y el título se ve.** `title` es obligatorio y **no existe un
   `titleHidden`**. `Section` sí lo tiene, porque agrupa cosas que a veces no necesitan encabezado
   a la vista —una región de filtros—; una tarjeta ya es una caja con borde propio, y una caja con
   borde y sin nombre obliga a deducir qué contiene mirando lo que hay adentro.
2. **Una ranura sin contenido no se rinde.** `actions`, `description`, `children` y `footer`
   ausentes no dejan un contenedor vacío: la `Card` reacciona a lo que tiene adentro, y un pie
   vacío le cambia el relleno a la tarjeta entera y le deja una franja con borde y fondo sin nada
   escrito.

Sólo dos piezas bajan a la primitiva `ui/card`, y las dos están declaradas: `MetricCard`, cuyo
título es la cifra, y la tarjeta enteramente clickable de Tools. Una tercera excepción se escribe
primero en `specs/001-gsc-sitemap-coverage/redesign/design-contract.md` §3.3 y recién después en el
`.tsx`.

## `max-w-prose` no se usa

**Regla dura, sin excepciones.** No se escribe `max-w-prose` en ninguna clase. Ni en páginas, ni
en componentes, ni en piezas del sistema. Si aparece en un diff, se saca antes de commitear.

El ancho de un bloque de texto lo decide el contenedor que lo contiene, no el párrafo.

## Una cifra se abre donde se la lee

**Toda cifra que representa un conjunto se despliega en el lugar, no en otra pantalla.** Enlazar a
otra vista se reserva para cuando el destino contesta **la misma pregunta** con **el mismo
conjunto**.

El caso que la origina: la ficha del lote decía «12 rastreadas y sin indexar» y enlazaba a la tabla
de cobertura filtrada por ese estado. La cifra era del recorrido; la tabla, del presente. Casi nunca
coincidían, y nada en pantalla explicaba la diferencia. El mismo defecto tenía el aviso de caída de
indexación, que llevaba a esa tabla sin decir cuál era la dirección.

Antes de escribir un enlace desde una cifra, la pregunta es **si el destino sabe de qué lote se está
hablando**. Si no lo sabe, es un modal: `UrlListDialog` es la pieza.

Dos reglas que van con ésta:

1. **Las listas viajan recortadas y con su total al lado.** El recorte se dice en pantalla, nunca se
   calla: un tope silencioso se lee como «esto es todo lo que hay».
2. **Lo que se muestra en un lote sale del lote.** De su resumen o de su historial, nunca del estado
   actual de la entidad, que un recorrido posterior ya pisó.

## Preguntar no es autorizar

Una pregunta se responde con una respuesta. Nada más.

Si en el mismo mensaje hay una autorización previa («arreglalo») **y** una pregunta, la pregunta la
suspende: se contesta y se espera. Una autorización dada antes de entender no vale para lo que se
entendió después.

Tampoco autorizan: «¿qué opinás?», «¿por qué pasa esto?», «no entiendo X».

Y si el agente escribe «te muestro antes de tocarlo», ahí termina el turno.

## Herramientas

- `Bash`/`PowerShell` son para instalaciones y comandos (`uv`, `npm`, `npx`, `git`).
- Los archivos se editan con las herramientas de edición, nunca con `sed`, heredocs ni scripts:
  acá ya causó ediciones que fallaron en silencio.

## Privacidad

Ningún nombre de persona real aparece en el código, en comentarios, en `TODO`, en strings, en
nombres de variable ni en mensajes de commit. Para atribuir un pendiente: `TODO:` a secas, o «lo
define el owner».
