/*!***********************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/tokens.css ***!
  \***********************************************************************************************************************/
/* ==========================================================================
   Factuser — Panel del tenant
   tokens.css — tipografía y variables de tema (claro / oscuro)

   TODO color, radio, sombra y espaciado del panel sale de acá. Ninguna hoja
   posterior escribe un color literal: si lo hiciera, ese elemento se quedaría
   en el tema claro al cambiar a oscuro, que es exactamente el tipo de bug que
   este archivo existe para prevenir.

   El tema se conmuta con un atributo en <html> (`data-theme="dark"`), no con
   una clase: es un valor único y excluyente —no se puede estar "medio
   oscuro"— y el atributo lo expresa mejor que una clase que se agrega y se
   saca. Lo escribe panel.js y lo persiste en localStorage; el <head> lo
   aplica antes de pintar (ver widget/tenant/head.php) para que no haya
   destello blanco al recargar en modo oscuro.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Noto Sans — autohospedada (2026-08-10, reemplaza a Poppins en este panel).

   Nada se pide a Google en runtime: los binarios se bajaron una vez desde
   fonts.gstatic.com y viven en source_assets/tenant/fonts/noto-sans/. El
   navegador del cliente nunca contacta un CDN de terceros — sin fuga de IPs
   y sin una dependencia externa en el camino crítico del render.

   ESTRUCTURA: una regla por peso y por subset, y dentro de cada `src` los dos
   formatos EN ORDEN. El navegador descarga el primero que soporta:

     woff2 (fuente VARIABLE)  →  navegadores modernos, ~98% del tráfico
     woff  (estática del peso) →  respaldo (IE11, Safari < 10)

   Por qué una regla por peso y no una sola con `font-weight: 100 900`:
   probándolo en Chrome se vio que declarar además reglas sueltas con peso
   exacto para los `.woff` hacía que el navegador PREFIRIERA esas —el matching
   de CSS elige la coincidencia exacta antes que un rango— y terminara bajando
   los estáticos de 250 KB en vez del variable de 36 KB. Medido: 510 KB de más
   por página. Con esta estructura, cada peso ofrece el variable primero y el
   estático sólo como alternativa, así que un navegador moderno baja UN archivo
   (el mismo para los cuatro pesos, servido desde caché) y uno viejo baja el
   estático correcto.

   Noto Sans es variable de verdad: los cuatro "pesos" que devuelve la API de
   Google son el MISMO binario (verificado por md5, los cuatro dan 86d512f8…).

   SUBSETS: sólo `latin` y `latin-ext`. El segundo no es opcional acá aunque
   pese más: el guaraní usa ẽ ĩ ũ ỹ, que no están en `latin`. Quedaron afuera
   cyrillic, greek, devanagari y vietnamese, que este producto no necesita.
   Los `.woff` de respaldo no vienen subseteados (Google no los ofrece así), de
   ahí que el mismo archivo cubra los dos rangos.
   -------------------------------------------------------------------------- */

/* 400 · latin */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable.woff2) format("woff2"),
       url(../fonts/NotoSans-Regular.woff) format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 400 · latin-ext */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable-ext.woff2) format("woff2"),
       url(../fonts/NotoSans-Regular.woff) format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* 500 · latin */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable.woff2) format("woff2"),
       url(../fonts/NotoSans-Medium.woff) format("woff");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 500 · latin-ext */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable-ext.woff2) format("woff2"),
       url(../fonts/NotoSans-Medium.woff) format("woff");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* 600 · latin */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable.woff2) format("woff2"),
       url(../fonts/NotoSans-SemiBold.woff) format("woff");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 600 · latin-ext */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable-ext.woff2) format("woff2"),
       url(../fonts/NotoSans-SemiBold.woff) format("woff");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* 700 · latin */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable.woff2) format("woff2"),
       url(../fonts/NotoSans-Bold.woff) format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 700 · latin-ext */
@font-face {
  font-family: "Noto Sans";
  src: url(../fonts/NotoSans-Variable-ext.woff2) format("woff2"),
       url(../fonts/NotoSans-Bold.woff) format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ---- Tipografía ---------------------------------------------------- */
  --ft-font: "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --ft-fs-xs: 11px;
  --ft-fs-sm: 13px;
  --ft-fs-md: 13.5px;
  --ft-fs-base: 14px;
  --ft-fs-lg: 16px;
  --ft-fs-xl: 20px;
  --ft-fs-2xl: 26px;
  --ft-fs-3xl: 32px;

  /* ---- Espaciado y formas -------------------------------------------- */
  --ft-radius-sm: 8px;
  --ft-radius: 12px;
  --ft-radius-lg: 16px;
  --ft-radius-xl: 22px;
  --ft-radius-pill: 999px;

  --ft-sidebar-w: 268px;
  --ft-sidebar-w-collapsed: 64px;
  --ft-header-h: 68px;

  /* Duración única para todas las transiciones de tema/hover: si cada
     componente elige la suya, el cambio de tema se ve como una cascada
     desprolija en vez de un solo movimiento. */
  --ft-transition: 160ms ease;

  /* ---- Marca (idéntica en ambos temas) ------------------------------- */
  --ft-brand: #2568ef;
  --ft-brand-strong: #1b4fc0;
  --ft-brand-soft: rgba(37, 104, 239, 0.10);
  --ft-brand-contrast: #ffffff;
}

/* --------------------------------------------------------------------------
   TEMA CLARO (default)

   El fondo de la página es el verde muy lavado del diseño; el "shell" flota
   encima con su propia sombra. Por eso hay dos niveles de fondo (--ft-bg y
   --ft-surface) más un tercero para las tarjetas internas.
   -------------------------------------------------------------------------- */
:root,
:root[data-theme="light"] {
  color-scheme: light;

  --ft-bg: #dde5f4;
  --ft-bg-accent: #eceff3;

  --ft-shell: #f7f8f8;
  --ft-surface: #ffffff;
  --ft-surface-2: #fbfbfc;
  --ft-surface-hover: #f2f3f5;
  --ft-surface-active: #eef0f3;

  --ft-text: #16181d;
  --ft-text-soft: #4c525c;
  --ft-text-muted: #8c939e;
  --ft-text-faint: #aab0ba;

  --ft-border: #e9eaee;
  --ft-border-strong: #dcdee4;
  --ft-border-dashed: #dfe1e7;

  --ft-success: #17a34a;
  --ft-success-soft: rgba(23, 163, 74, 0.10);
  --ft-danger: #e0503c;
  --ft-danger-soft: rgba(224, 80, 60, 0.10);
  --ft-warning: #e08b1f;
  --ft-warning-soft: rgba(224, 139, 31, 0.12);
  --ft-info: #2568ef;
  --ft-info-soft: rgba(37, 104, 239, 0.10);

  --ft-shadow-sm: 0 1px 2px rgba(19, 22, 28, 0.05);
  --ft-shadow: 0 4px 16px rgba(19, 22, 28, 0.06);
  --ft-shadow-lg: 0 24px 60px rgba(19, 22, 28, 0.14);

  --ft-overlay: rgba(20, 23, 28, 0.45);
  --ft-skeleton: #eef0f3;
}

/* --------------------------------------------------------------------------
   TEMA OSCURO

   No es el claro invertido: los grises suben de a poco (bg → shell → surface)
   para que el contenido siga leyéndose "más cerca" que el fondo, igual que en
   el claro. Los colores de estado se aclaran, porque los del tema claro sobre
   fondo oscuro quedan por debajo del contraste mínimo legible.
   -------------------------------------------------------------------------- */
:root[data-theme="dark"] {
  color-scheme: dark;

  --ft-bg: #0e1013;
  --ft-bg-accent: #131619;

  --ft-shell: #16191d;
  --ft-surface: #1b1f24;
  --ft-surface-2: #1f242a;
  --ft-surface-hover: #242a31;
  --ft-surface-active: #2a313a;

  --ft-text: #eceef2;
  --ft-text-soft: #c2c7d0;
  --ft-text-muted: #8b929d;
  --ft-text-faint: #6b727d;

  --ft-border: #272d34;
  --ft-border-strong: #70767c;
  --ft-border-dashed: #2f363e;

  --ft-success: #45c96f;
  --ft-success-soft: rgba(69, 201, 111, 0.14);
  --ft-danger: #f4705c;
  --ft-danger-soft: rgba(244, 112, 92, 0.14);
  --ft-warning: #f0a63c;
  --ft-warning-soft: rgba(240, 166, 60, 0.14);
  --ft-info: #5c8dff;
  --ft-info-soft: rgba(92, 141, 255, 0.14);

  --ft-brand: #4d82ff;
  --ft-brand-strong: #6d99ff;
  --ft-brand-soft: rgba(77, 130, 255, 0.16);

  --ft-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --ft-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
  --ft-shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.55);

  --ft-overlay: rgba(0, 0, 0, 0.62);
  --ft-skeleton: #242a31;
}

/* Respeta a quien pidió menos movimiento en su sistema operativo: el panel
   tiene transiciones por todos lados y para alguien con sensibilidad
   vestibular eso no es un detalle estético. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --ft-transition: 0ms;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/*!**********************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/reset.css ***!
  \**********************************************************************************************************************/
/* ==========================================================================
   reset.css — base mínima.

   Escrito desde cero para este panel: no hay Bootstrap ni normalize acá. El
   diseño no usa grilla de framework ni componentes de terceros, así que
   arrastrar 200 KB de CSS para después pisarlo entero no tendría sentido.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd {
  margin: 0;
}

ul[class],
ol[class] {
  margin: 0;
  padding: 0;
  list-style: none;
}

img,
picture,
svg {
  display: block;
  max-width: 100%;
}

svg {
  /* Los iconos son SVG inline con stroke="currentColor": así heredan el color
     del texto del elemento que los contiene y cambian solos con el tema, sin
     una regla por icono. */
  flex-shrink: 0;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

a {
  color: inherit;
  text-decoration: none;
}

table {
  border-collapse: collapse;
  width: 100%;
}

/* Foco visible SÓLO por teclado: el anillo en cada click de mouse es ruido,
   pero sacarlo del todo deja la navegación por teclado sin referencia — que
   es una barrera de accesibilidad real, no un detalle. */
:focus-visible {
  outline: 2px solid var(--ft-brand);
  outline-offset: 2px;
}

:focus:not(:focus-visible) {
  outline: none;
}

[hidden] {
  display: none !important;
}

/*!***********************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/layout.css ***!
  \***********************************************************************************************************************/
/* ==========================================================================
   layout.css — el "shell" del panel: fondo, tarjeta flotante, sidebar,
   cabecera y área de contenido.

   Estructura (ver Tenants/Home/view/index.php):

     body.app
       └── .app-shell            tarjeta redondeada que flota sobre el fondo
             ├── .app-header     marca + buscador + acciones (una sola fila)
             └── .app-body
                   ├── .sidebar
                   └── .app-main
                         └── #view-content   ← acá se inyectan las vistas

   El shell es `position: fixed` + `inset` en vez de un `margin` sobre el
   flujo normal: el panel no scrollea como página, scrollea sólo su área de
   contenido. Con margin, el fondo verde se iría con el scroll y la cabecera
   dejaría de ser fija.
   ========================================================================== */

html,
body {
  height: 100%;
}

body.app {
  font-family: var(--ft-font);
  font-size: var(--ft-fs-base);
  line-height: 1.5;
  color: var(--ft-text);
  /* Igual que el shell y no el degradado de antes: con el shell a ras de la
     ventana este fondo ya no se ve nunca, y dejarlo distinto sólo abre la
     puerta a que asome un color ajeno en un rebote de scroll o durante el
     primer pintado. El degradado sigue vivo en el login (body.auth), que sí
     lo muestra. */
  background: var(--ft-shell);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
}

body.noscroll {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   Shell
   -------------------------------------------------------------------------- */

/**
 * `inset: 0` — el shell ocupa la ventana entera, sin margen alrededor
 * (pedido de Luis, 2026-08-10; antes flotaba con 22px de aire, como en el
 * diseño original).
 *
 * Con los bordes pegados a la ventana, tres propiedades del estado anterior
 * dejan de tener sentido y por eso se sacan en vez de quedar como ruido:
 *
 *  - `border-radius`: unas esquinas redondeadas contra el borde del navegador
 *    dejarían ver el fondo de la página en los cuatro vértices, que se lee
 *    como un error de render y no como una decisión de diseño.
 *  - `border` y `box-shadow`: separaban el shell de lo que tenía detrás. Ya no
 *    hay nada detrás — la sombra no se proyecta sobre nada y el borde queda
 *    contra el marco de la ventana.
 */
.app-shell {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ft-shell);
  transition: background var(--ft-transition);
}

/* --------------------------------------------------------------------------
   Cabecera
   -------------------------------------------------------------------------- */

.app-header {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  height: var(--ft-header-h);
  padding-right: 18px;
  /*gap: 14px;*/
}

.app-header-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-shrink: 0;
  width: var(--ft-sidebar-w);
  padding: 0 16px 0 20px;
  transition: width var(--ft-transition);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-size: var(--ft-fs-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ft-text);
}

/* --------------------------------------------------------------------------
   Logo de la marca

   Dos piezas mutuamente excluyentes:
     .brand-full  logotipo completo (con sus variantes clara/oscura adentro)
     .brand-iso   sólo el isotipo, para cuando no hay ancho

   El contenedor `.brand-full` existe justamente para poder ocultar las dos
   variantes de una sola vez — ver la nota en Home/view/index.php.
   -------------------------------------------------------------------------- */

.brand-full {
  display: flex;
  align-items: center;
  min-width: 0;
}

.brand-logo {
  height: 26px;
  width: auto;
}

/* `height` fija y `width: auto`: el logo escala por altura sin deformarse,
   sin depender de la relación de aspecto exacta del archivo. */
.brand-iso {
  display: none;
  height: 26px;
  width: auto;
}

/* Variante según el tema. Misma mecánica que en el login: las dos imágenes
   están en el marcado y el CSS muestra la que corresponde, así alternar el
   tema es instantáneo y no hay un salto en blanco esperando la descarga. */
.brand-logo-dark {
  display: none;
}

:root[data-theme="dark"] .brand-logo-light {
  display: none;
}

:root[data-theme="dark"] .brand-logo-dark {
  display: block;
}

.app-header-main {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-width: 0;
}

.app-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   Buscador
   -------------------------------------------------------------------------- */

.search {
  position: relative;
  flex: 1;
  max-width: 320px;
}

