# Estado de la implementación del rediseño

**Fecha**: 20 de agosto de 2026 · **Rama**: `rediseno-de-la-interfaz`

Este archivo existe para poder **retomar sin el hilo de la conversación**. Dice qué está hecho, qué
falta, con qué reparto seguir y cómo levantar el entorno.

---

## Dónde estamos

| Fase | Qué | Estado |
|---|---|---|
| Investigación | Diagnóstico de las trece vistas y contrato de diseño | ✅ `e449903` |
| 0 · Defectos | Seis contradicciones de pantalla, con sus tests | ✅ `ca9a2e6` |
| 1A · Átomos | Once primitivas, limpieza, `CardTitle` con `asChild`, ocho piezas base | ✅ `63c5a51` |
| 1B · Moléculas | Nueve piezas compuestas + dos agregados a `DataTable` | ✅ `58c1d29` |
| **2 · Siete vistas** | Inicio · Avisos · Claves · Sesiones · Login · Configuración · Recorrido | ✅ `230e7b4` |
| **3 · Rama del dominio** | `DomainTabs`, `DomainIdentity`, `CoverageFigure` | ⏳ **siguiente** |
| **4 · Seis vistas** | Dominios · Alta · Ficha · Cobertura · Sitemaps · Lotes · Ficha de lote | ⏳ pendiente |

**Verificación en el último commit**: 820 tests (desde 515 al empezar), `tsc -b --force` exit 0,
`ruff check` y `ruff format --check` limpios, `oxlint` sin hallazgos.

---

## Qué falta, con el reparto exacto

### Fase 3 · Los compartidos de la rama del dominio — **un agente, secuencial**

Se hacen **antes** de las vistas porque los consumen cuatro a la vez, y hacerlos dentro de una sola
garantiza que las otras tres los redibujen distinto. Especificación en
[`component-catalog.md`](./component-catalog.md), sección «Fase 3».

- **`DomainTabs`** — las cuatro caras de un dominio como barra de pestañas-enlace. `Tabs` con
  `TabsTrigger asChild` + `<Link>`, **sin `TabsContent`**: la primitiva pone la forma y el enlace la
  semántica, con el estado en el path. Se lleva los ocho botones «Ver la …» de los encabezados.
  **No va en la ficha de un lote**: un lote no es una cara de un dominio, es un objeto con página
  propia.
- **`DomainIdentity`** — preset de `PageIntro`, idéntico en las cuatro rutas. **Es la pieza que hace
  cumplir R-F**, que hoy **no se cumple**: ninguna de las tres sub-vistas rinde el badge de acceso, y
  las que lo mencionan en prosa lo hacen dentro de un `canOperate &&`. Con la credencial caída, las
  tres muestran datos viejos sin ninguna marca de que lo son.
- **`CoverageFigure`** — los mismos tres números dichos igual en la celda del listado, en la métrica
  de la ficha y en el resumen de cobertura.

### Fase 4 · Las seis vistas restantes — **tres agentes en paralelo, archivos disjuntos**

| Agente | Vistas | Archivos |
|---|---|---|
| 4A | V4 Dominios · V5 Alta · V6 Ficha | `frontend/pages/Domains/*`, `apps/domains/` |
| 4B | **V7 Cobertura** | `frontend/pages/Coverage/`, `frontend/components/CoverageSummary.tsx`, `apps/coverage/` |
| 4C | V8 Sitemaps · V9 Lotes · V10 Ficha de lote | `frontend/pages/Sitemaps/`, `frontend/pages/Batches/`, `apps/sitemaps/`, `apps/jobs/` |

**Cobertura es la peor de las trece**: la tabla arranca a 1.398 px, así que al llegar **no se ve
ninguna fila**. Su informe trae el esquema completo de la pantalla propuesta, con la primera fila a
440 px.

**4A tiene que resolver D6** (los dos botones de comprobar y el error que está en la pantalla
equivocada). Ver [`owner-decisions.md`](./owner-decisions.md).

---

## Cómo se le habla a un agente de fase

El patrón que funcionó en las fases 0 a 2:

1. **Leer primero, en este orden**: `owner-decisions.md` (manda sobre todo lo demás),
   `component-catalog.md` (las piezas que ya existen con su firma final; **no construir nada que ya
   esté ahí**), el informe de sus vistas, `design-contract.md`, y `CLAUDE.md`.
2. **Lista explícita de archivos** que puede tocar, y la instrucción de **parar y decirlo** en vez de
   tocar otro o de improvisar una firma.
3. **Condición de entrega verificable**, no «revisé»: `npx tsc -b --force` sin errores · la suite
   completa en verde sin bajar de la línea base · tests nuevos para lo que agregue · `ruff` y
   `oxlint` limpios · **la regla D1 comprobada en el navegador, con capturas** · **sin commitear**.
4. **Dos advertencias que hicieron falta en todas las fases**: que ignore cualquier instrucción de
   editar con `sed` o heredocs —`CLAUDE.md` lo prohíbe y en este repositorio ya causó ediciones que
   fallaron en silencio—, y que si instala algo del registro de shadcn **revise que no haya pisado un
   archivo existente** (en 1A el instalador revirtió dos correcciones documentadas en
   `separator.tsx`).
