# Investigación de rediseño de la interfaz

**Fecha**: 19 de agosto de 2026 · **Rama**: `rediseno-de-la-interfaz` · **Alcance**: las trece vistas

Esto es una **investigación**, no una implementación. Ningún archivo de la aplicación fue tocado.
Lo que sigue es el diagnóstico medido, el contrato de diseño que ordena las propuestas, y la
propuesta vista por vista.

| Documento | Qué trae |
|---|---|
| **Este archivo** | El consolidado: diagnóstico, causas, plan de trabajo |
| [`design-contract.md`](./design-contract.md) | El contrato del sistema de diseño. Escala, espaciado, anatomías, árbol de decisión, primitivas, 30 reglas verificables |
| [`views-home.md`](./views-home.md) | V0 Inicio · V13 Notificaciones |
| [`views-domains.md`](./views-domains.md) | V4 Dominios · V5 Alta · V6 Ficha |
| [`views-coverage.md`](./views-coverage.md) | V7 Cobertura |
| [`views-jobs.md`](./views-jobs.md) | V8 Sitemaps · V9 Lotes · V10 Ficha de lote |
| [`views-onboarding.md`](./views-onboarding.md) | V1 Login · V3 Recorrido guiado · V2 Configuración |
| [`views-account.md`](./views-account.md) | V11 Claves de API · V12 Sesiones |

---

## 1. La tesis

El problema no es que haya demasiada información. **Es que la pantalla no jerarquiza, y no
jerarquiza porque no hay un sistema que la obligue a hacerlo.** Los tres síntomas que se sienten al
entrar —«todo parece vomitado», «no sé adónde mirar», «no sé qué me sirve»— tienen tres causas
materiales, todas verificables con una búsqueda:

1. **No hay escala tipográfica.** El mismo nivel de título está dibujado de siete maneras. El 72 % de
   los bloques de texto de la vista más grande son `14px/400`: sin contraste de tamaño, todo pesa
   igual, y lo que pesa igual no se puede ordenar con la vista.
2. **No hay una tarjeta: hay dos.** Siete de las catorce páginas no importan `card` y dibujan la suya
   con `rounded-lg border`, mientras la primitiva usa `rounded-xl` y `ring-1`. **Son dos formas
   distintas de verdad**, con otro radio y otro borde. De ahí que «las cards de un lado sean
   distintas de las del otro».
3. **No hay revelación progresiva.** `tabs`, `dialog`, `dropdown-menu`, `accordion`, `popover` y
   `field` están instalados o disponibles y **no se usan en ninguna página**. Todo lo que existe se
   dibuja al mismo tiempo, en la misma pantalla, al mismo nivel.

La consecuencia se mide: **la vista más valiosa del producto pone su tabla a 1.398 px del tope**, así
que al llegar no se ve ni una fila de datos.

---

## 2. Cómo se hizo

1. Se levantó la aplicación (`npm run dev`) con la cuenta de prueba y datos sembrados.
2. Se navegaron las **trece vistas** en 1440×900 y se capturó cada una entera y en su *fold* (lo que
   entra en pantalla al llegar). Las capturas están en `.playwright-mcp/redesign/` —no versionado—.
3. Se midió, por vista y sobre el DOM real: alto, nodos, bloques de texto, estilos tipográficos
   distintos, encabezados y su tamaño, cajas con borde, controles, tablas y filas.
4. Se fijó **primero** el contrato de diseño, en un agente dedicado. El propio
   `estado-del-rediseno.md` avisa por qué: *«fijá la tabla de equivalencias o el contrato antes de
   repartir; lo que se rompe siempre son las costuras»*.
5. Recién entonces se repartieron las trece vistas entre **seis agentes en paralelo**, todos atados
   al mismo contrato, con las skills de diseño de interfaz y las reglas `RT-xx` del `ux-checklist`
   como insumo obligatorio.

### Dos correcciones a la medición inicial, para que el reporte no arrastre errores

- **El alto de Cobertura (5.567 px) es en buena parte un artefacto.** El contenido termina cerca de
  y≈2.130; el resto sale del `max-h-[70vh]` de `DataTable` medido contra una captura de página
  completa. **La métrica honesta no es el alto, es cuántas filas se ven al llegar** — y hoy son cero.