.search-input {
  max-width: 18%;
  width: 100%;
  height: 38px;
  padding: 0 10px 0 36px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text);
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius);
  transition: border-color var(--ft-transition), background var(--ft-transition);
}

.search-input::placeholder {
  color: var(--ft-text-faint);
}

.search-input:focus {
  border-color: var(--ft-border-strong);
  outline: none;
}

.search-icon {
  position: absolute;
  top: 50%;
  left: 12px;
  transform: translateY(-50%);
  color: var(--ft-text-faint);
  pointer-events: none;
}

/* Atajo ⌘K: es una pista, no un control — `pointer-events: none` evita que
   robe el click destinado al input que tiene debajo. */
.search-hint {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  display: flex;
  gap: 3px;
  font-size: var(--ft-fs-xs);
  color: var(--ft-text-faint);
  pointer-events: none;
}

.search-hint kbd {
  min-width: 18px;
  padding: 1px 4px;
  font-family: inherit;
  font-size: var(--ft-fs-xs);
  text-align: center;
  background: var(--ft-surface-hover);
  border: 1px solid var(--ft-border);
  border-radius: 5px;
}

/* --------------------------------------------------------------------------
   Cuerpo: sidebar + contenido
   -------------------------------------------------------------------------- */

.app-body {
  display: flex;
  flex: 1;
  min-height: 0;
  gap: 0;
}

.sidebar {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  width: var(--ft-sidebar-w);
  padding: 0 12px 12px;
  overflow: hidden auto;
  scrollbar-width: thin;
  transition: width var(--ft-transition);
}

.app-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  padding: 0 12px 12px 0;
}

/* Alertas a nivel panel: hermano de #view-content, no su contenedor — ver el
   comentario en Home/view/index.php, donde está el bug que motivó separarlos.
   `:empty` lo colapsa del todo para que no deje un hueco cuando no hay nada
   que mostrar (que es casi siempre). */
.app-alerts {
  margin-bottom: 10px;
}

.app-alerts:empty {
  display: none;
}

/* La tarjeta blanca del contenido. `overflow-y: auto` acá y no en el body es
   lo que hace que la cabecera y el sidebar queden fijos mientras la vista
   scrollea. */
#view-content {
  flex: 1;
  min-height: 0;
  padding: 18px 20px;
  overflow: hidden auto;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  transition: background var(--ft-transition), border-color var(--ft-transition);
}

/* --------------------------------------------------------------------------
   Sidebar colapsado

   Un solo modificador en <body> gobierna el ancho de la columna de la
   cabecera Y el del sidebar, para que las dos sigan alineadas. Los textos se
   ocultan con `display: none` (no con width:0) para que no dejen huecos
   invisibles que capturen clicks.
   -------------------------------------------------------------------------- */

body.sidebar-collapsed .app-header-brand,
body.sidebar-collapsed .sidebar {
  width: var(--ft-sidebar-w-collapsed);
}

/* Marca + botón de colapso conviven en el riel: 26 + 34 + 6 de gap = 66px de
   contenido dentro de los 84px del riel, que es de dónde sale ese ancho. */
body.sidebar-collapsed .app-header-brand {
  justify-content: center;
  gap: 6px;
  padding: 0 8px;
}

body.sidebar-collapsed .brand {
  gap: 0;
  display: none;
}

/* En el riel sólo entra el isotipo. */
body.sidebar-collapsed .brand-iso {
  display: block;
}

/**
 * Qué se oculta al colapsar.
 *
 * `.sidebar-toggle` NO está en esta lista, y es a propósito: al principio sí
 * lo estaba y el resultado era un callejón sin salida — colapsabas el sidebar
 * y desaparecía el único control capaz de volver a expandirlo, así que la
 * única forma de recuperarlo era borrar localStorage a mano. Encontrado
 * mirando la captura del estado colapsado en el navegador.
 *
 * `.nav-soon` (el "Pronto" de los módulos futuros) tampoco estaba y sí tiene
 * que estar: al quedar visible ocupaba el ancho del riel y empujaba los iconos
 * fuera del centro.
 */
body.sidebar-collapsed .brand-full,
body.sidebar-collapsed .nav-label,
body.sidebar-collapsed .nav-badge,
body.sidebar-collapsed .nav-caret,
body.sidebar-collapsed .nav-soon,
body.sidebar-collapsed .nav-submenu,
body.sidebar-collapsed .workspace-label,
body.sidebar-collapsed .workspace-caret,
body.sidebar-collapsed .sidebar-widgets,
body.sidebar-collapsed .theme-switch-label,
body.sidebar-collapsed .nav-title {
  display: none;
}

body.sidebar-collapsed .nav-link,
body.sidebar-collapsed .workspace {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

body.sidebar-collapsed .theme-switch {
  justify-content: center;
}

/* --------------------------------------------------------------------------
   Responsive

   Por debajo de 1024px el sidebar deja de ocupar espacio y pasa a ser un
   panel deslizante sobre el contenido, con su propio backdrop. Es el mismo
   marcado: sólo cambia el posicionamiento, así no hay un segundo menú móvil
   que mantener en paralelo.
   -------------------------------------------------------------------------- */

.sidebar-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--ft-overlay);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--ft-transition), visibility var(--ft-transition);
}

/* Botón de menú: sólo existe por debajo del breakpoint del sidebar fijo.
   Se controla acá y no con un `style` inline en el PHP — ver el comentario en
   partials/topbar.php.

   El selector lleva LAS DOS clases (`.icon-button.sidebar-open-btn`, 0-2-0) y
   no sólo `.sidebar-open-btn` (0-1-0) a propósito: el botón también es
   `.icon-button`, que en sidebar.css declara `display: grid` con la misma
   especificidad y —al cargar esa hoja después de ésta— ganaba por orden. El
   resultado era una hamburguesa visible también en escritorio. Empatar por
   especificidad en vez de depender del orden de los archivos deja la regla a
   salvo de que mañana se reordene la cascada en webpack.config.tenant.js. */
.icon-button.sidebar-open-btn {
  display: none;
}

@media (max-width: 1024px) {
  .icon-button.sidebar-open-btn {
    display: grid;
  }

  /* (El shell ya está a ras de la ventana en la regla base — antes esta media
     query lo forzaba sólo en pantallas chicas.) */

  .app-header-brand {
    width: auto;
    padding-right: 4px;
  }

  .sidebar {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 50;
    width: var(--ft-sidebar-w);
    padding-top: 12px;
    background: var(--ft-shell);
    border-right: 1px solid var(--ft-border);
    transform: translateX(-100%);
    transition: transform var(--ft-transition);
  }

  body.sidebar-open .sidebar {
    transform: translateX(0);
  }

  body.sidebar-open .sidebar-backdrop {
    opacity: 1;
    visibility: visible;
  }

  .app-main {
    padding: 0 12px 12px;
  }

  /* En móvil el colapso no aplica: el sidebar ya está fuera de pantalla. */
  body.sidebar-collapsed .sidebar {
    width: var(--ft-sidebar-w);
  }

  body.sidebar-collapsed .nav-label,
  body.sidebar-collapsed .nav-badge,
  body.sidebar-collapsed .workspace-label,
  body.sidebar-collapsed .sidebar-widgets,
  body.sidebar-collapsed .theme-switch-label {
    display: revert;
  }

  /* El logotipo completo vuelve (y el isotipo se va) aunque haya quedado
     `sidebar-collapsed` guardado de una sesión en escritorio. */
  body.sidebar-collapsed .brand-full {
    display: flex;
  }

  body.sidebar-collapsed .brand-iso {
    display: none;
  }

  body.sidebar-collapsed .nav-link,
  body.sidebar-collapsed .workspace {
    justify-content: flex-start;
    padding-left: 12px;
    padding-right: 12px;
  }
}

@media (max-width: 720px) {
  .search {
    max-width: none;
  }

  .search-hint {
    display: none;
  }

  #view-content {
    padding: 20px 16px 24px;
  }
}

/**
 * Cabecera en pantallas angostas.
 *
 * Medido en el navegador: a 390px el menú de usuario quedaba 167px FUERA de la
 * ventana, así que en un teléfono no había manera de cerrar sesión ni de
 * cambiar la contraseña — las dos cuelgan de ahí. La cabecera tenía que entrar
 * marca + nombre + colapso + hamburguesa + buscador + tres acciones + chip de
 * usuario, que no da ni de lejos.
 *
 * Se recorta por prioridad, dejando siempre lo que no tiene otro camino:
 *  - el nombre de la marca (queda el isotipo, que ya identifica),
 *  - el botón de colapsar (en móvil el sidebar es deslizante, no colapsable:
 *    ese control no hace nada acá),
 *  - las acciones `is-optional` (crear y archivos).
 * Se conservan la hamburguesa, el buscador, la campana y el menú de usuario.
 */
@media (max-width: 620px) {
  .app-header {
    gap: 8px;
    padding-right: 10px;
  }

  .app-header-brand {
    padding: 0 2px 0 12px;
  }

  /* Header angosto: el logotipo completo se cambia por el isotipo.
     Las variantes con `body.sidebar-collapsed` van explícitas porque esa regla
     tiene más especificidad que un selector de clase suelto: sin repetirlas
     acá, un `sidebar-collapsed` guardado en localStorage volvería a mostrar el
     logotipo largo justo donde no entra. */
  .brand-full,
  body.sidebar-collapsed .brand-full,
  .app-header-brand .sidebar-toggle,
  .app-header-actions .icon-button.is-optional {
    display: none;
  }

  .brand-iso,
  body.sidebar-collapsed .brand-iso {
    display: block;
  }

  .app-header-actions {
    gap: 4px;
  }
}


.logo-collapsed{
  display: none;
}

.sidebar-collapsed .logo-collapsed{
  display: block;
}
/*!************************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/sidebar.css ***!
  \************************************************************************************************************************/
/* ==========================================================================
   sidebar.css — selector de espacio de trabajo, navegación, tarjetas
   auxiliares y conmutador de tema.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Botón que colapsa/expande el sidebar (vive en la cabecera, junto a la marca)
   -------------------------------------------------------------------------- */

.sidebar-toggle,
.icon-button {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  color: var(--ft-text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ft-radius-sm);
  transition: background var(--ft-transition), color var(--ft-transition), border-color var(--ft-transition);
}

.sidebar-toggle:hover,
.icon-button:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

/* Botones de la cabecera: tienen borde propio, a diferencia del toggle. */
.app-header-actions .icon-button {
  position: relative;
  width: 36px;
  height: 36px;
  background: var(--ft-surface);
  border-color: var(--ft-border);
}

.app-header-actions .icon-button:hover {
  border-color: var(--ft-border-strong);
}

/* Punto de notificación. `box-shadow` en vez de `border` para que el aro tome
   el color del fondo real de la campana y siga funcionando al cambiar de
   tema, sin repetir el color acá. */
.icon-button .dot {
  position: absolute;
  top: 7px;
  right: 8px;
  width: 7px;
  height: 7px;
  background: var(--ft-danger);
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--ft-surface);
}

/* --------------------------------------------------------------------------
   Usuario de la cabecera
   -------------------------------------------------------------------------- */

.user-chip {
  display: flex;
  align-items: center;
  text-align: left;
  gap: 10px;
  height: 40px;
  padding: 0 8px 0 6px;
  border-radius: var(--ft-radius);
  transition: background var(--ft-transition);
}

.user-chip:hover {
  background: var(--ft-surface-hover);
}

.user-chip-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.user-chip-name {
  font-size: var(--ft-fs-md);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.user-chip-mail {
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 860px) {
  .user-chip-text {
    display: none;
  }
}

/* Menú desplegable del usuario */
.user-menu {
  position: relative;
}

.user-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  min-width: 224px;
  padding: 6px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius);
  box-shadow: var(--ft-shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--ft-transition), transform var(--ft-transition), visibility var(--ft-transition);
}

.user-menu-panel.show {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.user-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-soft);
  border-radius: var(--ft-radius-sm);
  transition: background var(--ft-transition), color var(--ft-transition);
}

.user-menu-item:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

.user-menu-item.is-danger {
  color: var(--ft-danger);
}

.user-menu-item.is-danger:hover {
  background: var(--ft-danger-soft);
}

.user-menu-separator {
  height: 1px;
  margin: 6px 4px;
  background: var(--ft-border);
}

/* --------------------------------------------------------------------------
   Selector de espacio de trabajo (la empresa activa del tenant)
   -------------------------------------------------------------------------- */

.workspace {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 42px;
  padding: 0 12px;
  margin-bottom: 12px;
  color: var(--ft-text);
  border-radius: var(--ft-radius);
  transition: background var(--ft-transition);
}

.workspace:hover {
  background: var(--ft-surface-hover);
}

.workspace-icon {
  color: var(--ft-text-muted);
}

.sidebar-toggle svg,
.workspace-icon svg,
.nav-icon svg{
  width: 20px;
  height: 20px;
}

