# Lo que el owner encontró mirando la pantalla

Registro de las observaciones del owner sobre la interfaz, con su comentario textual, el diagnóstico
técnico y en qué quedó cada una. Se agrega al final, en orden de aparición.

**Cómo leer el estado**: *resuelto* trae el commit; *pendiente* trae la fase en la que se resuelve y
por qué no antes.

---

## F1 · El panel lateral corta los nombres de dominio

> «La barra sheets en muchos casos está quedando bastante corta, yo creo que deberías hacerla más
> grande donde se necesite. Si la sheet no recibe variantes de size que aumente su width, deberías
> implementarlo vos.»

**Diagnóstico.** El registro de shadcn trae un solo ancho para el panel, 384 px, y alcanza para un
mensaje corto y nada más. `DetailSheet` ya lo pisaba a 576 px y aun así
`website.patiospoolsanddriveways.test` se cortaba contra el borde derecho.

**Resuelto** (`230e7b4`). `ui/sheet.tsx` expone una escala de tres pasos —`sm` 384, `default` 576,
`lg` 768— y `DetailSheet` la pasa. El criterio quedó escrito como **cuánto ancho pide el contenido,
no cuánto se quiere lucir el panel**, para que no se vuelva «póngale grande a todo». Abajo de `sm` no
aplica: ahí el panel es un cajón a pantalla completa.

**Lo que el ancho no arregla**: con un hostname suficientemente largo ningún ancho alcanza. El
contenido necesita `wrap-anywhere` y `min-w-0`; el `min-w-0` del contenedor del panel se agregó acá.

---

## F2 · Un aviso y una tarjeta que se parecen demasiado

> «Tenemos cards con iconos, diferente borde, título más pequeño. Entonces o es una card o no es una
> card o son dos cards completamente diferentes una debajo de otra generando malestar visual. […]
> Parecen muy semejantes pero al mismo tiempo diferentes y no se logra entender qué comunica qué y
> cuál es más importante y cuál no.»

**Diagnóstico.** Era literal: el registro dibujaba `Alert` con `bg-card` y el mismo borde que `Card`
—**la misma superficie**—, y las dos piezas se distinguían sólo por el icono y el tamaño del título.
La variante destructiva apenas cambiaba el color del texto. Compartir superficie y diferir en
detalles es la peor combinación posible.

**Resuelto** (`230e7b4`). La separación pasa a ser de superficie: el aviso va **teñido** y la tarjeta
no. Los tonos son los mismos cuatro de `StatusBadge` y con los mismos valores, para que un estado y
el aviso que habla de ese estado se lean como la misma familia. La regla quedó en el contrato,
§3.11.b, con tres condiciones; la tercera es la que importa para el caso original:

> Dos avisos seguidos que dicen lo mismo son uno solo. Si un `Alert` anuncia una condición y la
> `Card` de abajo existe para resolverla, o el aviso se disuelve dentro de la tarjeta o la tarjeta no
> hacía falta.

---

## F3 · Dos botones de comprobar y un error que no dice qué falló

> «Acá no se entiende nada, hay dos botones de "comprobar"; además dice "no pudimos completar la
> acción" y lo primero que se me viene a la mente es "¿cuál acción hablás?"»

**Diagnóstico**, en tres capas:

1. Los dos botones **son la misma acción**: `Domains/Show.tsx:350` y `:426` disparan el mismo
   formulario. Uno vive dentro del bloque de error y el otro al pie del panel.
2. «No pudimos completar la acción» es el título de *no sé qué pasó*: sale de `UNMAPPED_TITLE`, que
   se usa cuando el código no está en el mapa cerrado. La ficha mapea tres códigos y el que aparece
   —clave rechazada— no está.
3. **Ese error no es del dominio: es de la cuenta.** Una clave rechazada se arregla en la
   configuración, así que mapear el código ahí sería explicar mejor un problema que igual está en la
   pantalla equivocada.

**Pendiente · fase 4.** No se parchea antes porque el arreglo correcto no es agregar el código al
mapa: es que la ficha distinga lo que es del dominio de lo que es de la cuenta. Lo primero se explica
y se acciona ahí; lo segundo va en una línea con su enlace, **sin botón de comprobar**, porque
volvería a fallar por el mismo motivo. Detalle completo en `owner-decisions.md`, D6.

