:root {
  /* Paleta medida de D-RepseBot (repse-conectado) — ver playbook: un look & feel
     compartido entre prototipos se copia con sus valores reales, no se reinventa. */
  --acento-50:  #eaf7f0;
  --acento-100: #d3eee0;
  --acento-200: #a8dcc0;
  --acento-400: #35d38c;
  --acento-500: #17a874;
  --acento-600: #0a7d4b;
  --acento-700: #086a3f;

  /* --acento-600 servía a la vez de color de TEXTO (enlaces, cifras de KPI) y de
     FONDO de botón con texto blanco. Sobre claro el mismo verde cumple los dos
     papeles, pero en oscuro tiran en direcciones opuestas: el texto tiene que
     aclararse y el fondo del botón tiene que quedarse oscuro para que el blanco
     encima siga contrastando. Por eso el papel de texto vive en su propio token. */
  --acento-texto: #0a7d4b;
  --acento-texto-fuerte: #086a3f;

  --fondo-app:  #f8fafc;
  --superficie: #ffffff;
  --texto-principal: #0f172a;
  --texto-secundario: #64748b;
  --linea: #e2e8f0;

  --menu-fondo: #020617;
  --menu-activo-fondo: #0f172a;
  --menu-texto: #94a3b8;
  --menu-texto-hover: #e2e8f0;
  --menu-placeholder: #64748b;
  --menu-punto: #475569;
  /* Borde del globo de ayuda. En claro basta la línea normal —el globo es oscuro
     sobre una tarjeta blanca y el contraste lo da el propio relleno—. En oscuro
     el relleno del globo y el de la tarjeta se diferencian 1.38:1, así que el
     panel se leía como texto pegado encima de la página, sin decir dónde acaba;
     ahí el borde deja de ser adorno y es lo único que lo delimita. */
  --pista-borde: var(--linea);

  --exito-fondo: #ecfdf5;    --exito-borde: #a7f3d0;    --exito-texto: #047857;
  --error-fondo: #fef2f2;    --error-borde: #fecaca;    --error-texto: #b91c1c;
  --pendiente-fondo: #f1f5f9; --pendiente-borde: #e2e8f0; --pendiente-texto: #475569;
  /* --noaplica-texto era #94a3b8 (slate-400): 2.45:1 sobre su propio fondo, muy
     por debajo del 4.5:1 de WCAG AA. Slate-500 da 4.6:1 y sigue leyéndose más
     apagado que --pendiente-texto, que es la jerarquía que se buscaba. */
  --noaplica-fondo: #f8fafc;  --noaplica-borde: #e2e8f0;  --noaplica-texto: #64748b;
  /* Ámbar: debajo del SLO pero todavía por encima del contrato. Es el estado MÁS
     importante del tablero —la ventana de contención— y no tenía token: con
     --pendiente-* (gris) se confundía con «sin dato», que es justo lo contrario.
     Una variable nueva va en LOS TRES bloques de paleta o el tema explícito y el
     automático se desincronizan. */
  --ambar-fondo: #fffbeb;  --ambar-borde: #fde68a;  --ambar-texto: #92400e;
  /* «Saltada por falla técnica» en el tablero semanal: no es una calificación, así que
     no puede ser verde, ámbar ni rojo; y no puede ser el gris de «sin revisión», que se
     lee como negligencia cuando aquí la falla es del equipo. */
  --tecnica-fondo: #eff6ff;  --tecnica-borde: #bfdbfe;  --tecnica-texto: #1d4ed8;

  --header-fondo: #f1f5f9;
  --header-borde-boton: #cbd5e1;
  --header-separador: #cbd5e1;
  --header-icono-hover: #e2e8f0;

  /* Hace que los controles nativos (calendario, hora, desplegables, barras de
     scroll) se pinten acordes al tema — si no, en oscuro salen blancos. */
  color-scheme: light;
}

/* --- Tema oscuro ---------------------------------------------------------
   Tres estados, y el orden importa:
   1. Sin elegir nada -> se sigue la preferencia del sistema operativo.
   2. Elección explícita -> gana sobre el sistema, en LOS DOS sentidos (por eso
      el bloque de arriba se excluye con :not([data-tema="claro"]): si no, quien
      tiene el SO en oscuro no podría forzar el modo claro).
   El atributo lo pinta el servidor desde una cookie, no JS: el CSP prohíbe
   scripts inline, así que el truco habitual de aplicarlo en un <script> del
   <head> no está disponible — y así tampoco hay parpadeo al cargar. */
:root[data-tema="oscuro"] { color-scheme: dark; }

@media (prefers-color-scheme: dark) {
  :root:not([data-tema="claro"]) { color-scheme: dark; }

  :root:not([data-tema="claro"]) {
    --acento-texto: #35d38c;
    --acento-texto-fuerte: #6ee7b7;
    --acento-50: #0d3d29;

    --fondo-app:  #0f172a;
    --superficie: #1e293b;
    --texto-principal: #e2e8f0;
    --texto-secundario: #94a3b8;
    --linea: #334155;

    --menu-fondo: #020617;
    --menu-activo-fondo: #1e293b;
    --menu-texto-hover: #f1f5f9;
    --pista-borde: #94a3b8;

    --exito-fondo: #052e21;     --exito-borde: #065f46;     --exito-texto: #6ee7b7;
    --error-fondo: #3f1212;     --error-borde: #7f1d1d;     --error-texto: #fca5a5;
    --pendiente-fondo: #1e293b; --pendiente-borde: #334155; --pendiente-texto: #cbd5e1;
    --noaplica-fondo: #172033;  --noaplica-borde: #334155;  --noaplica-texto: #94a3b8;
    --ambar-fondo: #3a2a08;  --ambar-borde: #78500d;  --ambar-texto: #fcd34d;
    --tecnica-fondo: #10203f;  --tecnica-borde: #1e40af;  --tecnica-texto: #93c5fd;

    --header-fondo: #0b1220;
    --header-borde-boton: #475569;
    --header-separador: #475569;
    --header-icono-hover: #1e293b;
  }
}

:root[data-tema="oscuro"] {
  --acento-texto: #35d38c;
  --acento-texto-fuerte: #6ee7b7;
  --acento-50: #0d3d29;

  --fondo-app:  #0f172a;
  --superficie: #1e293b;
  --texto-principal: #e2e8f0;
  --texto-secundario: #94a3b8;
  --linea: #334155;

  --menu-fondo: #020617;
  --menu-activo-fondo: #1e293b;
  --menu-texto-hover: #f1f5f9;
  --pista-borde: #94a3b8;

  --exito-fondo: #052e21;     --exito-borde: #065f46;     --exito-texto: #6ee7b7;
  --error-fondo: #3f1212;     --error-borde: #7f1d1d;     --error-texto: #fca5a5;
  --pendiente-fondo: #1e293b; --pendiente-borde: #334155; --pendiente-texto: #cbd5e1;
  --noaplica-fondo: #172033;  --noaplica-borde: #334155;  --noaplica-texto: #94a3b8;
  --ambar-fondo: #3a2a08;  --ambar-borde: #78500d;  --ambar-texto: #fcd34d;
  --tecnica-fondo: #10203f;  --tecnica-borde: #1e40af;  --tecnica-texto: #93c5fd;

  --header-fondo: #0b1220;
  --header-borde-boton: #475569;
  --header-separador: #475569;
  --header-icono-hover: #1e293b;
}

/* Qué botón del selector se ve, según el tema EFECTIVO. Se resuelve en CSS
   porque el servidor no sabe qué tiene el usuario en su sistema operativo:
   solo conoce la cookie, y cuando no hay cookie manda el SO. */
.solo-en-claro { display: inline-flex; }
.solo-en-oscuro { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-tema="claro"]) .solo-en-claro { display: none; }
  :root:not([data-tema="claro"]) .solo-en-oscuro { display: inline-flex; }
}
:root[data-tema="oscuro"] .solo-en-claro { display: none; }
:root[data-tema="oscuro"] .solo-en-oscuro { display: inline-flex; }
:root[data-tema="claro"] .solo-en-claro { display: inline-flex; }
:root[data-tema="claro"] .solo-en-oscuro { display: none; }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--fondo-app);
  color: var(--texto-principal);
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.6;
}

h1, h2, h3 {
  font-family: "Archivo", system-ui, sans-serif;
  color: var(--texto-principal);
  letter-spacing: -0.01em;
  margin: 0 0 8px;
}

code, .mono { font-family: "IBM Plex Mono", ui-monospace, monospace; }
a { color: var(--acento-texto); }

/* --- Layout de app autenticada: header fijo + sidebar oscuro + contenido --- */
.layout { min-height: 100vh; }