.workspace-label {
  flex: 1;
  min-width: 0;
  font-size: var(--ft-fs-base);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.workspace-caret {
  color: var(--ft-text-faint);
}

/* --------------------------------------------------------------------------
   Navegación
   -------------------------------------------------------------------------- */

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.nav-title {
  padding: 14px 12px 6px;
  font-size: var(--ft-fs-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ft-text-faint);
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  height: 40px;
  padding: 0 12px;
  font-size: var(--ft-fs-base);
  color: var(--ft-text-soft);
  border-radius: var(--ft-radius);
  transition: background var(--ft-transition), color var(--ft-transition);
}

.nav-link:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

/* Activo: fondo blanco + borde, o sea la misma "elevación" que las tarjetas
   del contenido — así el ítem activo se lee como la superficie que está
   abierta a la derecha, no como un simple resaltado de color. */
.nav-link.is-active {
  color: var(--ft-text);
  font-weight: 500;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  box-shadow: var(--ft-shadow-sm);
}

.nav-link.is-active .nav-icon {
  color: var(--ft-brand);
}

.nav-icon {
  color: var(--ft-text-muted);
  transition: color var(--ft-transition);
}

.nav-label {
  flex: 1;
  min-width: 0;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-badge {
  display: grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  font-size: var(--ft-fs-xs);
  font-weight: 600;
  color: var(--ft-text-soft);
  background: var(--ft-surface-active);
  border-radius: var(--ft-radius-pill);
}

.nav-link.is-active .nav-badge {
  color: var(--ft-brand);
  background: var(--ft-brand-soft);
}

/* --------------------------------------------------------------------------
   Acordeón (Punto de venta → Configuración/Caja/Operaciones, 2026-08-20)

   `.nav-group-toggle` es un `.nav-link` más (mismos estilos base: alto,
   tipografía, hover) — sólo se le agrega la flecha a la derecha
   (`.nav-caret`) y NO tiene `data-option` (no navega, sólo abre/cierra el
   submenú, ver `panel.js::_bind_nav_groups()`).

   El submenú colapsa con `max-height` (no `display:none`/`height:auto`):
   así la apertura/cierre se puede animar con una transición CSS común —
   `height: auto` no es animable de forma nativa, `max-height` con un techo
   generoso (más que suficiente para 3 ítems cortos) sí. `overflow: hidden`
   es lo que efectivamente "esconde" el contenido mientras `max-height` está
   en 0, no al revés.
   -------------------------------------------------------------------------- */

.nav-group-toggle {
  cursor: pointer;
}

.nav-caret {
  flex-shrink: 0;
  color: var(--ft-text-faint);
  transition: transform var(--ft-transition);
}

.nav-group.is-open .nav-caret {
  transform: rotate(180deg);
}

.nav-submenu {
  max-height: 0;
  overflow: hidden;
  transition: max-height var(--ft-transition);
}

.nav-group.is-open .nav-submenu {
  max-height: 200px;
}

/* Sub-ítem indentado, SIN ícono propio (a diferencia del resto de
   `.nav-link`) — el sangrado es lo que comunica la jerarquía, no hace falta
   repetir un ícono genérico al lado de cada uno. El padding-left iguala
   dónde arranca el TEXTO de los `.nav-link` de nivel superior (12px de
   padding + 18px de ícono + 11px de gap ≈ 41px), para que quede alineado
   visualmente con "Punto de venta" de arriba. */
.nav-link-sub {
  height: 36px;
  padding-left: 41px;
  font-size: var(--ft-fs-md);
}

/* --------------------------------------------------------------------------
   Flyout de acordeón con el sidebar COLAPSADO (2026-08-20)

   Con el sidebar colapsado, `.nav-submenu` queda en `display: none` sin
   excepción (ver layout.css) — el acordeón vertical no tiene dónde crecer en
   un riel de sólo íconos. En su lugar, al pasar el cursor sobre un
   `.nav-group` colapsado se despliega este panel flotante con las mismas
   sub-opciones (ver `panel.js::_bind_collapsed_flyout()`), inspirado en la
   referencia que mandó Luis: ícono + título arriba, lista de opciones con
   guión abajo.

   Se arma y posiciona enteramente en JS, igual que `.table-menu-dropdown-
   portal` (ver components.css/ui.js): se cuelga de `<body>`, no de
   `.sidebar`, así que el `overflow: hidden auto` de `.sidebar` (necesario
   para el scroll vertical del riel) nunca lo recorta — un descendiente
   `position: fixed` no responde al overflow de un ancestro que no sea su
   "containing block", y `<body>` nunca lo es. No hizo falta tocar ninguna
   propiedad de overflow existente.
   -------------------------------------------------------------------------- */

.nav-flyout {
  position: fixed;
  z-index: 1150; /* mismo nivel que .table-menu-dropdown-portal, ver components.css */
  min-width: 220px;
  padding: 14px 6px 8px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  box-shadow: var(--ft-shadow-lg);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity var(--ft-transition), transform var(--ft-transition);
}

.nav-flyout.show {
  opacity: 1;
  transform: translateX(0);
}

.nav-flyout-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px 10px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--ft-border);
}

.nav-flyout-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  color: var(--ft-brand);
  background: var(--ft-brand-soft);
  border-radius: var(--ft-radius-sm);
}

.nav-flyout-icon svg {
  width: 18px;
  height: 18px;
}

.nav-flyout-title {
  font-size: var(--ft-fs-lg);
  font-weight: 600;
  color: var(--ft-text);
  white-space: nowrap;
}

.nav-flyout-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.nav-flyout-link {
  position: relative;
  display: block;
  padding: 9px 14px 9px 22px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-soft);
  white-space: nowrap;
  border-radius: var(--ft-radius-sm);
  transition: background var(--ft-transition), color var(--ft-transition);
}

/* El guión de la referencia visual sale de `content`, no de un caracter
   metido en el texto del link — así el label sigue siendo el nombre de la
   opción solo, sin nada que limpiar si se reutiliza en otro lado. */
.nav-flyout-link::before {
  content: "\2013";
  position: absolute;
  left: 10px;
  color: var(--ft-text-faint);
}

.nav-flyout-link:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

/* Ítems que todavía no tienen módulo: se ven, para que el panel comunique a
   dónde va, pero no simulan funcionar. El cursor y la opacidad lo dicen antes
   de que el usuario haga click. */
.nav-link.is-soon {
  cursor: default;
  opacity: 0.55;
}

.nav-link.is-soon:hover {
  background: transparent;
}

.nav-soon {
  font-size: var(--ft-fs-xs);
  font-weight: 500;
  color: var(--ft-text-faint);
}

/* --------------------------------------------------------------------------
   Tarjetas auxiliares del sidebar
   -------------------------------------------------------------------------- */

.sidebar-widgets {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: auto;
  padding-top: 18px;
}

.side-card {
  padding: 14px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  box-shadow: var(--ft-shadow-sm);
}

.side-card-title {
  font-size: var(--ft-fs-md);
  font-weight: 600;
}

.side-card-text {
  margin-top: 2px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

.side-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
}

/* Progreso del onboarding: un anillo hecho con conic-gradient, sin SVG ni
   imagen. El porcentaje entra por la custom property --value desde el PHP. */
.progress-ring {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: conic-gradient(var(--ft-brand) calc(var(--value, 0) * 1%), var(--ft-surface-active) 0);
}

.progress-ring::after {
  content: "";
  width: 14px;
  height: 14px;
  background: var(--ft-surface);
  border-radius: 50%;
}

.progress-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

/* --------------------------------------------------------------------------
   Conmutador de tema (pie del sidebar)
   -------------------------------------------------------------------------- */

.sidebar-footer {
  padding-top: 12px;
  margin-top: 10px;
  border-top: 1px solid var(--ft-border);
}

.theme-switch {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 40px;
  padding: 0 12px;
  color: var(--ft-text-soft);
  border-radius: var(--ft-radius);
  cursor: pointer;
  transition: background var(--ft-transition);
}

.theme-switch:hover {
  background: var(--ft-surface-hover);
}

.theme-switch-label {
  flex: 1;
  font-size: var(--ft-fs-base);
  text-align: left;
}

/* El componente del switch (.switch-input / .switch) se mudó a forms.css el
   2026-08-13: dejó de ser exclusivo del sidebar cuando el login pasó a usarlo
   para "Recordarme". Acá quedan sólo las reglas del contenedor
   (.theme-switch), que sí son propias de esta barra. */


.sidebar-collapsed .sidebar-footer{
  display: none;
}


/* --------------------------------------------------------------------------
   Sin conexión (bug_fix `pwa_offline_navigation_fallback`, 2026-08-24)
   --------------------------------------------------------------------------
   Sin servidor, la única opción del panel que sigue sirviendo para algo es la
   Caja: todo lo demás consulta datos que sólo el servidor tiene. Se atenúan
   igual que los ítems "próximamente" (`.is-soon`, arriba) — mismo lenguaje
   visual para el mismo mensaje: "esto está, pero ahora no".

   NO se bloquea el click a propósito. Un ítem que no responde se lee como que
   la app se colgó; clickeándolo, en cambio, aparece la pantalla que EXPLICA
   qué pasa y ofrece ir a la Caja (ver `Panel._offline_markup()`).

   La clase la pone el router con lo que realmente devolvió la red, no con
   `navigator.onLine` — ver `Panel._paint_connection()`. */
body.is-offline .nav-link[data-option]:not([data-option="pos"]) {
  opacity: 0.5;
}

/*!***************************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/components.css ***!
  \***************************************************************************************************************************/
/* ==========================================================================
   components.css — piezas del área de contenido: cabecera de vista, tarjetas
   de métrica, grilla de items, tablas, botones, avatares y estados vacíos.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Cabecera de vista
   -------------------------------------------------------------------------- */

.view-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 5px;
  border-bottom: 1px solid var(--ft-border);
  padding-bottom: 10px;
}

.view-head.no-border{
  border: none!important;
}