---

## F4 · «URLs con dato de Google» no se entiende

> «Yo veo esta tarjeta y lo primero que me pregunto es "¿qué es una URL con dato de Google?" […] ¿Qué
> es 92 con dato? ¿Qué se supone que significa eso? ¿Qué es "con dato", qué dato, cuál dato, dónde
> veo esos datos?»

**Diagnóstico.** «Con dato» es vocabulario nuestro: nombra **nuestra contabilidad** —a cuántas
páginas ya nos contestó Google— en vez de un resultado que le sirva a alguien. Y enterraba en letra
chica el único número que la persona fue a buscar: cuántas están indexadas.

**Resuelto** (`230e7b4`). La tarjeta pasa a llamarse «URLs indexadas», la cifra grande es `62 de 120`
—las indexadas sobre el total— y la bajada dice cuántas tienen reporte y no quedaron indexadas. Las
que faltan consultar siguen aparte, con su familia visual propia: no son malas noticias, son ausencia
de noticia. Ninguna de las cuatro cifras se escribe dos veces.

**La regla que sobrevive**: las 58 que faltan para 120 **no son todas malas**. Son 30 que Google miró
y no indexó más 28 que no consultamos. Si la tarjeta dice `62 de 120` sin decir eso, alguien lee que
58 páginas están rotas, y de 28 no sabemos absolutamente nada.

---

## F5 · Las tarjetas del tablero sin agrupar

> «Las cards del dashboard están ordenadas como sea una al lado de la otra cuando naturalmente parece
> que deberían haber secciones.»

**Resuelto** (`230e7b4`). Tres secciones con rótulo, agrupadas **por la pregunta que contestan**:
*Tus sitios* (Dominios · Sitemaps), *Lo que sabemos* (cobertura), *Hoy* (Cupo · Lotes).

---

## F6 · La tarjeta de notificaciones duplicaba el contador

> «Eliminar las notificaciones porque es duplicar notificaciones en una card, notificación en lo
> primero que se ve antes de las cards y notificación en el contador del menú. Toca que decidas.»

**Resuelto** (`230e7b4`). Eliminada. El mismo dato estaba en tres lugares —el contador de la barra
lateral, el bloque de atención y la tarjeta—, y el contador ya lleva a la lista sin leer desde
`ca9a2e6`.

---

## F7 · El estado de la cuenta tenía que ser un lugar fijo

> «Esa acción que aparece arriba del todo, ¿qué? Debería haber alguna card en rojo fuerte tipo danger
> diciendo "acciones críticas requeridas ya", que yo abra y vea en modal esa data. De esta manera ya
> yo sé que esa tarjeta de acciones requeridas debe estar siempre en verde simbolizando health.»

**Resuelto** (`230e7b4`), con un matiz que quedó escrito. La tarjeta está siempre en el mismo lugar y
cambia de tono, **pero la distinción no puede ser sólo el color**: va icono + palabra en los dos
estados. Es RT-04, y además hay gente que no distingue el rojo del verde.

El detalle se abre en un panel lateral y no en un modal: lo que se abre es una **lista**, y un
diálogo con una lista adentro obliga a cerrarlo para volver a mirar el tablero. Además, abrir de una
forma distinta a los otros cuatro paneles del mismo tablero traería de vuelta el problema que
estamos sacando.

---

## F8 · «Agregar dominio» en el encabezado del tablero

> «En el dashboard arriba a la derecha sigue saliendo "agregar dominio" y no sé qué tiene que hacer
> eso ahí.»

**Resuelto** (`230e7b4`). El slot de acciones del tablero queda vacío, y está bien que quede vacío. La
objeción coincidía con lo que ya decía el informe de esa vista: era el único botón visible al entrar
y le ofrecía agregar un **segundo** dominio a quien tenía uno esperando autorización. La acción sigue
viviendo donde viven los dominios.

---

## F9 · El tablero se convirtió en una lista de reclamos

> «Yo no sé qué hicieron pero ahora está hasta peor jajaja, muchísimo peor, esto parece más una vista
> de acciones requeridas que de un dashboard.»