.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  height: 64px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 20px;
  background: var(--header-fondo);
  border-bottom: 1px solid var(--linea);
}
.app-header .marca { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.app-header .marca .isotipo { width: 24px; height: 24px; border-radius: 6px; object-fit: cover; }
.app-header .wordmark { font-family: "Archivo", sans-serif; font-weight: 800; font-size: 16px; letter-spacing: -0.01em; color: var(--texto-principal); }
.app-header .wordmark .acento { color: var(--acento-texto); }
.app-header .sep { color: var(--header-separador); }
.app-header .seccion { color: var(--acento-texto); font-weight: 600; font-size: 14px; white-space: nowrap; }
.app-header .usuario-actual {
  color: var(--texto-secundario);
  font-family: "IBM Plex Mono", monospace;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-header .espaciador { flex: 1; }
.app-header .btn-icono {
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px;
  color: var(--texto-secundario);
  text-decoration: none;
  flex-shrink: 0;
}
.app-header .btn-icono:hover { background: var(--header-icono-hover); color: var(--texto-principal); }
/* El selector de tema es un <button> (envía un formulario, no navega), así que
   hay que anular el estilo de botón primario que hereda de `button`. */
.selector-tema { flex-shrink: 0; margin: 0; }
.app-header button.btn-icono {
  background: none;
  border: none;
  padding: 0;
  color: var(--texto-secundario);
  cursor: pointer;
}
.app-header form.cerrar-sesion { flex-shrink: 0; }
.app-header button.cerrar-sesion {
  background: var(--superficie);
  color: var(--texto-principal);
  border: 1px solid var(--header-borde-boton);
  border-radius: 8px;
  padding: 7px 14px;
  font-size: 13px;
}

.cuerpo { display: grid; grid-template-columns: 288px minmax(0, 1fr); align-items: start; }

/* El footer va DENTRO de esta columna (no del grid completo) para poder
   anclarlo al fondo real del viewport con flex quando el contenido es corto
   -- antes el sidebar de altura completa empujaba el footer fuera de la
   vista y había que hacer scroll para verlo incluso en páginas casi vacías. */
.panel-derecho {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - 64px);
}
main.contenido { flex: 1 0 auto; }

.sidebar {
  position: sticky;
  top: 64px;
  height: calc(100vh - 64px);
  overflow-y: auto;
  padding: 16px 12px;
  background: var(--menu-fondo);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

nav.toc { display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
nav.toc .grupo-titulo {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 12px;
  font-weight: 600;
  color: var(--acento-400);
  padding: 8px 12px 6px;
  margin-top: 8px;
  border-radius: 8px;
}
nav.toc .grupo-titulo:first-child { margin-top: 0; }
nav.toc a {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--menu-texto);
  text-decoration: none;
  font-weight: 500;
  padding: 8px 12px;
  border-radius: 8px;
}
.icono-nav { width: 16px; height: 16px; flex-shrink: 0; }
nav.toc a:hover { color: var(--menu-texto-hover); }
nav.toc a.activo { background: var(--menu-activo-fondo); color: var(--acento-400); }

/* --- Role badges (no son estado de cumplimiento — pastilla simple) --- */
.rol {
  display: inline-flex; align-items: center;
  font-family: "IBM Plex Mono", monospace; font-size: 11px; font-weight: 600;
  padding: 2px 8px; border-radius: 20px; white-space: nowrap;
}
.rol-admin { background: var(--acento-50); color: var(--acento-texto-fuerte); }
.rol-supervisor { background: var(--pendiente-fondo); color: var(--pendiente-texto); }
.rol-agente { background: var(--noaplica-fondo); color: var(--noaplica-texto); border: 1px solid var(--linea); }
.rol-tablero { background: var(--ambar-fondo); color: var(--ambar-texto); border: 1px solid var(--ambar-borde); }
/* En el encabezado la insignia NO se encoge: `.usuario-actual` lleva ellipsis justamente
   para ser lo que cede primero, y un rol recortado a «Administr…» dice menos que nada.
   `.rol-tablero` nunca sale aquí —`panel_tablero.html` no extiende `base.html`, no hay
   navegación que esconder— y por eso el rol de pared no tiene que caber en 64px. */
.app-header .rol { flex-shrink: 0; }

/* --- Contenido principal --- */
main.contenido { padding: 32px; min-width: 0; }
main.contenido > .envoltura { max-width: 896px; margin: 0 auto; }
/* Pantallas de catálogo/tabla densa (Tiendas, etc.) necesitan todo el ancho
   disponible — el límite de 896px es correcto para lectura de texto/formularios,
   no para una tabla de 8 columnas. */
main.contenido > .envoltura.envoltura-ancha { max-width: none; }
main.contenido h1 { font-size: 22px; margin-bottom: 20px; }

.card {
  background: var(--superficie);
  border: 1px solid var(--linea);
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 20px;
}
/* El nivel del encabezado lo decide la ESTRUCTURA (h2 cuelga de h1, h3 de h2);
   el tamaño es el mismo para cualquier título de tarjeta. Elegir el nivel por
   cómo se ve es lo que hacía que las pantallas saltaran de h1 a h3 y rompieran
   la navegación por encabezados de un lector de pantalla. */
.card h2, .card h3 { font-size: 15px; margin-bottom: 12px; }

.grid-kpi { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; }
.kpi { text-align: center; }
/* Un KPI que corresponde a una lista filtrada es también el atajo para verla:
   evita volver al menú y rearmar el filtro a mano para saber de qué se compone. */
a.kpi { display: block; text-decoration: none; border-radius: 10px; padding: 6px 4px; }
a.kpi:hover { background: var(--fondo-app); }
a.kpi:hover .valor { color: var(--acento-texto-fuerte); }
.kpi .valor { font-family: "Archivo", sans-serif; font-size: 24px; font-weight: 600; color: var(--acento-texto); font-variant-numeric: tabular-nums; }
.kpi .etiqueta { color: var(--texto-secundario); font-size: 12px; margin-top: 4px; }

.tabla-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
table th a { color: var(--texto-secundario); text-decoration: none; }
table th a:hover { color: var(--acento-texto); }
.filtro-col { padding: 6px 8px !important; }
.filtro-col input { padding: 5px 7px; font-size: 12px; }

.paginacion {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 14px; flex-wrap: wrap;
}
.paginacion .paginas { display: flex; align-items: center; gap: 6px; }
.paginacion select { width: auto; padding: 6px 8px; font-size: 13px; }
.filtros-barra { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; }

/* Barra de filtros en línea: las etiquetas dejan de ser bloque para poder ir
   junto a su campo, y cada control se encoge en vez de forzar scroll. */
.filtros-form {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  padding-bottom: 14px; margin-bottom: 10px;
  border-bottom: 1px solid var(--linea);
}
.filtros-form label { margin: 0; white-space: nowrap; }
.filtros-form input, .filtros-form select { width: auto; min-width: 0; padding: 7px 9px; font-size: 13px; }
/* Antigüedad de un ticket vivo. A partir de una semana se marca en rojo, pero el
   color no va solo: el texto ya dice los días, así que se entiende igual sin ver
   el color (daltonismo, impresión en blanco y negro). */
.lista-items { margin: 0 0 16px; padding-left: 22px; }
.lista-items li { padding: 3px 0; }

.antiguedad { display: block; font-size: 12px; }
.antiguedad.alerta { color: var(--error-texto); font-weight: 500; }

.filtro-campo { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.filtro-campo.crece { flex: 1 1 200px; }
.filtro-campo.crece input { flex: 1 1 auto; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--linea); vertical-align: top; }
th {
  font-family: "IBM Plex Mono", monospace;
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--texto-secundario); font-weight: 500;
}

/* --- Semáforo de estado: pastilla + ícono, el color nunca va solo --- */
.badge-estado {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 500;
  border: 1px solid transparent;
}
.badge-estado svg { width: 14px; height: 14px; flex-shrink: 0; }
.badge-estado.grande { padding: 8px 16px; font-size: 14px; }
.badge-estado.grande svg { width: 16px; height: 16px; }
.badge-verde     { background: var(--exito-fondo); border-color: var(--exito-borde); color: var(--exito-texto); }
.badge-rojo      { background: var(--error-fondo); border-color: var(--error-borde); color: var(--error-texto); }
.badge-pendiente { background: var(--pendiente-fondo); border-color: var(--pendiente-borde); color: var(--pendiente-texto); }
.badge-noaplica  { background: var(--noaplica-fondo); border-color: var(--noaplica-borde); color: var(--noaplica-texto); }

/* Pares etiqueta/valor de una vista de detalle: dos columnas en pantalla ancha,
   apiladas en móvil. La etiqueta usa el mismo tratamiento que un <th>. */
.datos { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 10px 20px; margin: 0; }
.datos dt {
  font-family: "IBM Plex Mono", monospace;
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--texto-secundario); font-weight: 500;
  align-self: center;
}
.datos dd { margin: 0; align-self: center; }

.fila-clic { cursor: pointer; }
.fila-clic:hover { background: var(--fondo-app); }

form.inline { display: inline; }

label {
  display: block; margin: 14px 0 5px;
  font-size: 12px; font-weight: 500;
  color: var(--texto-secundario);
}
input, select, textarea {
  font: inherit;
  padding: 9px 11px;
  border: 1px solid var(--linea);
  border-radius: 8px;
  background: var(--superficie);
  color: var(--texto-principal);
  width: 100%;
}
textarea { width: 100%; }

/* Un checkbox/radio no es un campo de texto: con el width:100% de arriba se
   estiraba a todo lo ancho y el control quedaba centrado ARRIBA de su propia
   etiqueta, como si fuera otro elemento. Va aquí y no parchado por componente,
   que es como se venía tapando caso por caso. */
input[type="checkbox"], input[type="radio"] {
  width: auto;
  padding: 0;
  accent-color: var(--acento-600);
}

/* Etiqueta que contiene un checkbox: es contenido que se lee, no el rótulo
   apagado de 12px que usa `label` para los campos de texto. */
.check-linea {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 0;
  font-size: 14px;
  font-weight: 400;
  color: var(--texto-principal);
  cursor: pointer;
}
.check-linea input { margin: 3px 0 0; flex-shrink: 0; }