.view-title {
  font-size: var(--ft-fs-xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.view-subtitle {
  margin-top: 4px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
}

.view-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin: 30px 0 16px;
}

.section-title {
  font-size: var(--ft-fs-xl);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.section-subtitle {
  margin-top: 2px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
}

/* Grupo de flechas ‹ › del diseño */
.stepper {
  display: flex;
  align-items: center;
  gap: 6px;
}

.stepper .icon-button {
  width: 36px;
  height: 36px;
  background: var(--ft-surface);
  border-color: var(--ft-border);
}

.stepper .icon-button:hover {
  border-color: var(--ft-border-strong);
}

/* --------------------------------------------------------------------------
   Botones
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 38px;
  padding: 0 16px;
  font-size: var(--ft-fs-md);
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--ft-radius);
  transition: background var(--ft-transition), border-color var(--ft-transition), color var(--ft-transition), opacity var(--ft-transition);
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.btn-primary {
  color: var(--ft-brand-contrast);
  background: var(--ft-brand);
  border-color: var(--ft-brand);
}

.btn-primary:hover:not(:disabled) {
  background: var(--ft-brand-strong);
  border-color: var(--ft-brand-strong);
}

.btn-secondary {
  color: var(--ft-text-soft);
  background: var(--ft-surface);
  border-color: var(--ft-border);
}

.btn-secondary:hover:not(:disabled) {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
  border-color: var(--ft-border-strong);
}

.btn-ghost {
  color: var(--ft-text-soft);
  background: transparent;
}

.btn-ghost:hover:not(:disabled) {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

.btn-danger {
  color: #ffffff;
  background: var(--ft-danger);
  border-color: var(--ft-danger);
}

.btn-danger:hover:not(:disabled) {
  opacity: 0.9;
}

.btn-sm {
  height: 32px;
  padding: 0 12px;
  font-size: var(--ft-fs-sm);
}

.btn-block {
  width: 100%;
}

/* Estado "cargando" de un botón: el ancho se congela en JS antes de vaciarlo
   (ui.js::blockButton) para que la fila no salte cuando el texto se reemplaza
   por el spinner. */
.btn.is-loading {
  pointer-events: none;
  opacity: 0.75;
}

/* --------------------------------------------------------------------------
   Tarjetas de métrica (los 4 recuadros de arriba)
   -------------------------------------------------------------------------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

@media (max-width: 1180px) {
  .stat-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .stat-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Variante de `.stat-grid` para secciones con una cantidad de tarjetas que
   NO es siempre 4 (hoy: "Punto de venta", con 3) — auto-fit en vez de un
   `repeat(4, ...)` fijo, así ni deja una columna vacía con menos tarjetas ni
   hay que inventar una cuarta tarjeta sólo para completar la grilla.
   Refinamiento `dashboard_overview_update`, 2026-08-23. */
.stat-grid.stat-grid--auto {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.stat-card {
  position: relative;
  padding: 16px 18px 18px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  transition: border-color var(--ft-transition), box-shadow var(--ft-transition);
}

.stat-card:hover {
  border-color: var(--ft-border-strong);
  box-shadow: var(--ft-shadow);
}

.stat-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.stat-label {
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
}

.stat-link {
  color: var(--ft-text-faint);
  transition: color var(--ft-transition);
}

.stat-card:hover .stat-link {
  color: var(--ft-text-soft);
}

.stat-value {
  margin-top: 14px;
  font-size: var(--ft-fs-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.stat-value .unit {
  font-size: var(--ft-fs-lg);
  font-weight: 500;
  color: var(--ft-text-muted);
}

.stat-foot {
  margin-top: 6px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

.stat-foot.is-up {
  color: var(--ft-success);
}

.stat-foot.is-down {
  color: var(--ft-danger);
}

/* Mini gráfico de barras de la esquina. Son <span> con altura porcentual —
   ni SVG ni canvas: es decorativo y tiene que repintarse con el tema, y así
   hereda el color de la variable sin ninguna lógica extra. */
.stat-spark {
  position: absolute;
  right: 18px;
  bottom: 18px;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 26px;
  opacity: 0.75;
}

.stat-spark span {
  width: 4px;
  background: var(--ft-border-strong);
  border-radius: 2px;
}

.stat-card:hover .stat-spark span {
  background: var(--ft-brand);
  opacity: 0.55;
}

/* --------------------------------------------------------------------------
   Tarjetas de entidad (la fila de "columnas" del diseño)
   -------------------------------------------------------------------------- */

.entity-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px;
}

.entity-card {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px 14px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  text-align: left;
  transition: border-color var(--ft-transition), box-shadow var(--ft-transition);
}

.entity-card:hover {
  border-color: var(--ft-border-strong);
  box-shadow: var(--ft-shadow);
}

.entity-text {
  flex: 1;
  min-width: 0;
}

.entity-name {
  font-size: var(--ft-fs-base);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.entity-meta {
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.entity-action {
  color: var(--ft-text-faint);
}

/* --------------------------------------------------------------------------
   Avatar

   Sin foto muestra iniciales sobre un fondo tomado de una paleta fija. El
   índice lo calcula el PHP a partir del nombre, así que la misma persona
   siempre cae en el mismo color entre recargas — un color aleatorio en cada
   render haría el listado ilegible de un vistazo.
   -------------------------------------------------------------------------- */

.avatar {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  font-size: var(--ft-fs-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ft-text);
  background: var(--ft-surface-active);
  border-radius: 50%;
  overflow: hidden;
  user-select: none;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar-sm {
  width: 28px;
  height: 28px;
  font-size: var(--ft-fs-xs);
}

.avatar-lg {
  width: 44px;
  height: 44px;
  font-size: var(--ft-fs-base);
}

.avatar.tone-1 { background: #dce7ff; color: #1f4fbf; }
.avatar.tone-2 { background: #ffe6cc; color: #a35a10; }
.avatar.tone-3 { background: #d9f2e1; color: #17713d; }
.avatar.tone-4 { background: #f3ddf7; color: #7a2f8c; }
.avatar.tone-5 { background: #ffe0e0; color: #b23434; }
.avatar.tone-6 { background: #d7f0f5; color: #16697f; }

:root[data-theme="dark"] .avatar.tone-1 { background: #1e2f52; color: #9dbaff; }
:root[data-theme="dark"] .avatar.tone-2 { background: #4a3418; color: #f2c185; }
:root[data-theme="dark"] .avatar.tone-3 { background: #1c3d29; color: #8ce0ab; }
:root[data-theme="dark"] .avatar.tone-4 { background: #402045; color: #e0aeea; }
:root[data-theme="dark"] .avatar.tone-5 { background: #4a2626; color: #f5a3a3; }
:root[data-theme="dark"] .avatar.tone-6 { background: #17383f; color: #90d5e3; }

/* --------------------------------------------------------------------------
   Insignias
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 22px;
  padding: 0 9px;
  font-size: var(--ft-fs-sm);
  font-weight: 500;
  border-radius: var(--ft-radius-pill);
}

.badge-neutral { color: var(--ft-text-soft); background: var(--ft-surface-active); }
.badge-success { color: var(--ft-success); background: var(--ft-success-soft); }
.badge-danger  { color: var(--ft-danger);  background: var(--ft-danger-soft); }
.badge-warning { color: var(--ft-warning); background: var(--ft-warning-soft); }
.badge-info    { color: var(--ft-info);    background: var(--ft-info-soft); }

/* --------------------------------------------------------------------------
   Barra de lista (buscador + paginación) — PATRÓN ESTÁNDAR desde 2026-08-20
   (ver factuser-listados-toolbar en la memoria del proyecto).

   Referencia visual de Luis: buscador a la izquierda, contador + anterior/
   siguiente/actualizar a la derecha, todo en una sola franja arriba de la
   tabla. Se adaptó a los tokens propios del panel (`--ft-*`) en vez de
   clonar la paleta oscura del mockup — mismo criterio que el resto del
   panel con cualquier referencia visual externa.

   El buscador reusa el componente `.search`/`.search-icon`/`.search-input`
   que YA existía para el buscador global de la cabecera (topbar.php) — acá
   sin `.search-hint` (el "⌘K" es un atajo de ESE buscador puntual, no de
   cualquier buscador de lista) y sin el `max-width: 320px` que tenía ahí
   (ver la regla `.list-toolbar .search` más abajo): en una franja propia
   tiene que estirarse todo lo que el layout le deje.

   La lógica vive en `list-toolbar.js` (`ListToolbar`), instanciada una vez
   por vista con tabla (Clientes/Empresas/Documentos/Productos de POS).
   Desde 2026-08-20 (refinamiento `search_trigger_optimization`) la búsqueda
   es 100% SERVER-SIDE: Enter en el buscador (nunca "en vivo" mientras se
   tipea) dispara un fetch al Stored Procedure correspondiente
   (`sp_*_listing` con `query`/`page`, ver factuser-listados-toolbar en la
   memoria del proyecto) y el `<tbody>` se reemplaza con el HTML que
   devuelve el servidor — ningún filtrado ni ocultamiento de filas ocurre
   en el cliente.
   -------------------------------------------------------------------------- */

.list-toolbar {
  display: flex;
  align-items: center;
  gap: 14px;
  height: 52px;
  padding: 0 2px;
  margin-bottom: 14px;
  background: var(--ft-surface);
}

.list-toolbar .search {
  max-width: none;
}

/* --------------------------------------------------------------------------
   Rango de fechas del buscador (2026-08-22, refinamiento
   `returns_list_and_date_filter`, pasado a Flatpickr en modo rango el
   mismo día — refinamiento `returns_list_with_flatpickr_range`, UN SOLO
   input, y sin ícono decorativo + botón "×" propio el mismo día,
   refinamiento `returns_date_filter_cleanup`) — envuelve `.search` + el
   input de fecha opcional (ver list-toolbar.php, `$toolbarDateRange`) en
   un solo contenedor con `flex: 1`, así entre los dos ocupan el espacio
   libre de la izquierda y `.list-pagination` (que no crece) queda
   empujada contra el borde derecho — SIN esta envoltura, `.search` solo
   (con su propio `flex: 1`) se comería todo el espacio y el filtro de
   fecha terminaría pegado contra la paginación, lejos del buscador.
   -------------------------------------------------------------------------- */

.list-filters {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 1;
  min-width: 0;
}

/* `.input-clearable`/`.clear-date` son el componente GENÉRICO de forms.css
   (el mismo que ya usan las fechas de timbrado de Empresas) — acá sólo se
   fija el ANCHO propio (el rango de fechas no tiene que estirarse como el
   buscador de texto); el posicionamiento del botón "×" y el padding-right
   del input ya vienen de esa clase compartida, no se repiten acá. */
.list-date-range {
  flex-shrink: 0;
  width: 230px;
}

.list-date-input {
  width: 100%;
  height: 38px;
  padding: 0 10px;
  font-size: var(--ft-fs-sm);
  cursor: pointer;
}

/* Filtro de estado (2026-08-22, refinamiento
   `pos_cash_sessions_history_and_force_close`) — `<select>` nativo, SIN
   select2 a propósito: son 3 opciones fijas y sin búsqueda, meter el
   plugin acá sólo agregaría peso y una instancia más que destruir/
   reinicializar en cada recarga de la vista. */
/* --------------------------------------------------------------------------
   Filtro de estado (2026-08-28, corrección `cash_sessions_filter_layout_fix`).

   ⚠️ TODAS LAS REGLAS DE ACÁ VAN PREFIJADAS CON `.list-toolbar`, y no es
   cosmético: `forms.css` se carga DESPUÉS que este archivo (ver el orden de
   `styles` en webpack.config.tenant.js) y define `.form-control { width:100%;
   height:44px }`. Con la misma especificidad —una clase contra una clase— gana
   la última, así que la versión anterior de este bloque quedaba ANULADA por
   completo: el selector medía 910 px (toda la fila) en vez de los ~150 que
   decía el CSS, y con `flex-shrink: 0` empujaba al buscador hasta 48 px.
   Medido en el navegador antes de corregir.

   Es el mismo motivo por el que ya existía `.list-toolbar .search { max-width:
   none }` más arriba: alguien peleó esta misma cascada para el CONTENEDOR del
   buscador, pero el input de adentro quedó sin arreglar (ver abajo).
   -------------------------------------------------------------------------- */

/* El ANCHO vive en el envoltorio, no en el control: `.select2-container` tiene
   `width: 100% !important` en forms.css, así que Select2 sólo puede llenar a su
   padre. Mismo patrón que `.list-date-range`. */
.list-status {
  flex-shrink: 0;
  width: 190px;
}

.list-toolbar .list-status-select {
  width: 100%;
  height: 38px;
  padding: 0 10px;
  font-size: var(--ft-fs-sm);
  cursor: pointer;
}

/* El buscador ocupa el espacio libre: sin esto queda topeado al 18% que
   `.search-input` fija para el buscador GLOBAL del topbar (donde ese tope sí
   tiene sentido). La regla base no se toca para no alterar el topbar. */
.list-toolbar .search-input {
  max-width: none;
}

/* Altura pareja en toda la fila: los controles del toolbar son de 38 px, no de
   los 44 px de un campo de formulario. Sin el prefijo, `.form-control` volvía a
   ganar y las fechas quedaban 6 px más altas que el buscador. */
.list-toolbar .list-date-input {
  height: 38px;
}

/* Select2 dentro del toolbar: 38 px como el resto, no los 42 del formulario. */
.list-toolbar .select2-container .select2-selection--single {
  height: 38px;
  padding: 0 10px;
}

.list-toolbar .select2-container .select2-selection--single .select2-selection__rendered {
  font-size: var(--ft-fs-sm);
}

@media (max-width: 860px) {
  .list-status {
    width: 150px;
  }

  .list-date-range {
    width: 170px;
  }
}

.list-pagination {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.list-pagination-count {
  margin-right: 6px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Mismo look que `.app-header-actions .icon-button` (borde + fondo propio),
   repetido acá en vez de compartir esa clase: esa combinación es de la
   cabecera del panel, ésta es de cualquier barra de lista — nombres
   distintos para contextos distintos, aunque el resultado visual sea
   parecido a propósito (mismo lenguaje de "botón de ícono" en todo el
   panel). */
.list-pagination .icon-button {
  width: 34px;
  height: 34px;
  background: var(--ft-surface);
  border-color: var(--ft-border);
}

.list-pagination .icon-button:hover:not(:disabled) {
  border-color: var(--ft-border-strong);
}

.list-pagination .icon-button:disabled {
  color: var(--ft-text-faint);
  cursor: not-allowed;
  opacity: 0.6;
}

/* Fila de "sin resultados" DENTRO de la tabla — la imprime el servidor en
   el mismo parcial `*_rows.php` que arma las filas reales cuando el término
   buscado no matchea nada (2026-08-20: desde que la búsqueda es 100%
   server-side, ver factuser-listados-toolbar en la memoria del proyecto,
   ya no hace falta esconder/mostrar nada en el cliente — `list-toolbar.js`
   sólo reemplaza el `<tbody>` con lo que mandó el servidor, sea una fila de
   éste o filas reales). Distinta de `.empty-state`, que es la de fuera de
   la tabla para cuando NO hay ningún dato cargado todavía. */
.table-empty-row td {
  padding: 34px 20px;
  text-align: center;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
}

/* --------------------------------------------------------------------------
   Tabla
   -------------------------------------------------------------------------- */

.table-card {
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  overflow: hidden;
}

.table {
  font-size: var(--ft-fs-md);
}

.table th {
  padding: 11px 16px;
  font-size: var(--ft-fs-sm);
  font-weight: 600;
  color: var(--ft-text-muted);
  text-align: left;
  white-space: nowrap;
  background: var(--ft-surface-2);
  border-bottom: 1px solid var(--ft-border);
}

.table td {
  padding: 13px 16px;
  border-bottom: 1px solid var(--ft-border);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table tbody tr {
  transition: background var(--ft-transition);
}

.table tbody tr:hover {
  background: var(--ft-surface-hover);
}

/* Fila de una sucursal desactivada (ver view-companies.js::_render_establishments) —
   atenuada pero legible, no oculta: sigue siendo un registro real, sólo no cuenta
   como local activo de la empresa. */
.table tbody tr.row-muted {
  opacity: 0.55;
}

.table tbody tr.row-muted:hover {
  opacity: 0.85;
}

/* Par de filas de una sucursal (ver view-companies.js::_render_establishments):
   `.tr-establishment` (Código/Nombre/Estado/Acciones) + `.tr-establishment-detail`
   (dirección completa, en colspan sobre las 4 columnas) pegada justo abajo. Se
   quita el borde entre las dos para que se lean como una sola fila "expandida" en
   vez de dos filas de datos distintas — el borde real de cierre queda en la fila
   de detalle, que es la que de verdad separa una sucursal de la siguiente. */
.table tbody tr.tr-establishment td {
  padding-bottom: 4px;
  border-bottom: none;
}

.table tbody tr.tr-establishment-detail td {
  padding-top: 0;
  padding-bottom: 13px;
  color: var(--ft-text-muted);
  font-size: var(--ft-fs-sm);
}

.table tbody tr.tr-establishment-detail .entity-meta {
  /* `.table .entity-meta` (más abajo en este archivo) fuerza `display:block`
     para el patrón "nombre de fantasía debajo de la razón social" — acá se
     necesita lo contrario: en la misma línea que la dirección, separado por
     un punto medio en vez de un salto de línea. Mayor especificidad
     (`.tr-establishment-detail .entity-meta` vs `.table .entity-meta`) para
     poder pisarlo sin `!important`. */
  display: inline;
  margin-left: 6px;
}

.table tbody tr.tr-establishment-detail .entity-meta::before {
  content: "·";
  margin-right: 6px;
}

.table .cell-actions {
  width: 1%;
  text-align: right;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Menú de acciones de tabla — PATRÓN ESTÁNDAR del panel desde 2026-08-19
   (ver ui.js para la lógica de apertura/cierre/posicionamiento y
   factuser-sucursales-adicionales en la memoria del proyecto). Reemplaza a
   los botones sueltos que tenían antes las filas de Empresas y Sucursales.

   El disparador (`.table-menu-trigger`) queda en su lugar de origen, adentro
   de la celda — eso SÍ puede vivir recortado por el overflow de la tabla,
   nunca sobresale. Lo que NO puede vivir ahí es el desplegable: `ui.js` lo
   clona desde el <template> vecino y lo cuelga de <body>
   (`.table-menu-dropdown-portal`) recién al abrir, así que sus estilos
   asumen que flota LIBRE, sin ningún ancestro de la tabla de por medio.
   -------------------------------------------------------------------------- */

.table-menu {
  display: inline-flex;
}

.table-menu-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--ft-radius);
  background: transparent;
  color: var(--ft-text-muted);
  cursor: pointer;
  transition: background var(--ft-transition), color var(--ft-transition), border-color var(--ft-transition);
}

.table-menu-trigger:hover,
.table-menu-trigger.is-open {
  background: var(--ft-surface-hover);
  border-color: var(--ft-border);
  color: var(--ft-text);
}

/* El <template> nunca se pinta (los <template> no participan del layout) —
   esto es sólo documentación de que no hace falta ocultar nada a mano. */
template[data-menu-template] {
  display: none;
}

.table-menu-dropdown-portal {
  position: fixed;
  z-index: 1150; /* por encima de .modal (1000) y .toast-stack (1100); por debajo de .block-window (1200) */
  min-width: 210px;
  padding: 6px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  box-shadow: var(--ft-shadow-lg);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--ft-transition), transform var(--ft-transition);
}

.table-menu-dropdown-portal.show {
  opacity: 1;
  transform: translateY(0);
}

.table-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--ft-radius);
  background: transparent;
  color: var(--ft-text);
  font-size: var(--ft-fs-md);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--ft-transition);
}

.table-menu-item:hover {
  background: var(--ft-surface-hover);
}

.table-menu-item-danger {
  color: var(--ft-danger);
}

.table-menu-item-danger:hover {
  background: var(--ft-danger-soft);
}

.table .cell-strong {
  font-weight: 500;
  color: var(--ft-text);
}

.table-scroll {
  overflow-x: auto;
}

/* --------------------------------------------------------------------------
   Grilla temporal (la agenda del diseño, reutilizada para documentos por hora)
   -------------------------------------------------------------------------- */

.timeline {
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: 10px;
  margin-top: 4px;
}

.timeline-hours {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 14px;
}

.timeline-hour {
  height: 74px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-faint);
}

.timeline-slots {
  display: grid;
  gap: 10px;
}

.slot {
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: 64px;
  padding: 10px 14px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
}

/* Hueco libre: borde punteado y un "+" centrado, tal cual el diseño. Es un
   <button> real y no un <div>, porque su función es crear un documento —
   tiene que ser alcanzable con teclado. */
.slot.is-empty {
  justify-content: center;
  color: var(--ft-text-faint);
  background: transparent;
  border: 1px dashed var(--ft-border-dashed);
  transition: color var(--ft-transition), border-color var(--ft-transition), background var(--ft-transition);
}

.slot.is-empty:hover {
  color: var(--ft-brand);
  background: var(--ft-brand-soft);
  border-color: var(--ft-brand);
}

/* --------------------------------------------------------------------------
   Estado vacío
   -------------------------------------------------------------------------- */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 46px 24px;
  text-align: center;
  background: var(--ft-surface-2);
  border: 1px dashed var(--ft-border-dashed);
  border-radius: var(--ft-radius-lg);
}

.empty-icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  margin-bottom: 6px;
  color: var(--ft-text-faint);
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: 50%;
}

.empty-title {
  font-size: var(--ft-fs-lg);
  font-weight: 600;
}

.empty-text {
  max-width: 420px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
}

.empty-state .btn {
  margin-top: 12px;
}

/* Fila de acciones del estado vacío — cuando hay MÁS de un botón (la pantalla
   de "sin conexión" ofrece "Ir a la Caja" y "Reintentar", ver
   `Panel._offline_markup()`). El margen superior pasa al contenedor: con dos
   botones, el `margin-top` de cada `.btn` los desalinearía al envolverse en
   pantallas angostas. */
.empty-state .empty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin-top: 12px;
}

.empty-state .empty-actions .btn {
  margin-top: 0;
}

/* --------------------------------------------------------------------------
   Utilidades puntuales
   -------------------------------------------------------------------------- */

.text-muted { color: var(--ft-text-muted); }
.text-soft  { color: var(--ft-text-soft); }
.text-right { text-align: right; }
.text-nowrap { white-space: nowrap; }
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 14px; }
.mt-4 { margin-top: 22px; }
.mb-3 { margin-bottom: 14px; }
.flex { display: flex; }
.flex-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.gap-2 { gap: 8px; }
.gap-3 { gap: 14px; }

/* La celda de razón social puede llevar el nombre de fantasía debajo: se
   fuerza a bloque para que no quede pegado en la misma línea. */
.table .entity-meta {
  display: block;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

/* --------------------------------------------------------------------------
   Wizard / stepper de formularios largos en modal (molde: modal de empresas,
   ver Companies/view/companies.php + view-companies.js).

   Decisión de fondo: TODOS los campos de los 5 pasos quedan montados en el
   DOM todo el tiempo — sólo se desliza `.wizard-track` con CSS. Nada se
   re-renderiza al cambiar de paso, así que Select2/flatpickr/el repetidor de
   actividades siguen exactamente como en un formulario de una sola pantalla,
   sin reinicializarse por paso.
   -------------------------------------------------------------------------- */

.wizard-indicator {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 18px 20px 4px;
}

/* Línea conectora: UN solo elemento por debajo de TODOS los círculos, no un
   `::after` por paso. La versión anterior (un `::after` por
   `.wizard-indicator-step`, con `z-index:1` en el número) se veía cruzando
   POR ENCIMA de los círculos: cada línea vive dentro del stacking context de
   SU PROPIO paso, y compararla por z-index contra el número de OTRO paso
   (hermano) no da el resultado esperado con un `::after` posicionado a mitad
   de camino entre los dos — hay casos reales del navegador donde ese
   z-index cruzado no gana. Acá no hay ambigüedad posible: `.wizard-indicator
   -line*` son hijos directos de `.wizard-indicator` con z-index 0, y
   `.wizard-indicator-step` (círculo + etiqueta) es TAMBIÉN hijo directo con
   z-index 1 — es una comparación entre hermanos en el MISMO nivel, no entre
   subárboles distintos.

   El posicionamiento (`left`/`right`/`width`) lo calcula JS en
   `_reset_wizard()`/`_render_step()` a partir de la cantidad real de pasos
   (cuenta `.wizard-step` del DOM) — no está hardcodeado a 5 acá, por si el
   día de mañana este molde se reutiliza con otro número de pasos. */
.wizard-indicator-line,
.wizard-indicator-line-progress {
  position: absolute;
  top: 34px;
  height: 2px;
  background: var(--ft-border);
  z-index: 0;
}

.wizard-indicator-line-progress {
  background: var(--ft-brand);
  transition: width var(--ft-transition);
}

.wizard-indicator-step {
  position: relative;
  z-index: 1;
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}

.wizard-indicator-num {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  font-size: var(--ft-fs-sm);
  font-weight: 700;
  color: var(--ft-text-muted);
  background: var(--ft-surface);
  border: 2px solid var(--ft-border);
  border-radius: var(--ft-radius-pill);
  transition: color var(--ft-transition), background var(--ft-transition), border-color var(--ft-transition);
}

.wizard-indicator-step.is-active .wizard-indicator-num {
  color: var(--ft-brand-contrast);
  background: var(--ft-brand);
  border-color: var(--ft-brand);
}

/* NO usar `background: var(--ft-brand-soft)` acá: ese token es translúcido
   (`rgba(37,104,239,.10)`, ver tokens.css) — con la línea del indicador
   detrás (z-index 0) y el círculo apenas 10% opaco encima, la línea se veía
   IGUAL a través del círculo aunque el z-index estuviera bien. No era un
   problema de orden de apilamiento, era de opacidad: apilar bien algo
   translúcido no lo vuelve opaco.
   `background-color` (opaco, el mismo fondo del modal) + `background-image`
   con el MISMO `--ft-brand-soft` como degradado sólido componen el resultado
   final DENTRO del propio elemento antes de que nada de atrás importe — se
   ve exactamente igual (mismo cálculo de mezcla que antes, contra el fondo
   real del modal) pero ahora es opaco de verdad. Sigue sin haber un color
   literal nuevo: son los mismos dos tokens de siempre. */
.wizard-indicator-step.is-done .wizard-indicator-num {
  color: var(--ft-brand);
  background-color: var(--ft-surface);
  background-image: linear-gradient(var(--ft-brand-soft), var(--ft-brand-soft));
  border-color: var(--ft-brand);
}

.wizard-indicator-label {
  max-width: 92px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
  transition: color var(--ft-transition);
}

.wizard-indicator-step.is-active .wizard-indicator-label {
  color: var(--ft-text);
  font-weight: 600;
}

/* La etiqueta se oculta en pantallas angostas: con 5 pasos no entran los
   textos completos en el ancho de un modal en un teléfono, y el número ya
   identifica el paso (el título de la sección, adentro, dice lo mismo). */
@media (max-width: 560px) {
  .wizard-indicator-label { display: none; }
}

/* `overflow: hidden` recortaba TAMBIÉN en vertical — con `overflow-x: hidden`
   nada más no alcanza para dejar el eje Y libre de verdad: es una trampa
   real del spec de CSS (Overflow Module Level 3): si un eje no es `visible`,
   el OTRO eje declarado `visible` se recalcula SOLO a `auto` (comprobado con
   `getComputedStyle(...).overflowY`, daba `"auto"` — seguía recortando
   exactamente igual que `hidden`, sin scrollbar visible). Por eso acá también
   es `clip-path`, igual que en `.modal-content` (ver feedback.css): recorta
   sólo a los costados (para tapar los pasos que quedan fuera al deslizar
   `.wizard-track`) y deja arriba/abajo libre — así el calendario de
   flatpickr, en un paso corto como "Timbrado", puede sobresalir por encima
   del pie del modal en vez de quedar cortado o necesitar scroll propio.
   `position: relative; z-index: 1;` es lo que hace que ese sobresaliente se
   vea POR ENCIMA del pie del modal (`.modal-footer`, sin z-index propio) y
   no detrás — `clip-path` arma su propio contexto de apilamiento, así que
   sin esto el pie (que va después en el DOM) ganaría por orden y taparía
   el calendario igual, aunque ya no estuviera "cortado". */
.wizard-viewport {
  position: relative;
  z-index: 1;
  clip-path: inset(-9999px 0 -9999px 0);
}

.wizard-track {
  display: flex;
  width: 500%;
  transition: transform var(--ft-transition);
}

.wizard-step {
  width: 20%;
  flex: 0 0 20%;
  min-width: 0;
  padding: 20px;
}


.alert[data-alert="box"]{
  margin: 15px 20px;
}
/* ==========================================================================
   Tabs — componente genérico (primera vez que se usa, 2026-08-20: pantalla
   de Configuración del POS, ver Tenants/PosSettings/view/settings.php).
   Pensado para reutilizarse en cualquier otra vista que necesite separar
   contenido en pestañas — no es nada específico de POS, por eso vive acá
   (components.css) y no en pos.css.

   Todos los `.tabs-panel` quedan montados en el DOM desde el arranque
   (server-rendered), el JS sólo alterna `hidden`/`.is-active` — mismo
   criterio que el wizard de Empresas: "nada se re-renderiza al cambiar de
   pestaña", así que si algún día una pestaña suma un formulario con
   estado, ese estado no se pierde al ir y volver entre pestañas.
   ========================================================================== */

.tabs-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  border-bottom: 1px solid var(--ft-border);
  margin-bottom: 10px;
}

.tabs-link {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 42px;
  padding: 0 14px;
  margin-bottom: -1px;
  font-size: var(--ft-fs-md);
  font-weight: 500;
  color: var(--ft-text-muted);
  border-bottom: 2px solid transparent;
  transition: color var(--ft-transition), border-color var(--ft-transition);
}

.tabs-link svg {
  flex-shrink: 0;
}

.tabs-link:hover {
  color: var(--ft-text);
}

.tabs-link.is-active {
  color: var(--ft-brand);
  border-bottom-color: var(--ft-brand);
}

.tabs-panel .view-head{
  border: none!important;
}


.text-danger{
  color: var(--ft-danger);
}

/* Par de `.text-danger` (2026-08-22, refinamiento
   `pos_cash_sessions_history_and_force_close`) — la diferencia de arqueo se
   pinta en rojo cuando FALTA plata y en verde cuando SOBRA; hasta acá sólo
   existía la mitad roja de ese par. */
.text-success{
  color: var(--ft-success);
}


/* --------------------------------------------------------------------------
   Indicador de conexión / cola de la Caja (epic `pos_offline_mode`).

   Aparece SÓLO cuando hay algo que decir (sin conexión, o con ventas sin
   sincronizar). Un chip verde permanente de "conectado" sería ruido en una
   pantalla donde lo único que importa es cobrar.
   -------------------------------------------------------------------------- */
.pos-connection {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  font-size: var(--ft-fs-sm);
  border-radius: var(--ft-radius);
  border: 1px solid transparent;
  cursor: pointer;
}

.pos-connection.is-offline {
  color: var(--ft-danger);
  background: var(--ft-danger-soft);
  border-color: var(--ft-danger-soft);
}

.pos-connection.is-pending {
  color: var(--ft-warning);
  background: var(--ft-warning-soft);
  border-color: var(--ft-warning-soft);
}

.pos-pending-list {
  margin: 12px 0 0;
  padding-left: 18px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

.pos-pending-list li { margin-bottom: 4px; }

/*!**********************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/forms.css ***!
  \**********************************************************************************************************************/
/* ==========================================================================
   forms.css — campos, etiquetas y validación.
   ========================================================================== */

.form-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 14px;
  row-gap: 20px;
}

.form-group {
  grid-column: span 12;
}

.col-6 { grid-column: span 6; }
.col-5 { grid-column: span 5; }
.col-4 { grid-column: span 4; }
.col-8 { grid-column: span 8; }
.col-3 { grid-column: span 3; }
.col-9 { grid-column: span 9; }

@media (max-width: 620px) {
  .col-6, .col-5, .col-4, .col-8, .col-3, .col-9 {
    grid-column: span 12;
  }
}

.form-label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--ft-fs-md);
  font-weight: 500;
  color: var(--ft-text-soft);
}

/* Etiquetas con ícono (2026-08-21, refinamiento
   cash_register_confirmations_and_ui_alignment) — unos pocos `.form-label`
   del panel llevan un ícono SVG antes del texto (ej. "Buscar producto…" y
   "Descuento (opcional)" en pos.php, "Cód. de barras (opcional)" en
   Configuración de POS: `<label class="form-label">ícono texto</label>`).
   Sin esto, el ícono (inline por default) quedaba mal alineado contra el
   texto y sin espacio consistente entre los dos.

   `:has(svg)` en vez de aplicarlo a TODOS los `.form-label` a propósito: la
   inmensa mayoría de las etiquetas del panel son sólo texto (a veces largo,
   ej. la de un checkbox), y forzar `display:flex` ahí de más no aporta nada
   y arriesga romper el wrap normal de una etiqueta larga sin ningún
   beneficio — el arreglo queda acotado a los casos reales que lo necesitan.
   Mismo mecanismo (`:has()`) que ya usa este archivo para el estado
   enfocado de flatpickr, no es una técnica nueva en este proyecto. */
.form-label:has(svg) {
  display: flex;
  align-items: center;
  gap: 5px;
}

.form-label svg {
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   Etiquetas flotantes

   Mismo patrón que sample/source_assets/backoffice/css/forms.css
   (`.input-group.label-inside`): la etiqueta arranca DENTRO del campo, como si
   fuera el placeholder, y sube a montarse sobre el borde cuando el campo se
   enfoca o ya tiene contenido. Adaptado a los tokens de este panel (alturas,
   radios y colores propios) en vez de copiar los valores del sample, que
   vienen de otra escala tipográfica.

   Marcado (el orden importa):

     <div class="input-group label-inside">
         <input class="form-control" id="x" placeholder=" ">
         <label class="form-label" for="x">Etiqueta</label>
     </div>

   Dos detalles que no son opcionales:

   1. El <label> va DESPUÉS del <input>. Todo el efecto se apoya en el
      combinador de hermano adyacente (`+`), que sólo mira hacia adelante: con
      el label antes, ninguna de estas reglas engancha.

   2. El input necesita `placeholder=" "` (un espacio). El estado "vacío" se
      detecta con `:placeholder-shown`, que sólo existe si hay atributo
      placeholder; sin él, la etiqueta se quedaría flotando arriba incluso con
      el campo vacío. Va un espacio y no "" porque el string vacío no se
      comporta igual en todos los navegadores.
   -------------------------------------------------------------------------- */

.input-group {
  position: relative;
}

.input-group.label-inside .form-label {
  position: absolute;
  top: 50%;
  left: 12px;
  margin: 0;
  padding: 0 5px;
  font-weight: 400;
  color: var(--ft-text-faint);
  /* El fondo tapa el borde cuando la etiqueta sube y se monta encima. Es
     var(--ft-surface) y no un color literal para que siga al tema: todos los
     formularios de este panel viven sobre esa superficie (tarjeta del login y
     cuerpo de los modales). */
  background: var(--ft-surface);
  transform: translateY(-50%);
  transform-origin: left center;
  transition: transform var(--ft-transition), color var(--ft-transition), font-size var(--ft-transition);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}

/* Estado flotado: enfocado, o con contenido cargado. Los dos casos comparten
   posición para que no haya un salto al perder el foco con el campo lleno. */
.input-group.label-inside .form-control:focus + .form-label,
.input-group.label-inside .form-control:not(:placeholder-shown) + .form-label {
  transform: translateY(-50%) scale(0.86);
  color: var(--ft-text-muted);
}

.input-group.label-inside .form-control:focus + .form-label {
  color: var(--ft-brand);
}

/* La etiqueta sube hasta el borde superior del campo. Se calcula desde la
   altura real del control (42px) en vez de un `top` fijo, así cambiar esa
   altura en un solo lugar no descoloca las etiquetas. */
.input-group.label-inside .form-control:focus + .form-label,
.input-group.label-inside .form-control:not(:placeholder-shown) + .form-label {
  top: 0;
  transition: top .15s ease-in-out;
}

.input-group.label-inside .form-control.is-invalid + .form-label {
  color: var(--ft-danger);
}

/* Deshabilitado / sólo lectura: la etiqueta acompaña al campo en vez de
   quedar en primer plano sobre algo que no se puede editar. */
.input-group.label-inside .form-control:disabled + .form-label,
.input-group.label-inside .form-control[readonly] + .form-label {
  color: var(--ft-text-faint);
  background: var(--ft-surface-hover);
}

/* Con la etiqueta adentro, el hint deja de ir pegado al borde del campo. */
.input-group.label-inside .form-hint,
.input-group.label-inside .form-error {
  margin-left: 2px;
}

.form-control {
  width: 100%;
  height: 44px;
  padding: 0 13px;
  font-size: var(--ft-fs-base);
  color: var(--ft-text);
  background: var(--ft-surface);
  border: 1px solid var(--ft-border-strong);
  border-radius: var(--ft-radius);
  transition: border-color var(--ft-transition), box-shadow var(--ft-transition), background var(--ft-transition);
}

textarea.form-control {
  height: auto;
  min-height: 92px;
  padding: 11px 13px;
  resize: none;
}

select.form-control {
  cursor: pointer;
  /* La flecha nativa varía mucho entre navegadores y no toma el color del
     tema; se dibuja una propia en SVG data-uri sobre el fondo del campo. */
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2714%27 height=%2714%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%238c939e%27 stroke-width=%272.2%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27m6 9 6 6 6-6%27/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  padding-right: 36px;
}

.form-control::placeholder {
  color: var(--ft-text-faint);
}

.form-control:focus {
  border-color: var(--ft-brand);
  /*box-shadow: 0 0 0 3px var(--ft-brand-soft);*/
  outline: none;
}

.form-control:disabled,
.form-control[readonly] {
  color: var(--ft-text-muted);
  background: var(--ft-surface-hover);
  cursor: not-allowed;
}

/* Validación: el estado de error lo pone el JS con la clase `is-invalid`, no
   :invalid de CSS — :invalid marca en rojo un campo requerido apenas se pinta
   el formulario, antes de que el usuario haya escrito nada. */
.form-control.is-invalid {
  border-color: var(--ft-danger);
}

.form-control.is-invalid:focus {
  box-shadow: 0 0 0 3px var(--ft-danger-soft);
}

.form-hint {
  margin-top: 5px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

.form-error {
  margin-top: 5px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-danger);
}

/* Campo compuesto RUC + DV, mismo criterio visual que el registro público:
   el DV es de sólo lectura porque lo calcula el servidor. */
.input-joined {
  display: flex;
  align-items: center;
  gap: 8px;
}

.input-joined .form-control:last-child {
  width: 68px;
  flex-shrink: 0;
  text-align: center;
}

.input-joined .joiner {
  color: var(--ft-text-faint);
  font-weight: 600;
}

/* Campo de contraseña con botón de ver/ocultar */
.input-password {
  position: relative;
}

.input-password .form-control {
  padding-right: 42px;
}

.input-password .toggle {
  position: absolute;
  top: 50%;
  right: 6px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  transform: translateY(-50%);
  color: var(--ft-text-faint);
  border-radius: var(--ft-radius-sm);
  transition: color var(--ft-transition), background var(--ft-transition);
}

.input-password .toggle:hover {
  color: var(--ft-text-soft);
  background: var(--ft-surface-hover);
}

/* Botón "×" para vaciar un campo opcional de una sola vez (ej. "Fin de
   vigencia" del timbrado) — mismo tratamiento visual que
   `.input-password .toggle`, sin reutilizar esa clase porque no hay nada
   que mostrar/ocultar acá. Va oculto por default: view-companies.js lo
   muestra sólo cuando el campo YA tiene un valor (un campo opcional vacío
   no necesita un botón para vaciar algo que ya está vacío). */
.input-clearable {
  position: relative;
}

.input-clearable .form-control {
  padding-right: 34px;
}

.input-clearable .clear-date {
  position: absolute;
  top: 50%;
  right: 6px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  transform: translateY(-50%);
  color: var(--ft-text-faint);
  border-radius: var(--ft-radius-sm);
  transition: color var(--ft-transition), background var(--ft-transition);
}

.input-clearable .clear-date:hover {
  color: var(--ft-text-soft);
  background: var(--ft-surface-hover);
}

/* Checkbox */
.form-check {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-soft);
  cursor: pointer;
  user-select: none;
}

.form-check input[type="checkbox"] {
  width: 17px;
  height: 17px;
  accent-color: var(--ft-brand);
  cursor: pointer;
}

/* ==========================================================================
   Formularios largos: secciones, listas repetibles y archivos.
   Los usa el alta de empresas emisoras, que pide bastante más que un par de
   campos (identificación, actividades, timbrado, casa matriz, certificado).
   ========================================================================== */

/* --------------------------------------------------------------------------
   Secciones — parten un formulario largo en bloques con título, para que se
   pueda recorrer de un vistazo en vez de leerlo entero.
   -------------------------------------------------------------------------- */

.form-section + .form-section {
  margin-top: 26px;
  padding-top: 22px;
  border-top: 1px solid var(--ft-border);
}

.form-section-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  font-size: var(--ft-fs-base);
  font-weight: 600;
  color: var(--ft-text);
}

.form-section-title svg {
  color: var(--ft-brand);
}

/* El botón de "Agregar" del título va al extremo opuesto. */
.form-section-title .btn {
  margin-left: auto;
}

/* --------------------------------------------------------------------------
   Valor fijo — un dato que el formulario MUESTRA pero no deja editar porque lo
   decide el sistema (feature `pos_thermal_printer_configuration`, 2026-08-25:
   el lenguaje de una impresora estándar es siempre PDF/HTML).

   Por qué no un `<input disabled>`, que sería lo obvio: un campo deshabilitado
   invita a intentar tocarlo y se lee como "esto debería poder cambiarse pero
   algo lo impide". Esto, en cambio, se lee como lo que es — un dato informativo.
   Imita la altura y el ritmo vertical de un `.input-group` para que no rompa la
   grilla cuando comparte fila con campos reales.
   -------------------------------------------------------------------------- */

/* Nota al pie de una sección: ícono + texto en una fila. Hace falta porque los
   SVG del panel son `display: block` (reset.css) y, sueltos dentro de un
   párrafo, se van a su propia línea. */
.form-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 12px;
  font-size: var(--ft-fs-md);
}

.form-note svg {
  flex: none;
  margin-top: 2px;
}

.form-static {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  min-height: 46px;
  padding: 6px 12px;
  background: var(--ft-surface-2);
  border: 1px dashed var(--ft-border-dashed);
  border-radius: var(--ft-radius);
}

.form-static-label {
  font-size: var(--ft-fs-xs);
  color: var(--ft-text-muted);
}

.form-static-value {
  font-size: var(--ft-fs-base);
  font-weight: 500;
  color: var(--ft-text);
}

/* --------------------------------------------------------------------------
   Repetidor (actividades económicas)
   -------------------------------------------------------------------------- */

.repeater {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.repeater-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Una sola columna: desde que la actividad se busca en un <select> (catálogo
   CIIU4) en vez de tipear código y descripción en dos campos sueltos, no hay
   nada más que repartir en la fila — el selector se lleva todo el ancho.
   `max-width` para que el combo no se estire de más en modales anchos: el
   texto de una actividad ("56210 - ABASTECIMIENTO DE EVENTOS") se lee mejor
   en una caja acotada que ocupando los ~700px del modal entero. */
.repeater-fields {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  flex: 1;
  min-width: 0;
  max-width: 580px;
  margin-bottom: 8px;
}

/* El `max-width` de arriba acota la GRILLA, pero no alcanza solo: un hijo de
   grid/flex tiene `min-width: auto` por default, o sea que NUNCA encoge por
   debajo del ancho de su propio contenido. El combo de actividad tiene texto
   con `white-space: nowrap` adentro (ver `.select2-selection__rendered`), y
   hay descripciones reales de +170 caracteres en el catálogo de la SET — sin
   este `min-width: 0`, esa caja se sale del carril de 580px y mide el texto
   completo (medido: 1394px con la descripción más larga del catálogo). */
.repeater-fields .input-group {
  min-width: 0;
}

.repeater-primary {
  flex-shrink: 0;
  white-space: nowrap;
}

.repeater-row .icon-button {
  flex-shrink: 0;
  color: var(--ft-text-faint);
}

.repeater-row .icon-button:hover {
  color: var(--ft-danger);
  background: var(--ft-danger-soft);
}

@media (max-width: 620px) {
  .repeater-row {
    flex-wrap: wrap;
  }

  .repeater-fields {
    grid-template-columns: minmax(0, 1fr);
    width: 100%;
  }
}

/* --------------------------------------------------------------------------
   Archivo (certificado digital)

   El <input type="file"> nativo no se puede estilar de forma consistente entre
   navegadores; lo que sí es portable es dar formato al botón interno con
   ::file-selector-button, que es lo que se hace acá en vez de esconder el
   input detrás de un <label> falso (eso rompe el foco por teclado).
   -------------------------------------------------------------------------- */

.file-input {
  height: auto;
  padding: 8px 10px;
  font-size: var(--ft-fs-md);
  cursor: pointer;
}

.file-input::file-selector-button {
  margin-right: 12px;
  padding: 6px 12px;
  font-size: var(--ft-fs-sm);
  font-weight: 500;
  color: var(--ft-text-soft);
  background: var(--ft-surface-hover);
  border: 1px solid var(--ft-border-strong);
  border-radius: var(--ft-radius-sm);
  cursor: pointer;
  transition: background var(--ft-transition), color var(--ft-transition);
}

.file-input::file-selector-button:hover {
  color: var(--ft-text);
  background: var(--ft-surface-active);
}

/* Certificado ya cargado (modo edición): referencia de lo que hay, sin
   posibilidad de descargarlo ni de ver su contraseña. */
.cert-current {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 14px;
  padding: 11px 14px;
  background: var(--ft-success-soft);
  border: 1px solid transparent;
  border-radius: var(--ft-radius);
}

.cert-icon {
  display: grid;
  place-items: center;
  color: var(--ft-success);
}

.cert-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.3;
}

.cert-name {
  font-size: var(--ft-fs-md);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cert-meta {
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-muted);
}

/* --------------------------------------------------------------------------
   `label-float` — etiqueta SIEMPRE arriba

   Variante de `label-inside` para los campos que no pueden tener la etiqueta
   adentro esperando a que se llenen: un <select> siempre muestra algo (el
   placeholder o la opción elegida) y un <input type="date"> siempre muestra su
   máscara, así que `:placeholder-shown` nunca aplica y la etiqueta se
   superpondría al contenido.

   Estructura y nombre tomados de sample/source_assets/backoffice/css/forms.css
   (`.input-group.label-float`), para que los dos paneles del producto usen el
   mismo vocabulario. La posición se calcula con los tokens de acá, no con los
   valores del sample, que vienen de otra escala.
   -------------------------------------------------------------------------- */

.input-group.label-float > .form-label {
  position: absolute;
  top: 0;
  left: 12px;
  z-index: 1;
  margin: 0;
  padding: 0 5px;
  font-size: var(--ft-fs-md);
  font-weight: 400;
  color: var(--ft-text-muted);
  background: var(--ft-surface);
  transform: translateY(-50%) scale(0.86);
  transform-origin: left center;
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}

/* Los <input type="date"> siguen siendo controles nativos: se les da el mismo
   trato que a los select para que la fila quede pareja. */
.input-group.label-float input[type="date"] {
  width: 100%;
}

/* --------------------------------------------------------------------------
   Select2

   La librería trae su propio CSS (se carga antes, ver webpack.config.tenant.js)
   pensado para Bootstrap. Acá se lo reescribe contra los tokens del panel para
   que un <select> mejorado sea indistinguible de un .form-control común: misma
   altura, mismo radio, mismo borde, mismo foco, y que siga al tema.
   -------------------------------------------------------------------------- */

.select2-container {
  width: 100% !important;
}

.select2-container .select2-selection--single {
  display: flex;
  align-items: center;
  height: 42px;
  padding: 0 13px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border-strong);
  border-radius: var(--ft-radius);
  outline: none;
  transition: border-color var(--ft-transition), box-shadow var(--ft-transition);
}

.select2-container--open .select2-selection--single,
.select2-container--focus .select2-selection--single {
  border-color: var(--ft-brand);
  /*box-shadow: 0 0 0 3px var(--ft-brand-soft);*/
}

/* El lado del campo que queda pegado al desplegable pierde su radio mientras
   está abierto — sin esto, campo y desplegable se ven como dos cajas
   redondeadas sueltas apenas tocándose (ver la captura que motivó este
   cambio) en vez de una sola forma continua. select2 agrega solo
   `--below`/`--above` al CONTENEDOR según de qué lado abrió (ver
   select2.full.js, Dropdown.prototype._positionDropdown). */
.select2-container--open.select2-container--below .select2-selection--single {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.select2-container--open.select2-container--above .select2-selection--single {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.select2-container .select2-selection--single .select2-selection__rendered {
  /* `order` fuerza el texto a la izquierda pase lo que pase con el orden real
     en que select2 arma el DOM (con `allowClear`, agrega el botón "×" ANTES
     del texto — ver más abajo). */
  order: 1;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--ft-fs-base);
  line-height: 1.4;
  color: var(--ft-text);
}

.select2-container .select2-selection--single .select2-selection__placeholder {
  color: var(--ft-text-faint);
}

/* Botón "×" de `allowClear` (ver view-companies.js: sólo los selects
   OPCIONALES lo llevan). Por defecto select2 lo pinta como un glyph de
   navegador —negrita, pegado al borde izquierdo del texto— que no combina
   con nada del resto del panel. Se reposiciona DESPUÉS del texto (con
   `order`) y ANTES de la flecha propia (que es `position: absolute`, no
   ocupa espacio del flex), y se le baja el peso visual como el resto de los
   "cerrar" del panel (ver `.alert-dismiss`: opacidad, no color literal). */
.select2-container .select2-selection--single .select2-selection__clear {
  order: 2;
  flex-shrink: 0;
  margin-right: 24px;
  padding: 0;
  font-size: 18px;
  font-weight: 400;
  line-height: 1;
  color: var(--ft-text-faint);
  opacity: 0.8;
  transition: color var(--ft-transition), opacity var(--ft-transition);
}

.select2-container .select2-selection--single .select2-selection__clear:hover {
  color: var(--ft-text);
  opacity: 1;
}

/* Flecha propia: la de la librería es un triángulo con `border` que no se puede
   recolorear por variable. Se oculta y se dibuja una igual a la de los <select>
   nativos del panel (ver `select.form-control` más arriba). */
.select2-container .select2-selection--single .select2-selection__arrow b {
  display: none;
}

.select2-container .select2-selection--single .select2-selection__arrow {
  position: absolute;
  top: 0;
  right: 11px;
  width: 14px;
  height: 100%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2714%27 height=%2714%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%238c939e%27 stroke-width=%272.2%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27m6 9 6 6 6-6%27/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}

/* Campo deshabilitado (distrito y ciudad antes de elegir el nivel de arriba). */
.select2-container--disabled .select2-selection--single,
.select2-container .select2-selection--single[aria-disabled="true"] {
  background: var(--ft-surface-hover);
  cursor: not-allowed;
}

/* ---- Desplegable ---- */

.select2-container .select2-dropdown {
  background: var(--ft-surface);
  border: 1px solid var(--ft-brand);
  border-top: none;
  border-radius: var(--ft-radius);
  box-shadow: var(--ft-shadow-lg);
  overflow: hidden;
}

/* El lado que se pega al campo (arriba si abre hacia abajo, abajo si abre
   hacia arriba) queda recto — es el complemento de la regla de
   `.select2-selection--single` de más arriba: las dos cajas juntas tienen que
   leerse como una sola forma continua, no como dos rectángulos redondeados
   apilados.
   
   select2.css (la base de la librería, cargada ANTES en
   webpack.config.tenant.js) ya trae esta misma idea resuelta con
   `.select2-container--open .select2-dropdown--below {...}` — pero esa regla
   y `.select2-container .select2-dropdown` de acá arriba tienen la MISMA
   especificidad (0-2-0), así que gana la que carga después en el bundle (la
   nuestra), que redondea las 4 esquinas sin distinguir el lado. Por eso acá
   hace falta especificidad mayor (`.select2-container
   .select2-dropdown.select2-dropdown--below`, 0-3-0) y no alcanza con repetir
   sólo `.select2-dropdown--below` (0-1-0, pierde contra la regla de arriba). */
.select2-container .select2-dropdown.select2-dropdown--below {
  margin-top: -1px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.select2-container .select2-dropdown.select2-dropdown--above {
  margin-bottom: -1px;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.select2-container .select2-search--dropdown {
  padding: 8px;
}

.select2-container .select2-search--dropdown .select2-search__field {
  height: 36px;
  padding: 0 11px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text);
  background: var(--ft-surface-2);
  border: 1px solid var(--ft-border-strong);
  border-radius: var(--ft-radius-sm);
  outline: none;
}

.select2-container .select2-search--dropdown .select2-search__field:focus {
  border-color: var(--ft-brand);
}

.select2-container .select2-results__options {
  max-height: 260px;
  padding: 10px 6px 6px;
}

.select2-container .select2-results__option {
  padding: 8px 10px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-soft);
  border-radius: var(--ft-radius-sm);
}

.select2-container .select2-results__option--highlighted[aria-selected],
.select2-container .select2-results__option--highlighted[data-selected] {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

.select2-container .select2-results__option[aria-selected="true"],
.select2-container .select2-results__option[data-selected="true"] {
  color: var(--ft-brand);
  background: var(--ft-brand-soft);
  font-weight: 500;
}

.select2-container .select2-results__message {
  padding: 10px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
}

/* Nada de subirle el z-index al CONTENEDOR de Select2.
   
   Acá había un `.modal .select2-container { z-index: 1010 }` puesto "por las
   dudas" para el desplegable, y era el causante de un bug visible: con ese
   z-index, la caja cerrada del select quedaba por encima de la etiqueta
   flotante (z-index 1) y le tapaba la mitad inferior — las etiquetas de los
   cinco selects se veían cortadas. Detectado con elementsFromPoint, que mostró
   `SPAN.select2-selection` por delante de `LABEL.form-label`.
   
   No hacía falta: el desplegable ya se monta dentro del modal
   (`dropdownParent`, ver view-companies.js) y select2 le da `z-index: 1051` en
   su propio CSS, que dentro de ese contexto de apilamiento alcanza de sobra. */

/* La etiqueta va por encima de la caja del select: es lo que le permite tapar
   el borde y leerse montada sobre él. */
.input-group.label-float > .form-label {
  z-index: 2;
}

/* Estado inválido, coherente con .form-control.is-invalid */
.is-invalid + .select2-container .select2-selection--single {
  border-color: var(--ft-danger);
}

/* ==========================================================================
   flatpickr — calendarios de fecha
   ========================================================================== */

/* --------------------------------------------------------------------------
   La etiqueta flotante a través del wrapper

   Con `static: true`, flatpickr envuelve los inputs en un `.flatpickr-wrapper`
   y los saca de al lado del <label>. Eso rompe el combinador de hermano
   adyacente (`+`) en el que se apoyan las etiquetas flotantes: el label pasa a
   ser hermano del WRAPPER, no del input.

   Se resuelve con `:has()`, que permite preguntar "este .input-group contiene
   un campo enfocado / con contenido" sin importar cuántos niveles haya en el
   medio. La alternativa era alternar una clase desde los hooks de flatpickr,
   pero eso mete estado de presentación en el JS y hay que mantenerlo
   sincronizado a mano; acá el CSS lo deriva solo.

   Las reglas con `+` de más arriba siguen valiendo para todos los demás
   campos, y también para éstos si flatpickr no llegara a inicializarse.
   -------------------------------------------------------------------------- */

.input-group.label-inside:has(.flatpickr-alt:focus) > .form-label,
.input-group.label-inside:has(.flatpickr-alt:not(:placeholder-shown)) > .form-label {
  top: 0;
  transform: translateY(-50%) scale(0.86);
  color: var(--ft-text-muted);
}

.input-group.label-inside:has(.flatpickr-alt:focus) > .form-label {
  color: var(--ft-brand);
}

/* El wrapper es puramente estructural: no debe alterar el ancho del campo. */
.flatpickr-wrapper {
  display: block;
  width: 100%;
}

/* --------------------------------------------------------------------------
   El calendario, con los tokens del panel

   La hoja de la librería está pensada para un tema claro fijo; acá se la
   reescribe con variables para que siga al tema como todo lo demás.
   -------------------------------------------------------------------------- */

.flatpickr-calendar {
  background: var(--ft-surface);
  border: 1px solid var(--ft-brand);
  border-radius: var(--ft-radius);
  box-shadow: var(--ft-shadow-lg);
}

/* En modo `static` el calendario se posiciona dentro del campo: se lo levanta
   por encima de los controles que tenga debajo. */
.flatpickr-calendar.static.open {
  z-index: 3;
}

.flatpickr-calendar.arrowTop::before,
.flatpickr-calendar.arrowTop::after,
.flatpickr-calendar.arrowBottom::before,
.flatpickr-calendar.arrowBottom::after {
  display: none;
}

.flatpickr-months {
  padding: 6px 4px 2px;
}

.flatpickr-months .flatpickr-month,
.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year {
  color: var(--ft-text);
  fill: var(--ft-text);
  background: transparent;
}

.flatpickr-current-month .flatpickr-monthDropdown-months option {
  color: var(--ft-text);
  background: var(--ft-surface);
}

.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year{
  font-weight: 600;
}

.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
  padding: 18px 16px;
  fill: var(--ft-text-muted);
}

.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
  fill: var(--ft-brand);
}

.flatpickr-current-month{
  font-size: 110%;
}

span.flatpickr-weekday {
  color: var(--ft-text-faint);
  background: transparent;
  font-weight: 600;
}

.flatpickr-day {
  color: var(--ft-text-soft);
  border-radius: var(--ft-radius-sm);
  border-color: transparent;
}

.flatpickr-day:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
  border-color: transparent;
}

.flatpickr-day.today {
  color: var(--ft-brand);
  font-weight: 600;
  border-color: transparent;
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover {
  color: var(--ft-brand-contrast);
  background: var(--ft-brand);
  border-color: var(--ft-brand);
}

.flatpickr-day.flatpickr-disabled,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
  color: var(--ft-text-faint);
}

.flatpickr-day.flatpickr-disabled:hover {
  background: transparent;
}

/* Campos de fecha de flatpickr: SIEMPRE `readonly` (regla del proyecto, ver
   [[factuser-fechas-flatpickr]]) — se eligen del calendario, no se tipean.

   Pero `readonly` NO significa "deshabilitado" acá: la regla general
   (`.form-control[readonly]`, más arriba) los pinta en gris con
   `cursor: not-allowed`, y estos campos SÍ son operables — abren un
   calendario al hacer clic. Se les devuelve el aspecto de campo activo.

   Cubre los dos casos: el input visible que crea flatpickr cuando hay
   `altInput` (fechas simples) y el input directo de los rangos
   (`.list-date-input`, que no usa `altInput` — ver list-toolbar.js). */
.form-control.flatpickr-alt[readonly],
.form-control.list-date-input[readonly] {
  color: var(--ft-text);
  background: var(--ft-surface);
  cursor: pointer;
}

/* Reubicación del calendario cuando no entra — las pone view-companies.js
   después de medir (ver _place_calendar). flatpickr en modo `static` no
   reposiciona por su cuenta: deja el calendario en top:100%/left:0 y, si el
   campo está en la última columna, se sale del modal y queda cortado. */
.flatpickr-calendar.static.fp-align-right {
  left: auto;
  right: 0;
}

.flatpickr-calendar.static.fp-align-top {
  top: auto;
  bottom: calc(100% + 2px);
}

/* ==========================================================================
   Switch

   Componente compartido: lo usan el conmutador de tema del sidebar
   (.theme-switch) y la opción "Recordarme" del login (.form-switch). Vivía en
   sidebar.css hasta el 2026-08-13, cuando dejó de ser exclusivo de esa barra.

   El <input type="checkbox"> real NO se reemplaza ni se esconde con
   `display: none`: se deja en el DOM, visualmente oculto pero enfocable. Así
   sigue funcionando el teclado (Tab + Espacio), los lectores de pantalla lo
   anuncian como lo que es, el <label for> lo activa, y —clave para el
   formulario— el valor sigue viajando en el submit sin que el JS tenga que
   sincronizar nada. Lo que se ve es el <span class="switch"> que lo sigue.
   ========================================================================== */

.switch-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.switch {
  position: relative;
  width: 38px;
  height: 22px;
  flex-shrink: 0;
  background: var(--ft-surface-active);
  border-radius: var(--ft-radius-pill);
  transition: background var(--ft-transition);
}

.switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  background: #ffffff;
  border-radius: 50%;
  box-shadow: var(--ft-shadow-sm);
  transition: transform var(--ft-transition);
}

/* El combinador adyacente es el motor del componente: el <input> tiene que ir
   INMEDIATAMENTE antes del <span class="switch">. */
.switch-input:checked + .switch {
  background: var(--ft-brand);
}

.switch-input:checked + .switch::after {
  transform: translateX(16px);
}

/* Foco visible sólo por teclado — el input real está oculto, así que sin esto
   no habría ninguna señal de dónde está parado el usuario al tabular. */
.switch-input:focus-visible + .switch {
  outline: 2px solid var(--ft-brand);
  outline-offset: 2px;
}

/* Switch dentro de un formulario: interruptor a la izquierda y texto a la
   derecha, que es el orden de lectura de una opción. (El del sidebar va al
   revés porque ocupa una fila completa y el interruptor cierra a la derecha.) */
.form-switch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-soft);
  cursor: pointer;
  user-select: none;
}


.select2-container .select2-results__option[aria-selected=true], .select2-container .select2-results__option[data-selected=true]{
  font-weight: normal!important;
}

/* ==========================================================================
   Ítems de documentos (Documentos → Nuevo documento)

   Repetidor con VARIAS columnas por fila (descripción/cantidad/precio/IVA/
   subtotal), a diferencia de `.repeater-fields` (actividades económicas,
   una sola columna) — por eso son clases propias y no una reutilización de
   esas. `.item-row-head` es la fila de encabezados impresa una sola vez por
   companies.php/documents.php (no se repite en cada fila) — mismo ancho de
   columnas que `.item-fields` para que quede alineada.
   ========================================================================== */

.item-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.item-row-head {
  padding: 0 0 6px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-faint);
}

.item-row-head .item-fields > span {
  display: block;
}

.item-fields {
  display: grid;
  /* desc | cant. | unidad | precio | iva | subtotal — la unidad se agregó
     2026-08-26 (refinamiento `documents_manual_unit_of_measure`), junto a la
     cantidad porque son el mismo dato leído en conjunto ("2,5 kg"). */
  grid-template-columns: 1fr 70px 90px 110px 100px 100px;
  gap: 10px;
  flex: 1;
  min-width: 0;
  align-items: center;
}

/* Mismo motivo que `.repeater-fields .input-group` (ver más arriba en este
   archivo): sin esto, un hijo de grid no encoge por debajo del ancho de su
   propio contenido y la fila se desborda. */
.item-fields .input-group {
  min-width: 0;
}

.item-col-subtotal {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--ft-text);
  white-space: nowrap;
}

.item-row .icon-button {
  flex-shrink: 0;
  color: var(--ft-text-faint);
}

.item-row .icon-button:hover {
  color: var(--ft-danger);
  background: var(--ft-danger-soft);
}

@media (max-width: 620px) {
  .item-fields {
    grid-template-columns: minmax(0, 1fr);
  }

  .item-row-head {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Totales — resumen de sólo lectura debajo del repetidor de ítems. El
   servidor recalcula todo igual al guardar (ver
   documents.sp_panel_document_save): esto es feedback en vivo mientras se
   cargan los ítems, no la fuente de verdad.
   -------------------------------------------------------------------------- */

.doc-totals {
  margin-top: 16px;
  margin-left: auto;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.doc-totals-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: var(--ft-fs-sm);
  color: var(--ft-text-soft);
}

.doc-totals-row span:last-child {
  font-variant-numeric: tabular-nums;
}

.doc-totals-grand {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--ft-border);
  font-size: var(--ft-fs-md);
  font-weight: 700;
  color: var(--ft-text);
}

/*!*************************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/feedback.css ***!
  \*************************************************************************************************************************/
/* ==========================================================================
   feedback.css — modales, alertas, toasts, spinner y bloqueo de UI.

   Todo lo que dibuja ui.js. Se conservan los NOMBRES de clase de la
   convención del framework base (.modal, .modal-dialog, .modal-content,
   .alert, .block-window, .spinner) aunque la implementación sea propia: el JS
   del proyecto —y quien venga a leerlo— ya conoce esos nombres, y renombrarlos
   sólo agregaría fricción. Lo que NO se conserva es la dependencia de
   Bootstrap: acá no hay ninguna, el show/hide lo maneja ui.js con una clase.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Modal
   -------------------------------------------------------------------------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 24px 16px;
  overflow-y: auto;
  background: var(--ft-overlay);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--ft-transition), visibility var(--ft-transition);
}

.modal.show {
  opacity: 1;
  visibility: visible;
}

.modal-dialog {
  width: 100%;
  max-width: 520px;
  margin: auto;
  transform: translateY(-10px) scale(0.99);
  transition: transform var(--ft-transition);
}

.modal.show .modal-dialog {
  transform: translateY(0) scale(1);
}

.modal-dialog.sm { max-width: 420px; }
.modal-dialog.lg { max-width: 760px; }

.modal-content {
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  box-shadow: var(--ft-shadow-lg);
  /* NO `overflow: hidden` — eso recortaba TAMBIÉN en vertical, y un popup que
     necesita más alto que el modal (el calendario de flatpickr en un paso
     corto del wizard de empresas) quedaba cortado contra el pie en vez de
     sobresalir por encima, que es lo que se pidió. `clip-path` con el eje Y
     "infinito" (-9999px) recorta SÓLO a los costados (donde sí hace falta,
     para que las esquinas redondeadas se vean bien) y deja arriba/abajo
     completamente libre — el `round` mantiene el redondeado en los bordes
     izquierdo/derecho, que son los únicos que este truco sigue recortando.
     Las esquinas de arriba/abajo (que este truco YA NO recorta) se redondean
     aparte en `.modal-header`/`.modal-footer` — ver ahí. */
  clip-path: inset(-9999px 0 -9999px 0 round var(--ft-radius-lg));
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--ft-border);
  /* Antes venía "gratis" del `overflow: hidden` + `border-radius` de
     .modal-content, que ya no recorta en vertical (ver ahí el porqué). */
  border-top-left-radius: var(--ft-radius-lg);
  border-top-right-radius: var(--ft-radius-lg);
}

/* Flex (2026-08-21, refinamiento cash_register_confirmations_and_ui_alignment)
   — el título va SIEMPRE como `<h5 class="modal-title">` + ícono SVG inline
   (`ft_icon()`) + texto (ver cualquier modal del panel, ej. pos.php). Sin
   flex, el SVG (inline por default del navegador) quedaba mal alineado
   verticalmente contra el texto y sin separación consistente — `gap`
   reemplaza cualquier espacio en blanco "a mano" entre el ícono y el texto
   en el marcado PHP. Título SIN ícono (hay varios, ver Customers/Home/
   Documents/etc.) no se ve afectado: un solo nodo de texto en un flex
   container se comporta igual que en uno normal. */
.modal-title {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: var(--ft-fs-lg);
  font-weight: 600;
}

.modal-title svg {
  flex-shrink: 0;
}

.modal-close {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  color: var(--ft-text-muted);
  border-radius: var(--ft-radius-sm);
  transition: background var(--ft-transition), color var(--ft-transition);
}

.modal-close:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

.modal-body {
  padding: 20px;
}

/* Para modales cuyo contenido maneja su propio espaciado (ej. el wizard de
   empresas: el indicador y cada paso ya traen su padding) — sin esto, el
   padding del modal se sumaría al de adentro y descuadraría el indicador
   respecto del borde del modal. */
.modal-body.no-padding {
  padding: 0;
}

.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 16px 20px;
  background: var(--ft-surface-2);
  border-top: 1px solid var(--ft-border);
  /* Mismo motivo que el redondeo agregado en .modal-header. */
  border-bottom-left-radius: var(--ft-radius-lg);
  border-bottom-right-radius: var(--ft-radius-lg);
}