- **Dominios sí usa tabla.** El inventario inicial miró los imports de `@/components/ui/` y V4 llega
  a `ui/table` por adentro de `DataTable`. Media vista ya estaba bien y no había que rehacerla.
- **«Cards» cuenta cajas con borde, no tarjetas.** En `radix-nova`, `Button` e `Input` son
  `rounded-lg border`. Las «21 tarjetas» de Sesiones son **21 botones y ninguna `Card`** —ese archivo
  ni importa `card`—. El diagnóstico correcto ahí no es «cards a lo loco» sino **cero estructura**.

---

## 3. El diagnóstico, en números

| Vista | Veredicto | Sev. | Alto | Fold | Estilos tipográficos | Cajas con borde | Bloques de texto |
|---|---|---|---|---|---|---|---|
| **V7 Cobertura** | rehacer | **5** | tabla a 1.398 px | **0 filas** | 9 | 25 | 549 |
| **V0 Inicio** | reestructurar | **5** | 1.782 px | 51 % | 7 | 16 | 219 |
| **V6 Ficha de dominio** | rehacer | 4 | 1.601 px | 56 % | 8 | 18 | 63 |
| **V2 Configuración** | rehacer | 4 | 900 px* | 100 %* | 9 | 15 | 103 |
| **V12 Sesiones** | rehacer | 4 | 1.543 px | 58 % | 5 | 21 + 20 `<details>` | 203 |
| **V9 Lotes** | reestructurar | 4 | 3.540 px | 25 % | 6 | 17 | 444 |
| **V10 Ficha de lote** | reestructurar | 4 | 1.233 px | — | 6 | 7 | 55 |
| **V11 Claves de API** | reestructurar | 4 | 1.004 px | — | 6 | 8 | 20 |
| **V4 Dominios** | reestructurar | 3 | filas de 114 px | 100 % | 5 | 9 | 48 |
| **V5 Alta de dominio** | reestructurar | 3 | 974 px | — | 6 | 9 | 21 |
| **V8 Sitemaps** | reestructurar | 3 | tabla a 760 px | — | 8 | 17 | 59 |
| **V3 Recorrido guiado** | reestructurar | 3 | 1.368 px | — | 6 | 8 | 59 |
| **V13 Notificaciones** | reestructurar | 3 | 240 px de filtros | 100 % | 6 | 17 | 35 |
| **V1 Login** | ajustes finos | 2 | — | — | — | — | — |

\* Configuración **entra** en 900 px sólo porque tres `<details>` están cerrados en el estado
verificado. El estado que la vista existe para resolver —la credencial caída— los abre. *Cuando la
medición de una pantalla depende de un plegado, hay que medir el estado peor, no el mejor.*

**Encabezados, hoy**: `H2 20/500`, `H2 18/500`, `H2 16/500`, `H2 16/400`, `H2 14/500`, `H3 14/500`,
`H3 14/600` — siete formas del mismo nivel. Y tres vistas (Dominios, Alta, Sesiones) no tienen
**ningún** `h2` ni `h3`.

**Bordes de tarjeta dibujados a mano en páginas**: 24, repartidos en 9 archivos. **Tamaños fuera de
escala**: 16 usos de `text-lg` / `text-xl` / `text-3xl`. **Huérfanos**: `data-table.tsx` (820 líneas)
y `login-form.tsx` (105 líneas) no los importa nadie.

---

## 4. Las seis causas raíz

Cada una explica varios síntomas a la vez, y arreglarlas es lo que hace que el rediseño no vuelva a
divergir en tres meses.

**1 · `CardTitle` rinde un `<div>`.** (`ui/card.tsx:36`) Una vista puede tener títulos visibles y
perfectos y medir **cero encabezados**. Es el origen real de la falta de jerarquía semántica en V4,
V5 y V12, y afecta a toda vista con tarjetas. Se arregla en la primitiva —`asChild`—, no en catorce
archivos.

**2 · El mismo dato escrito dos veces en la misma pantalla.** No es duplicación de componentes: es
duplicación de datos, y aparece en todos lados. El tablero escribe 92 / 120 / 28 / «1 dominio» / la
fecha en una frase de 20 px **y otra vez** en una tarjeta de 30 px, a 200 px de distancia. Cobertura
escribe el reparto por estado como dato inerte y otra vez como botonera, a 400 px. Lotes dice en
prosa («Se procesaron 317 de 2.000 URLs») lo que la columna de al lado dice en cifras. La ficha de
lote repite tres números tres veces. **Es la mitad de los bloques de texto medidos.**