/* La explicación queda pegada a su checkbox —que es lo que hace falta cuando hay
   varios seguidos— pero FUERA del <label>, atada con aria-describedby. Estuvo
   dentro y traía dos defectos que solo se ven usándolo:

   1. Todo lo que un <label> envuelve conmuta el control. El párrafo de ayuda del
      alta de ticket son 5 líneas y el 80% del blanco: tocarlo para leerlo, o
      querer seleccionar el texto, marcaba «Incidente de cliente» sin avisar.
   2. El texto envuelto entra en el NOMBRE accesible, no en su descripción: un
      lector de pantalla anunciaba la casilla leyendo las 44 palabras completas.

   El sangrado alinea la ayuda con el título, no con la casilla: es el ancho del
   control (13px) más el hueco de .check-linea (9px). */
.check-bloque { margin: 0; }
.check-bloque .ayuda-inline {
  display: block;
  margin: 2px 0 0;
  padding-left: 22px;
  font-size: 13px;
  color: var(--texto-secundario);
}

/* Agrupación temática dentro de una tarjeta: <fieldset> real para que un lector
   de pantalla anuncie a qué bloque pertenece cada campo, sin el borde ni el
   padding que el navegador le pone por default. */
.grupo-campos { border: none; padding: 0; margin: 0; min-width: 0; }
.grupo-campos + .grupo-campos { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--linea); }
.grupo-campos > legend {
  padding: 0;
  font-family: "Archivo", system-ui, sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--texto-principal);
}

/* `.btn` es un <a> que se ve y se comporta como botón. Antes las plantillas
   anidaban <a href><button></button></a>: HTML inválido (contenido interactivo
   dentro de un enlace), dos paradas de tabulador para una sola acción, y un
   lector de pantalla anunciando "enlace, botón" para lo mismo. */
button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-weight: 600;
  padding: 9px 16px;
  border: none;
  border-radius: 8px;
  background: var(--acento-600);
  color: #ffffff;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
button:hover, .btn:hover { background: var(--acento-700); color: #ffffff; }
button.secondary, .btn.secondary {
  background: var(--superficie);
  color: var(--texto-principal);
  border: 1px solid var(--linea);
}
button.secondary:hover, .btn.secondary:hover {
  background: var(--fondo-app);
  color: var(--texto-principal);
}
button:disabled {
  background: var(--pendiente-fondo);
  color: var(--noaplica-texto);
  cursor: not-allowed;
}

/* Formulario secundario plegado tras un <details> nativo: la pantalla abre
   mostrando su contenido principal en vez del formulario de alta. Nativo =
   sin JS = sin excepción que pedirle al CSP. */
.desplegable { margin-bottom: 20px; }
.desplegable > summary {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  font-weight: 600;
  padding: 9px 16px;
  border-radius: 8px;
  background: var(--acento-600);
  color: #ffffff;
  cursor: pointer;
  list-style: none;
}
.desplegable > summary::-webkit-details-marker { display: none; }
.desplegable > summary:hover { background: var(--acento-700); }
.desplegable[open] > summary { margin-bottom: 16px; }
.desplegable > .card { margin-bottom: 0; }

/* Filas de acciones — reemplazan los margin-left:8px inline que obligaban a
   mantener 'unsafe-inline' en el style-src del CSP (playbook cap. 16). */
.barra-acciones { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.form-acciones { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 16px; }
.control-inline { display: flex; align-items: center; gap: 6px; margin: 0; }

/* Texto de ayuda pegado al campo que explica, no al párrafo anterior. */
.ayuda { color: var(--texto-secundario); font-size: 13px; margin: 2px 0 8px; }

/* Marca de campo obligatorio. Se marcan los 3 obligatorios en vez de los 6
   opcionales — menos ruido para la misma información. */
.req { color: var(--error-texto); font-weight: 600; }

/* Foco visible propio: los controles con fondo de marca pierden contraste
   contra el anillo por default de algunos navegadores (WCAG 2.4.7). */
:is(a, button, .btn, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--acento-600);
  outline-offset: 2px;
}
.sidebar a:focus-visible, .app-header .btn-icono:focus-visible {
  outline-color: var(--acento-400);
}

/* Salto al contenido: sin esto, un usuario de teclado recorre las 8 entradas
   del sidebar en cada página antes de llegar a lo que vino a hacer (WCAG 2.4.1). */
.salto-contenido {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 20;
}
.salto-contenido:focus {
  left: 8px;
  background: var(--superficie);
  color: var(--texto-principal);
  padding: 10px 16px;
  border-radius: 8px;
  border: 1px solid var(--linea);
  text-decoration: none;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.error { color: var(--error-texto); font-size: 13.5px; }
.success { color: var(--exito-texto); font-size: 13.5px; }
.etiqueta { color: var(--texto-secundario); font-size: 13px; }

.dias-checklist { display: flex; gap: 10px; flex-wrap: wrap; }
.dia-check {
  display: inline-flex !important; align-items: center; gap: 6px;
  margin: 0 !important; font-weight: 500 !important; font-size: 13px;
  padding: 7px 12px; border: 1px solid var(--linea); border-radius: 8px;
  background: var(--superficie); color: var(--texto-principal);
}

/* Avance del checklist — <progress> nativo, repintado con la paleta de marca.
   Cada motor expone su propia pseudo-clase, por eso las reglas van repetidas. */
.avance label { display: block; margin: 0 0 8px; font-size: 13px; font-weight: 400; color: var(--texto-principal); }
.avance progress {
  width: 100%; height: 8px; display: block;
  appearance: none; border: none;
  background: var(--pendiente-fondo); border-radius: 999px; overflow: hidden;
}
.avance progress::-webkit-progress-bar { background: var(--pendiente-fondo); }
.avance progress::-webkit-progress-value { background: var(--acento-500); border-radius: 999px; }
.avance progress::-moz-progress-bar { background: var(--acento-500); border-radius: 999px; }

.checklist-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 12px 0; border-bottom: 1px solid var(--linea);
}
.checklist-item:last-child { border-bottom: none; }
.checklist-item .nombre { flex: 1; font-size: 14px; }
.checklist-item .opciones { display: flex; gap: 14px; font-size: 13px; }
.checklist-item label { display: inline-flex; align-items: center; gap: 5px; margin: 0; font-weight: 400; }
/* El blanco táctil de un radio no es el círculo de 13px: es su etiqueta, y con
   `font-size: 13px` esa etiqueta medía 19px de alto — por debajo del mínimo
   accesible de 24, el mismo umbral que obligó a agrandar el `?`. Son los controles
   que más se tocan de toda la app (11 ítems por dos opciones, dos veces por semana
   por tienda) y un toque errado cambia la calificación sin avisar. Se sube con
   min-height y NO agrandando el círculo, que competiría con el texto del ítem. */
.checklist-item label { min-height: 24px; }

/* El punto del radio, del color de lo que significa (21-sep-2026).

   El `accent-color` global los pinta todos de verde, así que marcar «No cumple»
   dejaba un punto VERDE. En un producto donde el color es el semáforo, esa es la
   lectura equivocada de un vistazo: la pantalla diría que está bien lo que el número
   cuenta como mal. El dato estaba bien; lo que mentía era el color.

   CSS puro por el valor del control: la CSP va con `script-src 'self'` sin
   `unsafe-inline`, así que pintarlo con JS desde la plantilla se descartaría en
   silencio — y el control se vería perfecto.

   El `[value="2"]` de aquí era el ámbar, retirado el 9-oct-2026 junto con su opción.

   Cap. 21, la paleta se mide al declararla. Contra `--superficie`, en los dos temas:
   éxito 5.48 / 9.60, error 6.47 / 7.71. Los cuatro por encima del 3:1 que piden los
   componentes de interfaz. Son los mismos tokens del semáforo del tablero, no unos
   nuevos: un color propio aquí se separaría del de allá la primera vez que alguien
   ajustara uno. */
.checklist-item input[type="radio"][value="1"] { accent-color: var(--exito-texto); }
.checklist-item input[type="radio"][value="0"] { accent-color: var(--error-texto); }

/* Los símbolos de la cuadrícula del reporte de cierre de intervención.

   Van con color Y con forma distinta (✓ ✗): este documento se imprime y se reenvía,
   a veces en blanco y negro, y ahí el color no existe. Es la misma regla del mapa —la
   señal cambia de forma además de color—.

   Los tokens son los del semáforo, no unos nuevos. Medidos contra `--superficie` en
   los dos temas: éxito 5.48 / 9.60, error 6.47 / 7.71. */
.celda-resultado { text-align: center; }
.simbolo-verde, .simbolo-rojo {
  font-size: 16px; font-weight: 700; line-height: 1;
}
.simbolo-verde     { color: var(--exito-texto); }
.simbolo-rojo      { color: var(--error-texto); }

/* --- Footer del shell --- */
.app-footer {
  height: 36px;
  flex-shrink: 0;
  background: var(--superficie);
  border-top: 1px solid var(--linea);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--texto-secundario);
  font-size: 11px;
}
.app-footer img { width: 14px; height: 14px; border-radius: 3px; }

/* --- Página anónima (login) ---
   Pantalla aislada a propósito: paleta y tipografía fijas (system-ui), sin las
   variables/tokens de Archivo+IBM Plex que usa el resto de la app — es la única
   pantalla sin sidebar ni navegación, con su propio look definido por diseño. */