/* Popup de confirmación (ui.popup): más compacto, con icono de color según el
   tipo. `.is-alert` lo distingue de los modales de formulario. */
.modal.is-alert .modal-body {
  padding: 22px 20px;
  font-size: var(--ft-fs-base);
  color: var(--ft-text-soft);
}

.popup-icon {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  margin-bottom: 14px;
  border-radius: 50%;
}

.popup-icon.info,
.popup-icon.primary { color: var(--ft-info);    background: var(--ft-info-soft); }
.popup-icon.success { color: var(--ft-success); background: var(--ft-success-soft); }
.popup-icon.warning { color: var(--ft-warning); background: var(--ft-warning-soft); }
.popup-icon.danger  { color: var(--ft-danger);  background: var(--ft-danger-soft); }

/* --------------------------------------------------------------------------
   Alertas embebidas (las que van dentro de una vista o de un modal)
   -------------------------------------------------------------------------- */

.alert {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 12px 14px;
  font-size: var(--ft-fs-md);
  border: 1px solid transparent;
  border-radius: var(--ft-radius);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--ft-transition), transform var(--ft-transition);
}

.alert.show {
  opacity: 1;
  transform: translateY(0);
}

.alert-icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  margin-top: 1px;
}

.alert-message {
  flex: 1;
  min-width: 0;
}