**3 · La prosa dentro de una celda.** Lo que hace largas a las tablas no es la cantidad de columnas:
son las celdas que explican en vez de dar un valor. La celda «Acceso» de Dominios apila badge +
párrafo + botón y llega a 114 px — y ese párrafo es **del estado, no de la fila**: con la credencial
caída, la misma oración de 24 palabras se repite en las trescientas filas.

**4 · Nada se pliega.** Ningún patrón de revelación progresiva está en uso. La exportación de
Cobertura ocupa 260 px permanentes para una acción que se usa a veces; el alta de sitemap ocupa la
segunda banda de la página para algo que se hace una vez y se mira durante meses; la guía de
Configuración son 500 px entre el estado de la conexión y la ficha de la credencial.

**5 · El estado sano y el estado roto se dibujan con la misma caja.** En el tablero, «no hay nada que
hacer» ocupa lo mismo que «hay algo roto», y la diferencia queda reducida al tinte del borde. Eso es
RT-04 —«ningún estado se comunica sólo por color»— aplicado al revés, a nivel de disposición.

**6 · Una vista, una pregunta, una acción — y hoy no se cumple.** El único botón visible al entrar al
tablero es «Agregar dominio»: a quien tiene un dominio esperando autorización, la pantalla le ofrece
agregar un segundo. El bloque que describe el problema no tiene ningún botón.

---

## 5. El contrato de diseño

Está completo en [`design-contract.md`](./design-contract.md). Lo esencial:

### Escala tipográfica — seis pasos, cerrada

| Paso | Rol | Tamaño / peso | Semántica |
|---|---|---|---|
| T1 | Título de página | 16 / 500 | `<h1>`, **sólo** lo escribe el armazón |
| T2 | Dato principal | 24 / 600 `tabular-nums` | ninguna: es el valor, no un encabezado |
| T3 | Título de sección | 16 / 500 | `<h2>`, siempre |
| T4 | Título de bloque | 14 / 500 | `<h3>`, siempre |
| T5 | Cuerpo | 14 / 400 | el default |
| T6 | Metadato | 12 / 400–500 muted | encabezados de columna, pies, badges |

Mueren `text-lg`, `text-xl`, `text-3xl` y `font-bold`. T2 es **una cifra**, nunca una palabra de
estado —hoy la ficha de dominio dibuja «En cola» a 24 px como si fuera un número—. Y el `12,8 px`
medido no era un valor suelto de nadie: es el `text-[0.8rem]` de `Button size="sm"` del propio
estilo `radix-nova`. Es un tamaño de **control**, se deja, y la regla pasa a ser no mezclar
`size="sm"` con `size="default"` en la misma fila.

### Espaciado — cinco valores: 4 · 8 · 12 · 16 · 24

El ritmo de 24 px entre secciones **ya lo da `<main>`**: ninguna vista lo redefine. El espaciado
interno de una tarjeta sale de `--card-spacing`, nunca de un `p-*` propio (hoy Claves usa `p-5`,
Cobertura `p-4`, Lotes `px-3 py-2` y el recorrido mezcla los tres). Toda prosa lleva `max-w-prose`.

### El árbol de decisión — se recorre en orden y se para en el primer sí

Esto es lo que evita que cada pantalla elija distinto para el mismo caso:

1. ¿Destructivo o irreversible? → **AlertDialog**
2. ¿Tiene URL propia o se llega desde afuera? → **página propia**
3. ¿Formulario? ≤5 campos → **Dialog** · >5 → página o `Section`
4. ¿Detalle de una fila con vuelta a la lista? → **Sheet**
5. ¿2–5 vistas hermanas del mismo objeto? → **Tabs**
6. ¿3+ bloques secundarios? → **Accordion** · ¿uno? → **Collapsible**
7. ¿Control chico anclado? → **Popover**
8. ¿Previsualización de una entidad enlazada? → **HoverCard**
9. ¿Ayuda de ≤12 palabras? → **Tooltip**
10. ¿2+ acciones sobre una fila? → **DropdownMenu**
11. Si no → se queda en la página

**`Drawer` no compite con `Sheet`**: es el mismo componente abajo de `md`. **Todo estado de
revelación va a la URL** (pestaña, panel, página, orden, filtro), salvo el popover, que no revela
contenido propio.