**Diagnóstico.** Con la credencial caída se apilaban unos 800 px de problemas antes de la primera
cifra. La causa: el tablero **repetía lo que el aviso del armazón ya había dicho**, y el propio
microcopy lo confesaba —el ítem terminaba diciendo «El aviso de arriba lleva a donde se resuelve»—.
Si el texto tiene que explicar que lo de arriba es lo mismo que esto, entonces esto no hacía falta.

**Resuelto** (`230e7b4`). La tarjeta de estado cuenta dominios y lotes, **no la conexión**: eso ya lo
dice el aviso, con su botón. Con la conexión caída y los dominios sanos, la tarjeta va en verde. La
primera cifra pasó de ~800 px a 432. Y quedó escrito en el código el criterio que lo evita a futuro:

> Este tablero se mira todos los días y casi siempre está sano. El caso a optimizar es «no pasa
> nada», no «se rompió todo». Si con un problema de cuenta la pantalla se convierte en una lista de
> reclamos, el día que haya algo urgente de verdad no se va a distinguir del ruido.

---

## F10 · La zona horaria

> «No sé qué pedos es esto: "Todas las horas en America/Argentina/Buenos_Aires", cuando deberíamos
> estar sacando esto en EST. […] Toda nuestra DB debe funcionar en UTC, pero la data a las vistas
> debe convertirse para poder sacar los rangos bien. […] Las conversiones se hacen por timezone (no
> haciendo un offset quemado de X horas) porque entonces cuando cambia de horario verano hay
> chicharrón.»

**Diagnóstico.** La política descrita **ya estaba implementada**: la base en UTC, la conversión con
`zoneinfo.ZoneInfo` por nombre de zona, el frontend formateando con `Intl.DateTimeFormat` y la zona
que manda el servidor, y cero offsets quemados en Python y en TypeScript. Lo único que faltaba era el
valor.

**Resuelto** (`230e7b4`). `America/New_York`, en el default del producto y en los cuatro archivos de
entorno. **No «EST»**: eso es UTC-5 clavado todo el año, o sea el chicharrón que se quería evitar;
`America/New_York` es EST en invierno y EDT en verano y lo cambia sola la biblioteca de zonas. De
paso, el test que tenía la zona escrita a mano ahora la lee de la configuración — ese patrón era
justamente lo que hacía doloroso el cambio.

**Anotado, sin resolver**: hoy la zona es **de la instalación y no de cada cuenta**, así que todas
comparten el corte del día. Un cliente en Madrid y otro en Nueva York verían el cupo renovarse a la
misma hora absoluta. Cuando importe, el camino está preparado: `account_state()` ya publica
`display_timezone` por cuenta, así que es cambiar de dónde sale el valor y no rehacer el manejo de
fechas.

---

## F11 · El cartel de «sesión expirada» en el ingreso

> «Horrible ese mensaje ahí, wtf, nunca había visto un mensaje de login así en toda mi vida. Es más,
> ni siquiera lo necesitamos.»

**Diagnóstico.** Era el estado «sesión expirada» que pedía el checklist de esa vista: cuando alguien
llega expulsado de una pantalla protegida, un aviso explicaba por qué está ahí. Pero **llegar a un
formulario de ingreso ya explica por sí solo qué hay que hacer**, y ningún producto conocido lo
anuncia con un cartel.

**Resuelto** (`3cf1718`). El bloque se saca. El destino se sigue conservando en el campo oculto, que
es lo único que hacía falta: quien entra aterriza donde quería ir, sin que nadie se lo cuente antes.

El motivo quedó escrito en el código, en el lugar donde estaba el bloque, para que no vuelva a
aparecer por seguir el checklist al pie de la letra.

---

## F12 · El aviso de credencial caída, en todas las vistas

> «Necesito saber esta alerta acá qué pedo, se aparece en todas las vistas. No entiendo.»

**Qué es.** La credencial de Search Console de la cuenta está en `INVALID`, así que la plataforma no
puede consultarle nada a Google. La cuenta de prueba está degradada a propósito, por eso se ve
siempre.