.alert-dismiss {
  flex-shrink: 0;
  color: inherit;
  opacity: 0.6;
  transition: opacity var(--ft-transition);
}

.alert-dismiss:hover {
  opacity: 1;
}

.alert-info    { color: var(--ft-info);    background: var(--ft-info-soft);    border-color: var(--ft-info-soft); }
.alert-success { color: var(--ft-success); background: var(--ft-success-soft); border-color: var(--ft-success-soft); }
.alert-warning { color: var(--ft-warning); background: var(--ft-warning-soft); border-color: var(--ft-warning-soft); }
.alert-danger  { color: var(--ft-danger);  background: var(--ft-danger-soft);  border-color: var(--ft-danger-soft); }

.mb-4 { margin-bottom: 18px; }

/* --------------------------------------------------------------------------
   Toasts

   Abajo al centro, apilados. El contenedor es `pointer-events: none` para no
   tapar clicks en lo que hay debajo; cada toast lo reactiva para su propio
   botón de cierre.
   -------------------------------------------------------------------------- */

.toast-stack {
  position: fixed;
  bottom: 26px;
  left: 50%;
  z-index: 1100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  transform: translateX(-50%);
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(460px, calc(100vw - 32px));
  padding: 11px 16px;
  font-size: var(--ft-fs-md);
  color: #ffffff;
  background: #22262c;
  border-radius: var(--ft-radius);
  box-shadow: var(--ft-shadow-lg);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity var(--ft-transition), transform var(--ft-transition);
  pointer-events: auto;
}