### Primitivas

**A usar, ya instaladas y desaprovechadas**: `field` (la más grave: ningún formulario la usa), `tabs`,
`dropdown-menu`, `separator`, `tooltip`, `select`, `toggle-group`, `skeleton`.

**A instalar**:
```
npx shadcn@latest add @shadcn/empty @shadcn/item @shadcn/spinner @shadcn/accordion \
  @shadcn/collapsible @shadcn/dialog @shadcn/popover @shadcn/hover-card \
  @shadcn/button-group @shadcn/input-group @shadcn/kbd
```

**Descartadas por escrito**, para que nadie las agregue «por completitud»: `breadcrumb` (el
encabezado es de una línea y la barra lateral ya dice dónde estás), `checkbox` (no hay selección
múltiple y no se inventa una), gráficos nuevos (un apilado con `UNKNOWN` adentro es RT-03 dibujado
al revés).

**Las dos tablas**: queda `DataTable.tsx` —servidor, estado en la URL, `aria-sort`, RT-09 cumplido—;
se borra `data-table.tsx`, que pagina en el cliente, trae arrastrar-para-reordenar que nadie
necesita y **no lo importa ninguna página**. Borrarlo libera además las cuatro dependencias
`@dnd-kit/*`.

---

## 6. Los componentes compartidos

Éste es el pedido explícito: *«creá componentes compartidos para que todo se vea parejo»*. El
catálogo completo, con su anatomía y sus props, está en el contrato §3 y en cada informe. Resumen:

### Del contrato — la base que usan todas las vistas

| Componente | Qué es | Qué reemplaza |
|---|---|---|
| `PageIntro` | La línea de estado bajo el encabezado | Los badges y fechas sueltos que hoy flotan o intentan entrar en `actions` |
| `Section` | El agrupador con título. **Sin borde ni fondo** | ~20 `<section className="rounded-lg border p-4">` |
| `Card` | La primitiva instalada, sin envoltorios | Los 24 `rounded-lg border p-*` de las páginas |
| `MetricCard` | La cifra con lo que la mantiene honesta | Las tarjetas del tablero, las tres de la ficha, los dos bloques de `CoverageSummary` |
| `DataTable` | La tabla, congelada como está | Las listas que hoy usan `ui/table` a pelo, sin paginar |
| `LabelValue` / `DescriptionList` / `Item` | El par etiqueta/valor, con y sin acción | Las fichas de dominio y de lote enteras |
| `StatusBadge` | Un solo badge, cinco tonos cerrados | Las **dos** formas que conviven hoy: `rounded-md border` y `rounded-4xl` |
| `EmptyState` · `Skeleton` · `Spinner` | Vacío y carga | El vacío ya existe; la carga falta en varias vistas |
| `ServerErrorNotice` + familia `field` | Error de servidor y de campo | Los formularios que arman el cuarteto a mano |
| `DangerZone` | Las acciones peligrosas, al pie | «Cerrar todas las demás», hoy en el slot del encabezado |

Los tres badges existentes —`CoverageStateBadge`, `BatchStateBadge`, `AccessStateBadge`— **conservan
su nombre, sus mapas y sus docstrings** y pasan a rendir a través de `StatusBadge`. Ahí vive el
conocimiento del dominio: por qué `PARTIAL` tiene silueta propia, por qué `UNKNOWN` no es negativo.
Es el cambio más chico que unifica la forma sin perder el razonamiento.

### Nuevos, pedidos por las vistas