/* .content-anonimo es un <main>, así que hereda `main { padding: 40px... }` de
   arriba (pensado para el panel interno) si no se anula explícitamente — eso era
   la franja clara que aparecía arriba del login, el fondo del body asomando por
   detrás del padding. */
.content-anonimo { min-height: 100vh; padding: 0; margin: 0; }

.login-page {
  min-height: 100vh;
  background-color: #1B2432;
  background-image: url('/static/branding/fondo.jpg');
  /* contain, no cover: el fondo trae el wordmark "Data Intelligence" centrado en
     el propio archivo — con cover se recorta en cualquier viewport cuya relación
     de aspecto no sea exactamente la de la imagen (1920x1080). contain garantiza
     que nunca se corta, a costa de dejar ver el color base en los bordes. */
  background-size: contain;
  background-position: center;
  background-attachment: fixed;
  background-repeat: no-repeat;
  display: flex;
  justify-content: center;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}

.login-card {
  width: 100%;
  max-width: 420px;
  height: fit-content;
  margin-top: 40px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.35);
  padding: 24px 32px;
}

.login-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-bottom: 10px;
}
.login-logo {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  object-fit: cover;
  box-shadow: 0 2px 6px rgba(0,0,0,0.25);
  flex-shrink: 0;
}
.login-nombre {
  color: #2D3748;
  font-size: 20px;
  font-weight: 600;
  font-style: normal;
}
.login-badge {
  background: #05D96B;
  color: #2D3748;
  font-size: 11px;
  font-weight: 700;
  border-radius: 10px;
  padding: 2px 8px;
  text-transform: none;
  text-decoration: none;
}

.login-titulo {
  color: #2D3748;
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 2px;
}
.login-tagline {
  color: #718096;
  font-size: 13px;
  margin: 0 0 14px;
  white-space: nowrap;
}

.login-error {
  background: #fff3cd;
  border: 1px solid #ffe69c;
  color: #664d03;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 13.5px;
  margin-bottom: 16px;
}