.toast.show {
  opacity: 1;
  transform: translateY(0);
}

.toast-icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
}

.toast.success .toast-icon { color: #57d98a; }
.toast.danger  .toast-icon { color: #ff8f7c; }
.toast.warning .toast-icon { color: #ffc061; }
.toast.info    .toast-icon { color: #7fabff; }

:root[data-theme="dark"] .toast {
  background: #2b3138;
  border: 1px solid var(--ft-border-strong);
}

/* --------------------------------------------------------------------------
   Bloqueo de UI y spinner
   -------------------------------------------------------------------------- */

.block-window {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: grid;
  place-items: center;
  background: var(--ft-overlay);
  opacity: 0;
  transition: opacity var(--ft-transition);
}

.block-window.show {
  opacity: 1;
}

.spinner {
  display: inline-block;
  width: 18px;
  height: 18px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: ft-spin 640ms linear infinite;
}

.block-window .spinner {
  width: 34px;
  height: 34px;
  border-width: 3px;
  color: #ffffff;
}

@keyframes ft-spin {
  to { transform: rotate(360deg); }
}

/* Barra de progreso indeterminada, para la carga de vistas del panel: se
   monta arriba del área de contenido y no bloquea la interacción, a
   diferencia de .block-window. */
.view-loader {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  overflow: hidden;
  background: var(--ft-brand-soft);
}

.view-loader::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ft-brand);
  transform: translateX(-100%);
  animation: ft-loading 900ms ease-in-out infinite;
}

@keyframes ft-loading {
  0%   { transform: translateX(-100%); }
  60%  { transform: translateX(100%); }
  100% { transform: translateX(100%); }
}

/*!**********************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/login.css ***!
  \**********************************************************************************************************************/
/* ==========================================================================
   login.css — pantalla de acceso al panel.

   Vive fuera del shell (no hay sidebar ni cabecera todavía) pero usa los
   MISMOS tokens, así el login ya se ve en el tema que el usuario dejó
   elegido la última vez.
   ========================================================================== */

body.auth {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 24px;
  font-family: var(--ft-font);
  font-size: var(--ft-fs-base);
  line-height: 1.5;
  color: var(--ft-text);
  background: var(--ft-bg);
  background-image: radial-gradient(120% 90% at 50% 0%, var(--ft-bg-accent) 0%, var(--ft-bg) 70%);
  -webkit-font-smoothing: antialiased;
  overflow: auto;
}

.auth-card {
  width: 100%;
  max-width: 408px;
  padding: 32px;
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius-lg);
  box-shadow: var(--ft-shadow-lg);
}

/* --------------------------------------------------------------------------
   Marca — el mismo logo del sitio público, en sus dos variantes de tema.
   Ver el comentario en Authentication/view/login.php.
   -------------------------------------------------------------------------- */

.auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 26px;
}