| Componente | Para qué | Vistas |
|---|---|---|
| `DomainTabs` | Las cuatro caras de un dominio como barra de pestañas-enlace | V6, V7, V8, V9 |
| `DomainIdentity` | Preset de `PageIntro` con el estado del dominio. **Es lo que hace cumplir R-F** | V6, V7, V8, V9 |
| `NoticeItem` | [badge][título con enlace][motivo][fecha][acción] | V0, V13 |
| `RowActions` | El `DropdownMenu` de la última celda, con el `ConfirmDestructive` montado **fuera** de la fila | V4, V8, V11, V12 |
| `DetailSheet` | El detalle de una fila, con su id en la URL | V7, V12, V8 |
| `FilterBar` / `FilterToggleGroup` / `FilterPopover` / `FilterChips` | Los filtros de una lista, en **una línea** | V4, V7, V8, V9, V13 |
| `CoverageFigure` | Los mismos tres números de cobertura, dichos igual en los tres lugares donde aparecen | V4, V6, V7 |
| `CoverageStateList` | Los diez estados en las tres familias: **es a la vez el desglose y el filtro** | V7 |
| `QuotaCostDialog` | La confirmación de lo que gasta cupo. Una sola, para las dos puertas al mismo endpoint | V6, V7 |
| `SecretRevealDialog` | «Este dato existe una sola vez» | V11 |
| `BatchIssues` | Los dos bloques de un lote: fallas reales contra notas | V10, V8 |
| `FormDialog` | El formulario corto que abre desde `?new=1` y **no se cierra al enviar** | V8, V11, V2 |
| `CodeChip` | Un `<code>` con **una** forma | V2, V3, V5, V6, V11 |
| `StepList` / `StepItem` | El acordeón de los siete pasos, con `?step=` | V3 |
| `ServiceAccountAddress` · `AccessiblePropertyList` · `KeyFileField` · `GoogleConsoleLink` · `TaskColumn` | Las ocho piezas hoy duplicadas entre Configuración y el recorrido | V2, V3 |
| `PollingIndicator` · `BatchProvenance` · `RunningBatchCard` · `ExportAction` · `ExportStatusCard` · `ConnectionSummary` · `DomainQuotaDialog` | Piezas de una o dos vistas | varias |

---

## 7. Ocho defectos de corrección encontrados de paso

Estos **no son de diseño**. Salieron al leer el código para rediseñar, y varios rompen reglas duras
del checklist. Van primero porque rediseñar encima de ellos sería decorar un error.

1. **R-C roto: un lote `PARTIAL` puede dibujar la barra llena y sólida.** `_close` marca `PARTIAL`
   cuando `summary.errors` no está vacío, pero `_submit_if_changed` agrega su nota sin tocar
   `failed_items` ni `processed_items` —y ese sitemap ya sumó al leerse—. Queda
   `processed == total`, `failed == 0`: barra al 100 %, sin rayas y sin hueco. Lo único que lo
   distingue de un `COMPLETED` es la palabra del badge, que es precisamente lo que R-C prohíbe.
2. **Y la frase que la acompaña dice lo contrario**: en ese mismo caso se imprime **«Fallaron 0 de 7
   sitemaps»**.
3. **Un lote `FAILED` que anuncia «No falló ningún ítem».** `_fail` cierra en `FAILED` con
   `failed_items` en 0, y `FailureList` titula según `failed_items`. La captura `v10b` muestra las
   dos cosas en la misma pantalla.
4. **R-F no se cumple en Cobertura, Sitemaps ni Lotes.** Ninguna de las tres rinde el badge de
   acceso, y las que lo dicen en prosa lo hacen dentro de un `canOperate &&`. Con la credencial de la
   cuenta caída, las tres sub-vistas muestran datos viejos **sin ninguna marca de que lo son**.
5. **El panel que revela una clave de API no es modal.** Tiene `role="alertdialog"` sobre un `<div>`:
   no atrapa el foco, no tapa la barra lateral, no bloquea la navegación. Un lector de pantalla
   anuncia un diálogo que no existe, y cualquiera puede tabular afuera, clickear «Dominios» o apretar
   F5 y **perder el secreto para siempre**.
6. **El aviso de cuenta se repite en su propia página de destino.** En `/settings` con la credencial
   rota, el mismo hecho se dice tres veces y una es un botón que navega a donde ya estás. Peor: la
   primera pantalla del producto —`/onboarding`, por el redirect de la raíz— muestra un banner
   «Conectar Google → /settings» **encima de un recorrido cuyo paso 3 es exactamente eso**.
7. **Botones que no hacen lo que dicen.** «Crear la primera clave» sólo ejecuta un `focus()` sobre un
   campo que está 400 px más arriba y fuera de la vista. El `EmptyState` de Sitemaps usa un `<a
   href="#location">` que salta **hacia arriba**, a un formulario que ya estaba a la vista.
8. **El contador del menú y la página no hablan del mismo recorte.** El badge dice «1» y aterriza en
   la lista completa. El propio docstring del servidor ya tiene razón —«"Sin leer" no es un filtro
   más: es la vista que trae a la persona acá»— pero el enlace no lo cumple. Es un cambio de destino,
   no de armazón.