.login-form { display: flex; flex-direction: column; gap: 14px; }
.login-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: #2D3748;
  font-size: 14px;
  font-weight: 400;
  font-style: normal;
}
.login-form input {
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 14px;
  font-family: inherit;
  color: #2D3748;
  background: #ffffff;
}
.login-form button {
  width: 100%;
  background: #16A34A;
  color: #ffffff;
  border: none;
  border-radius: 6px;
  padding: 8px 16px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.login-form button:hover { background: #14532D; }

@media (max-width: 480px) {
  .login-card { margin-top: 24px; padding: 20px; }
  .login-tagline { white-space: normal; }
}

@media (max-width: 880px) {
  /* minmax(0, 1fr), no 1fr: `1fr` equivale a `minmax(auto, 1fr)` y ese mínimo
     `auto` deja que la columna CREZCA hasta el ancho de su contenido. Con una
     tabla ancha adentro, el grid entero se ensanchaba, el .tabla-wrap nunca
     llegaba a tener un límite que respetar (su overflow no se activaba nunca) y
     la página terminaba con scroll horizontal, arrastrando también al sidebar. */
  .cuerpo { grid-template-columns: minmax(0, 1fr); }
  nav.toc { min-width: 0; }
  /* En pantalla angosta se esconden el nombre y la sección, pero **el rol se queda**:
     es lo único del encabezado que cambia lo que la pantalla permite hacer, y esconderlo
     justo en el teléfono —donde el NOC captura— deja al agente sin saber por qué no ve
     un botón. */
  .app-header .seccion, .app-header .sep, .app-header .usuario-actual { display: none; }
  .sidebar {
    position: relative;
    top: 0;
    height: auto;
    padding: 10px 12px;
    flex-direction: row;
    flex-wrap: wrap;
  }
  nav.toc { flex-direction: row; flex-wrap: wrap; }
  nav.toc .grupo-titulo { display: none; }
  main.contenido { padding: 20px; }

  /* Etiqueta arriba del valor: 180px de columna fija no caben junto al valor
     en un teléfono, y el <dl> quedaba con los valores partidos en dos líneas. */
  .datos { grid-template-columns: 1fr; gap: 2px 0; }
  .datos dd { margin-bottom: 12px; }

  /* El ítem de checklist deja de ser fila: el nombre completo arriba y las dos
     opciones abajo, en vez de un nombre truncado peleando con los radios. */
  .checklist-item { flex-direction: column; align-items: flex-start; gap: 8px; }
  /* En angosto las opciones tienen su propio renglón, así que hay alto de sobra:
     se le da al dedo más que el mínimo. El hueco pasa de 14px a 20 porque aquí
     «Cumple» y «No cumple» se tocan con el pulgar, no se apuntan con un ratón. */
  .checklist-item label { min-height: 32px; }
  .checklist-item .opciones { gap: 20px; }

  /* Dos KPI por renglón. Con el mínimo de 160px no cabían dos en 293px de ancho
     útil, así que caían en columna: los cuatro de tiendas ocupaban unos 400px de
     alto y había que pasar toda la pantalla de largo antes de llegar a la lista,
     que es a lo que se entra. */
  .grid-kpi { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; }

  /* Cada filtro toma su propio renglón, con la etiqueta encima. Con etiqueta y
     campo lado a lado, el ancho que le queda al campo depende del largo de la
     etiqueta: «Inicio desde» y «hasta» dejaban dos campos de fecha de anchos
     distintos, empezando y terminando cada uno en un sitio, y la fila se leía
     como una escalera en vez de como un formulario. */
  .filtros-form { flex-direction: column; align-items: stretch; }
  .filtros-form > .filtro-campo {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
  }
  .filtros-form input, .filtros-form select { width: 100%; }
  /* `crece` reparte el ancho sobrante en la barra horizontal; al girar el eje a
     columna ese mismo `flex: 1 1` reparte ALTO, y el campo de tienda se estiraba
     hasta unos 200px de alto. Aquí no hay ancho que repartir: todos ocupan la
     línea completa. */
  .filtros-form > .filtro-campo.crece,
  .filtros-form > .filtro-campo.crece input { flex: 0 0 auto; }
  /* El del checkbox no: ahí la etiqueta va al lado, que es lo que lo hace legible
     —y de paso lo que le da al dedo un blanco del ancho de la línea—. */
  .filtros-form .check-linea { flex-direction: row; align-items: center; }
  .filtros-form .check-linea input { width: auto; }
}

/* Celda de corrida en la rejilla mensual: cada una es un enlace a su checklist,
   así que el mes no es solo un reporte — se puede llegar al detalle desde ahí. */
.celda-corrida {
  display: block;
  padding: 3px 0;
  text-decoration: none;
  white-space: nowrap;
}
.celda-corrida:hover { text-decoration: underline; }

/* --- Pantalla de Integración ---------------------------------------------
   Todo se apoya en los tokens de color que ya existen (--exito-*, --error-*,
   --pendiente-*), así el tema oscuro sale gratis y no hay un segundo juego de
   colores que se desalinee del resto de la app. */

/* Avisos de una línea. Son .card para heredar borde y radio; la variante solo
   cambia fondo, borde y color de texto. */
.card.ok     { background: var(--exito-fondo);     border-color: var(--exito-borde);     color: var(--exito-texto); }
.card.aviso  { background: var(--pendiente-fondo); border-color: var(--pendiente-borde); color: var(--pendiente-texto); }
.card.error  { background: var(--error-fondo);     border-color: var(--error-borde);     color: var(--error-texto); }

.grid-tarjetas {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  /* minmax con minmax(280px,1fr) y un <dl> largo adentro ensancharía el grid
     completo; min-width:0 deja que cada tarjeta se encoja en vez de empujar. */
}
.grid-tarjetas > .card { min-width: 0; margin-bottom: 0; }
.grid-tarjetas form { margin-top: 12px; }

/* `list-style: none` quita la viñeta pero NO la sangría que el navegador reserva
   para ella: quedaban 40px de hueco a la izquierda sin nada que ponerles, que en
   un teléfono son el 14% del ancho útil y hacen que las alertas se lean como
   subordinadas al título en vez de ser el contenido. */
.lista-alertas { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.alerta {
  display: grid;
  gap: 2px;
  padding: 10px 12px;
  border-radius: 8px;
  border-left: 3px solid transparent;
}
.alerta strong { font-size: 13.5px; }
.alerta-critico  { background: var(--error-fondo);     border-left-color: var(--error-borde);     color: var(--error-texto); }
.alerta-atencion { background: var(--pendiente-fondo); border-left-color: var(--pendiente-borde); color: var(--pendiente-texto); }
/* .etiqueta adentro de una alerta hereda el color de la alerta: si se quedara
   gris, el detalle se leería como texto deshabilitado y no como parte del aviso. */
.alerta .etiqueta { color: inherit; opacity: .85; }

/* Pares dato/valor de una corrida. Dos columnas fijas para que los valores
   queden alineados entre sí y se puedan comparar de un vistazo. */
.datos-corrida {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 16px;
  margin: 12px 0;
  font-size: 13.5px;
}
.datos-corrida dt { color: var(--texto-secundario); }
.datos-corrida dd { font-variant-numeric: tabular-nums; }

/* La huella SHA-256 son 64 caracteres sin espacios: sin el corte explícito
   ensancha la tarjeta y saca barra horizontal a la página entera. */
.huella { word-break: break-all; font-size: 12px; }

.form-en-linea { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.form-en-linea input[type="text"] { width: auto; min-width: 110px; padding: 5px 8px; font-size: 13px; }

/* --- Pista contextual -----------------------------------------------------
   Reemplaza al atributo `title` nativo, que no se puede estilizar, tarda ~1s en
   aparecer, no sale al tabular y no existe en táctil.

   Se muestra con :hover Y con :focus-within — esto último es lo que la hace
   alcanzable con teclado, que es justo lo que el `title` nunca resolvió. */
.pista { position: relative; display: inline-flex; vertical-align: middle; }

.pista-boton {
  width: 15px;
  height: 15px;
  padding: 0;
  margin-left: 5px;
  border: 1px solid var(--linea);
  border-radius: 50%;
  background: var(--superficie);
  color: var(--texto-secundario);
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  cursor: help;
  flex-shrink: 0;
  position: relative;
}
/* El disco dibujado mide 15px, que con un ratón sobra y con un dedo es
   inalcanzable: el mínimo accesible son 24x24. Se agranda el ÁREA de toque, no
   el círculo — subir el círculo lo convertiría en un botón que compite con el
   dato al que acompaña, y ese dato es lo importante de la celda. */
.pista-boton::before {
  content: "";
  position: absolute;
  inset: -12px;
}
.pista-boton:hover {
  background: var(--acento-600);
  border-color: var(--acento-600);
  color: #ffffff;
}

.pista-texto {
  /* fixed, no absolute, y en TODOS los anchos. Un globo `absolute` lo recorta
     cualquier ancestro con overflow, y `.tabla-wrap` tiene overflow-x:auto —que
     por especificación arrastra el eje Y a auto—, así que toda pista dentro del
     encabezado de una tabla abría hacia arriba contra el borde del contenedor y
     se veía cortada: medido, entre el 72% y el 96% de su alto. Al estar fija ya
     no la recorta nadie; la posición la calcula app.js, que es quien sabe dónde
     quedó el botón. Ningún ancestro tiene transform/filter/contain, así que
     ninguno le crea bloque contenedor y el viewport sigue siendo la referencia. */
  position: fixed;
  z-index: 30;
  width: max-content;
  /* Tope para que una explicación larga no se convierta en una tira de una línea
     que se sale de la pantalla. */
  max-width: 280px;
  padding: 9px 12px;
  border-radius: 8px;
  background: var(--menu-fondo);
  color: #f1f5f9;
  border: 1px solid var(--pista-borde);
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  text-transform: none;
  letter-spacing: normal;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
  /* visibility, no display: `display:none` saca al elemento del árbol de
     accesibilidad y aria-describedby dejaría de encontrarlo.

     Pero `visibility:hidden` por sí solo NO basta: el globo sigue midiendo sus
     280px, y al estar posicionado absoluto cerca del borde derecho agranda el
     área de scroll de la página aunque nadie lo vea — en móvil eso daba 13px de
     desplazamiento horizontal fantasma. Por eso mientras está oculto también se
     reduce a cero: sigue en el árbol de accesibilidad, pero no ocupa nada. */
  visibility: hidden;
  opacity: 0;
  width: 0;
  height: 0;
  padding: 0;
  border-width: 0;
  overflow: hidden;
  transition: opacity 0.12s ease;
}
.pista:hover .pista-texto,
.pista:focus-within .pista-texto {
  visibility: visible;
  opacity: 1;
  width: max-content;
  height: auto;
  padding: 9px 12px;
  border-width: 1px;
  overflow: visible;
}

/* Punta del globo. Ya no va fija al 50%: cuando el globo se pega a un borde de la
   pantalla deja de estar centrado sobre su botón, y una punta al medio apuntaría
   a cualquier otra cosa. app.js pone `--punta-x` con el centro real del botón
   medido desde el borde izquierdo del globo. */
.pista-texto::after {
  content: "";
  position: absolute;
  top: 100%;
  left: var(--punta-x, 50%);
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--menu-fondo);
}
/* Cuando no cabe arriba, el globo abre hacia abajo y la punta se voltea. */
.pista.punta-arriba .pista-texto::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--menu-fondo);
}

/* Dentro de un <th>, que va en mayúsculas y con letra espaciada. */
th .pista-boton { margin-left: 4px; }

/* Pista en pantalla angosta. Este media query va al final A PROPÓSITO: las reglas
   base de .pista-texto se declaran más arriba, y un @media colocado antes que
   ellas pierde la cascada aunque su condición se cumpla. */
@media (max-width: 880px) {
  /* `fixed` ya viene de la regla base; aquí solo cambia que ocupe todo el ancho.
     Un globo de 280px no cabe junto a los bordes de una pantalla angosta. */
  .pista-texto {
    left: 12px;
    right: 12px;
    max-width: none;
    /* Ancla de respaldo, no decorativa: sin ella un elemento fixed sin top ni
       bottom nace en su posición estática, que dentro de una tabla con scroll
       puede caer fuera de la pantalla. app.js lo recoloca al mostrarlo, pero
       entre el toque y ese reajuste hay un cuadro pintado; y si el JS no cargó,
       tocar el ? no muestra nada en absoluto. Pegado abajo siempre se ve. */
    top: auto;
    bottom: 12px;
  }
  /* Un globo de 280px no cabe junto a los bordes de una pantalla angosta, y
     dentro de una tabla con scroll horizontal se corta casi siempre. Anclado al
     viewport deja de depender de dónde quedó su botón; la altura la fija app.js,
     que es el único que la sabe. */
  .pista:hover .pista-texto,
  .pista:focus-within .pista-texto { width: auto; }
  /* Sin punta: ya no apunta a un punto concreto. */
  .pista-texto::after { display: none; }
}


/* --- Comparador de estructura -------------------------------------------------- */

/* El par viejo -> nuevo de cada campo. El color NUNCA va solo: el valor anterior va
   además tachado, porque en una lista de 130 filas el matiz de gris no alcanza para
   distinguirlos de un vistazo —y con daltonismo no alcanza nunca—. */
.lista-cambios { margin: 0; padding-left: 16px; font-size: 13px; line-height: 1.6; }
.lista-cambios .antes { color: var(--texto-secundario); text-decoration: line-through; }
.lista-cambios .ahora { color: var(--texto-principal); font-weight: 600; }

/* Secciones colapsadas con <details> nativo: sin una línea de JS, así que no hay nada
   que la CSP pueda descartar en silencio, y funciona con el teclado y con lector de
   pantalla sin que haya que agregarle ARIA. */
details.card > summary {
  cursor: pointer;
  font-weight: 600;
  padding: 2px 0;
  list-style-position: outside;
}
details.card > summary:focus-visible {
  outline: 2px solid var(--acento-500);
  outline-offset: 3px;
  border-radius: 4px;
}
details.card > summary .etiqueta { font-weight: 400; margin-left: 6px; }
details.card[open] > summary { margin-bottom: 12px; border-bottom: 1px solid var(--linea); padding-bottom: 10px; }

/* «Todos» de un filtro de texto libre: quita ESE filtro sin tocar los demás. Se dibuja
   pequeño y debajo del campo porque es una salida, no una acción principal — y solo
   aparece cuando hay algo que quitar. */
.quitar-filtro {
  display: inline-block;
  margin-top: 3px;
  font-size: 11px;
  color: var(--texto-secundario);
}
.quitar-filtro:hover, .quitar-filtro:focus-visible { color: var(--acento-600); }
.filtro-col select { width: 100%; }

/* --- Refresco en vivo ------------------------------------------------------------ */
.pulso { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; }
/* El punto NO va solo: al lado siempre hay texto que dice lo mismo. Un indicador que
   solo es color no lo lee quien no distingue ese color, ni un lector de pantalla. */
.pulso-punto {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--acento-500);
  box-shadow: 0 0 0 0 var(--acento-500);
  animation: dops-latido 2s ease-out infinite;
  flex-shrink: 0;
}
@keyframes dops-latido {
  0%   { box-shadow: 0 0 0 0 rgba(23, 168, 116, .5); }
  70%  { box-shadow: 0 0 0 6px rgba(23, 168, 116, 0); }
  100% { box-shadow: 0 0 0 0 rgba(23, 168, 116, 0); }
}
/* Quien pidió menos movimiento no quiere un punto latiendo en la esquina. */
@media (prefers-reduced-motion: reduce) {
  .pulso-punto { animation: none; }
}

/* ===================================================================================
   TABLERO DE ASEGURAMIENTO DEL SERVICIO
   =================================================================================== */

.panel-cabeza { display: flex; flex-wrap: wrap; align-items: baseline; gap: 16px; }
.panel-cabeza h1 { margin-right: auto; }

.pestanas { display: flex; flex-wrap: wrap; gap: 4px; }
.pestanas a {
  padding: 6px 12px; border-radius: 8px; font-size: 13.5px; font-weight: 600;
  color: var(--texto-secundario); text-decoration: none; border: 1px solid transparent;
}
.pestanas a:hover { background: var(--header-icono-hover); }
.pestanas a.activa {
  background: var(--acento-50); color: var(--acento-texto-fuerte);
  border-color: var(--acento-200);
}

/* --- El semáforo: color Y FORMA ---------------------------------------------------
   Rojo/verde plano falla para ~8% de los hombres y a tres metros en una pantalla de
   pared. La señal cambia de forma además de color: círculo sano, triángulo caído,
   cuadro para «sin dato». */