**La razón de que esté en todas las vistas es buena** y está escrita en `AccountNotice.tsx`: si la
credencial dejó de servir, cualquier pantalla muestra estados que ya no se actualizan, y el aviso es
lo único que impide leer un dato viejo como si fuera de hoy. No se discute que exista. Se discute
dónde aparece, cuántas veces, y qué dice.

**Diagnóstico**, en tres capas:

1. **En Dominios, tres elementos dicen lo mismo en el mismo pliegue.** El botón negro del encabezado
   —el más pesado de la pantalla— **es «Agregar dominio» disfrazado**: con `canOperate` en falso,
   `AddDomainAction` lo reemplaza por uno que dice «Ir a la configuración». Más el aviso rojo, más el
   «sin credencial para consultarlo» de cada fila. Dos de los tres son botones al mismo `/settings`,
   y el que más se ve es el que menos explica: F3 otra vez.
2. **En tres pantallas aparece sin aplicar.** De las trece, en ocho es pertinente y en dos ya se
   suprime; en **Claves de API, Sesiones y Notificaciones** no hay un solo dato de Google en pantalla
   y el bloque rojo sale igual. Ahí no previene ninguna lectura equivocada: es ruido. La regla dice
   «toda pantalla»; lo que su propia razón justifica es «toda pantalla que muestre datos de Google».
3. **El texto es de sistema, no de producto.** «La clave de tu cuenta de servicio» sólo lo entiende
   quien la cargó, y «dejó de funcionar» no distingue vencida, borrada de Google Cloud, o que nunca
   anduvo. Peor: el mensaje es `credential.last_error_detail or <texto genérico>`, así que cuando
   Google devuelve un motivo, **ese motivo se publica crudo** — texto de una API en el lugar donde va
   microcopy en español.

**Resuelta la capa 2**, y no con la regla que había propuesto. El owner eligió algo mejor: el aviso
deja de ser un bloque y pasa a ser un indicador en la barra superior que se abre en un panel. Con
eso, «dónde aplica» deja de ser una pregunta —un icono no estorba en ninguna pantalla— y las dos
funciones de supresión que decidían callarlo se eliminaron. Es [D7](./owner-decisions.md).

**Pendiente · fase 4A** lo del botón, que es de la vista de Dominios: vuelve a ser «Agregar dominio»,
deshabilitado y con su motivo, porque uno que cambia de destino según el estado de la cuenta es una
trampa.

**Pendiente, sin fase asignada**, la capa 3: `_notices()` publica
`credential.last_error_detail or <texto genérico>`, así que cuando Google devuelve un motivo, ese
motivo sale crudo en el aviso. El detalle técnico va al panel de Configuración —donde
`CredentialErrorPanel` ya tiene el mapa cerrado de códigos— y el aviso se queda con una frase
escrita por nosotros.

---

## F13 · Un botón y un enlace subrayado a la vez

> «No necesita tener eso dentro un botón con underline —o sea, está horrible—: o dejás el botón
> normal o dejás sólo el texto underline link. Yo voto por el link text y ya.»

**Diagnóstico.** Choque de dos reglas que por separado están bien. `AlertDescription` subraya
**cualquier** `<a>` que tenga adentro, pensado para enlaces en prosa; y la acción del aviso era un
`<Button asChild>`, que pone las clases del botón **sobre el `<a>`**. El resultado tenía borde,
relleno y subrayado al mismo tiempo: las dos cosas a la vez, que es peor que cualquiera de las dos.

Pasaba en tres lugares con exactamente la misma forma: el aviso de cuenta, el bloque de acceso
perdido de Cobertura y el paso bloqueado del recorrido.

**Resuelto.** Enlace de texto en los tres. El subrayado ya lo pone `AlertDescription`, así que la
llamada no repite estilos.

**Lo que quedó escrito, porque es lo que evita que vuelva**: dentro de un `Alert`, la acción es un
enlace de texto. Y el owner marcó de paso que las clases de foco que le había puesto no aportaban
nada —`focus-visible:outline-none` mata el foco nativo del navegador para reponerlo con un `ring`, o
sea trabajo para quedar igual—. Sin clase, el navegador pone el foco solo.