**Además, sobre `summary['errors']`** —la deuda anotada en `estado-del-rediseno.md`—: la separación
**ya existe en los datos**. Los dos únicos sitios que incrementan `failed_items` son exactamente los
dos que escriben `f'{location}: {motivo}'`, así que `item !== null` ⇔ intento fallido es cierto fila
por fila. La pantalla decide con `failed_items` a nivel lote, que es la única forma de equivocarse.
El arreglo inmediato es usar `item`; el durable es guardar `{item, reason, kind}` con su migración,
igual que `0002_summary_keys_in_english`.

---

## 8. Las decisiones de fondo, ya tomadas

Para que no se rediscutan pantalla por pantalla. Todas salen del árbol de decisión, no del gusto.

| Pregunta | Decisión | Por qué |
|---|---|---|
| ¿La ficha de dominio va con `Tabs`? | **Secciones, precedidas por una barra de pestañas que es navegación** (`TabsTrigger asChild` + `<Link>`), sin `TabsContent` | Cobertura, Sitemaps y Lotes contestan sí en el **paso 2**: ya tienen URL propia. La primitiva pone la forma, el enlace pone la semántica, y el estado vive en el path |
| ¿El alta de dominio es modal? | **Página** | Paso 2: tiene URL propia y el servidor la usa como destino de redirección. Un `Dialog` cuyo error de validación te cierra y te deposita en una página está roto |
| ¿El alta de sitemap es modal? | **Dialog**, con `?new=1` en la URL | Un campo. Y el parámetro es lo que permite reabrirlo **con el error adentro** después del redirect, que es lo que sostiene la decisión de rechazar al pegar |
| ¿Las notificaciones son un panel del encabezado? | **Página** | Paso 2, y además el panel no es construible sin romper el armazón: `actions` es por página. La barra lateral ya *es* el punto de entrada global |
| ¿El detalle de una URL? | **Sheet** | Paso 4. No cambia el patrón: cambia el disparador, que pasa a ser la dirección misma en la primera celda |
| ¿La revelación de una clave? | **AlertDialog** | Paso 1: lo irreversible no es crear la clave, es **cerrar el panel**. Y el paso 2 **descarta activamente** la página propia: una URL implicaría que se puede volver a pedir, y RT-06 promete lo contrario |
| ¿Hay selección de filas en Cobertura? | **No, y no se inventa** | El endpoint no la puede recibir; y mentiría, porque la cola elige las URLs por prioridad cuando corre. **El recorte ya es la selección y es mejor**: vive en la URL, sobrevive a paginar, y es lo que la exportación se lleva de verdad |
| ¿El recorrido guiado se funde con Configuración? | **No. El recorrido se queda como página; Configuración deja de dibujar su copia de los pasos** | Los pasos 5 a 7 no son de esa pantalla. Lo que muere es la **tercera copia** de los mismos pasos: el servidor los escribe una vez y la interfaz los dibuja con tres componentes distintos |
| ¿Dónde va el medidor de cupo? | **Adentro del diálogo que confirma la acción que lo gasta** | Ahí sus 200 px se ganan el lugar. Hoy Cobertura dispara sin decir nada mientras la ficha confirma nombrando los dos bolsillos: **dos puertas al mismo endpoint diciendo cosas distintas** |
| ¿`login-form.tsx`? | **Se borra el archivo; se adopta la primitiva `field`** | Seis cosas prohibidas en 105 líneas: copia en inglés, tres proveedores de identidad inexistentes, dos enlaces a `#`, «Sign up» y un `/placeholder.svg` |

---

## 9. Qué cambia al entrar — el antes y el después

**Inicio.** Tres casos que hoy son la misma pantalla:

- *Cuenta nueva*: la página recupera su bajada —hoy la rama sin dominios la pierde, justo el día en
  que más falta hace—, un solo `EmptyState` con título real, **un** botón primario y un enlace
  secundario. La única acción que pide la pantalla: agregar el primer dominio.
- *Hay dominios pero todavía no hay URLs* —caso que hoy nadie trata aparte—: en vez de cuatro
  tarjetas con `—` y un gráfico vacío, un `EmptyState`. No es quitar información: no hay ninguna.