.senal { display: inline-block; width: 12px; height: 12px; flex: none; }
.senal-verde { background: var(--exito-texto); border-radius: 50%; }
.senal-ambar {
  width: 0; height: 0; background: none;
  border-left: 7px solid transparent; border-right: 7px solid transparent;
  border-bottom: 12px solid var(--ambar-texto);
}
.senal-rojo {
  width: 0; height: 0; background: none;
  border-left: 7px solid transparent; border-right: 7px solid transparent;
  border-bottom: 12px solid var(--error-texto);
}
.senal-sin_dato { background: var(--noaplica-texto); border-radius: 2px; }

.semaforo-verde    { --semaforo-fondo: var(--exito-fondo);     --semaforo-borde: var(--exito-borde);     --semaforo-texto: var(--exito-texto); }
.semaforo-ambar    { --semaforo-fondo: var(--ambar-fondo);     --semaforo-borde: var(--ambar-borde);     --semaforo-texto: var(--ambar-texto); }
.semaforo-rojo     { --semaforo-fondo: var(--error-fondo);     --semaforo-borde: var(--error-borde);     --semaforo-texto: var(--error-texto); }
.semaforo-sin_dato { --semaforo-fondo: var(--noaplica-fondo);  --semaforo-borde: var(--noaplica-borde);  --semaforo-texto: var(--noaplica-texto); }
/* «Saltada por falla técnica» (16-sep-2026): NO entra a la escala verde/amarillo/rojo
   —no es una calificación, es la ausencia de una— y tampoco puede ser el mismo gris que
   «sin revisión», que se lee como negligencia cuando aquí la falla es del equipo. Toma
   los tokens de `--info`, el azul con que este sistema ya dice «pasó algo que no es
   bueno ni malo, es otra cosa», y que `tests/test_paleta.py` ya mide. */
.semaforo-tecnica  { --semaforo-fondo: var(--tecnica-fondo);   --semaforo-borde: var(--tecnica-borde);   --semaforo-texto: var(--tecnica-texto); }

/* --- Tarjeta de KPI de compromiso -------------------------------------------------- */
.kpi-sla {
  background: var(--superficie); border: 1px solid var(--linea);
  /* La barra de estado va con el token de TEXTO y no con el de borde. Medido: con
     --semaforo-borde da entre 1.23:1 y 2.06:1 sobre la superficie —invisible, y por
     debajo del 3:1 que WCAG 1.4.11 pide a un indicador no textual— y con
     --semaforo-texto, entre 4.76:1 y 10.15:1 en los cuatro estados y los dos temas.
     A tres metros esa barra es lo primero que se lee de la tarjeta. */
  border-top: 4px solid var(--semaforo-texto); border-radius: 12px;
  padding: 14px 16px; display: flex; flex-direction: column; gap: 6px; min-width: 0;
}
.kpi-sla-cabeza { display: flex; align-items: center; gap: 8px; }
.kpi-sla-titulo { font-size: 13px; font-weight: 600; color: var(--texto-secundario); }
.kpi-sla-valor {
  font-family: Archivo, system-ui, sans-serif; font-weight: 800;
  font-size: 34px; line-height: 1; color: var(--semaforo-texto);
  font-variant-numeric: tabular-nums;
}
.kpi-sla-valor .unidad { font-size: 18px; margin-left: 2px; }
.kpi-sla-valor .sin-dato { font-size: 20px; color: var(--noaplica-texto); }
.kpi-sla-metas { font-size: 12px; color: var(--texto-secundario); display: flex; flex-wrap: wrap; gap: 6px; }
.kpi-sla-bruto { font-size: 12px; color: var(--texto-secundario); font-variant-numeric: tabular-nums; }
.kpi-sla-detalle { font-size: 12.5px; color: var(--texto-secundario); }
/* La acción va escrita en la tarjeta: un KPI sin respuesta definida al ponerse rojo
   no entra al panel — se va al export mensual, donde documentar sin accionar sí tiene
   sentido. */
.kpi-sla-accion {
  font-size: 12.5px; background: var(--semaforo-fondo); color: var(--semaforo-texto);
  border-radius: 8px; padding: 6px 8px;
}
.etiqueta-estabilizacion {
  background: var(--noaplica-fondo); color: var(--noaplica-texto);
  border: 1px solid var(--noaplica-borde); border-radius: 999px;
  padding: 0 6px; font-size: 11px;
}