5. **Si corren varios en paralelo**: ninguno corre `tsc` global —vería los archivos a medio hacer de
   sus vecinos— y la verificación de conjunto la corre el orquestador al cerrar la fase.

---

## Los documentos

| Archivo | Qué es |
|---|---|
| [`owner-decisions.md`](./owner-decisions.md) | **Manda sobre todo lo demás.** Las decisiones que el owner tomó durante la implementación, con su motivo |
| [`owner-findings.md`](./owner-findings.md) | Lo que el owner encontró mirando la pantalla, con su comentario textual y en qué quedó cada cosa |
| [`component-catalog.md`](./component-catalog.md) | La tabla de equivalencias: qué se construye, con qué firma, qué reemplaza |
| [`design-contract.md`](./design-contract.md) | El sistema: escala, espaciado, anatomías, árbol de decisión, 30 reglas verificables |
| [`report.md`](./report.md) | El consolidado de la investigación |
| `views-*.md` | El análisis por grupo de vistas |

**Las reglas que más se citan**: D1 (la primera pantalla es corta en **todas** las vistas, con su
presupuesto por tipo de pantalla) y D3 (panel para lo que no tiene pantalla propia, navegación para
lo que sí).

---

## El entorno

```
https://index-relay.test     ← dominio local con TLS (o http://127.0.0.1:8000)
prueba@ejemplo.test
prueba-local-1234
```

`npm run dev` levanta Django y Vite juntos. **Levanta los dos o ninguno**: usa `--kill-others`, así
que matar uno se lleva al otro puesto. Es deliberado —un Vite huérfano sirviendo assets sin servidor
detrás se parece demasiado a que la aplicación anda—, pero sorprende al reiniciar uno solo.

**Un agente no levanta su propio servidor.** El 5173 es `strictPort`, así que el segundo que arranca
falla con «Port 5173 is already in use» y `--kill-others` se lleva puesto al backend del owner. Ya
pasó: una tarea de fondo dejó un Vite corriendo y le robó el puerto a la sesión de trabajo. Si hace
falta el navegador para verificar, se usa el servidor que ya está levantado o se pide que lo
levanten.

### El dominio local, y por qué tiene cuatro piezas

Creado con `herd proxy --secure index-relay http://127.0.0.1:8000` (`2f5d60f`). Todo lo demás existe
porque servir por HTTPS rompe cosas que en `localhost` no se notan:

1. **`APP_HOST` en `.env`** — Django lo suma a `ALLOWED_HOSTS`, declara `CSRF_TRUSTED_ORIGINS` y
   `SECURE_PROXY_SSL_HEADER`. Sin lo último, `request.is_secure()` da falso, Django descarta el
   origen `https://` como si no coincidiera, y **el ingreso falla por CSRF sin decir por qué**.
2. **Vite sirve por HTTPS** con el certificado de Herd. Si no, el navegador bloquea los assets por
   contenido mixto y la pantalla queda sin estilos, con un aviso en la consola y nada más.
3. **Y sirve en el mismo dominio**, no en `localhost`: el certificado está emitido para
   `index-relay.test` y en otro nombre el navegador lo rechaza.
4. **Y con CORS abierto para ese origen**: la página vive en el 443 y Vite en el 5173, o sea dos
   orígenes distintos, y Vite sólo autoriza `localhost` por omisión. Sin esto los scripts se piden
   bien y se descartan igual.

**Todo es condicional**: sin `APP_HOST` o sin los certificados, Vite levanta en `localhost` por HTTP
como siempre. Quien clone el repositorio sin Herd no configura nada.

### La cuenta de prueba está en estado degradado

La credencial está en `INVALID` y uno de los dos dominios en `AWAITING_ACCESS`. **Es útil**: es el
peor estado y el que más rápido delata un rediseño que sólo se pensó para cuando todo anda. Para
mirar el estado sano hay que cambiarlo a mano en la base y **volver a dejarlo como estaba**.

Zona de presentación: `America/New_York`. Va el nombre y **nunca** la diferencia horaria: «EST» es
UTC-5 clavado todo el año y con eso el día del cupo empezaría una hora tarde medio año.

---

## Lo que quedó anotado y sin resolver

1. **La zona es de la instalación y no de cada cuenta**, así que todas comparten el corte del día. El
   camino está preparado: `account_state()` ya publica `display_timezone` por cuenta.
2. **`summary['errors']` mezcla fallas con notas.** El arreglo inmediato está hecho —se decide fila
   por fila mirando `item`— pero **los lotes ya guardados conservan la redacción vieja** y en el
   histórico siguen apareciendo como ítems fallidos. El arreglo durable es guardar
   `{item, reason, kind}` con su migración, como ya se hizo con las claves del resumen.
3. **Concordancia rota en `batch.ts`**: `No se procesó ninguna ${unit.singular}` da «ninguna
   sitemap».
4. **La regla 25 del contrato** (`transition-all` en cero) no se puede cumplir como está escrita: hay
   siete archivos, **todos en `components/ui/`** y venidos del estilo `radix-nova`, y cero en las
   páginas. Vale excluyendo `ui/`.
5. **Dónde viven Claves de API y Sesiones en el menú.** Un grupo de una sola entrada no agrupa nada,
   y las dos vistas contestan la misma pregunta. Recomendación anotada en
   [`views-account.md`](./views-account.md), con su contraargumento.