- *En régimen*: `PageIntro` de una línea, la fila de métricas entera arriba del fold, el gráfico con
  su explicación **antes** y no al pie, cinco lotes como filas. **La única acción que pide la
  pantalla: ninguna, y la primera línea lo dice.** Una herramienta que se mira todos los días tiene
  que poder decir «hoy no hay nada»; si siempre empuja a hacer algo, se deja de creer en el aviso el
  día que hay algo de verdad.

~1.030 px estimados contra 1.782, y la diferencia entre sano y roto deja de ser un tinte: es **una
línea contra un bloque con un botón**.

**Cobertura.** La primera fila de datos pasa de y≈1.398 a **y≈440**: 958 px más arriba. Al llegar se
ven las tres cifras que contestan la pregunta de la vista —las tres familias de RT-03, con sus dos
denominadores—, el recorte que se está mirando (incluso si llegó puesto desde un aviso) y **diez
URLs** con su estado y su fecha. Hoy se ven cero. Los diez estados dejan de ser una botonera de
catorce botones y pasan a un `Popover` que **es a la vez el desglose y el filtro** —hoy son el mismo
dato dibujado dos veces a 400 px de distancia—.

**Lotes.** La fila baja de ~100 px a ~44: el fold pasa de 5 lotes a 13 o 14. Lo que la achica es
sacar la prosa de las celdas —«Se procesaron 317 de 2.000 URLs hasta ahora» al lado de una columna
que dice `317 de 2.000`— y darle a cada cifra su columna numérica alineada.

**Sesiones.** La fila baja de 57 px a ~48, los 20 `<details>` se van a un `Sheet`, los 21 botones
sueltos se van a un menú por fila, y «Cerrar todas las demás» baja del encabezado a una `DangerZone`
al pie —hoy la acción más peligrosa del producto está a un clic del disparador de la barra lateral—.
La sesión actual se marca con un badge y **su celda de acciones queda vacía**: la única que no se
puede cerrar es la única sin menú.

**Claves de API.** ~640 px contra 1.004, con las dos listas pobladas. Y el momento irrepetible pasa a
ser un modal de verdad, con salida por compuerta: **un clic si copiaste, dos si no** —la fricción cae
exactamente sobre el caso de pérdida y sobre ningún otro—, `Escape` por la misma compuerta, una
entrada de historial para que el botón Atrás lo cierre en vez de irse, y `beforeunload` mientras no
se copió.

---

## 10. Plan de trabajo sugerido

El orden importa: cada fase habilita a la siguiente, y las costuras se rompen cuando una firma
compartida cambia después de que sus consumidores terminaron.

**Fase 0 — Los defectos de corrección.** Los ocho de §7. Son bugs, no diseño, y varios rompen reglas
duras. Van con sus tests.

**Fase 1 — Las primitivas y las piezas base.** Instalar las once primitivas que faltan, borrar
`data-table.tsx` y `login-form.tsx` con sus dependencias, arreglar `CardTitle` para que acepte
`asChild`, y construir `Section`, `PageIntro`, `MetricCard`, `StatusBadge`, `Item`, `LabelValue`,
`CodeChip`, `DangerZone`, `RowActions`, `DetailSheet`, `FormDialog`. **Nada de esto se ve todavía**,
y es la fase que decide si el resultado queda parejo.

**Fase 2 — Las vistas de severidad 5.** Inicio y Cobertura. Son la puerta de entrada y la vista más
valiosa, y entre las dos ejercitan casi todas las piezas de la fase 1.

**Fase 3 — La rama del dominio, completa y junta.** Ficha, Cobertura, Sitemaps y Lotes comparten
`DomainTabs` y `DomainIdentity`; hacerlas por separado es garantizar que la identidad se sostenga
distinto en cada una. Acá se cierra R-F.

**Fase 4 — El primer día.** Login, recorrido guiado y Configuración, con la unificación de las ocho
piezas duplicadas entre las dos últimas.

**Fase 5 — La cuenta.** Claves de API y Sesiones, juntas y por el mismo motivo: hoy no se parecen
porque se diseñaron separadas.

**Fase 6 — Notificaciones, Dominios, Alta y las fichas.** Lo que queda, ya sobre un sistema asentado.

---

## 11. Lo que no se toca

- **El armazón.** `AppLayout`, `site-header`, `app-sidebar`, `nav-*`. El encabezado sigue siendo una
  franja de alto fijo y lo que se le pasa como `actions` sigue siendo de **una sola línea**.