/* `height` fija y `width: auto` para que el logo escale por altura sin
   deformarse, sin importar la relación de aspecto exacta del archivo. */
.auth-logo {
  height: 42px;
  width: auto;
}

/* Sólo una de las dos variantes se muestra. Los `<img>` de la otra igual se
   descargan —son ~8 KB y quedan cacheados un año por el .htaccess—, lo que a
   cambio hace que alternar el tema sea instantáneo, sin un salto en blanco
   mientras baja el archivo de la variante nueva. */
.logo-on-dark {
  display: none;
}

:root[data-theme="dark"] .logo-on-light {
  display: none;
}

:root[data-theme="dark"] .logo-on-dark {
  display: block;
}

.auth-title {
  font-size: var(--ft-fs-xl);
  font-weight: 600;
  text-align: center;
}

.auth-subtitle {
  margin-top: 5px;
  margin-bottom: 24px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
  text-align: center;
}

.auth-form .form-group + .form-group {
  margin-top: 18px;
}

/* --------------------------------------------------------------------------
   Estado "sólo mostrar un aviso" — se usa cuando se pidió el enlace de
   restablecimiento (revisá tu correo) y cuando se confirmó la contraseña
   nueva (contraseña actualizada). Reemplaza el CONTENIDO de #forgot-panel /
   #reset-panel por vía de JS; no es una vista aparte (ver el comentario de
   Authentication/view/forgot.php).
   -------------------------------------------------------------------------- */

.auth-notice {
  text-align: center;
}

.auth-notice-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin-bottom: 16px;
  color: var(--ft-brand);
  background: var(--ft-brand-soft);
  border-radius: var(--ft-radius-pill);
}

.auth-notice .auth-title {
  margin-top: 0;
}

.auth-notice .btn {
  margin-top: 22px;
}

.auth-form .btn {
  height: 44px;
  margin-top: 20px;
}

.auth-options {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
}

.auth-link {
  font-size: var(--ft-fs-md);
  color: var(--ft-brand);
  transition: color var(--ft-transition);
}

.auth-link:hover {
  color: var(--ft-brand-strong);
}

.auth-foot {
  margin-top: 22px;
  padding-top: 18px;
  font-size: var(--ft-fs-md);
  color: var(--ft-text-muted);
  text-align: center;
  border-top: 1px solid var(--ft-border);
}

/* El conmutador de tema del login: flotante arriba a la derecha, sin el
   sidebar donde normalmente vive. */
.auth-theme {
  position: fixed;
  top: 20px;
  right: 20px;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  color: var(--ft-text-soft);
  background: var(--ft-surface);
  border: 1px solid var(--ft-border);
  border-radius: var(--ft-radius);
  box-shadow: var(--ft-shadow-sm);
  transition: color var(--ft-transition), background var(--ft-transition);
}

.auth-theme:hover {
  color: var(--ft-text);
  background: var(--ft-surface-hover);
}

@media (max-width: 480px) {
  .auth-card {
    padding: 24px 20px;
    border-radius: var(--ft-radius-lg);
  }
}

/*!***************************************************************************************************************************!*\
  !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].use[1]!./source_assets/backoffice/css/backoffice.css ***!
  \***************************************************************************************************************************/
/* ==========================================================================
   backoffice.css — lo que es PROPIO de este contexto (2026-08-28).

   Todo lo demás (tokens, layout, sidebar, formularios, feedback, login) es
   una copia de la base de Tenants: son el mismo diseño, no dos sistemas
   distintos. Este archivo es donde vive lo que SÍ es distinto.
   ========================================================================== */

/**
 * Insignia junto al logo — el mismo logo que el panel del tenant, pero hay
 * que poder distinguir a simple vista en qué panel se está (sobre todo con
 * las dos pestañas abiertas a la vez, que va a ser el caso normal).
 */
.brand-badge {
  font-size: var(--ft-fs-xs, 11px);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ft-text-soft);
  background: var(--ft-surface-alt, rgba(127, 127, 127, 0.12));
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
}


/*# sourceMappingURL=styles.c6806dbd.css.map*/