/* --- La banda «ahora» -------------------------------------------------------------- */
.banda-ahora {
  display: flex; flex-wrap: wrap; gap: 20px; align-items: center;
  background: var(--superficie); border: 1px solid var(--linea);
  border-radius: 12px; padding: 14px 18px; margin-bottom: 16px;
}
.banda-cifra { display: flex; flex-direction: column; gap: 2px; min-width: 220px; }
.banda-valor {
  font-family: Archivo, system-ui, sans-serif; font-weight: 800; font-size: 40px;
  line-height: 1; color: var(--texto-principal); font-variant-numeric: tabular-nums;
}
.banda-valor .unidad { font-size: 20px; }
.banda-etiqueta { font-size: 12.5px; color: var(--texto-secundario); }
.lista-caidos { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; flex: 1; min-width: 240px; }
.lista-caidos li { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.caido-nombre { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.caido-tiempo { color: var(--texto-secundario); font-variant-numeric: tabular-nums; }
.sin-caidos { color: var(--texto-secundario); font-size: 13px; }

/* Una pantalla encendida no puede mentir en silencio: con el barrido caído, el tablero
   entero cambia de estado y lo DICE con palabras. Todo verde con datos viejos es peor
   que no tener tablero, porque genera confianza activa. */
.banda-degradada {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline;
  border: 2px solid; border-radius: 12px;
  padding: 12px 18px; margin-bottom: 10px; font-size: 14px;
}
/* Rojo solo cuando es avería. La ingesta apagada a propósito es ámbar: pintar de rojo
   una decisión de configuración deja el tablero en rojo permanente, y un color que
   siempre está encendido es un color que nadie mira. */
.banda-degradada.es-averia  { background: var(--error-fondo); color: var(--error-texto); border-color: var(--error-borde); }
.banda-degradada.es-apagado { background: var(--ambar-fondo); color: var(--ambar-texto); border-color: var(--ambar-borde); }
.banda-degradada strong { letter-spacing: .06em; }
.banda-en-duda { opacity: .55; }

/* --- Presupuesto de error ---------------------------------------------------------- */
.presupuesto { display: flex; flex-wrap: wrap; gap: 24px; align-items: center; }
.presupuesto-cifra { display: flex; flex-direction: column; gap: 2px; min-width: 200px; }
.presupuesto-cifra .valor {
  font-family: Archivo, system-ui, sans-serif; font-weight: 800; font-size: 44px;
  line-height: 1; color: var(--semaforo-texto); font-variant-numeric: tabular-nums;
}
.presupuesto-cifra .valor .unidad { font-size: 20px; }
.presupuesto .datos-corrida { flex: 1; min-width: 280px; }

/* --- El mapa ----------------------------------------------------------------------- */
.mapa-envoltura { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.mapa {
  flex: 1 1 420px; min-width: 0; max-height: 420px;
  background: var(--fondo-app); border: 1px solid var(--linea); border-radius: 12px;
}
/* El contorno del país, para TODOS los mapas del sistema.
   El color va en el <g> y no en los `path`, por dos razones que se suman: es lo que
   permite que cruce al clon que hace `<use>` —`fill` y `stroke` se heredan, un selector
   de la hoja externa no alcanza un shadow tree— y es una sola regla para los cuatro
   mapas en vez de dos que divergen.

   Medido: con `--linea` el trazo daba 1.18:1 en claro y 1.57:1 en oscuro sobre la
   tierra, y la tierra 1.00:1 y 1.10:1 sobre el fondo — o sea que la forma del país no se
   veía y lo que se percibía era un fantasma. Con `--texto-secundario` a media fuerza da
   2.4:1 y 3.67:1: visible como referencia geográfica y por DEBAJO de los puntos, que son
   lo que hay que leer. El contorno es contexto y va `aria-hidden`. */
.mapa .contorno { fill: var(--noaplica-fondo); stroke: var(--texto-secundario); stroke-width: .5; opacity: .65; }
/* `vector-effect` NO es heredable, así que esta sí tiene que ir en los `path`. Los del
   mapa de tienda lo llevan como atributo, porque a través de `<use>` ni el selector ni
   la herencia lo alcanzan. */
.mapa .contorno path { vector-effect: non-scaling-stroke; }
.punto-sano { fill: var(--exito-texto); opacity: .75; }
.punto-caido { fill: var(--error-texto); stroke: var(--superficie); stroke-width: .6; vector-effect: non-scaling-stroke; }
.mapa-fuera { fill: var(--texto-secundario); font-family: "IBM Plex Sans", system-ui, sans-serif; }
.mapa-fuera-caido { fill: var(--error-texto); font-weight: 700; }
.punto-numero { fill: var(--superficie); font-weight: 700; text-anchor: middle; font-family: Archivo, system-ui, sans-serif; }
.mapa-lista { flex: 0 1 240px; }
.mapa-lista h3 { margin-top: 0; }
.lista-simple { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: 13px; }

/* --- Mapa para LOCALIZAR una tienda ------------------------------------------------
   No es el mapa de estatus. Las vecinas van en UN tono neutro y sin semáforo: este mapa
   dice dónde está la tienda, no cómo está, y un semáforo aquí competiría con el del
   panel — quien busca una dirección acabaría leyendo un rojo que no venía al caso. */
.contorno-oculto { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Mapa y texto LADO A LADO cuando hay ancho. Apilados, un mapa cuadrado de 420 px en
   una tarjeta de 840 dejaba media tarjeta vacía; el texto es justo lo que la llena, y
   además se lee junto al mapa que explica. Se apilan solos cuando no caben. */
.mapa-tienda { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.mapa-tienda .mapa-tienda-pie { flex: 1 1 260px; }
/* `auto` en los dos, con topes: el <svg> trae su proporción en los atributos
   width/height, así que se dimensiona a su contenido. Con `width:100%` era un buzón —el
   dibujo al 9 % del área en el caso de Torreón—. */
.mapa-tienda .mapa { width: auto; height: auto; max-width: 100%; }
/* Los topes ENCOGEN un elemento cuyo tamaño intrínseco es grande (lado mayor
   1000 px). Con las medidas crudas del viewBox el intrínseco era de decenas de
   pixeles y `auto` lo dejaba así: 59 × 113 px. Visto en pantalla. */
/* El color del contorno se pone en el <g> y NO en `.contorno path`.
   `<use>` clona el contorno en un shadow tree, y un selector de la hoja externa no
   alcanza a los nodos clonados: los `path` se pintaban con el relleno por omisión, o sea
   NEGRO, y el mapa entero salía como una mancha. `fill` y `stroke` sí son heredables, así
   que puestos en el contenedor cruzan al clon. Visto en pantalla, no deducido. */

.mapa-tienda-chico .mapa { max-height: 420px; max-width: min(100%, 560px); }
.mapa-tienda-mini .mapa { max-height: 200px; max-width: min(100%, 300px); }
/* Las vecinas SIN opacidad reducida. Medido: al 50 % daban 1.94:1 en claro y 2.60:1 en
   oscuro, por debajo del mínimo de 3:1 de WCAG 1.4.11 para gráficos — el mismo hallazgo
   que ya había costado la barra de estado del KPI. A opacidad completa dan 4.55:1 y
   6.96:1.

   Y siguen siendo GRISES a propósito. Un punto de color se lee como un estado, y este
   mapa dice dónde está la tienda, no cómo está: el único elemento con color es la que se
   está buscando. */
.punto-vecina { fill: var(--texto-secundario); }

/* La tienda buscada: forma propia y no solo otro color. Hay que distinguirla de un
   vistazo entre decenas, y el color solo falla para ~8 % de los hombres. */
.localizada-rombo { fill: var(--acento-texto-fuerte); stroke: var(--superficie); stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.localizada-anillo { fill: none; stroke: var(--acento-texto-fuerte); stroke-width: 1.6; vector-effect: non-scaling-stroke; opacity: .55; }
.mapa-tienda-pie { margin: 0; font-size: 13px; color: var(--texto-secundario); line-height: 1.55; }
.mapa-tienda-pie strong { color: var(--texto-principal); }
.liga-externa { white-space: nowrap; }

/* --- Mosaico nacional --------------------------------------------------------------
   Tarjeta por ciudad, dimensionada por número de tiendas y coloreada por estado. Tres
   escalones y no un tamaño continuo: a tres metros una diferencia de 4 px no se lee. */
.mosaico { display: flex; flex-wrap: wrap; gap: 8px; }
.mosaico-tarjeta {
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  background: var(--semaforo-fondo); border: 1px solid var(--semaforo-borde);
  color: var(--semaforo-texto); border-radius: 10px; padding: 8px 12px;
}
.mosaico-ciudad { font-size: 12px; font-weight: 600; }
.mosaico-cifra { font-family: Archivo, system-ui, sans-serif; font-weight: 800; line-height: 1; }
.mosaico-caidos { font-size: 11.5px; font-weight: 600; }
.mosaico-chico  { min-width: 92px; }  .mosaico-chico .mosaico-cifra  { font-size: 18px; }
.mosaico-medio  { min-width: 116px; } .mosaico-medio .mosaico-cifra  { font-size: 26px; }
.mosaico-grande { min-width: 148px; } .mosaico-grande .mosaico-cifra { font-size: 38px; }
/* Quién tapa a quién. Va en el mismo color del semáforo de la tarjeta y en cursiva:
   es una nota sobre el acomodo, no un dato de operación, y no debe competir con la
   cifra de sitios. */
.mosaico-bloqueo { font-size: 11px; font-style: italic; opacity: .85; }

/* --- Mapa nacional de insignias ----------------------------------------------------
   Una insignia por plaza, en SVG y no en HTML posicionado: la CSP prohíbe `style=` en
   línea, así que un `left:`/`top:` calculado por render se descartaría EN SILENCIO —la
   página se vería bien con las nueve insignias apiladas en la esquina—. En SVG la
   posición son atributos `x`/`y`, que sí se permiten. */
.mapa-nacional { max-height: 460px; }
.insignia { cursor: pointer; }
.insignia-caja {
  fill: var(--semaforo-fondo); stroke: var(--semaforo-texto);
  stroke-width: 1.5; vector-effect: non-scaling-stroke;
}
.insignia-ciudad, .insignia-cifra {
  fill: var(--semaforo-texto); text-anchor: middle;
  font-family: Archivo, system-ui, sans-serif;
}
.insignia-ciudad { font-weight: 600; }
.insignia-cifra { font-weight: 800; font-variant-numeric: tabular-nums; }
/* El foco tiene que verse: la insignia es un control y se alcanza con teclado. */
.insignia:focus { outline: none; }
.insignia:focus-visible .insignia-caja { stroke-width: 3.5; }
.insignia:hover .insignia-caja { stroke-width: 3; }

/* Las capas de plaza que no se están mostrando.
   Clase y NO el atributo `hidden`: la regla del navegador para `hidden` pertenece al
   espacio de nombres HTML, así que sobre un `<g>` de SVG no hace nada — y el síntoma
   sería las nueve plazas dibujadas una encima de otra. */
.capa-oculta { display: none; }

/* --- Tendencia --------------------------------------------------------------------- */
.tendencia { width: 100%; height: 70px; }
.tendencia-linea { stroke: var(--acento-500); stroke-width: 2; vector-effect: non-scaling-stroke; }

.racimo {
  border: 1px solid var(--linea); border-radius: 10px; padding: 12px 14px;
  margin-bottom: 10px; display: grid; gap: 6px;
}
.rejilla-campos { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }

/* ===================================================================================
   La vista TABLERO — pantalla encendida, legible a 3 metros
   =================================================================================== */

.cuerpo-tablero { background: var(--fondo-app); color: var(--texto-principal); padding: 20px 24px; }
.tablero-cabeza { display: flex; flex-wrap: wrap; align-items: baseline; gap: 16px; margin-bottom: 14px; }
.tablero-cabeza h1 { font-size: 26px; margin: 0; margin-right: auto; }
.tablero-periodo { font-size: 14px; color: var(--texto-secundario); font-variant-numeric: tabular-nums; }

/* Seis celdas: el titular ocupa dos y los otros cuatro una cada uno. El mínimo bajó de
   240 a 200 px al entrar el quinto KPI — **cede la retícula, no el tamaño de la letra**:
   a tres metros, un número más chico deja de leerse y entonces la tarjeta sobra. */
.tablero-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-bottom: 16px; }
/* El TITULAR es disponibilidad y ocupa el doble: un tablero que el director general
   deja prendido cambia la conducta de quien sale en él, así que lo grande tiene que ser
   lo que no se puede mejorar cerrando tickets. */
.tablero-kpis .kpi-grande { grid-column: span 2; }
.tablero-kpis .kpi-grande .kpi-sla-valor { font-size: 72px; }
.tablero-kpis .kpi-grande .kpi-sla-valor .unidad { font-size: 30px; }
.tablero-kpis .kpi-sla-valor { font-size: 46px; }
.tablero-kpis .kpi-sla-titulo { font-size: 15px; }

.tablero-abajo { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; align-items: start; }
.tablero-abajo h2 { font-size: 16px; margin: 0 0 8px; }
.tablero-abajo section {
  background: var(--superficie); border: 1px solid var(--linea);
  border-radius: 12px; padding: 14px 16px;
}
/* La salida del tablero va con el MISMO aspecto que los demás «← Volver» de la app:
   el verde de `--acento-texto`, que es lo que `a {}` le da a cualquier liga. Un color
   propio la habría convertido en un control distinto de todos los otros regresos, y lo
   que se quiere es lo contrario — que se reconozca sin leerla. */
.tablero-salir { margin-left: 16px; font-size: 14px; font-weight: 600; white-space: nowrap; }
/* El mismo aspecto para la liga y para el botón de cerrar sesión: son dos salidas de
   la misma pantalla y verse distintas haría dudar de cuál es cuál. El botón va sin
   caja porque en una pared encendida todo el día el control que no se quiere apretar
   por accidente es justo este. */
.tablero-salir-form { display: inline; }
button.tablero-salir {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--acento-texto); font-family: inherit;
}
.cuerpo-tablero .banda-valor { font-size: 52px; }
.cuerpo-tablero .lista-caidos li { font-size: 15px; }
/* El mapa se achica en el tablero, no en el panel: aquí compite por el alto con los
   cuatro KPI, que son lo primero que tiene que caber. Se recorta el mapa y no la lista
   de caídos — un dato que desaparece sin decirlo es peor que un mapa más chico. */
.cuerpo-tablero .mapa { max-height: 320px; }

/* El nacional a TODO lo ancho, en su propia banda. Medido en la columna angosta de la
   tendencia (425 px): la insignia salía a 38×19 px con letra de 5.6 px, ilegible a tres
   metros — o sea un mapa nacional que no informa, que es justo lo que vino a arreglar.
   A lo ancho de la pared la misma insignia pasa de 38 px a ~170. Y el `max-height` del
   tablero no aplica aquí: este mapa necesita el alto para que el país quepa. */
.tablero-nacional {
  background: var(--superficie); border: 1px solid var(--linea);
  border-radius: 12px; padding: 14px 16px; margin-top: 16px;
}
.tablero-nacional h2 { font-size: 16px; margin: 0 0 8px; }
.cuerpo-tablero .tablero-nacional .mapa { max-height: 520px; width: 100%; }

@media (max-width: 900px) {
  .tablero-abajo { grid-template-columns: 1fr; }
  .tablero-kpis .kpi-grande { grid-column: span 1; }
}

/* La envoltura del refresco lento no puede ser una celda de la retícula: el id tiene
   que estar en un contenedor estable para reemplazarle los hijos, pero quien ocupa la
   celda debe seguir siendo la tarjeta. `display: contents` resuelve exactamente eso. */
.envoltura-refresco { display: contents; }

/* El quinto KPI. Se ve como los otros cuatro salvo en lo que lo distingue: no lleva
   línea de «meta · contrato», porque no es un compromiso. */
.kpi-hoy {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  background: var(--noaplica-fondo); color: var(--noaplica-texto);
  border: 1px solid var(--noaplica-borde); border-radius: 999px; padding: 0 6px;
}
.kpi-particion { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12.5px; }
.particion-parte { color: var(--texto-secundario); }
/* La cifra que decide algo: cuántos tienen a alguien expuesto ahora mismo. */
.particion-expuesta { color: var(--semaforo-texto); font-weight: 600; }

/* Aplicar un cambio del comparador, fila por fila.
   El botón es chico a propósito: va dentro de la lista de cambios, uno por línea, y con
   el tamaño normal la celda se vuelve una columna de botones y deja de leerse el cambio,
   que es lo que hay que mirar antes de decidir. */
.btn-mini {
  padding: 2px 8px;
  font-size: 12px;
  line-height: 1.4;
  margin-left: 8px;
  vertical-align: middle;
}
.aplicado-si {
  color: var(--exito-texto);
  background: var(--exito-fondo);
  border: 1px solid var(--exito-borde);
  border-radius: 4px;
  padding: 1px 7px;
  margin-left: 8px;
}
/* El rechazo va en ámbar y no en rojo: no es una falla, es la regla diciendo que ese
   campo no se aplica desde aquí. Rojo entrenaría a leerlo como error del sistema. */
.aplicado-no {
  color: var(--ambar-texto);
  background: var(--ambar-fondo);
  border: 1px solid var(--ambar-borde);
  border-radius: 4px;
  padding: 1px 7px;
  margin-left: 8px;
}

/* Los filtros del historial de envíos, en una línea.
   `flex-wrap` y no una rejilla fija: son cuatro controles de anchos muy distintos —un
   desplegable, un texto y dos fechas— y en una pantalla angosta tienen que bajar de
   renglón solos en vez de encogerse hasta ser inservibles. */
.filtros-linea {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
}
.filtros-linea label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--texto-secundario);
}

/* KPI «con visión / sin visión» sobre el mapa nacional (16-sep-2026).
   El mapa de insignias muestra la partición por plaza, que es lo suyo; lo que no daba
   era el número nacional de un vistazo, y a tres metros nadie suma insignias.
   La señal va además del color —círculo y triángulo, los mismos de la franja— porque
   una pantalla de pared tiene que servirle también a quien no separa verde de rojo. */
.kpi-vision { display: flex; align-items: baseline; flex-wrap: wrap; gap: 12px 28px; margin-bottom: 12px; }
.kpi-vision-cifra { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.kpi-vision-valor { font-size: 40px; font-weight: 700; line-height: 1; }
.kpi-vision-etiqueta { font-size: 15px; font-weight: 600; }
.kpi-vision-reparto, .kpi-vision-base { font-size: 13px; color: var(--texto-secundario); }
.cuerpo-tablero .kpi-vision-valor { font-size: 56px; }
.cuerpo-tablero .kpi-vision-etiqueta { font-size: 18px; }

/* Las notas del sistema conservan sus saltos de línea: son una bitácora, una línea por
   hecho, y unidas en un párrafo dejan de poder leerse en diagonal. */
.notas-sistema { white-space: pre-line; }

/* El fabricante del equipo. Chip y no texto corrido: se busca con la vista, no se lee
   — quien abre el ticket ya sabe qué es y solo necesita saber cuál. */
.chip-fabricante {
  display: inline-block; font-size: 12px; font-weight: 600; letter-spacing: .04em;
  padding: 2px 8px; border-radius: 999px; vertical-align: middle;
  background: var(--noaplica-fondo); color: var(--noaplica-texto);
  border: 1px solid var(--noaplica-borde);
}

/* ===================================================================================
   Tablero semanal del checklist (16-sep-2026)

   Sin un solo `style=` calculado al renderizar: `style-src` va sin 'unsafe-inline' y un
   ancho en línea se descartaría EN SILENCIO — la barra se vería vacía y la página bien.
   Por eso la barra apilada no lleva porcentajes: es un cuadrito POR TIENDA, ordenados
   por color. La proporción sale sola y el denominador se puede contar con el dedo, que
   es justo lo que se le pide a este tablero.
   =================================================================================== */
.cinta-semana {
  display: flex; flex-wrap: wrap; gap: 2px; margin: 6px 0;
  border-radius: 4px; overflow: hidden;
}
.cinta-semana .celda {
  width: 10px; height: 18px; border-radius: 2px; flex: 0 0 auto;
  background: var(--semaforo-texto);
}
/* La semana EN CURSO se raya: su barra todavía puede cambiar y leerla como resultado
   es el error que este rayado existe para evitar. */
.cinta-semana.en-curso .celda { opacity: .55; }
/* La saltada cambia de FORMA además de color, que es la misma regla del mapa nacional.
   Contra el gris de «sin revisión» el azul es diferencia de TONO y no de luminancia
   —medido: 1.41:1—, así que a diez píxeles y sin distinguir colores serían el mismo
   cuadrito. Redonda no se confunde con nada. */
.cinta-semana .celda.semaforo-tecnica { border-radius: 50%; }

.leyenda-tablero { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; }
.leyenda-tablero .clave { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
.leyenda-tablero .muestra {
  width: 12px; height: 12px; border-radius: 3px; background: var(--semaforo-texto);
  display: inline-block;
}

/* La celda del cruce tienda × semana. Fondo y texto del mismo par de tokens que el
   resto del semáforo, medidos en `tests/test_paleta.py`: un color nuevo aquí sería un
   quinto verde que nadie volvió a medir. */
.celda-semana {
  display: block; min-width: 54px; text-align: center; border-radius: 6px;
  padding: 4px 6px; font-variant-numeric: tabular-nums; font-weight: 600;
  font-size: 13px; text-decoration: none;
  background: var(--semaforo-fondo); color: var(--semaforo-texto);
  border: 1px solid var(--semaforo-borde);
}
.celda-semana .cuantas { display: block; font-size: 11px; font-weight: 400; }
td.col-semana { padding: 4px; }

/* --- Secciones ancladas: volver a donde se actuó (8-oct-2026) --------------------
   Una pantalla larga con varios formularios manda al navegador a la sección donde se
   actuó. Dos detalles sin los cuales el arreglo se siente igual de roto:

   1. `scroll-margin-top` tiene que cubrir el header PEGAJOSO de 64px. Sin esto el ancla
      deja el título de la sección escondido debajo del header, que es exactamente el
      tipo de detalle que hace que el usuario siga sin ver lo que buscaba.
   2. El resaltado es `:target` en CSS y no JavaScript: la CSP descarta el JS en línea en
      silencio. Ver `app/templates/_seccion.html`.

   El resaltado es un BORDE y un fondo tenue, no un color de alerta: la sección no tiene
   un problema, solo es la que se acaba de tocar. */
.seccion-anclada {
  scroll-margin-top: 80px;
}

.seccion-anclada:target {
  /* Tokens que YA existen y que el tema oscuro redefine: `--acento-texto` y
     `--acento-50`. Inventar un token nuevo aquí lo dejaría sin valor en oscuro —el ámbar
     nuevo ya quedó invisible una vez por eso, y no se notó porque ninguna tarjeta estaba
     en ámbar todavía—. `tests/test_paleta.py` lee los tokens de este archivo y los mide:
     un token que no existe no lo mide nadie. */
  border-color: var(--acento-texto);
  background: var(--acento-50);
}

/* El letrero dentro de la sección: pegado a lo que cambió, no al principio de la
   página. Margen abajo para que no quede encimado al contenido que confirma. */
.aviso-de-seccion {
  margin-bottom: 12px;
}

/* No hay `prefers-reduced-motion` que atender: el resaltado es un borde y un fondo, sin
   transición ni animación, y el desplazamiento lo hace el navegador con la preferencia
   del sistema que ya respeta. Un @media que repitiera la misma regla sería ruido. */

/* La franja de hora en la cola del checklist.

   Cifras tabulares: la columna es una lista de horas que se recorre de arriba abajo, y
   con cifras proporcionales los dos puntos bailan de renglón en renglón. `nowrap`
   porque «22:00–23:30» partido en dos líneas duplica el alto de la fila por una franja
   —la última de la ventana, que es la más larga—. */
td.franja { font-variant-numeric: tabular-nums; white-space: nowrap; }