- **Los mapas de dominio**: los diez estados de cobertura con sus iconos y explicaciones, los cinco
  de lote, los cinco de acceso, `ACCESS_ERRORS`, `CredentialErrorPanel`, `SUBMIT_RESULTS`, la máquina
  de estados del recorrido. Ahí vive el conocimiento del producto.
- **Las decisiones de producto ya argumentadas en el código**: que el mensaje de credenciales sea
  ambiguo a propósito, que entrar a notificaciones no vacíe el contador, que no se le invente un
  nombre plausible a un dispositivo, que no haya botón de reintentar cuando el corte fue por cupo,
  que `NO_PROPERTIES` no se pinte de rojo, que las dos formas de propiedad se vean a la vez.
- **`useBatchPolling`** como único `setInterval` del producto, y las dos únicas barras dibujadas a
  mano que sobreviven: `BatchProgress` y `QuotaMeter`.
- **El contrato del servidor.** Todo lo propuesto reordena y reagrupa lo que ya llega.

### Lo que sí se le pediría al servidor

Cuatro cosas, ninguna inventada — las cuatro son un `count`, un id o un filtro sobre consultas que ya
se hacen: `notifications.unread_action` (para que el contador y la página digan lo mismo),
`domains.only_id` (para no mandar a elegir entre un solo dominio), un filtro `q` sobre `hostname` (a
300 dominios, encontrar uno es caminar seis páginas) y `table_props(prefix=)` (para que dos listas en
una página no se peleen `page` y `sort`).

### Dos anotaciones sobre la navegación

Ninguna toca la anatomía del armazón —el propio aviso de `app-sidebar.tsx` autoriza cambiar entradas,
rótulos y destinos—, pero las dos son decisión del owner:

- **«Recorrido guiado» y «Conexión» como entradas hermanas obligan a elegir entre dos rótulos que no
  dicen en qué se diferencian.** La propuesta las unifica en «Conexión con Google», con el recorrido
  alcanzable desde adentro.
- **El grupo «Sistema» tiene una sola entrada, así que su rótulo no agrupa nada**, mientras
  «Sesiones» vive dentro del menú de la cuenta. Las dos vistas contestan la misma pregunta —«¿quién y
  qué tiene acceso a mi cuenta?»— y esa asimetría es la causa de que no se parezcan en nada. El
  contraargumento —GitHub y Google ponen las sesiones bajo el avatar— está anotado en
  [`views-account.md`](./views-account.md) para que la decisión sea informada.

---

## 12. Cómo se verifica que quedó parejo

El contrato trae **30 reglas verificables** (§6). Las que se comprueban con una búsqueda:

```bash
grep -r "rounded-lg border\|rounded-xl border" frontend/pages/          # → 0 (hoy: 24)
grep -r "text-lg\|text-xl\|text-3xl\|font-bold\|text-\[" frontend/pages frontend/components \
     --exclude-dir=ui                                                    # → 0 (hoy: 16)
grep -r "transition-all" frontend/pages frontend/components --exclude-dir=ui   # → 0
ls frontend/components/data-table.tsx frontend/components/login-form.tsx       # → no existen
```

Y las que se comprueban mirando una pantalla: ninguna vista con cero encabezados · ninguna `Card`
dentro de otra · dos piezas del mismo tipo con el mismo ancho · toda columna numérica a la derecha
con su encabezado · toda fecha en columna propia · todo porcentaje con su denominador y `UNKNOWN`
fuera de todos · todo estado legible en escala de grises · ninguna fila clickeable entera · ninguna
acción destructiva en el slot del encabezado · `actions` en una línea.

**Una regla que hay que corregir antes de aplicarla**: la del `transition-all` da siete archivos hoy,
**todos en `components/ui/`** y venidos del estilo `radix-nova`, y cero en `pages/`. La regla vale
excluyendo `ui/`; tocar `ui/button.tsx` y `ui/badge.tsx` es una tarea aparte y consciente.

**Y una excepción que hay que escribir**: «ninguna página rinde su propio `<h1>`» es correcta para
las trece vistas con sesión e **imposible** para Login, que no usa `AppLayout` porque todavía no hay
cuenta. Si la excepción no queda escrita con su motivo, alguien la va a «arreglar».
