/* Shared: Components */

/* Tamaño base de iconos SVG inline (migrados de Font Awesome). Replica el
   comportamiento de FA: el icono se dimensiona con el font-size del contexto
   (1em) y se alinea con el texto. Las reglas más específicas (.nav .icon,
   .facturas-view-switch-btn .icon, .btn-* .icon, .cell-icon, empty-state…)
   tienen mayor especificidad y sobrescriben esto cuando hace falta. */
.icon {
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
    flex-shrink: 0;
}

/* Icono inline genérico dentro de celdas/badges (tamaño coherente). Global:
   antes vivía solo en facturas.css, lo que dejaba sin dimensionar los SVG de
   esta clase en páginas que no la cargan (p.ej. Centro de costes). Fuente
   única de verdad — no redefinir en hojas de módulo. */
.cell-icon { width: 15px; height: 15px; vertical-align: -2px; }

/* ============================================================================
   Rótulo de versión del producto (componente global)
   ----------------------------------------------------------------------------
   HTML en `templates/components/version.j2`, incluido UNA vez desde
   `layouts/base.j2`. Nació en el dashboard (`.dashboard-version`) y se subió
   aquí al mostrarse en todas las vistas: dos copias del mismo rótulo con dos
   posiciones distintas es exactamente lo que este componente evita.
   ========================================================================== */
.app-version {
    /* `absolute` contra el BODY (que por eso es `position: relative` en
       base.css), no `fixed` ni en flujo:
        · `fixed` flotaría sobre el contenido al hacer scroll y taparía la
          última fila de cualquier listado — el motivo por el que ya se
          descartó en el dashboard.
        · en FLUJO añadiría su altura al documento, y en las pantallas que
          miden justo un viewport (inicio, con `min-height: calc(100vh - 74px)`)
          eso saca una barra de scroll por un rótulo de 11px.
       Anclado al fondo del body queda al final del documento en las páginas
       largas y en la esquina de la ventana en las cortas, que es como se veía
       en inicio. Cae dentro del `padding-bottom` de `.main-content`, que es
       espacio vacío. */
    position: absolute;
    left: 1rem;            /* borde real de la ventana: el body ocupa todo el ancho */
    /* Con sesión, el lanzador del Asistente ocupa esa esquina (20 + 56 px): el rótulo se corre a
       su derecha para poder LEERSE —ver la regla `body:has(#asistenteFab)` de abajo—. */
    /* Dentro de los 24px de `padding-bottom` de `.main-content`, con holgura:
       a .6rem quedaban 3px contra el contenido y cualquier vista que recorte
       ese padding lo pegaría sin avisar. */
    bottom: calc(0.5rem + var(--safe-bottom));
    z-index: 1;            /* por encima del canvas de nodos del dashboard (0) */
    font-size: 0.6875rem;  /* 11px: discreto, pero aún legible */
    line-height: 1;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    /* Baja el contraste sin inventar un gris: sobre el token, el número queda
       presente y no compite con el contenido. Nada de `color: #999`, que en
       modo oscuro se volvería ilegible.
       .8 y no el .55 que traía del dashboard: medido, .55 daba 2.1:1 en claro y
       2.8:1 en oscuro sobre 11px, y en pantalla táctil no hay `:hover` que lo
       suba — el rótulo se quedaba ilegible justo donde no se puede recuperar.
       El texto es el número que hay que poder LEER para reportar un fallo. */
    opacity: .8;
    /* Se puede seleccionar y copiar: es para lo que está. */
    user-select: text;
    transition: opacity 150ms var(--ease);
}
/* El rótulo está para leerlo al reportar un fallo: que no quede debajo del lanzador del Asistente. */
body:has(#asistenteFab) .app-version { left: calc(20px + 56px + 12px + var(--safe-left)); }
.app-version:hover { opacity: 1; }

/* Main Content */
.main-content {
    /* Con el móvil en horizontal, la muesca queda a un lado (0 en el resto). */
    padding: 1.5rem calc(1.5rem + var(--safe-right)) calc(1.5rem + var(--safe-bottom)) calc(1.5rem + var(--safe-left));
    max-width: 1400px;
    margin: 0 auto;
}

.main-content.full-width {
    max-width: 100%;
}

.filter-bar-container.full-width {
    max-width: 100%;
}

.view {
    display: none;
}

.view.active {
    display: block;
    animation: fadeIn 0.25s var(--ease);
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Header */
.page-header {
    text-align: center;
    margin-bottom: 1.5rem;
    padding: 1rem 0;
}

.page-header h1 {
    color: var(--gray-900);
    font-size: 1.5rem;
    font-weight: 600;
    margin-bottom: 0.25rem;
    letter-spacing: -0.02em;
}
/* Icono SVG (migrado de Font Awesome) en el título de página. */
.page-header h1 .icon {
    width: 1.35rem;
    height: 1.35rem;
    vertical-align: -0.2em;
    margin-right: 0.5rem;
    color: var(--gray-900);
}

.page-header h1::after {
    display: none;
}

.page-header p {
    color: var(--gray-500);
    font-size: 0.85rem;
    margin-top: 0.25rem;
}

/* Controls */
.controls {
    background: var(--surface);
    border-radius: var(--radius);
    padding: 1rem 1.25rem;
    margin-bottom: 1.25rem;
    box-shadow: var(--shadow);
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    border: 1px solid var(--gray-200);
}

.controls:hover {
    box-shadow: var(--shadow-md);
}

.nav-btn {
    background: var(--primary);
    color: white;
    border: none;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-md);
    font-size: 0.9rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: var(--transition);
}

.nav-btn:hover {
    background: var(--primary-light);
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

.month-selector {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

.custom-select {
    appearance: none;
    padding: 0.5rem 2rem 0.5rem 0.75rem;
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    font-size: 0.85rem;
    font-family: inherit;
    font-weight: 450;
    background-color: var(--surface);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.65rem center;
    cursor: pointer;
    transition: var(--transition);
    min-width: 130px;
    color: var(--gray-700);
}

.custom-select:focus {
    outline: none;
    border-color: var(--primary-light);
    box-shadow: 0 0 0 3px rgba(26, 54, 93, 0.08);
}

.today-btn {
    background: var(--surface-inverse);
    color: white;
    border: none;
    padding: 0.5rem 1rem;
    border-radius: var(--radius-md);
    font-weight: 500;
    font-size: 0.82rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    transition: var(--transition);
}

.today-btn:hover {
    background: var(--surface-inverse-strong);
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

/* Toast notification */
.toast {
    position: fixed;
    bottom: calc(1.5rem + var(--safe-bottom));
    right: calc(1.5rem + var(--safe-right));
    background: var(--surface-inverse-strong);
    color: white;
    padding: 0.75rem 1.25rem;
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    display: flex;
    align-items: center;
    gap: 0.6rem;
    transform: translateY(100px);
    opacity: 0;
    transition: all 0.25s var(--ease);
    z-index: 2000;
    font-size: 0.85rem;
}

/* El motivo cabe entero: el texto se parte y la caja no pasa del ancho útil. */
.toast { max-width: min(460px, calc(100vw - 2rem)); }
.toast #toastMessage { white-space: normal; overflow-wrap: anywhere; line-height: 1.35; }
.toast.is-fijo { align-items: flex-start; }
.toast-close {
    flex: 0 0 auto; margin-left: auto; display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; margin-top: -4px; margin-right: -6px; padding: 0;
    border: 0; border-radius: var(--radius-sm); background: transparent; color: inherit; cursor: pointer; opacity: .9;
}
.toast-close:hover { background: color-mix(in srgb, currentColor 18%, transparent); opacity: 1; }
.toast-close:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
.toast-close[hidden], .toast-timer[hidden] { display: none; }
.toast-close .icon { width: 16px; height: 16px; }
@media (pointer: coarse) { .toast-close { width: 44px; height: 44px; margin-top: -12px; margin-right: -14px; margin-bottom: -12px; } }
/* Oculto de verdad: ni se tabula a la × ni roba clics a lo que queda debajo. */
.toast:not(.show) { visibility: hidden; pointer-events: none; }
.toast { transition: all 0.25s var(--ease), visibility 0s linear 0.25s; }
.toast.show { transition: all 0.25s var(--ease), visibility 0s; }
/* Un icono por tipo. */
.toast .toast-icono { display: none; flex: 0 0 auto; }
.toast .toast-icono--ok { display: block; }
.toast.error .toast-icono--ok, .toast.warning .toast-icono--ok { display: none; }
.toast.error .toast-icono--error, .toast.warning .toast-icono--aviso { display: block; }
/* El rojo lleva texto largo: fondo más oscuro para 4,5:1 también en oscuro. */
.toast.error { background: color-mix(in srgb, var(--danger) 78%, var(--surface-inverse-strong)); }
/* En móvil sale ARRIBA, bajo la barra: abajo tapaba el pie de los modales y
   el botón del Asistente mientras el rojo espera a que se cierre. */
@media (max-width: 640px) {
    .toast { top: calc(var(--nav-height, 56px) + 0.5rem); bottom: auto; left: 0.75rem; right: 0.75rem; max-width: none;
             transform: translateY(-120px); }
    .toast.show { transform: translateY(0); }
}
.toast.show {
    transform: translateY(0);
    opacity: 1;
}

.toast.success {
    background: var(--success);
}

.toast.error {
    background: var(--danger);
}

/* Aviso (parciales, omitidas...): ámbar con texto oscuro para contraste. */
.toast.warning {
    background: var(--warning);
    color: #1a202c;
}

.toast-timer {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    margin-left: auto;
}

.toast-timer circle {
    fill: none;
    stroke: rgba(255, 255, 255, 0.3);
    stroke-width: 2.5;
}

.toast-timer .toast-timer-fg {
    stroke: #fff;
    stroke-dasharray: 50.27;
    stroke-dashoffset: 0;
    transform: rotate(-90deg);
    transform-origin: center;
}

.toast-timer.animate .toast-timer-fg {
    animation: toastCountdown 3s linear forwards;
}

@keyframes toastCountdown {
    from { stroke-dashoffset: 0; }
    to { stroke-dashoffset: 50.27; }
}

/* Search */
.search-box {
    padding: 0.6rem;
    border-bottom: 1px solid var(--gray-100);
}

.search-input {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    font-size: 0.82rem;
    color: var(--gray-700);
    transition: var(--transition);
}

.search-input:focus {
    outline: none;
    border-color: var(--primary-light);
    box-shadow: 0 0 0 3px rgba(26, 54, 93, 0.08);
}

.search-input::placeholder {
    color: var(--gray-400);
}

/* ============================================================
   Confirm Modal
   ============================================================ */
.confirm-modal .modal-dialog {
    max-width: 440px;
}

.confirm-modal-content {
    border: none;
    border-radius: 18px;
    overflow: hidden;
    box-shadow: 0 25px 60px -15px rgba(15, 23, 42, 0.35), 0 10px 25px -10px rgba(15, 23, 42, 0.2);
    background: var(--surface);
}

.confirm-modal-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    margin: 1.75rem auto 0.5rem;
    border-radius: 50%;
    background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
    color: #b45309;
    font-size: 1.85rem;
    box-shadow: 0 8px 20px -8px rgba(180, 83, 9, 0.4);
    animation: confirmIconPop 0.32s var(--ease-rebote);
}

.confirm-modal-icon.danger {
    background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
    color: #b91c1c;
    box-shadow: 0 8px 20px -8px rgba(185, 28, 28, 0.4);
}

.confirm-modal-icon.info {
    background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
    color: #1d4ed8;
    box-shadow: 0 8px 20px -8px rgba(29, 78, 216, 0.4);
}

@keyframes confirmIconPop {
    0%   { transform: scale(0.4); opacity: 0; }
    60%  { transform: scale(1.08); opacity: 1; }
    100% { transform: scale(1); }
}

.confirm-modal-body {
    text-align: center;
    padding: 0.5rem 2rem 1.25rem;
    border: none;
}

.confirm-modal-title {
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--gray-800, #1f2937);
    margin: 0 0 0.5rem;
    letter-spacing: -0.01em;
}

.confirm-modal-message {
    font-size: 0.92rem;
    color: var(--gray-600, #4b5563);
    margin: 0;
    line-height: 1.5;
    white-space: pre-line;
}

.confirm-modal-footer {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
    padding: 0 2rem 1.75rem;
    border: none;
}

.confirm-modal-footer .btn {
    flex: 1;
    max-width: 160px;
    padding: 0.65rem 1rem;
    border-radius: 12px;
    font-weight: 600;
    font-size: 0.9rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    border: none;
    transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease), background 0.15s var(--ease);
}

.btn-confirm-cancel {
    background: var(--gray-100);
    color: var(--gray-600);
}

.btn-confirm-cancel:hover {
    background: var(--gray-200);
    color: var(--gray-800);
    transform: translateY(-1px);
}

.btn-confirm-ok {
    background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
    color: #fff;
    box-shadow: 0 8px 18px -8px rgba(220, 38, 38, 0.6);
}

.btn-confirm-ok:hover {
    background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
    color: #fff;
    transform: translateY(-1px);
    box-shadow: 0 12px 22px -8px rgba(185, 28, 28, 0.6);
}

.btn-confirm-ok.variant-info {
    background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
    box-shadow: 0 8px 18px -8px rgba(37, 99, 235, 0.6);
}

.btn-confirm-ok.variant-info:hover {
    background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
    box-shadow: 0 12px 22px -8px rgba(29, 78, 216, 0.6);
}

.btn-confirm-ok.variant-warning {
    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
    box-shadow: 0 8px 18px -8px rgba(217, 119, 6, 0.6);
}

.btn-confirm-ok.variant-warning:hover {
    background: linear-gradient(135deg, #d97706 0%, #b45309 100%);
    box-shadow: 0 12px 22px -8px rgba(180, 83, 9, 0.6);
}

.confirm-modal.fade .modal-dialog {
    transform: scale(0.9) translateY(-10px);
    transition: transform 0.22s var(--ease-rebote), opacity 0.18s var(--ease);
}

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

/* Shared modal overlay (used by login history, AI export, etc.) */
.ai-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.4);
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.ai-modal {
    background: var(--surface);
    border-radius: var(--radius-md);
    width: 480px;
    max-width: 90vw;
    box-shadow: 0 8px 30px rgba(0,0,0,0.15);
    overflow: hidden;
}
.ai-modal-header {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border-color);
    font-weight: 600;
    font-size: 0.95rem;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.ai-modal-body { padding: 1.25rem; }
.ai-modal-body textarea {
    width: 100%;
    border: 1px solid var(--border-color);
    background: var(--surface);
    color: var(--text-color);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    font-size: 0.85rem;
    resize: vertical;
    font-family: inherit;
    box-sizing: border-box;
    min-height: 80px;
}
.ai-modal-body label { font-size: 0.8rem; color: var(--text-muted); display: block; margin-bottom: 0.4rem; }
.ai-modal-footer {
    padding: 0.75rem 1.25rem;
    border-top: 1px solid var(--border-color);
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}
.ai-modal-footer button {
    padding: 0.45rem 1rem;
    border-radius: 6px;
    font-size: 0.85rem;
    cursor: pointer;
    border: 1px solid var(--border-color);
    background: var(--surface);
    color: var(--text-color);
}
.ai-modal-footer .ai-modal-submit { background: #0052CC; color: #fff; border-color: #0052CC; }
.ai-modal-footer .ai-modal-submit:disabled { opacity: 0.6; cursor: not-allowed; }

/* iOS-style toggle switch. Shared across all views. */
.switch {
    position: relative;
    display: inline-block;
    width: 48px;
    height: 26px;
    flex-shrink: 0;
}
.switch input {
    opacity: 0;
    width: 0;
    height: 0;
}
.switch .slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: var(--gray-300);
    transition: 0.2s;
    border-radius: 26px;
}
.switch .slider::before {
    position: absolute;
    content: "";
    height: 20px;
    width: 20px;
    left: 3px;
    bottom: 3px;
    background-color: var(--surface);
    transition: 0.2s;
    border-radius: 50%;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.switch input:checked + .slider {
    background-color: var(--success, #16a34a);
}
.switch input:checked + .slider::before {
    transform: translateX(22px);
}
.switch input:disabled + .slider {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ============================================================
   View switch — control de pestañas pill reutilizable.
   Usado en /facturas, /presupuestos, /proyectos, /configuracion, /historial.
   Antes vivía dividido entre facturas.css y presupuestos.css; promovido aquí
   para que cualquier vista lo reciba sin tener que cargar CSS específico.
   ============================================================ */
/* Indicador deslizante (thumb estilo iOS) — base común de TODOS los switches
   segmentados. El motor (core.js → initSegThumbs) inserta un .seg-thumb por
   contenedor y lo posiciona sobre la opción .is-active; cada control define el
   color/forma del thumb sobreescribiéndolo (ver .facturas-view-switch .seg-thumb,
   .rrhh-seg/.rrhh-segmented en rrhh.css). Ver .claude/agents/front.md punto 13. */
.seg-thumb {
    position: absolute; top: 0; left: 0; z-index: 0; opacity: 0;
    border-radius: var(--radius-full);
    background: var(--primary);
    box-shadow: var(--shadow);
    transition: transform 0.28s var(--ease-gesto),
                width 0.28s var(--ease-gesto),
                opacity 0.15s var(--ease);
    pointer-events: none;
}

.facturas-view-switch {
    position: relative;
    /* Centrado en la página: la pastilla se ajusta a su contenido (fit-content)
       y se centra con márgenes auto. En móvil, max-width + el overflow propio de
       cada switch mantienen el scroll horizontal. La variante a lo ancho
       (.metodo-tipo-switch) lo sobreescribe con width:100%. */
    display: flex;
    width: fit-content;
    max-width: 100%;
    overflow-x: auto;   /* si no cabe (muchas pestañas en móvil), scroll DENTRO, no de la página */
    margin: 0.9rem auto 0;
    gap: 0;
    background: var(--gray-100);
    border-radius: 999px;
    padding: 0.25rem;
    border: 1px solid var(--gray-200);
}
/* El thumb de este switch imita su look activo histórico: pastilla clara
   elevada (no el primary del thumb por defecto). */
.facturas-view-switch .seg-thumb { background: var(--surface); }
/* El atributo `hidden` debe anular el display inline-flex (mayor especificidad
   que la regla base) para poder ocultar el switch o pestañas concretas. */
.facturas-view-switch[hidden],
.facturas-view-switch-btn[hidden] {
    display: none;
}
.facturas-view-switch-btn {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 1rem;
    font-size: 0.82rem;
    font-weight: 500;
    color: var(--gray-600);
    background: transparent;
    border: none;
    border-radius: 999px;
    cursor: pointer;
    transition: var(--transition);
    white-space: nowrap;
}
.facturas-view-switch-btn i {
    font-size: 0.85rem;
}
.facturas-view-switch-btn .icon {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}
.facturas-view-switch-btn:not(.is-active):hover {
    color: var(--gray-900);
    background: var(--surface-hover);
}
/* La pastilla activa la pinta el .seg-thumb (fondo + sombra); el botón solo
   aporta el color de texto y el peso. */
.facturas-view-switch-btn.is-active {
    background: transparent;
    color: var(--primary);
    font-weight: 600;
}
.facturas-view-switch-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4rem;
    height: 1.1rem;
    padding: 0 0.4rem;
    font-size: 0.7rem;
    font-weight: 600;
    border-radius: 999px;
    background: var(--gray-200);
    color: var(--gray-700);
    line-height: 1;
}
.facturas-view-switch-btn.is-active .facturas-view-switch-count {
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    color: var(--primary);
}

/* ============================================================
   Bloque colapsable (details/summary) reutilizable.
   Estilo original del bloque de sub-tareas / etiquetas del modal de issue
   en /proyectos. Promovido a global para reutilizarlo en otros modales
   (p.ej. envíos de presupuesto en /presupuestos).
   ============================================================ */
.proy-subtareas-block {
    border: 1px solid var(--gray-200);
    border-radius: 8px;
    padding: 0.5rem 0.85rem;
    background: var(--surface);
}
.proy-subtareas-block > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--gray-800);
    padding: 0.15rem 0;
}
.proy-subtareas-block > summary::-webkit-details-marker { display: none; }
/* La flecha, dibujada con bordes: era un glifo de Font Awesome, que en las
   zonas ya migradas no se carga —y donde no está, salía un cuadrado. */
.proy-subtareas-block > summary::before {
    content: '';
    width: 0.45rem; height: 0.45rem; flex: 0 0 auto;
    margin-right: 0.35rem;
    border-right: 2px solid var(--gray-400);
    border-bottom: 2px solid var(--gray-400);
    transform: rotate(-45deg);
    transition: transform 0.15s;
}
.proy-subtareas-block[open] > summary::before { transform: rotate(45deg); }
.proy-subtareas-block > summary i { color: var(--primary); }
/* Por TOKENS: el teal escrito a mano no cambia con el tema, así que en modo
   oscuro el contador quedaba apagado sobre superficie oscura. */
.proy-subtareas-count {
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    color: var(--primary);
    padding: 0.05rem 0.5rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    margin-left: 0.1rem;
}
.proy-subtareas-body {
    margin-top: 0.65rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--gray-100);
}
.proy-subtareas-list {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

/* ============== Form Modal — estilo "guapo" reutilizable ==============
 * Igual al modal de lead del embudo. Estructura:
 *   .form-modal
 *     .form-modal-header (gradiente + icono pintado)
 *     .form-modal-body   (gris claro, contiene secciones)
 *       .form-modal-section
 *         .form-modal-section-title
 *         .form-modal-grid (+ .form-modal-grid--full para anchura completa)
 *     .form-modal-footer */
.form-modal {
    border: 0;
    border-radius: 14px;
    box-shadow: 0 25px 60px -20px rgba(15, 23, 42, 0.35);
    overflow: hidden;
}
.form-modal-header {
    background: linear-gradient(135deg,
        var(--primary-dark, var(--primary-dark)) 0%,
        var(--primary, var(--primary)) 100%);
    border-bottom: 1px solid var(--primary-dark, var(--primary-dark));
    padding: 1rem 1.25rem;
}
.form-modal-header .modal-title {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    font-size: 1rem;
    font-weight: 700;
    color: white;
}
.form-modal-header .modal-title > i {
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.15);
    color: white;
    border-radius: 8px;
    font-size: 0.95rem;
}
/* Versión SVG (estándar) — cuadrado translúcido sobre el header oscuro. */
.form-modal-header .modal-title > svg,
.form-modal-header .modal-title > [data-svg-icon] > svg {
    width: 32px;
    height: 32px;
    padding: 6px;
    background: rgba(255, 255, 255, 0.15);
    color: white;
    border-radius: 8px;
    flex-shrink: 0;
    box-sizing: border-box;
}
.form-modal-body {
    padding: 1.25rem 1.5rem 1rem;
    background: var(--gray-50, var(--gray-50));
}
/* El cuerpo desplazable de un modal alto.
   Bootstrap solo da scroll a `.modal-dialog-scrollable .modal-body`, y la
   mayoría de nuestros modales escriben `class="form-modal-body"` A SECAS —sin
   `modal-body`—, así que se quedaban sin él: como `.form-modal` es
   `overflow: hidden`, el contenido que no cabía no se cortaba «hacia fuera»,
   simplemente NO SE PODÍA ALCANZAR (el caso medido: editar una cuenta personal
   de Correos, donde los campos de SMTP quedaban fuera de tiro). Se arregla aquí
   y no plantilla a plantilla porque afecta a Correos, Calendario, Viajes y
   Videollamadas por igual, y porque escribir las dos clases es justo lo que
   nadie recuerda.
   `min-height: 0` es imprescindible: sin él, un hijo flex no encoge por debajo
   de su contenido y `overflow-y` no llega a activarse nunca. */
.modal-dialog-scrollable .form-modal-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
.form-modal-footer {
    background: var(--surface);
    border-top: 1px solid var(--gray-200, var(--gray-200));
    padding: 0.85rem 1.25rem;
}
/* Zonas seguras (v2.163.1). Un modal centrado no se mete bajo la muesca ni la
   barra de inicio: el hueco se le quita al contenedor `.modal`. Uno a pantalla
   completa (`modal-fullscreen-*`) llega al borde: el hueco va DENTRO, para que su
   pie no quede bajo la barra de inicio. Todo vale 0 fuera de los iPhone. */
.modal:not(:has(> .modal-dialog[class*="modal-fullscreen"])) {
    padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
}
/* Un `modal-fullscreen-*-down` por ENCIMA de su corte se ve centrado: también lleva el hueco. */
@media (min-width: 576px)  { .modal:has(> .modal-fullscreen-sm-down)  { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); } }
@media (min-width: 768px)  { .modal:has(> .modal-fullscreen-md-down)  { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); } }
@media (min-width: 992px)  { .modal:has(> .modal-fullscreen-lg-down)  { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); } }
@media (min-width: 1200px) { .modal:has(> .modal-fullscreen-xl-down)  { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); } }
@media (min-width: 1400px) { .modal:has(> .modal-fullscreen-xxl-down) { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); } }
.modal-fullscreen .modal-content {
    padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
}
@media (max-width: 575.98px) {
    .modal-fullscreen-sm-down .modal-content { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); }
}
@media (max-width: 767.98px) {
    .modal-fullscreen-md-down .modal-content { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); }
}
@media (max-width: 991.98px) {
    .modal-fullscreen-lg-down .modal-content { padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); }
}

.form-modal-section {
    background: var(--surface);
    border: 1px solid var(--gray-200, var(--gray-200));
    border-radius: 10px;
    padding: 0.9rem 1.1rem 1rem;
    margin-bottom: 0.85rem;
}
.form-modal-section:last-of-type {
    margin-bottom: 0;
}
/* Acción al pie de una sección (botón a la derecha dentro de la propia
   tarjeta), p.ej. "Guardar datos" / "Actualizar contraseña" en el perfil. */
.form-modal-footer-inline {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.85rem;
}
.form-modal-section-title {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-500, var(--gray-500));
    margin-bottom: 0.85rem;
    padding-bottom: 0.55rem;
    border-bottom: 1px dashed var(--gray-200, var(--gray-200));
    width: 100%;
}
.form-modal-section-title > i {
    color: var(--primary, #3b82f6);
    font-size: 0.85rem;
}
/* Versión SVG (estándar) — mismo color azul, tamaño equivalente a 0.85rem. */
.form-modal-section-title > svg,
.form-modal-section-title > [data-svg-icon] > svg {
    color: var(--primary, #3b82f6);
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

/* Texto introductorio de una sección de modal (debajo de la cabecera de
   sección, antes de la rejilla de campos). Promovida desde plataforma.css al
   reutilizarse en un segundo módulo (WordPress). */
.form-modal-section__hint {
    margin: 0 0 0.65rem;
    color: var(--text-muted);
}

.form-modal-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem 1rem;
}
/* Nada de lo que va en una celda puede ser más ancho que la celda: hijos y
   controles con `min-width: 0` (un select o un input tienen ancho intrínseco y
   desbordarían), y `max-width: 100%` para lo que traiga ancho propio. */
.form-modal-grid > * { min-width: 0; }
.form-modal-grid .form-input, .form-modal-grid .form-select, .form-modal-grid .form-textarea,
.form-modal-grid .form-control, .form-modal-grid select.form-select, .form-modal-grid .form-field__control,
.form-modal-grid .form-field-card { min-width: 0; max-width: 100%; }
/* El corte a UNA columna se decide por el ancho del MODAL, no del navegador:
   un modal-sm (300px) o un diálogo de 500px en tablet estaban por encima de
   los 600px de viewport y seguían a dos columnas de 130-220px. */
.form-modal-body { container-type: inline-size; }
@container (max-width: 440px) {
    .form-modal-grid { grid-template-columns: 1fr; }
    .form-modal-grid > * { grid-column: auto; }
}
/* Cuerpo a dos columnas de secciones (`.form-modal-body--2col`): en modal-xl
   dentro de 992-1199px el diálogo mide 800 y cada sección ~370 → sus rejillas
   interiores van a UNA columna. NO hacer contenedor a `.form-modal-section`:
   cada sección sería un stacking context y un desplegable de la primera
   quedaría tapado por la segunda. */
@container (max-width: 900px) {
    .form-modal-body--2col .form-modal-section .form-modal-grid { grid-template-columns: 1fr; }
}
/* Estos selectores usan ">" intencionalmente: solo aplican a la estructura
   legacy en la que <label> es hijo directo del grid. La nueva estructura
   estándar (<div class="form-field"> con .form-field__label dentro) queda
   intacta y se rige por btn-actions.css. */
.form-modal-grid > label {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin: 0;
    min-width: 0;
}
.form-modal-grid > label > span {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--gray-500, var(--gray-500));
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.form-modal-grid > label > span > i {
    color: var(--gray-400, var(--gray-400));
    font-size: 0.78rem;
    width: 0.95rem;
    text-align: center;
}
.form-modal-grid > label > span > em {
    color: var(--danger, #dc2626);
    font-style: normal;
    font-weight: 700;
}
.form-modal-grid--full {
    grid-column: 1 / -1;
}

/* Grupo de acciones de fila (btn-edit/btn-delete icon-only) en la columna
   «Acciones» de cualquier tabla: SIEMPRE en una sola línea, alineadas a la
   derecha y con separación uniforme. Reutilizable en cualquier vista. */
.row-actions {
    display: inline-flex;
    flex-wrap: nowrap;
    justify-content: flex-end;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
}
.form-modal-grid input.form-control,
.form-modal-grid select.form-select,
.form-modal-grid textarea.form-control {
    background: var(--gray-50, var(--gray-50));
    border: 1px solid var(--gray-200, var(--gray-200));
    transition: background 0.12s, border-color 0.12s, box-shadow 0.12s;
}
.form-modal-grid input.form-control:focus,
.form-modal-grid select.form-select:focus,
.form-modal-grid textarea.form-control:focus {
    background: var(--surface);
    border-color: var(--primary, #3b82f6);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12);
}

/* Override Bootstrap: el .form-switch del estándar (label con
   .form-switch__input + .form-switch__track + .form-switch__label) NO debe
   recibir el padding-left: 2.5em que Bootstrap aplica a la clase .form-switch.
   Lo detectamos por la presencia de .form-switch__track, exclusiva del estándar. */
.form-switch:has(.form-switch__track) {
    padding-left: 0;
}

/* ------------------------------------------------------------------
 * TABS DE MODAL — .modal-tabs (reutilizable)
 *
 * Pensado para envolver una <ul class="nav nav-tabs modal-tabs"> con
 * <button class="nav-link"> que sigue gestionando Bootstrap Tab vía
 * data-bs-toggle="tab". El override aquí solo cambia la apariencia
 * para casarlo con la paleta del estándar (azul marino --primary)
 * y con los SVG lucide-style.
 *
 * Selectores acoplados a .modal-tabs para no contaminar otros .nav-tabs
 * legacy del proyecto.
 * ------------------------------------------------------------------ */
.modal-tabs {
    border-bottom: 1px solid var(--gray-200, var(--gray-200));
    margin: 0 0 1rem 0;
    padding: 0;
    flex-wrap: wrap;
    gap: 0.15rem;
}
.modal-tabs .nav-link {
    color: var(--gray-600, var(--gray-600));
    font-size: 0.85rem;
    font-weight: 600;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: 6px 6px 0 0;
    padding: 0.5rem 0.95rem;
    background: transparent;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.modal-tabs .nav-link .icon {
    width: 16px;
    height: 16px;
    stroke-width: 2;
}
.modal-tabs .nav-link:hover {
    color: var(--primary, var(--primary));
    background: rgba(26, 54, 93, 0.05);
}
.modal-tabs .nav-link.active {
    color: var(--primary, var(--primary));
    background: var(--surface);
    border-color: var(--gray-200, var(--gray-200));
    border-bottom-color: white;
    margin-bottom: -1px;
}
.modal-tabs__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.1rem;
    padding: 0 0.35rem;
    margin-left: 0.15rem;
    background: var(--gray-100, var(--gray-100));
    color: var(--gray-700, var(--gray-700));
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
}
.modal-tabs .nav-link.active .modal-tabs__badge {
    background: rgba(26, 54, 93, 0.15);
    color: var(--primary, var(--primary));
}
.modal-tabs__content {
    min-height: 200px;
}

/* Wrapper de tarjeta gris que se coloca DENTRO de un .form-field, justo después
   del .form-field__label, para agrupar el control y el help-text en un recuadro
   visualmente diferenciado (típico de switches y bloques de ajuste). El label
   queda fuera, arriba, alineado con el resto de labels del modal. */
.form-field-card {
    display: flex;
    flex-direction: column;
    gap: var(--field-gap, 6px);
    background: var(--gray-50, var(--gray-50));
    border: 1px solid var(--gray-200, var(--gray-200));
    border-radius: 8px;
    padding: 0.75rem 1rem;
}

/* === Datepicker Hermes — overrides de Flatpickr ==================================
   Hace que el calendario casa con el estándar (border 1.5px negro, radius 8px,
   primary azul). El input lo decora flatpickr cuando se invoca; el ícono del
   calendario lo provee el .form-date legacy a través de su filtro CSS, que
   sigue funcionando. Para inputs `<input class="form-date">` text-only que no
   usan flatpickr, los estilos heredados del estándar siguen aplicando. */
.flatpickr-calendar {
    border: 1.5px solid var(--btn-action-border, var(--primary-dark));
    border-radius: 8px;
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.15);
    font-family: inherit;
    font-size: 0.85rem;
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after { display: none; }

.flatpickr-months {
    background: var(--primary, var(--primary));
    color: white;
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
    padding: 0.4rem 0.5rem;
}
.flatpickr-months .flatpickr-month,
.flatpickr-current-month,
.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year,
.flatpickr-months .numInputWrapper input.numInput {
    /* Solo la CABECERA (fondo azul): la fila de hora también es un
       .numInputWrapper y va sobre blanco — con esto en toda .flatpickr-calendar
       la hora y los minutos salían blanco sobre blanco. */
    color: white !important;
    fill: white !important;
}
.flatpickr-current-month,
.flatpickr-current-month input.cur-year,
.flatpickr-current-month .flatpickr-monthDropdown-months {
    font-size: 0.85rem;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
    background: transparent;
    font-weight: 600;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
    background: rgba(255, 255, 255, 0.10) !important;
}
/* Las opciones del dropdown se renderizan en un panel del SO; ahí sí van
   en oscuro sobre fondo blanco para que sean legibles. */
.flatpickr-current-month .flatpickr-monthDropdown-months .flatpickr-monthDropdown-month {
    color: var(--gray-900, var(--gray-900)) !important;
    background: var(--surface);
}
.flatpickr-current-month input.cur-year {
    font-weight: 600;
}
.flatpickr-current-month input.cur-year[disabled],
.flatpickr-current-month input.cur-year[disabled]:hover {
    color: rgba(255, 255, 255, 0.5) !important;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
    color: white;
    fill: white;
    padding: 0.3rem;
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
    color: white;
    fill: white;
    background: rgba(255, 255, 255, 0.12);
    border-radius: 4px;
}
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
    fill: white;
}
.flatpickr-weekdays { background: var(--primary, var(--primary)); }
.flatpickr-weekday {
    background: var(--primary, var(--primary));
    color: rgba(255, 255, 255, 0.8) !important;
    font-weight: 600;
    font-size: 0.75rem;
}
.flatpickr-day {
    border-radius: 6px;
    color: var(--gray-800, #1f2937);
    font-size: 0.85rem;
}
.flatpickr-day:hover {
    background: var(--btn-action-bg-hover, rgba(26, 32, 44, 0.06));
    border-color: transparent;
}
.flatpickr-day.today {
    border-color: var(--primary, var(--primary));
    color: var(--primary, var(--primary));
    font-weight: 700;
}
.flatpickr-day.today:hover {
    background: var(--primary, var(--primary));
    color: white;
}
.flatpickr-day.selected,
.flatpickr-day.selected.startRange,
.flatpickr-day.selected.endRange,
.flatpickr-day.startRange,
.flatpickr-day.endRange {
    background: var(--primary, var(--primary));
    border-color: var(--primary, var(--primary));
    color: white;
    font-weight: 600;
}
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover {
    background: var(--primary-light, var(--primary-light));
    border-color: var(--primary-light, var(--primary-light));
}
.flatpickr-day.inRange {
    background: rgba(26, 54, 93, 0.10);
    border-color: transparent;
    color: var(--gray-900, var(--gray-900));
    box-shadow: -5px 0 0 rgba(26, 54, 93, 0.10), 5px 0 0 rgba(26, 54, 93, 0.10);
}
.flatpickr-day.flatpickr-disabled {
    color: var(--gray-300, #cbd5e0);
}
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
    color: var(--gray-300, #cbd5e0);
}
.flatpickr-months .numInputWrapper:hover { background: rgba(255, 255, 255, 0.08); }
.flatpickr-months .numInputWrapper span.arrowUp:after { border-bottom-color: white; }
.flatpickr-months .numInputWrapper span.arrowDown:after { border-top-color: white; }
/* Fila de hora (sobre blanco): tinta legible y flechas grises. */
.flatpickr-time input.numInput { color: var(--gray-800, #2d3748); }
.flatpickr-time .numInputWrapper span.arrowUp:after { border-bottom-color: var(--gray-500, #718096); }
.flatpickr-time .numInputWrapper span.arrowDown:after { border-top-color: var(--gray-500, #718096); }

/* Pareja de fechas separada por un guión: contenedor inline con gap mínimo. */
.date-range-pair {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.date-range-pair__sep {
    color: var(--gray-500, var(--gray-500));
    font-weight: 600;
    user-select: none;
}
.date-range-pair .form-input,
.date-range-pair .form-date {
    width: 140px;
}

/* Icono de calendario en .form-date text-only (cuando lo decora flatpickr,
   el input nativo no tiene el ::calendar-picker-indicator). Sustituye al
   filtro CSS legacy para inputs date nativos sin perder la pista visual. */
input.form-date[type="text"] {
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%231a202c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='4' width='18' height='18' rx='2'/><line x1='16' y1='2' x2='16' y2='6'/><line x1='8' y1='2' x2='8' y2='6'/><line x1='3' y1='10' x2='21' y2='10'/></svg>");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 14px 14px;
    padding-right: 30px;
    cursor: pointer;
}

/* === Filtro multi-select con look del estándar (.filter-multi) =================
   Botón outline 36×36 (heredado de .btn-action) con icono SVG, badge azul
   superpuesto con el count, y dropdown debajo. Para los filtros de Proveedor,
   Cliente, Moneda, Suscripción, etc. — donde el estándar `.swap-filter` no
   aplica porque el contenido (lista con checkboxes, search interno, "Todos/
   Ninguno") es propio del proyecto y se reutiliza con `toggleSharedDropdown`. */
.filter-multi {
    position: relative;
    display: inline-block;
}
.filter-multi__btn {
    position: relative;
}
.filter-multi__count {
    position: absolute;
    top: -7px;
    right: -7px;
    min-width: 20px;
    height: 18px;
    padding: 0 5px;
    background: var(--primary, var(--primary));
    color: white;
    border-radius: 9px;
    font-size: 0.66rem;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    box-shadow: 0 0 0 2px var(--surface);
    pointer-events: none;
    font-variant-numeric: tabular-nums;
}
.filter-multi__count[hidden] { display: none; }
/* El input de búsqueda interna del dropdown usa la apariencia del estándar
   (.form-input) pero sin el width:100% por defecto que lo expandiría. */
.filter-multi__search {
    width: 100%;
    height: 32px;
    font-size: 0.82rem;
}
/* El .filter-dropdown legacy se posiciona con left:0; right:0; respecto al
   ancestro posicionado. Como .filter-multi es position:relative con ancho
   de botón (36px), el dropdown saldría a 36px de ancho. Forzamos un ancho
   mínimo y lo alineamos al borde derecho (right:0; left:auto) para que se
   expanda a la izquierda quedando bajo el botón. */
.filter-multi .filter-dropdown {
    left: auto;
    right: 0;
    min-width: 240px;
}
.filter-multi .filter-dropdown.filter-dropdown--wide {
    min-width: 300px;
}

/* Dirección de apertura de los desplegables de la barra de filtros según el
   LADO donde vive el botón. Especificidad (0,3,0) para ganar al default legacy
   de arriba (.filter-multi .filter-dropdown, que alinea a la derecha):
     · botón en la zona izquierda (.filter-bar-left) → abre hacia la DERECHA
       del botón (left:0);
     · botón en la zona derecha (.filter-bar-right) → abre hacia la IZQUIERDA
       (desde 2026-08-27 esa zona se pinta en la NAV, no en la barra: las dos
       reglas prefijadas con `.shared-filter-bar` que hay abajo ya no casan y
       de la apertura se encarga `nav.css`)
       (right:0).
   Cubre los dos mecanismos de desplegable: .filter-dropdown y .swap-filter__panel. */
.shared-filter-bar .filter-bar-left .filter-dropdown,
.shared-filter-bar .filter-bar-left .swap-filter__panel {
    left: 0;
    right: auto;
}
.shared-filter-bar .filter-bar-right .filter-dropdown,
.shared-filter-bar .filter-bar-right .swap-filter__panel {
    left: auto;
    right: 0;
}

/* === Filtro desplegable estándar Hermes (.swap-filter) ============================
   Botón icon-only outline 36x36 (hereda de .btn-action) + badge "filtrados/total"
   superpuesto + panel desplegable con opciones. Reemplaza al `<select>` cuando
   queremos un filtro compacto en la barra: el icono representa la dimensión
   (rol, estado, prioridad…) y el texto descriptivo aparece dentro del panel.

   Estructura HTML:
     <div class="swap-filter" id="X">
       <button class="swap-filter__btn btn-action" aria-haspopup="listbox" ...>
         <svg class="icon" .../>
         <span class="swap-filter__count" hidden></span>
       </button>
       <div class="swap-filter__panel" role="listbox" hidden>
         <button class="swap-filter__option is-active" data-value="" onclick="...">
           <svg class="swap-filter__option-check" .../>
           <span>Todos</span>
         </button>
         ...
       </div>
     </div>
*/
.swap-filter {
    position: relative;
    display: inline-block;
}
/* `inline-block` gana al [hidden] de la hoja del navegador: sin esto, una vista
   que retire un filtro por sub-vista lo sigue pintando. Vive aquí (y no en cada
   módulo, donde ya hacía falta parchearlo con !important) porque le pasa a
   todos los que usan el componente. */
.swap-filter[hidden] { display: none; }
.swap-filter__btn {
    position: relative;
}
.swap-filter__btn[aria-expanded="true"] {
    background-color: var(--btn-action-bg-pressed, rgba(26, 32, 44, 0.10));
    border-color: var(--btn-action-border-pressed, var(--primary-dark));
}
.swap-filter__count {
    position: absolute;
    top: -7px;
    right: -7px;
    min-width: 20px;
    height: 18px;
    padding: 0 5px;
    background: var(--primary, var(--primary));
    color: var(--on-accent, #fff);
    border-radius: 9px;
    font-size: 0.66rem;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    box-shadow: 0 0 0 2px var(--surface);
    pointer-events: none;
    font-variant-numeric: tabular-nums;
}
.swap-filter__count[hidden] { display: none; }

.swap-filter__panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 100;
    min-width: 220px;
    /* Con la barra envuelta en varias filas, un control de la mitad derecha
       abría su panel hacia fuera del viewport y arrastraba scroll horizontal a
       toda la página. El tope lo mantiene dentro de la pantalla. */
    max-width: calc(100vw - 1.5rem);
    background: var(--surface);
    border: 1.5px solid var(--btn-action-border, var(--primary-dark));
    border-radius: 8px;
    box-shadow: var(--shadow-lg, 0 10px 28px rgba(15, 23, 42, 0.15));
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    /* Con muchas opciones (p. ej. 24 meses en el selector de periodo) el panel
       debe hacer scroll interno y no desbordar el viewport, sobre todo en móvil. */
    max-height: min(320px, 60vh);
    overflow-y: auto;
}
.swap-filter__panel[hidden] { display: none; }
.swap-filter__panel--right {
    left: auto;
    right: 0;
}

.swap-filter__option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border: none;
    background: transparent;
    border-radius: 6px;
    cursor: pointer;
    font-size: 0.85rem;
    color: var(--gray-700, var(--gray-700));
    text-align: left;
    transition: background 0.12s, color 0.12s;
    font: inherit;
    line-height: 1.2;
}
/* Área táctil: el botón que abre el panel ya sube a 44px en punteros gruesos
   (`--btn-action-size`), pero las OPCIONES de dentro se quedaban en ~32px, que
   es justo donde se falla al elegir con el dedo. Crece el área pulsable, no la
   letra. */
@media (pointer: coarse) {
    .swap-filter__option {
        min-height: 44px;
        padding-top: 0.65rem;
        padding-bottom: 0.65rem;
    }
}
.swap-filter__option:hover {
    background: var(--btn-action-bg-hover, rgba(26, 32, 44, 0.06));
    color: var(--gray-900, var(--gray-900));
}
.swap-filter__option.is-active {
    background: var(--btn-action-bg-pressed, rgba(26, 32, 44, 0.10));
    color: var(--gray-900, var(--gray-900));
    font-weight: 600;
}
.swap-filter__option-check {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--primary, var(--primary));
    /* A la DERECHA, como la marca de activo del menú Acciones: las dos listas
       (filtros y acciones) se leen igual (decisión del dueño, 2026-09-06). */
    order: 2;
    margin-left: auto;
}
.swap-filter__option:not(.is-active) .swap-filter__option-check {
    visibility: hidden;
}

/* --- Variante ETIQUETADA: el botón muestra el valor activo -------------------
   Para cuando el swap-filter elige un REGISTRO o contexto maestro (empresa,
   sitio, dominio) en vez de una dimensión de filtrado: ahí el usuario necesita
   ver CUÁL está activo sin abrir el panel. Es el patrón del selector de empresa
   de la topbar (.company-switcher), promovido aquí para no reescribirlo por
   módulo. Desde el 2026-09-06 TODOS los swaps del panel de filtros son
   etiquetados (`core.js` les inyecta la etiqueta): esta variante queda como el
   markup que un swap escribe a mano cuando quiere gestionar su propio texto.

   Doble clase (0,2,0) a propósito: btn-actions.css se carga DESPUÉS de esta
   hoja y su `.btn-action { width: var(--btn-action-size) }` ganaría el empate
   dejando el botón cuadrado con la etiqueta recortada. Misma técnica que
   `.company-switcher .company-switcher__btn` en nav.css. */
.swap-filter .swap-filter__btn--labeled {
    width: auto;
    max-width: 260px;
    padding: 0 0.55rem;
    gap: 0.4rem;
}
.swap-filter__label {
    max-width: 150px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.82rem;
    font-weight: 500;
}
.swap-filter__chevron {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    opacity: 0.7;
}

/* --- Filtros ETIQUETADOS en el panel de filtros (decisión del dueño, 2026-09-06)
   «Los botones sin texto, a algo más mnemotécnico, como el botón de
   Acciones». `core.js` (bloque «Panel de FILTROS») inyecta en cada botón de
   la zona de filtros su etiqueta —el nombre de la dimensión o el valor
   elegido— y un chevron (abre panel) o un check (interruptor). Lo que sigue
   es su vestido: mismo alto y borde que el botón «Acciones», texto a la
   izquierda, y el estado «puesto» pintado como un interruptor activo del
   menú Acciones. Especificidad (0,3,0)+ a propósito: `btn-actions.css` se
   carga después y fija `width: var(--btn-action-size)` en `.btn-action`. */
.shared-filter-bar .filter-bar-left .filtro-etq,
.shared-filter-bar .filter-bar-left .swap-filter .swap-filter__btn--labeled {
    width: auto;
    max-width: 280px;
    padding: 0 0.6rem 0 0.55rem;
    gap: 0.45rem;
    justify-content: flex-start;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 500;
    line-height: 1.2;
    text-align: left;
}
.shared-filter-bar .filter-bar-left .filtro-etq .icon { flex-shrink: 0; }
.filtro-etq__label,
.shared-filter-bar .filter-bar-left .swap-filter__label {
    min-width: 0;
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.filtro-etq__check {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    margin-left: auto;
    color: var(--primary-ink, var(--primary));
    display: none;
}
.filtro-etq[aria-pressed="true"] .filtro-etq__check { display: block; }
/* El globo de recuento («3/40», «2») deja la esquina y se pone en línea. */
.shared-filter-bar .filter-bar-left .swap-filter__btn--labeled .swap-filter__count,
.shared-filter-bar .filter-bar-left .filtro-etq .filter-multi__count,
.shared-filter-bar .filter-bar-left .filtro-etq .count {
    position: static;
    margin-left: 0.1rem;
    box-shadow: none;
    flex-shrink: 0;
    color: var(--on-accent, #fff);
}
/* «Puesto»: hay un valor elegido. Mismo tinte que el ítem interruptor activo
   del menú Acciones, para que se lea como «esto está actuando». */
/* El texto va en `--primary-ink` y no en `--primary`: sobre la pastilla al 10 %
   el azul de marca da 2,7:1 en modo oscuro con letra de 13px. Borde y fondo sí
   pueden seguir en `--primary`. Regla aparte para la del `:has()`: en una lista
   con comas, un motor sin `:has()` invalidaría también las otras dos. */
.shared-filter-bar .filter-bar-left .swap-filter__btn.is-puesto,
.shared-filter-bar .filter-bar-left .filtro-etq[aria-pressed="true"] {
    border-color: var(--primary);
    color: var(--primary-ink, var(--primary));
    background: color-mix(in srgb, var(--primary) 10%, var(--surface));
}
.shared-filter-bar .filter-bar-left .filtro-etq:has(> .filter-multi__count:not([hidden])) {
    border-color: var(--primary);
    color: var(--primary-ink, var(--primary));
    background: color-mix(in srgb, var(--primary) 10%, var(--surface));
}
.shared-filter-bar .filter-bar-left .swap-filter__btn.is-puesto .icon,
.shared-filter-bar .filter-bar-left .swap-filter__btn.is-puesto .swap-filter__chevron,
.shared-filter-bar .filter-bar-left .filtro-etq[aria-pressed="true"] .icon,
.shared-filter-bar .filter-bar-left .filtro-etq[aria-pressed="true"] .filtro-etq__check { color: var(--primary-ink, var(--primary)); }
/* Las opciones del panel llevan hueco de icono opcional y la misma fila que
   el menú Acciones (40px; 44 en táctil ya está arriba). */
.shared-filter-bar .filter-bar-left .swap-filter__option { min-height: 40px; }

/* --- Opción ENRIQUECIDA (dos líneas) ----------------------------------------
   Lo que pide el estándar de selectores de entidad: cada ítem identifica el
   REGISTRO de un vistazo (nombre + badges arriba, dato identificativo abajo:
   host, banco, email…), nunca un nombre escueto. Es el equivalente en barra de
   .cliente-picker-option-name/-meta, que es de modal. */
.swap-filter__option--rich { align-items: flex-start; }
.swap-filter__option--rich .swap-filter__option-check { margin-top: 3px; }
.swap-filter__option-body {
    display: flex;
    flex-direction: column;
    gap: 0.12rem;
    min-width: 0;
}
.swap-filter__option-name {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
    flex-wrap: wrap;
}
.swap-filter__option-meta {
    font-size: 0.74rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Sección secundaria collapsible dentro de un modal CRUD (ej. SMTP en
   submarca). El SVG del summary necesita tamaño explícito porque la clase
   .icon solo aplica width/height cuando vive dentro de un .btn-* del estándar. */
.form-modal-details {
    border: 1px solid var(--gray-200, var(--gray-200));
    border-radius: 8px;
    padding: 0.75rem 1rem;
    background: var(--surface);
}
.form-modal-details__summary {
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--gray-800, #1f2937);
    list-style: none;
}
.form-modal-details__summary::-webkit-details-marker {
    display: none;
}
.form-modal-details__summary > svg {
    width: 16px;
    height: 16px;
    color: var(--primary, var(--primary));
    flex-shrink: 0;
}
.form-modal-details__badge {
    margin-left: 0.5rem;
    font-weight: 600;
    font-size: 0.78rem;
    color: #16a34a;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}
.form-modal-details__hint {
    font-weight: 400;
    font-size: 0.78rem;
    color: var(--gray-500, var(--gray-500));
    margin-left: auto;
}

/* Inputs numéricos SIN spinners (flechas arriba/abajo) — ESTÁNDAR GLOBAL del
   proyecto. Las flechas nativas ensucian el campo, descuadran la anchura y no
   aportan nada con teclado. Se ocultan en TODOS los input[type=number]. Regla
   transversal: @front debe conservarla y aplicarla a cualquier campo nuevo. */
input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* Extensión local del estándar: .form-select con la misma estética que
   .form-input (border 1.5px, height 36px, radius 8px). El estándar
   btn-actions.css no documenta selects; esto los pone a juego sin tocar
   el artefacto. La flecha es un SVG dataURI con el color del estándar. */
.form-select {
    width: 100%;
    min-width: 0;   /* como .form-input: un <select> con opción larga no puede desbordar su celda */
    height: var(--field-height, 36px);
    padding: 0 calc(var(--field-padding-x, 12px) + 22px) 0 var(--field-padding-x, 12px);
    /* Fondo blanco (no transparente) para que se distinga del fondo de la
       sección y para que el contraste de la flecha sea consistente, igual
       que un <select> nativo "normal". */
    background-color: var(--surface);
    /* Chevron descendente más grueso y oscuro — análogo a la flecha del
       <select> nativo, en lugar del trazo fino que apenas se notaba. */
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%230f172a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right calc(var(--field-padding-x, 12px) - 2px) center;
    background-size: 14px 14px;
    border: var(--field-border, 1.5px) solid var(--btn-action-border, var(--primary-dark));
    border-radius: var(--field-radius, 8px);
    color: var(--btn-action-icon, var(--primary-dark));
    font: inherit;
    font-size: var(--field-font-size, 14px);
    line-height: 1.2;
    transition: var(--btn-action-transition, all 0.18s var(--ease));
    outline: none;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    cursor: pointer;
}
.form-select:hover {
    border-color: var(--btn-action-border-hover, #2d3748);
}
.form-select:focus {
    border-color: var(--btn-action-border-active, #000000);
    box-shadow: 0 0 0 3px var(--btn-action-focus-ring, rgba(26, 32, 44, 0.30));
}
.form-select:disabled {
    border-color: var(--btn-action-border-disabled, #a0aec0);
    color: var(--btn-action-icon-disabled, #a0aec0);
    cursor: not-allowed;
}
/* En la barra de filtros el .form-select se ajusta al contenido en lugar
   de ocupar el 100% del padre (que sería demasiado ancho dentro del flex
   de .filter-bar-left). Min-width modesto para que el chevron tenga aire
   y los textos cortos no queden apelmazados. */
.shared-filter-bar .form-select {
    width: auto;
    min-width: 140px;
    max-width: 280px;
}
.form-field.has-error .form-select {
    border-color: var(--field-error, #c53030);
}
.form-field.has-error .form-select:focus {
    box-shadow: 0 0 0 3px rgba(197, 48, 48, 0.25);
}

/* Switch in-line dentro del grid — legacy (Bootstrap form-switch).
   Usa ">" para que la nueva estructura estándar (.form-field > .form-switch)
   no herede estos estilos: el .form-switch del estándar debe quedar limpio
   (sin fondo/padding ni borde extra). */
.form-modal-grid > .form-switch {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.5rem 0.75rem;
    background: var(--gray-50, var(--gray-50));
    border: 1px solid var(--gray-200, var(--gray-200));
    border-radius: 8px;
    margin: 0;
}
.form-modal-grid > .form-switch .form-check-input {
    margin: 0;
    flex-shrink: 0;
}
.form-modal-grid > .form-switch .form-check-label {
    font-size: 0.82rem;
    color: var(--gray-700, var(--gray-700));
    margin: 0;
}
.form-modal-grid > .form-switch .form-check-label small {
    color: var(--gray-500, var(--gray-500));
}

@media (max-width: 600px) {
    .form-modal-grid { grid-template-columns: 1fr; }
    .form-modal-body { padding: 1rem; }
}

/* ============================================================
   .id-badge — ID monoespaciado en tablas
   ============================================================
   Píldora pequeña con fondo verde-azulado claro y texto en --primary.
   Reutilizada en las columnas "Nº" de Gastos, Emitidas y Presupuestos
   para que los identificadores tengan un look consistente.
   Aplicar al elemento que contiene el id (típicamente un <code>):
       <code class="id-badge">F-2026-001</code>
   Variante --noid: amarillo claro para los IDs autogenerados
   "NO-ID-XXXX" en gastos, como aviso de que falta el nº real. */
/* Número recortado en tablas (5 + … + 5): la elipsis con aire a los lados
   y algo más tenue, para que se lea «empieza así… termina así». */
.id-badge__elipsis { margin: 0 0.45em; opacity: 0.65; letter-spacing: 0.18em; }
.id-badge {
    display: inline-block;
    font-size: 0.78rem;
    font-weight: 600;
    background: rgba(56, 178, 172, 0.10);
    color: var(--primary);
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
    white-space: nowrap;
}
.id-badge--noid {
    background: rgba(234, 179, 8, 0.10);
    color: #ca8a04;
    font-weight: 500;
    letter-spacing: 0.02em;
}

/* ------------------------------------------------------------------
 * UPLOAD — Dropzone + queue (compartido entre facturas e importación
 * de leads del embudo). Vivieron originalmente en facturas.css con el
 * prefijo .factura-* (legacy); se mueven aquí para que cualquier vista
 * pueda reutilizarlos sin duplicar estilos.
 * ------------------------------------------------------------------ */
.factura-dropzone {
    border: 2px dashed var(--gray-300);
    border-radius: 10px;
    padding: 2rem 1.5rem;
    text-align: center;
    background: var(--gray-50);
    cursor: pointer;
    transition: var(--transition);
    min-height: 180px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.factura-dropzone:hover {
    border-color: var(--primary);
    background: rgba(56, 178, 172, 0.06);
}
.factura-dropzone.is-dragging {
    border-color: var(--primary);
    background: rgba(56, 178, 172, 0.12);
    transform: scale(1.01);
}
.factura-dropzone.is-processing {
    cursor: default;
    border-style: solid;
    border-color: var(--primary);
}
.factura-dropzone .factura-dropzone-idle i {
    font-size: 2.5rem;
    color: var(--gray-400);
    margin-bottom: 0.75rem;
    display: block;
}
/* Espejo SVG de la regla legacy de arriba (zonas migradas sin Font Awesome). */
.factura-dropzone .factura-dropzone-idle > .icon {
    width: 2.5rem;
    height: 2.5rem;
    color: var(--gray-400);
    margin: 0 auto 0.75rem;
    display: block;
}
.factura-dropzone-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--gray-700);
    margin: 0 0 0.25rem;
}
.factura-dropzone-hint {
    font-size: 0.85rem;
    color: var(--gray-500);
    margin: 0 0 0.5rem;
}
.factura-dropzone-types {
    font-size: 0.75rem;
    color: var(--gray-400);
    margin: 0;
}
/* Atajo de la cámara (solo táctil): el botón y, al lado, lo que hace. */
.factura-dropzone-camara {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    margin-top: 0.9rem;
    font-size: 0.85rem;
    color: var(--gray-500);
}
.factura-dropzone-camara[hidden] { display: none; }
.factura-dropzone-camara { cursor: pointer; }
/* En la cabecera de la cola, la cámara va junto al «+» (a la derecha), no en medio. */
.factura-queue-header #facturaQueueCamara { margin-left: auto; margin-right: 0.4rem; }
.factura-dropzone-file {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    text-align: left;
    width: 100%;
    justify-content: center;
}
.factura-dropzone-file i {
    font-size: 2rem;
    color: var(--primary);
}
.factura-dropzone-file .factura-file-name {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--gray-800);
    margin: 0;
    word-break: break-all;
}
.factura-dropzone-file .factura-file-size {
    font-size: 0.75rem;
    color: var(--gray-500);
    margin: 0;
}

/* ============================================================
   File field — campo de subida de archivos (ESTÁNDAR GLOBAL)
   ------------------------------------------------------------
   Sustituye al feo <input type="file"> nativo. El <input> va
   `hidden` y el <label class="file-field"> es la zona visible:
   clic para abrir el explorador + drag & drop. Se auto-cablea con
   window.initFileField() (core.js): muestra el nombre del archivo
   elegido, su tamaño, el botón de quitar y el estado de arrastre.
   Markup de referencia en .claude/ui-standard.md (sección File field).
   ============================================================ */
.file-field {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.7rem 0.9rem;
    border: 1.5px dashed var(--border-color);
    border-radius: var(--radius-md, 8px);
    background: var(--surface);
    cursor: pointer;
    transition: var(--transition);
}
.file-field:hover {
    border-color: var(--primary);
    background: var(--surface-hover);
}
.file-field:focus-within {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
    outline: none;
}
.file-field.is-dragging {
    border-color: var(--primary);
    border-style: solid;
    background: var(--surface-hover);
}
/* Subida en curso: el campo queda inerte mientras se sube el archivo. */
.file-field.is-uploading {
    opacity: 0.6;
    pointer-events: none;
    cursor: progress;
}
.file-field__icon {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: linear-gradient(135deg, rgba(99, 102, 241, 0.12), rgba(99, 102, 241, 0.05));
    color: var(--primary);
}
.file-field__icon .icon { width: 20px; height: 20px; }
.file-field__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.file-field__title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-strong, var(--gray-900));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.file-field__link { color: var(--primary); text-decoration: underline; }
.file-field__hint {
    font-size: 0.74rem;
    color: var(--text-muted, var(--gray-500));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.file-field__clear {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted, var(--gray-500));
    cursor: pointer;
    transition: var(--transition);
}
.file-field__clear:hover { background: var(--surface-hover); color: var(--danger); }
.file-field__clear .icon { width: 16px; height: 16px; }
/* Estado "archivo elegido": el icono pasa a verde de éxito. */
.file-field.has-file { border-style: solid; border-color: var(--border-color); }
.file-field.has-file .file-field__icon {
    background: linear-gradient(135deg, rgba(22, 163, 74, 0.15), rgba(22, 163, 74, 0.05));
    color: var(--success, #16a34a);
}

/* Upload queue */
.factura-queue {
    margin-top: 1rem;
    border: 1px solid var(--gray-200);
    border-radius: 8px;
    background: var(--surface);
    overflow: hidden;
}
.factura-queue-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.5rem 0.75rem;
    background: var(--gray-50);
    border-bottom: 1px solid var(--gray-200);
    font-size: 0.8rem;
    color: var(--gray-600);
}
.factura-queue-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 320px;
    overflow-y: auto;
}
.factura-queue-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--gray-100);
}
.factura-queue-item:last-child { border-bottom: none; }
.factura-queue-status {
    font-size: 1.1rem;
    width: 1.5rem;
    text-align: center;
    color: var(--gray-400);
    flex-shrink: 0;
}
.factura-queue-info {
    flex: 1;
    min-width: 0;
}
.factura-queue-name {
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--gray-800);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.factura-queue-meta {
    font-size: 0.75rem;
    color: var(--gray-500);
}
.factura-queue-collision {
    font-size: 0.78rem;
    color: #ca8a04;
    margin-top: 0.15rem;
}
.factura-queue-collision code {
    background: rgba(234, 179, 8, 0.12);
    padding: 0.05rem 0.35rem;
    border-radius: 3px;
}
.factura-queue-actions {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.4rem;
}
.factura-queue-remove {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--gray-400);
    padding: 0.2rem 0.4rem;
}
.factura-queue-remove:hover { color: #dc2626; }

.factura-queue-item--processing { background: rgba(56, 178, 172, 0.06); }
.factura-queue-item--processing .factura-queue-status { color: var(--primary); }
.factura-queue-item--collision { background: rgba(234, 179, 8, 0.06); }
.factura-queue-item--collision .factura-queue-status { color: #ca8a04; }
.factura-queue-item--done .factura-queue-status { color: #16a34a; }
.factura-queue-item--overwritten .factura-queue-status { color: #6366f1; }
.factura-queue-item--skipped { opacity: 0.65; }
.factura-queue-item--skipped .factura-queue-status { color: var(--gray-400); }
.factura-queue-item--error { background: rgba(220, 38, 38, 0.05); }
.factura-queue-item--error .factura-queue-status { color: #dc2626; }

/* Item del queue compacto (CSV embudo): nombre + tamaño en línea. */
.factura-queue-item-name {
    flex: 1;
    min-width: 0;
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--gray-800);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.factura-queue-item-size {
    font-size: 0.75rem;
    color: var(--gray-500);
    flex-shrink: 0;
}
.factura-queue-item > .icon {
    width: 18px;
    height: 18px;
    color: var(--primary);
    flex-shrink: 0;
}

/* Utility: spinner SVG (sustituye a fa-spin de Font Awesome). */
.icon-spin { animation: icon-spin 0.9s linear infinite; transform-origin: center; }
@keyframes icon-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* `hidden` debe ganar SIEMPRE: sin esta regla, el display:flex de .form-field
   pisa el display:none del user-agent y los campos "ocultos" siguen visibles
   (bug real: el test SMTP del modal de submarca). */
.form-field[hidden] {
    display: none;
}


/* =====================================================================
   Chips de estado (.cfg-chip) — promovidos desde el módulo de correo al
   replicarse en un segundo módulo (regla del estándar): píldora con icono
   + texto, tinte al 12% y borde al 35% del color de la variante. Los
   tokens semánticos se aclaran solos en tema oscuro.
   ===================================================================== */
.cfg-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.68rem;
    font-weight: 600;
    padding: 0.08rem 0.5rem;
    border-radius: var(--radius-full);
}
.cfg-chip .icon {
    width: 0.75rem;
    height: 0.75rem;
    flex: 0 0 auto;
}
.cfg-chip--success {
    background: color-mix(in srgb, var(--success) 12%, transparent);
    color: var(--success);
    border: 1px solid color-mix(in srgb, var(--success) 35%, transparent);
}
.cfg-chip--danger {
    background: color-mix(in srgb, var(--danger) 10%, transparent);
    /* Un punto más oscuro que el token: el chip es texto de 11 px en negrita
       y con --danger a secas se quedaba por debajo de 4,5:1. */
    color: color-mix(in srgb, var(--danger) 85%, var(--text-strong));
    border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
}
.cfg-chip--primary {
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    color: var(--primary);
    border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
}
.cfg-chip--muted {
    background: color-mix(in srgb, var(--gray-500) 12%, transparent);
    color: var(--text-muted);
    border: 1px solid var(--border-color);
}

/* =====================================================================
   Modal de test de conexión (.correo-test-*) — promovido al usarse en
   Configuración → Correo y en Mis cuentas del módulo Correos: fila por
   protocolo con spinner → ✓/✗ y el mensaje exacto del servidor.
   ===================================================================== */
.correo-test-cuenta {
    margin: 0 0 0.85rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}
.correo-test-rows {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.correo-test-row {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.7rem;
    align-items: flex-start;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--surface);
}
.correo-test-row__status {
    display: inline-flex;
    margin-top: 0.1rem;
    color: var(--text-muted);
}
.correo-test-row.is-ok {
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
    background: color-mix(in srgb, var(--success) 8%, transparent);
}
.correo-test-row.is-ok .correo-test-row__status { color: var(--success); }
.correo-test-row.is-fail {
    border-color: color-mix(in srgb, var(--danger) 40%, transparent);
    background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.correo-test-row.is-fail .correo-test-row__status { color: var(--danger); }
.correo-test-row.is-testing .correo-test-row__status { color: var(--primary); }
.correo-test-row__body {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.correo-test-row__title {
    font-weight: 600;
    font-size: 0.88rem;
    color: var(--text-strong);
}
.correo-test-row__msg {
    font-size: 0.82rem;
    color: var(--text-color);
    overflow-wrap: anywhere;
}
@media (max-width: 600px) {
    /* Diagnósticos largos (timeouts, DNS, certificados) legibles sin zoom. */
    .correo-test-row__msg { font-size: 0.9rem; }
}
.correo-test-spinner {
    animation: correo-test-spin 0.9s linear infinite;
}
@keyframes correo-test-spin {
    to { transform: rotate(360deg); }
}

/* ============== Visores de PDF: overlay «Cargando preview…» ============== */
/* CSS canónico del patrón (helpers en core.js: pdfPreviewLoading /
 * pdfPreviewEmbed). La activación es GENÉRICA: cualquier contenedor con
 * .is-loading enciende su overlay hijo directo — los selectores específicos
 * antiguos (.emitida-preview-box, .gencon-pdfbox) siguen funcionando.
 * facturas.css conserva una copia idéntica del overlay por orden de carga;
 * si se toca este bloque, tocar también aquel. */
.preview-loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Velo sobre --surface: sigue al tema (claro u oscuro) sin overrides. */
    background: color-mix(in srgb, var(--surface) 60%, transparent);
    backdrop-filter: blur(2px);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.18s var(--ease);
    z-index: 5;
}
.is-loading > .preview-loading-overlay { opacity: 1; }
.preview-loading-overlay .spinner {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    background: var(--surface);
    padding: 0.55rem 1rem;
    border-radius: 999px;
    box-shadow: 0 4px 12px rgba(15, 23, 42, 0.10), 0 1px 3px rgba(15, 23, 42, 0.08);
    font-size: 0.82rem;
    font-weight: 500;
    color: var(--gray-700, var(--gray-700));
}
.preview-loading-overlay .spinner i,
.preview-loading-overlay .spinner .icon-spin {
    color: var(--primary, #3b82f6);
}
/* Estado de error del visor (pdfPreviewEmbed cuando el documento no llega). */
.pdf-preview-error {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 2rem 1rem;
    text-align: center;
    font-size: 0.85rem;
    color: var(--gray-500, var(--gray-500));
}

/* === Tooltip ligero por atributo (data-tooltip) ==========================
   Para iconos/badges sin texto (ej. matriz de permisos de Usuarios). Por
   defecto aparece ARRIBA del elemento; con data-tooltip-placement="bottom",
   debajo (útil en cabeceras de tabla). Requiere que ningún ancestro con
   overflow recorte justo encima/debajo del elemento. */
.has-tooltip {
    position: relative;
}
.has-tooltip::after {
    content: attr(data-tooltip);
    position: absolute;
    left: 50%;
    bottom: calc(100% + 6px);
    transform: translateX(-50%) translateY(2px);
    background: var(--surface-inverse-strong);
    color: var(--on-inverse);
    font-size: 0.72rem;
    font-weight: 500;
    line-height: 1.2;
    padding: 0.3rem 0.55rem;
    border-radius: var(--radius-sm);
    white-space: nowrap;
    box-shadow: var(--shadow);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s var(--ease), transform 0.15s var(--ease);
    z-index: 30;
    /* Neutraliza herencias de contexto (th uppercase, etc.) */
    text-transform: none;
    letter-spacing: normal;
}
/* :focus (no solo :focus-visible) para que en táctil un tap sobre el
   elemento —enfocable vía tabindex— también muestre el tooltip. */
.has-tooltip:hover::after,
.has-tooltip:focus::after {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}
.has-tooltip[data-tooltip-placement="bottom"]::after {
    bottom: auto;
    top: calc(100% + 6px);
    transform: translateX(-50%) translateY(-2px);
}
.has-tooltip[data-tooltip-placement="bottom"]:hover::after,
.has-tooltip[data-tooltip-placement="bottom"]:focus::after {
    transform: translateX(-50%) translateY(0);
}

/* Apunte aclaratorio junto al label de un campo («· opcional», etc.):
   usado por el modal de campaña del embudo (form-field__label-hint). */
.form-field__label .form-field__label-hint {
    font-weight: 400;
    font-size: 0.85em;
    color: var(--text-muted);
}

/* Variante multilínea del tooltip: para textos largos (explicaciones),
   envuelve y acota el ancho en vez del nowrap por defecto. */
.has-tooltip--multiline::after {
    white-space: normal;
    width: max-content;
    max-width: 260px;
    text-align: left;
    line-height: 1.35;
}

/* ==================== Conmutador mosaico/lista (.view-mode-toggle) =======
   Barra de filtros de las vistas de documentos (presupuestos, dossiers,
   generador de contratos): alterna entre el mosaico .doc-mosaic y la tabla.
   Píldora segmentada con thumb deslizante (motor initSegThumbs de core.js;
   dada de alta en SEG_SELECTORS). Fondo de botón SIEMPRE transparente: la
   pastilla activa la pinta el .seg-thumb. */
.view-mode-toggle {
    position: relative;
    display: inline-flex;
    gap: 0.15rem;   /* separa los objetivos táctiles */
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-full);
    padding: 0.2rem;
}
/* `inline-flex` gana al [hidden] de la hoja del navegador: sin esto, una vista
   que oculte el conmutador por sub-vista lo sigue pintando. Vive aquí, y no en
   cada módulo, porque le pasa a todos. */
.view-mode-toggle[hidden] { display: none; }
.view-mode-toggle .seg-thumb { background: var(--surface-raised); }
.view-mode-toggle__btn {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    /* Con etiqueta (decisión del dueño, 2026-09-06): ancho por contenido y
       nunca menos de 44px, el objetivo táctil. Mismo peso de letra activa e
       inactiva: el thumb mide el botón y un cambio de peso lo descuadra. */
    min-width: 2.75rem;
    padding: 0 0.7rem;
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    height: 2rem;
    background: transparent;
    border: none;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.12s var(--ease);
}
.view-mode-toggle__btn:hover:not(.is-active) { color: var(--text-color); }
/* Con etiqueta ya no se tiñe solo un icono, se tiñe TEXTO: sobre la pastilla
   clara del thumb, `--primary` en oscuro no llega al contraste mínimo. */
.view-mode-toggle__btn.is-active { color: var(--primary-ink, var(--primary)); }
.view-mode-toggle__btn .icon { width: 1.1rem; height: 1.1rem; flex-shrink: 0; }
.view-mode-toggle__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Objetivo táctil: este botón no hereda `--btn-action-size`, así que lo dice. */
@media (pointer: coarse) {
    .view-mode-toggle__btn { min-height: 44px; }
}

/* ============================================================
   TRANSFER — dos listas con flechas para mover elementos
   ------------------------------------------------------------
   Promovido desde el bloque .rrhh-transfer de People, que ahora
   lo consume igual que Configuración → Correo. Marcado:

     <div class="transfer">
       <div class="transfer__col">
         <div class="transfer__title">Disponibles <span class="transfer__count">12</span></div>
         <input class="transfer__search" type="search" placeholder="Buscar…">   (opcional)
         <ul class="transfer__list" role="listbox"></ul>
       </div>
       <div class="transfer__actions"> … botones .btn-action … </div>
       <div class="transfer__col"> … </div>
     </div>

   El comportamiento (selección simple o múltiple, filtrado) lo
   pone cada módulo: aquí solo vive la forma.
   ============================================================ */
.transfer {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 1rem;
    align-items: stretch;
}
.transfer__col { display: flex; flex-direction: column; min-width: 0; }
.transfer__title {
    display: flex; align-items: center; gap: .5rem;
    font-weight: 600; color: var(--text-strong); margin-bottom: .5rem;
}
/* Mismo aspecto que el .rrhh-pill que usaba People antes de compartir el
   componente: la migración no debe cambiar un píxel de lo que ya existía. */
.transfer__count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.5rem; padding: .05rem .45rem;
    border-radius: var(--radius-full);
    background: var(--surface-sunken);
    color: var(--text-muted); font-size: .8rem; font-weight: 600;
}
.transfer__search { margin-bottom: .4rem; }
/* La X nativa de los `type=search` de WebKit ignora el tema; se oculta, igual
   que en .search-bar__input. */
.transfer__search::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.transfer__list {
    list-style: none; margin: 0; padding: .35rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--surface-sunken);
    min-height: 180px; max-height: 320px; overflow-y: auto;
    /* Al llegar al final de la lista, el gesto no debe seguir scrollando el
       modal que hay detrás. */
    overscroll-behavior: contain;
}
.transfer__item {
    display: flex; flex-direction: column; gap: .1rem;
    padding: .45rem .6rem; border-radius: var(--radius-sm);
    cursor: pointer; color: var(--text-color); font-size: .9rem;
    /* El doble clic mueve el elemento: sin esto deja el nombre resaltado. */
    user-select: none;
}
.transfer__item:hover { background: var(--surface-hover); }
.transfer__item:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;      /* sigue el radio del propio elemento */
}
.transfer__item.is-selected { background: var(--primary); color: var(--on-accent); }
.transfer__item.is-selected:hover { background: var(--primary-dark); color: var(--on-accent); }
.transfer__item.is-selected:focus-visible { outline-color: var(--on-accent); }
/* Segunda línea (email, referencia…): hereda el color en la fila seleccionada
   para no quedar ilegible sobre el fondo de marca. */
.transfer__item-sub {
    display: block; font-size: .78rem; color: var(--text-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.transfer__item.is-selected .transfer__item-sub { color: inherit; opacity: .85; }
/* No movible (p. ej. un usuario inactivo que aún no está asignado). La opacidad
   sola no dice por qué —y `cursor` no existe en táctil—, así que el motivo va
   en una etiqueta visible; el lector de pantalla lo recibe por aria-disabled. */
.transfer__item.is-locked { opacity: .6; cursor: not-allowed; }
.transfer__item.is-locked:hover { background: transparent; }
.transfer__badge {
    align-self: flex-start;
    padding: 0 .35rem;
    border: 1px solid currentColor;
    border-radius: var(--radius-full);
    color: var(--text-muted);
    font-size: .68rem; font-weight: 600; text-transform: uppercase;
    letter-spacing: .02em;
}
.transfer__empty {
    padding: .6rem; color: var(--text-muted); font-size: .85rem; font-style: italic;
}
.transfer__actions {
    display: flex; flex-direction: column; justify-content: center; gap: .5rem;
}
@media (max-width: 720px) {
    /* Columnas apiladas y flechas en horizontal: en vertical, la de «mover a la
       derecha» apuntaría a una lista que está debajo. */
    .transfer { grid-template-columns: 1fr; }
    .transfer__actions { flex-direction: row; justify-content: center; }
    .transfer__actions .icon { transform: rotate(90deg); }
    /* Dos listas de 320px apiladas no caben en una pantalla de móvil: con la
       cabecera, los buscadores y las flechas en medio, la segunda quedaría
       siempre fuera de vista. */
    .transfer__list { min-height: 140px; max-height: 220px; }
    .transfer__item-sub { font-size: .8rem; }
}

/* ============== Modal visor de PDF (a pantalla de modal) ==============
 * Patrón compartido por los modales que solo enseñan un PDF (facturas SaaS en
 * Plataforma, facturas de Centro de costes…). Promovido desde `.cc-pdf-*` de
 * centro_costes.css al reutilizarse.
 *
 * El porqué de cada regla: `.emitida-preview-box` está pensada para vivir en la
 * COLUMNA de un modal de formulario, donde el layout le da altura. Sola dentro
 * de un `.modal-body` no la tiene, y entonces el `height: 100%` del `<embed>`
 * no resuelve contra un padre sin altura definida: el visor cae a su tamaño
 * intrínseco (unos 150px) y se ve una franja de PDF arriba con el resto del
 * modal vacío. La cadena `modal-content` con altura fija → body `flex:1` con
 * `min-height:0` → preview al 100% le da al `<embed>` una altura real que
 * heredar.
 *
 * NO usar `modal-dialog-scrollable` con esto: el visor de PDF trae su propio
 * scroll, y el del modal se le suma dejando dos barras y un cuerpo que no llena. */
.pdf-modal-dialog { max-width: min(1100px, 96vw); }
.pdf-modal-dialog .modal-content { height: 90vh; }
.pdf-modal-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0 !important;
    overflow: hidden;
}
/* Neutraliza las restricciones que `.emitida-preview-box` trae para el modal
   pequeño (proporción A4, tope de altura, borde y radius): aquí el PDF llena. */
.pdf-modal-preview {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    max-height: none;
    min-height: 0;
    border: 0;
    border-radius: 0;
}
.pdf-modal-preview embed,
.pdf-modal-preview iframe,
.pdf-modal-preview .emitida-preview-pdf {
    width: 100%;
    height: 100%;
    display: block;
    border: 0;
}
@media (max-width: 640px) {
    /* En móvil la barra del navegador se come parte del viewport: 90vh dejaba
       el pie del modal (donde está descargar) fuera de alcance. */
    .pdf-modal-dialog .modal-content { height: 92vh; }
}

/* ============== Switch pro (segmentado compacto) ==============
 * Control de 2-3 opciones EXCLUYENTES con indicador deslizante, para elegir un
 * valor dentro de un formulario o una tarjeta — no para navegar entre vistas
 * (eso es `.facturas-view-switch`, que es la pastilla grande de la cabecera).
 *
 * Misma identidad que aquel (pastilla, thumb que se desliza, tipografía) pero a
 * escala de campo: cabe en la tarjeta de un módulo o junto a un input sin
 * robarle protagonismo.
 *
 * El thumb lo pinta el motor compartido `initSegThumbs()` (core.js), que
 * reconoce `.seg-pro` — NUNCA un cambio de fondo seco. Marcar la opción activa
 * con `.is-active` es lo único que tiene que hacer el JS del módulo; el motor
 * mueve el indicador solo.
 *
 * Markup:
 *   <div class="seg-pro" role="radiogroup" aria-label="Qué se elige">
 *     <button type="button" class="seg-pro__btn is-active" role="radio"
 *             aria-checked="true"  data-valor="a">Opción A</button>
 *     <button type="button" class="seg-pro__btn" role="radio"
 *             aria-checked="false" data-valor="b">Opción B</button>
 *   </div>
 *
 * `role="radiogroup"` cuando las opciones eligen un VALOR (un lector de
 * pantalla las anuncia como alternativas). Si REVELAN un panel, `tablist` +
 * `tab`/`tabpanel` (Impuestos → 036, vista del proyecto de Energía); si
 * elegir GUARDA al momento, `group` + `aria-pressed`. Ver ui-standard.md.
 */
.seg-pro {
    position: relative;
    display: inline-flex;
    max-width: 100%;
    /* SIN `overflow`: este control NUNCA hace scroll. Si no cabe, encogen los
       botones (`flex: 1 1 0` + `min-width: 0`); una barra de scroll dentro de
       una píldora de 42px es intocable con el dedo, y además `overflow-x: auto`
       obliga a `overflow-y: auto` por especificación, lo que recortaba la
       sombra del `.seg-thumb` (solo tiene 0.2rem de holgura arriba y abajo). */
    gap: 0;
    padding: 0.2rem;
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-full);
}
/* Pastilla clara elevada, como la del switch de vistas (no el primary del
   thumb por defecto). */
.seg-pro .seg-thumb { background: var(--surface); }
.seg-pro__btn {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    /* `1 1 0` y no `1 1 auto`: los dos botones se reparten el ancho a partes
       iguales, sea cual sea el largo del rótulo. `min-width: 0` es OBLIGATORIO
       —sin él, el `min-width: auto` implícito de un flex item fija el mínimo en
       el texto entero (por el `nowrap` de abajo) y el botón no encoge jamás:
       ahí nacía el desbordamiento. Efecto lateral bueno: el ancho deja de
       depender del `font-weight` del activo, así que el thumb solo se desliza. */
    flex: 1 1 0;
    min-width: 0;
    padding: 0.3rem 0.75rem;
    font-size: 0.76rem;
    font-weight: 500;
    line-height: 1.4;
    text-align: center;
    color: var(--text-muted);
    background: transparent;
    border: none;
    border-radius: var(--radius-full);
    cursor: pointer;
    white-space: nowrap;
    transition: color 0.18s var(--ease);
}
.seg-pro__btn .icon { width: 14px; height: 14px; flex-shrink: 0; }
.seg-pro__btn:not(.is-active):hover { color: var(--text-color); }
.seg-pro__btn.is-active { color: var(--primary-ink); font-weight: 600; }
.seg-pro__btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* Variante a lo ancho: ocupa la línea completa (útil bajo un input) y vive en
   tarjetas estrechas. Ahí el rótulo SÍ puede partirse en dos líneas antes que
   salirse: los dos botones crecen a la vez y el thumb los sigue, porque el
   motor lee el `offsetHeight` del activo. */
.seg-pro--full { display: flex; width: 100%; align-items: stretch; }
/* Dentro de una tarjeta de campo el carril (`--surface-sunken`) es el MISMO gris
   que la tarjeta y el control desaparecía (tercera vez: dossiers.css y
   `.plat-exento-field` ya lo parcheaban módulo a módulo). Aquí, una sola vez:
   carril un punto más oscuro que la tarjeta y pastilla elevada (blanca sobre
   blanco solo se distinguía por la sombra). */
/* También sobre la tarjeta de precio de Productos (`.prod-precio-card`), del
   mismo gris hundido. */
.form-field-card .seg-pro,
.prod-precio-card .seg-pro { background: var(--gray-100); border-color: var(--border-strong); }
.form-field-card .seg-pro .seg-thumb,
.prod-precio-card .seg-pro .seg-thumb { background: var(--surface-raised); }
/* El carril transiciona con el thumb: si no, el color salta en seco mientras la
   pastilla tarda 0,28 s en llegar. */
.seg-pro { transition: background-color .18s var(--ease), border-color .18s var(--ease); }

/* Switch pro BOOLEANO (`.seg-pro--bool`): cuando la opción marcada vale «1» el
   control entero se pinta en color —borde, fondo claro y texto—, para que el
   estado se lea de un vistazo sin comparar dos pastillas grises. Verde = un
   estado (Activado); `--bool-primario` = una designación (Por defecto), en el
   color de marca, para que no se confunda con el estado. Decisión del dueño,
   2026-09-05. La clase `is-on` la pone `segProSet` (core.js) al marcar; el
   texto usa las TINTAS `--*-ink`, no los semánticos: `--success` a 12 px no
   llega a 4,5:1 sobre la pastilla. */
.seg-pro--bool.is-on {
    border-color: var(--success);
    background: color-mix(in srgb, var(--success) 16%, var(--surface));
}
.seg-pro--bool.is-on .seg-pro__btn.is-active { color: var(--success-ink); }
.seg-pro--bool.is-on .seg-pro__btn:not(.is-active) { color: var(--text-color); }
.seg-pro--bool.is-on .seg-thumb {
    background: var(--surface-raised);
    box-shadow: 0 0 0 1.5px var(--success), var(--shadow);
}
.seg-pro--bool.seg-pro--bool-primario.is-on {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 16%, var(--surface));
}
.seg-pro--bool.seg-pro--bool-primario.is-on .seg-pro__btn.is-active { color: var(--primary-ink); }
.seg-pro--bool.seg-pro--bool-primario.is-on .seg-thumb {
    box-shadow: 0 0 0 1.5px var(--primary), var(--shadow);
}
/* Campo en error: el segmentado culpable se marca como cualquier otro control.
   Va DESPUÉS de la variante y con más peso, o el borde verde/azul ganaría. */
.form-field.has-error .seg-pro,
.form-field.has-error .seg-pro--bool.is-on { border-color: var(--field-error, var(--danger)); }
.seg-pro--full .seg-pro__btn { white-space: normal; overflow-wrap: anywhere; }
@media (pointer: coarse) {
    /* Área táctil: dos opciones seguidas de 24px son imposibles de acertar. El
       alto lo da `min-height`, NO el padding vertical: subir el padding lateral
       era pagar los 44px con el ancho que en móvil no hay. */
    .seg-pro__btn { min-height: 44px; padding: 0.4rem 0.5rem; font-size: 0.8rem; }
}

/* ============================================================
   Modal de formulario en DOS COLUMNAS de secciones.
   Para modales con varias secciones independientes que, apiladas,
   obligan a un scroll largo (p. ej. «IA de la empresa»: modelo y
   cobro por un lado, crédito y disponibilidad por otro).
   Las secciones se reparten en dos columnas a partir de tablet
   ancha; por debajo vuelven a apilarse (una sección estrecha con
   campos dentro es peor que una lista larga).
   `align-items: start` evita que la sección corta se estire hasta
   la altura de la larga dejando un hueco vacío en medio.
   ============================================================ */
@media (min-width: 992px) {
    .form-modal-body--2col {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0 1.25rem;
        align-items: start;
    }
    /* Los campos sueltos del cuerpo (hidden, avisos globales) ocupan las dos
       columnas: no son una sección y partirlos rompería la lectura. */
    .form-modal-body--2col > :not(.form-modal-section) {
        grid-column: 1 / -1;
    }
}


/* Acuse del AUTOGUARDADO en la cabecera de los modales de edición (motor en
   core.js). Va ahí y no en un toast: sin botón de guardar, el usuario necesita
   ver el estado mientras sigue editando, no un aviso que se desvanece. */
.autosave-acuse {
    margin-left: auto;
    padding-left: 0.75rem;
    font-size: 0.72rem;
    font-weight: 500;
    white-space: nowrap;
    opacity: 0.85;
}
.autosave-acuse.is-ok { opacity: 1; }
.autosave-acuse.is-error {
    opacity: 1;
    font-weight: 700;
    text-decoration: underline;
}
/* La cabecera del modal es una superficie oscura: el acuse hereda su color
   (--on-inverse) en vez de traer uno propio, para no romper el contraste. */
.form-modal-header .modal-title { display: flex; align-items: center; width: 100%; }


/* ===== Asistente por pasos del modal de perfil =====
   Clon EXACTO de `.dossier-stepper` (dossiers.css): el estándar del proyecto
   exige un ÚNICO stepper visual en toda la aplicación, así que aquí solo cambia
   el prefijo. Si algún día se toca el de dossiers, este va detrás. */
/* `display:flex` gana al [hidden] del navegador: con un solo paso (bajo «Acceder
   como») el stepper se oculta entero. */
.profile-stepper[hidden] { display: none; }
.profile-stepper {
    list-style: none;
    display: flex;
    margin: 0 0 1.1rem 0;
    padding: 0;
}
.profile-stepper__item {
    flex: 1 1 0;
    min-width: 0;
    position: relative;
    text-align: center;
}
/* Línea conectora entre pasos */
.profile-stepper__item::before {
    content: "";
    position: absolute;
    top: 15px;
    left: -50%;
    width: 100%;
    height: 2px;
    background: var(--border-color);
    z-index: 0;
}
.profile-stepper__item:first-child::before { display: none; }
/* El paso de Touch ID se retira en un navegador sin WebAuthn. La regla es
   REDUNDANTE hoy —el reboot de Bootstrap ya aplica `[hidden]{display:none
   !important}`, como recuerda btn-actions.css— y se deja como red por si algún
   día este item recibe un `display` propio, que sí ganaría al atributo. */
.profile-stepper__item[hidden] { display: none; }
.profile-stepper__item.is-active::before,
.profile-stepper__item.is-done::before { background: var(--primary); }
.profile-stepper__item > button {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .35rem;
    width: 100%;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    color: var(--text-muted);
}
.profile-stepper__num {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-full);
    background: var(--surface);
    border: 2px solid var(--border-color);
    color: var(--text-muted);
    font-weight: 700;
    font-size: .85rem;
    transition: background .15s, border-color .15s, color .15s;
}
.profile-stepper__label { font-size: .78rem; font-weight: 600; }
.profile-stepper__item.is-active .profile-stepper__num {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-accent);
}
.profile-stepper__item.is-active > button { color: var(--text-strong); }
.profile-stepper__item.is-done .profile-stepper__num {
    border-color: var(--primary);
    color: var(--primary);
}
@media (max-width: 680px) { .profile-stepper__label { display: none; } }

.profile-wizard { display: flex; flex-direction: column; min-height: 0; }
/* La primera sección de cada paso no repite el margen superior: el stepper ya
   separa. Mismo ajuste que `.dossier-step`. */
.profile-step > .form-modal-section:first-child { margin-top: 0; }

/* ===================== Dispositivos con passkey ======================
   Lista del modal de perfil (Touch ID / Face ID / llaves de seguridad).
   Vive aquí y no en una hoja de módulo porque el modal es GLOBAL: se abre
   desde la barra de navegación en cualquier vista. Solo tokens: se adapta a
   claro y oscuro sin reglas propias. */
.profile-passkey-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.profile-passkey-intro { margin-bottom: 0.75rem; }

/* ---- Verificación en dos pasos (paso 4 del perfil) ---- */
.profile-2fa-pasos { margin: 0 0 0.75rem; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 1rem; color: var(--text-color); font-size: 0.85rem; }
/* El QR va SIEMPRE sobre blanco, también en tema oscuro: una cámara lee
   contraste, no tokens, y un QR claro sobre fondo oscuro falla en media app. */
.profile-2fa-qr { display: flex; justify-content: center; margin: 0.75rem 0; }
.profile-2fa-qr img { width: 220px; height: 220px; max-width: 100%; background: #fff; padding: 8px; border-radius: 8px; border: 1px solid var(--border-color); }
.profile-2fa-clave { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; background: var(--surface-sunken); border: 1px solid var(--border-color); border-radius: 8px; }
.profile-2fa-clave code { flex: 1; min-width: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85rem; color: var(--text-strong); background: none; overflow-wrap: anywhere; -webkit-user-select: all; user-select: all; }
.profile-2fa-campo { margin-top: 0.5rem; max-width: 16rem; }
.profile-2fa-acciones { margin-top: 0.75rem; }
/* `.form-input.` delante: `btn-actions.css` carga DESPUÉS y su `.form-input
   { font: inherit }` empataba en especificidad y ganaba por orden. */
/* 1rem: con menos de 16px iOS hace zoom al enfocar el campo. */
.form-input.profile-2fa-codigo { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 1rem; letter-spacing: 0.15em; }
.profile-2fa-codigos { list-style: none; margin: 0.5rem 0 0.75rem; padding: 0.75rem; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.4rem 1rem; background: var(--surface-sunken); border: 1px solid var(--border-color); border-radius: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85rem; color: var(--text-strong); -webkit-user-select: all; user-select: all; }
.profile-2fa-estado { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.profile-2fa-chip { display: inline-flex; align-items: center; padding: 0.1rem 0.55rem; border-radius: 999px; font-size: 0.72rem; font-weight: 700; color: var(--success-ink); background: color-mix(in srgb, var(--success) 10%, transparent); }
.profile-2fa-subtitulo { margin-top: 1.25rem; }
/* ---- Sesiones abiertas (paso 5) y dispositivos de confianza ----
   Cada sesión es una tarjeta con TRES pisos —aparato, cómo y desde dónde,
   cuándo— en vez de una línea larga que en móvil partía por cualquier sitio. */
.profile-sesiones { gap: 0.65rem; }
/* `.profile-passkey-item.` delante en las tres: `.profile-passkey-item` se declara
   MÁS ABAJO con el mismo peso y volvía a fijar align-items, gap, padding, fondo y
   borde — estas reglas eran CSS muerto y la sesión actual no se distinguía. */
.profile-passkey-item.profile-sesion { align-items: flex-start; gap: 0.85rem; padding: 0.8rem 0.9rem; }
.profile-passkey-item.profile-sesion--actual { border-color: color-mix(in srgb, var(--success) 45%, var(--border-color)); background: color-mix(in srgb, var(--success) 6%, var(--surface-sunken)); }
.profile-sesion__icono { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; border-radius: 8px; color: var(--text-muted); background: var(--surface); border: 1px solid var(--border-color); }
.profile-sesion--actual .profile-sesion__icono { color: var(--success-ink); }
.profile-sesion__icono .icon { width: 1.15rem; height: 1.15rem; }
.profile-sesion__cabecera { display: flex; align-items: center; flex-wrap: wrap; gap: 0.35rem 0.5rem; }
.profile-sesion .profile-passkey-item__info { gap: 0.3rem; }
.profile-sesion__linea { display: block; font-size: 0.75rem; line-height: 1.4; color: var(--text-muted); overflow-wrap: anywhere; }
.profile-sesion .profile-passkey-item__accion { align-self: center; }
.profile-sesion-actual { display: inline-flex; align-items: center; padding: 0.1rem 0.55rem; border-radius: 999px; font-size: 0.7rem; font-weight: 700; color: var(--success-ink); background: color-mix(in srgb, var(--success) 12%, transparent); white-space: nowrap; }
/* La acción general va APARTE de la lista: una línea y aire por encima. */
.profile-sesiones-general { margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px solid var(--border-color); }
.profile-passkey-item.profile-passkey-item--accion { background: transparent; }
/* Sobre el tinte claro, el gris de `--text-muted` a 12px baja de AA (4,27:1). */
.profile-sesion--actual .profile-sesion__linea { color: var(--text-color); }
/* El ERP depende del `[hidden]` del reboot de Bootstrap; esta lista es flex. */
.profile-passkey-list[hidden] { display: none; }
@media (max-width: 480px) { .profile-passkey-item.profile-sesion { gap: 0.6rem; padding: 0.7rem 0.75rem; } }
@media (max-width: 480px) { .profile-2fa-codigos { grid-template-columns: 1fr; } }

.profile-passkey-aviso { margin-top: 0.5rem; }

.profile-passkey-confirm { margin-top: 0.75rem; }

.profile-passkey-error { color: var(--danger); }

.profile-passkey-reintentar {
    background: none;
    border: none;
    padding: 0;
    color: var(--primary);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

.profile-passkey-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.75rem;
    /* `--surface-sunken`, no `--surface`: la sección del modal ya es `--surface`
       y con el mismo fondo la tarjeta solo se distinguía por el borde, que en
       tema oscuro es demasiado sutil para leerse como una pieza aparte. */
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    border-radius: 8px;
}

.profile-passkey-item__info {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;      /* deja que el texto largo se recorte en vez de desbordar */
    flex: 1;
}

.profile-passkey-item__name {
    font-weight: 600;
    color: var(--text-strong);
    overflow-wrap: anywhere;
}

.profile-passkey-item__meta {
    font-size: 0.75rem;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

/* Acciones de cada fila (renombrar, quitar). Nombre propio y no
   `.btn-icon-danger`: esa clase ya existe en `facturas.css` con hex fijos, y
   dos definiciones del mismo nombre en la misma página se resuelven por
   especificidad, que es una forma frágil de decidir un color. `flex-shrink: 0`
   para que no se compriman cuando el nombre del dispositivo es largo, que es
   justo cuando hay que poder pulsarlas. */
.profile-passkey-item__accion {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding: 0.35rem;
    background: none;
    border: none;
    border-radius: 6px;
    color: var(--text-muted);
    cursor: pointer;
    flex-shrink: 0;
    transition: var(--transition);
}

.profile-passkey-item__accion:hover,
.profile-passkey-item__accion:focus-visible {
    background: var(--surface-hover);
    color: var(--text-strong);
}

/* Solo el destructivo tira a rojo al apuntarlo. */
.profile-passkey-item__remove:hover,
.profile-passkey-item__remove:focus-visible {
    color: var(--danger);
}

/* Nombre en edición: ocupa el ancho del bloque de información y no empuja a
   los botones fuera de la fila. */
.profile-passkey-item__input {
    width: 100%;
    min-width: 0;
    font-weight: 600;
}

/* En táctil, 44×44 — el mínimo del estándar del proyecto. Mismo patrón que
   `.seg-pro__btn` más arriba en este fichero. */
@media (pointer: coarse) {
    .profile-passkey-item__accion {
        min-width: 44px;
        min-height: 44px;
    }
}

/* En pantalla estrecha el meta ocupa tres o cuatro líneas y el botón centrado
   queda flotando a media altura, lejos del nombre al que se refiere. */
@media (max-width: 480px) {
    .profile-passkey-item { align-items: flex-start; }
}

/* Perfil: el correo lleva al lado el botón que manda el enlace de
   confirmación (no se autoguarda). El control ya es flex; el botón no encoge
   (el input lleva `min-width: 0` y se llevaría su ancho por debajo de 44 px). */
.profile-email-control { gap: .5rem; }
.profile-email-control .form-input { flex: 1 1 auto; }
.profile-email-control .btn-action { flex: 0 0 auto; }
@media (pointer: coarse) {
    .profile-email-control { --field-height: 44px; }
}
/* «Anular el cambio» dentro de la ayuda del correo: acción secundaria inline en
   un aviso (excepción 1 de icon-only: el texto ES la información). */
.profile-email-cancel {
    display: inline-flex; align-items: center; padding: 0 .25rem;
    background: none; border: 0; margin-left: .25rem;
    /* `--primary-ink` y no `--primary`: en oscuro --primary da 2,5:1 sobre la tarjeta. */
    color: var(--primary-ink); font: inherit; font-weight: 600; text-decoration: underline; cursor: pointer;
}
.profile-email-cancel:focus-visible { outline: 2px solid var(--primary-ink); outline-offset: 2px; border-radius: 2px; }
@media (pointer: coarse) {
    .profile-email-cancel { min-height: 44px; }
}

/* ---------- Selector de idioma del perfil ----------
   Switch segmentado del estándar (thumb deslizante de `core.js`; su selector
   está dado de alta en SEG_SELECTORS). Enseña el CÓDIGO y el nombre EN LA MISMA
   línea: «ES» se reconoce de un vistazo, pero por sí solo no dice si es español
   o castellano de otro sitio, y quien no lo tenga claro necesita leerlo. Iban
   apilados, y eso obligaba a un botón de dos alturas de texto que dejaba la
   píldora tan alta como un campo de formulario. Nace dentro de un modal oculto:
   el motor ya recoloca al revelarse. */
.profile-lang-switch {
    position: relative;
    /* `flex` y no `inline-flex`: la píldora ocupa TODO el ancho del campo, como
       el resto de controles del formulario. Con `inline-flex` se encogía a su
       contenido y quedaba flotando a la izquierda de un hueco vacío. */
    display: flex;
    width: 100%;
    gap: 0.15rem;
    padding: 0.2rem;
    border-radius: var(--radius-full);
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    /* Red de seguridad, ya casi inalcanzable: con `flex: 1 1 0` + `min-width: 0`
       las opciones ENCOGEN (y el nombre cae por el ellipsis o por la media query)
       mucho antes de llegar al scroll. Si algún día se llega, scrollea dentro de
       su caja en vez de empujar la página. */
    max-width: 100%;
    overflow-x: auto;
}
.profile-lang-switch__btn {
    position: relative;
    z-index: 1;
    /* En FILA, no en columna: el código y el nombre en una sola línea es lo que
       adelgaza el control. Apilados obligaban a un botón de dos alturas de texto
       y la píldora quedaba tan alta como un campo de formulario. */
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    /* Se reparten el ancho a partes iguales: así el thumb tiene una medida
       constante y solo se DESPLAZA al cambiar de idioma, sin cambiar de tamaño. */
    flex: 1 1 0;
    min-width: 0;
    background: transparent;
    border: 0;
    border-radius: var(--radius-full);
    padding: 0.3rem 0.7rem;
    /* 34px con ratón. En TÁCTIL sube a 44 (regla de abajo): el estándar exige ese
       objetivo mínimo, pero imponerlo también en escritorio es lo que engordaba
       el control sin que nadie lo necesitara. */
    min-height: 34px;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.2s var(--ease);
}
@media (hover: none), (pointer: coarse) {
    .profile-lang-switch__btn { min-height: 44px; }
}
.profile-lang-switch__btn:hover:not(.is-active) { color: var(--text-strong); }
/* Anillo de foco propio, como `.seg-pro__btn`: al adelgazar el control, el del
   navegador quedaba pegado al borde de la píldora y apenas se distinguía. */
.profile-lang-switch__btn:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
}
.profile-lang-switch__btn.is-active { color: var(--on-accent); }
/* Sobre la opción activa el anillo `--primary` caía encima del thumb, que también
   es `--primary`: invisible justo donde está el foco al abrir. */
.profile-lang-switch__btn.is-active:focus-visible { outline-color: var(--on-accent); }
.profile-lang-switch__code {
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.04em;
}
.profile-lang-switch__name {
    /* Sin `opacity`: a este cuerpo bajaba el contraste por debajo de AA tanto en
       el inactivo (gris sobre superficie hundida) como en el activo (blanco
       sobre el thumb). La jerarquía la da el tamaño, no la transparencia. */
    font-size: 0.78rem;
    line-height: 1;
    /* Si el nombre partiera en dos líneas cambiaría el ALTO del botón, y el
       thumb solo transiciona `transform` y `width`: el salto sería seco. */
    white-space: nowrap;
    /* Con muchos idiomas o en pantallas estrechas, el nombre cede antes de
       desbordar; el código de dos letras nunca se recorta. */
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Muy estrecho: se queda solo el código («ES», «EN»), que es lo que identifica
   la opción. Antes el nombre completo forzaba el scroll horizontal de la
   píldora en cuanto había tres idiomas. 480px es el breakpoint vecino que este
   mismo fichero ya usa: el estándar pide reutilizarlo, no inventar otro. (El
   `440px` que hay más arriba NO es un breakpoint, es el ancho de un diálogo.) */
@media (max-width: 480px) {
    .profile-lang-switch__name { display: none; }
}
/* Más de tres idiomas (2026-09-19: es · en · fr · it · ca). «Código + nombre»
   ya no cabe sin recortar los nombres, así que en ancho normal va SOLO el nombre
   —el idioma se nombra a sí mismo, que es como se reconoce— con menos relleno, y
   en estrecho vuelve el código. Nunca los dos a la vez. */
.profile-lang-switch--muchos .profile-lang-switch__btn { padding-inline: 0.45rem; gap: 0; }
.profile-lang-switch--muchos .profile-lang-switch__code { display: none; }
@media (max-width: 480px) {
    .profile-lang-switch--muchos .profile-lang-switch__code { display: inline; }
}


/* ---------- Calendario del buzón (CalDAV) en el modal de cuenta —
   compartido por Configuración → Correo y Mis cuentas (macro correo_caldav.j2) ---------- */
.correo-caldav__campos[hidden] { display: none; }
.correo-caldav__campos > .form-modal-grid { margin-top: 0.25rem; }
.correo-caldav__urlrow { display: flex; align-items: center; gap: 0.5rem; }
.correo-caldav__urlrow > .form-input { flex: 1 1 auto; min-width: 0; }
/* Botón e input a ras: 36px con ratón, 44px bajo dedo (los dos siguen el
   mismo token, así que el área táctil no se pierde). */
.correo-caldav__urlrow > .btn-action,
.correo-caldav__urlrow > .form-input { height: var(--btn-action-size); }
.correo-caldav__urlrow > .btn-action { flex: 0 0 auto; width: var(--btn-action-size); }
.correo-caldav__estado-fila { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/* El estado se LEE entero (motivo del error incluido): el chip no recorta. */
.correo-caldav__estado-txt { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; flex: 1 1 auto; min-width: 0; }
.correo-caldav__estado-txt .cfg-chip { max-width: none; white-space: normal; overflow: visible; text-overflow: clip; }
.correo-caldav__estado-txt .form-field__error { display: block; }

.correo-caldav__intro { margin: 0 0 0.85rem; font-size: 0.82rem; line-height: 1.45; color: var(--text-muted); }
.correo-caldav__aviso { font-size: 0.82rem; color: var(--text-muted); padding: 0.6rem 0.8rem; border: 1px dashed var(--border-color); border-radius: 8px; }
.correo-caldav__aviso[hidden], .correo-caldav__gestionado[hidden], .correo-caldav__crear[hidden],
.correo-caldav__disp[hidden], .correo-caldav__disp-form[hidden], .correo-caldav__cred[hidden],
.correo-caldav__estado-fila[hidden] { display: none; }
.correo-caldav__crear { display: flex; align-items: center; gap: 0.7rem; padding: 0.7rem 0.85rem; border: 1px solid color-mix(in srgb, var(--primary) 30%, var(--border-color)); border-radius: 8px; background: color-mix(in srgb, var(--primary) 5%, var(--surface)); }
.correo-caldav__crear-txt { font-size: 0.82rem; line-height: 1.4; color: var(--text-color); }
.correo-caldav__acciones { margin-left: auto; display: inline-flex; gap: 0.35rem; align-items: center; }
.correo-caldav__disp { margin-top: 0.9rem; }
.correo-caldav__disp-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.4rem; }
.correo-caldav__disp-head .form-field__label { margin: 0; }
.correo-caldav__disp-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; }
.correo-caldav__disp-item { display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.7rem; border: 1px solid var(--border-color); border-radius: 8px; background: var(--surface); font-size: 0.82rem; }
.correo-caldav__disp-item.is-revocado { opacity: 0.6; }
.correo-caldav__disp-ico { color: var(--text-muted); display: inline-flex; }
.correo-caldav__disp-ico .icon { width: 18px; height: 18px; }
.correo-caldav__disp-txt { flex: 1; min-width: 0; line-height: 1.35; }
.correo-caldav__disp-vacio { font-size: 0.8rem; color: var(--text-muted); padding: 0.4rem 0.2rem; }
/* El servidor de los dispositivos, siempre visible sobre la lista. `anywhere`
   porque es un nombre de host que no se puede partir por espacios: en móvil,
   sin él, desborda la tarjeta del modal en lugar de plegarse. */
.correo-caldav__disp-servidor { margin: 0 0 0.4rem; font-size: 0.8rem; color: var(--text-muted); overflow-wrap: anywhere; }
/* Mismo `code` que el del acuse de credencial (`.correo-caldav__cred code`),
   y no por simetría: son el MISMO dato a dos dedos de distancia, y sin esto
   uno sale con fondo y el otro en el rosa que Bootstrap le da a `code` por
   omisión. `user-select: all` porque es un host partido por puntos: con el
   dedo no hay forma de seleccionarlo entero. */
.correo-caldav__disp-servidor code { font-size: 0.85em; color: var(--text-color); background: color-mix(in srgb, var(--gray-500) 12%, transparent); padding: 0.1rem 0.35rem; border-radius: 4px; user-select: all; }
.correo-caldav__disp-servidor[hidden] { display: none; }
.correo-caldav__disp-form { margin-top: 0.6rem; padding: 0.7rem 0.85rem; border: 1px solid var(--border-color); border-radius: 8px; background: var(--surface-sunken); }
.correo-caldav__disp-form-acciones { flex-direction: row; justify-content: flex-end; gap: 0.4rem; }
.correo-caldav__cred { margin-top: 0.7rem; padding: 0.8rem 0.95rem; border: 1px solid color-mix(in srgb, var(--success) 40%, var(--border-color)); border-radius: 8px; background: color-mix(in srgb, var(--success) 6%, var(--surface)); font-size: 0.83rem; line-height: 1.45; }
.correo-caldav__cred-head { margin-bottom: 0.5rem; }
.correo-caldav__cred-datos { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 0.75rem; margin: 0 0 0.6rem; }
.correo-caldav__cred-datos dt { font-weight: 600; color: var(--text-muted); }
.correo-caldav__cred-datos dd { margin: 0; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; overflow-wrap: anywhere; }
.correo-caldav__cred code { font-size: 0.85em; background: color-mix(in srgb, var(--gray-500) 12%, transparent); padding: 0.1rem 0.35rem; border-radius: 4px; user-select: all; }
.correo-caldav__cred-guia ol { margin: 0.2rem 0 0 1.1rem; padding: 0; }
.correo-caldav__cred-guia li { margin: 0.15rem 0; }
.correo-caldav__externo { margin-top: 0.9rem; }
.correo-caldav__externo > .form-modal-details__summary > svg { transition: transform 0.15s var(--ease); }
.correo-caldav__externo[open] > .form-modal-details__summary > svg { transform: rotate(90deg); }
.correo-caldav__disp-cta { display: inline-flex; align-items: center; gap: 0.5rem; }
.correo-caldav__cred { overflow-wrap: anywhere; }
.correo-caldav__intro[hidden] { display: none; }
@media (pointer: coarse) { .correo-caldav__externo > .form-modal-details__summary { min-height: 44px; } }

/* ==================== Chip de MARCA (submarca) ====================
 * El color identificativo viene de la BASE DE DATOS
 * (`submarcas.color_primario`), así que no puede ser un token: entra por
 * `--submarca-color` en el `style` del elemento. Tinte al 14% + texto al
 * propio color sobre `--surface`, y pareja oscura en pastel, como manda el
 * estándar para paletas dinámicas.
 *
 * Vivía en `correos.css`. Se PROMUEVE aquí al usarse en un segundo módulo
 * (Generador de contratos): reutilizarla desde otra vista no fallaba «un
 * poco», simplemente no se aplicaba nada —esa hoja no se carga fuera de
 * Correos— y el chip salía sin fondo ni color, en claro y en oscuro. */
.marca-chip {
    background: color-mix(in srgb, var(--submarca-color, var(--primary)) 14%, transparent);
    color: var(--submarca-color, var(--primary));
    border: 1px solid color-mix(in srgb, var(--submarca-color, var(--primary)) 35%, transparent);
}
html[data-theme="dark"] .marca-chip {
    /* Pastel: aclara el color de marca para que el texto contraste en oscuro
       (blanco solo como extremo de la mezcla, no como color directo). */
    color: color-mix(in srgb, var(--submarca-color, var(--primary)) 55%, white);
    background: color-mix(in srgb, var(--submarca-color, var(--primary)) 24%, transparent);
}


/* ==================== Tarjetas KPI (.cc-kpi) ====================
   Nacieron en Centro de costes (de ahí el prefijo) y las usan Plataforma y
   Energía (todas migradas a `Hermes.kpiMando` + `.kpis-mando` desde la v2.168.0): se
   promovieron aquí (ui-standard.md, «si se reutiliza en otro
   módulo, se promueve»). Acento por `--kpi-accent`. */
.cc-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 1rem;
}
.cc-kpi {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 1rem 1.15rem;
    background: var(--surface-raised);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.cc-kpi:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.cc-kpi::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--kpi-accent, var(--primary));
}
.cc-kpi__icon {
    flex: 0 0 auto;
    width: 2.7rem;
    height: 2.7rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--kpi-accent, var(--primary)) 14%, transparent);
    color: var(--kpi-accent, var(--primary));
}
.cc-kpi__icon .icon { width: 1.35rem; height: 1.35rem; }
.cc-kpi__body { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.cc-kpi__num {
    font-size: 1.55rem;
    font-weight: 700;
    color: var(--text-strong);
    font-variant-numeric: tabular-nums;
}
.cc-kpi__label { font-size: 0.78rem; font-weight: 600; color: var(--text-color); }
.cc-kpi__sub { font-size: 0.72rem; color: var(--text-muted); }

/* ==================== KPI «cuadro de mando» (.cc-kpi.en-kpi) ====================
   Nació en Energía → Resumen (de ahí el prefijo en-) y la usa también la ficha
   del cliente: se promovió aquí (ui-standard.md → Tarjetas KPI, «si otro módulo
   la quiere, se sube a components.css»). Lo que depende de la escala de
   urgencia de Energía (--en-t-*) sigue en energia.css.
   Misma familia que components.css → .cc-kpi (papel, borde, sombra, chip con
   color-mix y franja de acento), con otra composición: etiqueta + chip arriba,
   cifra grande, y un pie que dice el ESTADO en texto. Acento por --kpi-accent
   y tinta legible por --kpi-ink; todo token, claro/oscuro sin CSS extra. */
.en-kpis { gap: 1rem; margin-bottom: 1.25rem; }

.cc-kpi.en-kpi {
    --kpi-accent: var(--primary);
    --kpi-ink: var(--primary-ink);
    display: grid;
    grid-template-rows: auto 1fr auto;
    align-items: start;
    gap: 0.35rem;
    padding: 0.95rem 1.1rem 0.9rem 1.3rem;
    min-width: 0;
    color: inherit;
    text-decoration: none;
    /* Tinte del acento solo en la esquina del chip: se intuye, no se ve. */
    background:
        radial-gradient(120% 90% at 100% 0%, color-mix(in srgb, var(--kpi-accent) 7%, transparent), transparent 60%),
        var(--surface-raised);
    transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease), border-color 0.18s var(--ease);
}
/* Franja de acento: fina, redondeada y separada de los bordes. */
.cc-kpi.en-kpi::before {
    top: 0.9rem; bottom: 0.9rem; left: 0.55rem;
    width: 3px;
    border-radius: var(--radius-full);
    background: var(--kpi-accent);
}

.en-kpi__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.6rem; min-width: 0; }
.en-kpi__label {
    font-size: 0.8rem; font-weight: 600; line-height: 1.25;
    color: var(--text-color); overflow-wrap: anywhere; min-width: 0;
}
.cc-kpi__icon.en-kpi__icon {
    width: 2.1rem; height: 2.1rem;
    background: color-mix(in srgb, var(--kpi-accent) 13%, transparent);
    border: 1px solid color-mix(in srgb, var(--kpi-accent) 22%, transparent);
    color: var(--kpi-ink);
}
.cc-kpi__icon.en-kpi__icon .icon { width: 1.1rem; height: 1.1rem; }

.en-kpi__valor { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.4rem; min-width: 0; }
.en-kpi__num {
    font-size: 2rem; font-weight: 700; line-height: 1.05; letter-spacing: -0.02em;
    color: var(--text-strong); font-variant-numeric: tabular-nums;
}
.en-kpi__unit { font-size: 0.78rem; font-weight: 600; color: var(--text-muted); }

/* Barra partida de «Por renovar»: vencidas | próximas. Los anchos los pone el
   JS en --p-venc/--p-prox; la leyenda de debajo lleva las cifras en texto. */
.en-kpi__meter {
    display: flex; gap: 2px; height: 6px; margin-top: 0.15rem;
    border-radius: var(--radius-full); overflow: hidden;
    background: color-mix(in srgb, var(--kpi-accent) 12%, var(--surface-sunken));
}
.en-kpi__seg { height: 100%; border-radius: var(--radius-full); transition: flex-basis 0.4s var(--ease-gesto); }

.en-kpi__foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 0.5rem; min-width: 0; }
.en-kpi__sub { font-size: 0.74rem; color: var(--text-muted); line-height: 1.3; overflow-wrap: anywhere; }
.en-kpi__leyenda { display: flex; flex-wrap: wrap; gap: 0.15rem 0.7rem; min-width: 0; }
.en-kpi__ley, .en-kpi__estado {
    display: inline-flex; align-items: center; gap: 0.35rem;
    font-size: 0.74rem; font-weight: 600; line-height: 1.3;
    font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.en-kpi__ley[hidden], .en-kpi__estado[hidden] { display: none; }
.en-kpi__ley::before, .en-kpi__estado::before {
    content: ""; flex: 0 0 auto; width: 0.5rem; height: 0.5rem; border-radius: var(--radius-full);
    background: var(--dot, var(--kpi-accent));
}
.en-kpi__estado { color: var(--kpi-ink); }

/* Señal de «esto lleva a una vista»: flecha discreta que avanza al pasar. */
.en-kpi__go {
    display: inline-flex; flex: 0 0 auto; margin-left: auto;
    color: var(--kpi-ink); opacity: 0.45;
    transition: opacity 0.18s var(--ease), transform 0.18s var(--ease);
}
.en-kpi__go .icon { width: 1rem; height: 1rem; }
a.cc-kpi.en-kpi:hover { border-color: color-mix(in srgb, var(--kpi-accent) 38%, var(--border-color)); }
a.cc-kpi.en-kpi:hover .en-kpi__go, a.cc-kpi.en-kpi:focus-visible .en-kpi__go { opacity: 1; transform: translateX(2px); }
a.cc-kpi.en-kpi:focus-visible { outline: 3px solid color-mix(in srgb, var(--kpi-accent) 45%, transparent); outline-offset: 2px; }
@media (pointer: coarse) { .en-kpi__go { opacity: 0.7; } }

/* Acentos (+ su tinta legible como texto pequeño). */
.en-kpi--primary { --kpi-accent: var(--primary); --kpi-ink: var(--primary-ink); }
.en-kpi--success { --kpi-accent: var(--success); --kpi-ink: var(--success-ink); }
.en-kpi--info    { --kpi-accent: var(--info);    --kpi-ink: var(--info-ink); }
.en-kpi--warning { --kpi-accent: var(--warning); --kpi-ink: var(--warning-ink); }
.en-kpi--danger  { --kpi-accent: var(--danger);  --kpi-ink: var(--danger-ink); }
.en-kpi--neutro  { --kpi-accent: var(--gray-500); --kpi-ink: var(--text-color); }
/* Estado que fija el JS (data-estado), con la escala de urgencia del módulo. */
.en-kpi[data-estado="revisar"] { --kpi-accent: var(--info);      --kpi-ink: var(--info-ink); }
.en-kpi[data-estado="ok"]      { --kpi-accent: var(--success);   --kpi-ink: var(--success-ink); }

/* La cifra de ahorro no lleva a ningún sitio: ni se levanta ni cambia el borde. */
.en-kpis .en-kpi--fijo:hover { transform: none; box-shadow: var(--shadow); }

/* ---- Rejilla «kpis-mando»: la de TODO Hermes (dueño, 2026-10-17: «que siempre sean
   iguales»). Con `Hermes.kpiMando()` (core.js). La usa todo Hermes salvo Gestión → KPIs,
   que conserva su variante rica (`kpi()` de kpis.js: delta y medidor).
   · Fila flexible CENTRADA de ancho fijo, no rejilla a partes iguales: un bloque de 3
     y otro de 5 saldrían de anchos distintos, y la que sobra en una fila impar queda
     centrada. Alto mínimo común; si un texto largo pide más, la fila crece entera.
   · Cifra algo menor y a 600: en estas pantallas se ven muchas a la vez.
   · ACENTOS que llegan de verdad: `.cc-kpi.en-kpi` fija el azul con el mismo peso que
     las clases de color de abajo, así que fuera de esta rejilla `en-kpi--success…` no
     pinta nada (deuda.md). Aquí sí: los colores de cada módulo se respetan. */
.kpis-mando {
    --kpi-mando-w: 15.5rem;
    --kpi-mando-h: 8.5rem;
    /* Alto del PEOR caso en móvil (etiqueta y pie de 3 líneas, unidad debajo). */
    --kpi-mando-h-movil: 10.5rem;
    --kpi-mando-gap: 1rem;
}
.cc-kpis.en-kpis.kpis-mando {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: stretch;
    gap: var(--kpi-mando-gap);
}
.cc-kpis.en-kpis.kpis-mando[hidden] { display: none; }
.cc-kpis.en-kpis.kpis-mando:last-child { margin-bottom: 0; }
.kpis-mando > .cc-kpi.en-kpi {
    flex: 0 0 var(--kpi-mando-w);
    min-height: var(--kpi-mando-h);
}
.kpis-mando .en-kpi__num { font-size: 1.5rem; font-weight: 600; }
/* El icono, del color de la tarjeta, como en las tarjetas que esta sustituye (Monitorización,
   Centro de costes, Fidelización, WordPress): el color es parte de lo que dicen. */
.kpis-mando .cc-kpi__icon.en-kpi__icon { color: var(--kpi-accent); }
.kpis-mando .en-kpi--primary { --kpi-accent: var(--primary); --kpi-ink: var(--primary-ink); }
.kpis-mando .en-kpi--success { --kpi-accent: var(--success); --kpi-ink: var(--success-ink); }
.kpis-mando .en-kpi--info    { --kpi-accent: var(--info);    --kpi-ink: var(--info-ink); }
.kpis-mando .en-kpi--warning { --kpi-accent: var(--warning); --kpi-ink: var(--warning-ink); }
.kpis-mando .en-kpi--danger  { --kpi-accent: var(--danger);  --kpi-ink: var(--danger-ink); }
.kpis-mando .en-kpi--neutro  { --kpi-accent: var(--gray-500); --kpi-ink: var(--text-color); }
/* La que lleva a algún sitio es un <button>: sin la tipografía ni el relleno del
   navegador, y con el mismo realce y foco que la que es un enlace. */
.kpis-mando > button.cc-kpi.en-kpi { font: inherit; text-align: start; cursor: pointer; }
.kpis-mando > button.cc-kpi.en-kpi:hover { border-color: color-mix(in srgb, var(--kpi-accent) 38%, var(--border-color)); }
.kpis-mando > button.cc-kpi.en-kpi:hover .en-kpi__go,
.kpis-mando > button.cc-kpi.en-kpi:focus-visible .en-kpi__go { opacity: 1; transform: translateX(2px); }
.kpis-mando > button.cc-kpi.en-kpi:focus-visible { outline: 3px solid color-mix(in srgb, var(--kpi-accent) 45%, transparent); outline-offset: 2px; }

@media (max-width: 720px) {
    .en-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
    .cc-kpi.en-kpi { padding: 0.8rem 0.8rem 0.75rem 1.1rem; gap: 0.3rem; }
    .cc-kpi.en-kpi::before { left: 0.45rem; top: 0.75rem; bottom: 0.75rem; }
    .cc-kpi__icon.en-kpi__icon { width: 1.8rem; height: 1.8rem; }
    .cc-kpi__icon.en-kpi__icon .icon { width: 0.95rem; height: 0.95rem; }
    .en-kpi__label { font-size: 0.76rem; }
    .en-kpi__num { font-size: 1.5rem; }
    /* La descripción fija sobra en móvil; el ESTADO y la leyenda se quedan. */
    .en-kpi__sub { display: none; }
}
@media (max-width: 380px) {
    .en-kpi__num { font-size: 1.35rem; }
}
@media (max-width: 720px) {
    /* Dos por fila a la mitad exacta (dos de 15,5 rem no caben en 360 px); la impar,
       centrada y del mismo ancho. La cifra, elástica e IGUAL en todas: «12.345,67 €»
       dentro de una tarjeta de sección no cabía a 1.25rem (el espacio antes del € no parte). */
    .kpis-mando { --kpi-mando-gap: 0.75rem; }
    .kpis-mando > .cc-kpi.en-kpi {
        flex-basis: calc((100% - var(--kpi-mando-gap)) / 2);
        min-height: var(--kpi-mando-h-movil);
    }
    .kpis-mando .en-kpi__num { font-size: clamp(1.05rem, 5vw, 1.25rem); }
    /* La leyenda fija (`pie`) se queda en móvil: aquí el alto ya cuenta con ella. */
    .kpis-mando .en-kpi__sub { display: block; }
}



/* Iconos hidratados (`<span data-svg-icon>`) en cabeceras de modal y de sección:
   el estándar estiliza el `svg` HIJO DIRECTO y el span lo aleja un nivel; las
   reglas de arriba incluyen también `> [data-svg-icon] > svg`. */
.form-modal-header .modal-title > [data-svg-icon],
.form-modal-section-title > [data-svg-icon] { display: inline-flex; align-items: center; flex-shrink: 0; }

/* `hidden` gana al display propio del estándar (no depender de Bootstrap). */
.file-field[hidden], .form-field__control[hidden] { display: none; }

/* Movido de admin.css (v2.112.0): lo comparten Configuración → Pagos y el alta
   pública `/alta`, que ha de verse igual (plan «Alta en autoservicio», F3). */
/* ==========================================================================
   Configuración → Pagos: el alta en TRES pasos siempre visibles (v2.91.0).
   Stepper: copia EXACTA de .dossier-stepper (ui-standard → «Wizard / asistente
   por pasos — identidad ÚNICA»). Todo por tokens: claro y oscuro.
   ========================================================================== */
.pago-stepper { list-style: none; display: flex; margin: 0 0 1.1rem 0; padding: 0; }
.pago-stepper__item { flex: 1 1 0; min-width: 0; position: relative; text-align: center; }
.pago-stepper__item::before {
    content: ""; position: absolute; top: 15px; left: -50%; width: 100%; height: 2px;
    background: var(--border-color); z-index: 0;
}
.pago-stepper__item:first-child::before { display: none; }
.pago-stepper__item.is-active::before,
.pago-stepper__item.is-done::before { background: var(--primary); }
.pago-stepper__item > button {
    position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center;
    gap: .35rem; width: 100%; background: none; border: none; cursor: pointer; padding: 0;
    color: var(--text-muted);
}
.pago-stepper__num {
    display: flex; align-items: center; justify-content: center; width: 32px; height: 32px;
    border-radius: var(--radius-full); background: var(--surface);
    border: 2px solid var(--border-color); color: var(--text-muted);
    font-weight: 700; font-size: .85rem; transition: background .15s, border-color .15s, color .15s;
}
.pago-stepper__label { font-size: .78rem; font-weight: 600; }
.pago-stepper__item.is-active .pago-stepper__num { background: var(--primary); border-color: var(--primary); color: var(--on-accent); }
.pago-stepper__item.is-active > button { color: var(--text-strong); }
.pago-stepper__item.is-done .pago-stepper__num { border-color: var(--primary); color: var(--primary); }
@media (max-width: 680px) { .pago-stepper__label { display: none; } }
@media (pointer: coarse) { .pago-stepper__item > button { min-height: 44px; } }

.pago-alta { container-type: inline-size; }
.pago-alta[hidden] { display: none; }
.pago-pasos { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; }
@container (min-width: 720px) {
    .pago-pasos { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.pago-paso {
    display: flex; flex-direction: column; gap: .75rem; min-width: 0;
    padding: 1rem 1.15rem; background: var(--surface);
    border: 1px solid var(--border-color); border-radius: var(--radius-lg);
    box-shadow: var(--shadow); scroll-margin-top: 5rem;
}
.pago-paso:focus { outline: none; }
.pago-paso:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.pago-paso--actual { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary), var(--shadow); }
.pago-paso--hecho { border-color: color-mix(in srgb, var(--success) 45%, var(--border-color)); }
.pago-paso--bloqueado { background: var(--surface-sunken); border-style: dashed; box-shadow: none; }
/* Rejilla fija: «Paso N de 3» arriba a la izquierda, el chip de estado arriba a
   la DERECHA en la misma fila, y el título debajo a todo el ancho. Con flex el
   chip compartía fila con el título y, si este era largo, bajaba: los tres
   pasos lo enseñaban a alturas distintas (decisión del dueño, 2026-09-24). */
.pago-paso__head {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "kicker estado" "title title";
    align-items: center; gap: .6rem;   /* fila: un poco de aire entre «Paso N» y el título */
}
.pago-paso__kicker {
    grid-area: kicker; margin: 0; font-size: .72rem; font-weight: 700;
    letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted);
}
.pago-paso__title { grid-area: title; min-width: 0; margin: 0; font-size: .98rem; font-weight: 700; color: var(--text-strong); }
.pago-paso__estado {
    grid-area: estado; justify-self: end;
    display: inline-flex; align-items: center; gap: .35rem; padding: .2rem .55rem;
    border-radius: var(--radius-full); font-size: .76rem; font-weight: 600; white-space: nowrap;
}
.pago-paso__estado .cell-icon { width: .9rem; height: .9rem; }
/* Hasta que el JS lo pinta, el chip está vacío: sin esto sale una pastilla sin texto. */
.pago-paso__estado:empty { display: none; }
.pago-paso--hecho .pago-paso__estado { color: var(--success-ink, var(--success)); background: color-mix(in srgb, var(--success) 14%, transparent); }
.pago-paso--actual .pago-paso__estado { color: var(--primary-ink, var(--primary)); background: color-mix(in srgb, var(--primary) 14%, transparent); }
.pago-paso--bloqueado .pago-paso__estado { color: var(--text-muted); background: color-mix(in srgb, var(--text-muted) 12%, transparent); }
.pago-paso__body { display: flex; flex-direction: column; gap: .6rem; min-width: 0; }
.pago-paso__desc, .pago-paso__hint { margin: 0; font-size: .82rem; color: var(--text-muted); }
.pago-paso__medio {
    margin: 0; display: inline-flex; align-items: center; gap: .4rem;
    font-weight: 600; color: var(--text-strong); overflow-wrap: anywhere;
}
.pago-paso__acciones { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.pago-paso__motivo {
    margin: 0; padding: .5rem .65rem; font-size: .8rem; color: var(--text-color);
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--warning) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border-color));
}
.pago-paso--bloqueado .pago-paso__motivo { background: transparent; border-color: var(--border-color); color: var(--text-muted); }
.pago-paso__guardado {
    margin: auto 0 0; display: flex; align-items: center; gap: .4rem;
    font-size: .8rem; font-weight: 600; color: var(--success-ink, var(--success));
}
.pago-paso__guardado[hidden] { display: none; }
.pago-paso__guardado.is-saving { color: var(--text-muted); }
.pago-consent {
    display: flex; align-items: flex-start; gap: .6rem; min-height: 44px;
    padding: .55rem .65rem; border: 1px solid var(--border-color); border-radius: var(--radius);
    background: var(--surface-sunken); cursor: pointer;
    font-size: .86rem; line-height: 1.4; color: var(--text-color);
}
.pago-consent .form-check-input { flex: 0 0 auto; width: 1.15rem; height: 1.15rem; margin: .1rem 0 0; }
.pago-consent.is-disabled { cursor: not-allowed; color: var(--text-muted); }
.pago-consent:has(input:checked) {
    border-color: color-mix(in srgb, var(--success) 45%, var(--border-color));
    background: color-mix(in srgb, var(--success) 7%, var(--surface));
}

.pago-avisos {
    margin-bottom: 1.25rem; border: 1px solid color-mix(in srgb, var(--danger) 42%, var(--border-color));
    border-radius: var(--radius); background: var(--surface); overflow: hidden;
}
.pago-avisos[hidden] { display: none; }
.pago-avisos__cab {
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; padding: .5rem .7rem;
    border-bottom: 1px solid color-mix(in srgb, var(--danger) 30%, var(--border-color));
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    font-size: .82rem; font-weight: 600; color: var(--text-strong);
}
.pago-avisos__ico { display: inline-flex; color: var(--danger); }
.pago-avisos__ico .icon { width: 1rem; height: 1rem; }
.pago-avisos__acciones { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
.pago-avisos__lista { list-style: none; margin: 0; padding: 0; max-height: 240px; overflow-y: auto; overscroll-behavior: contain; }
.pago-avisos__item {
    padding: .55rem .8rem; font-size: .85rem; line-height: 1.45; color: var(--text-color); overflow-wrap: anywhere;
    border-bottom: 1px solid color-mix(in srgb, var(--danger) 22%, var(--border-color));
}
.pago-avisos__item:last-child { border-bottom: 0; }
.pago-avisos__detalle { display: block; margin-top: .2rem; font-size: .78rem; color: var(--text-muted); }
.pago-avisos--aviso { border-color: color-mix(in srgb, var(--warning) 42%, var(--border-color)); }
.pago-avisos--aviso .pago-avisos__cab {
    border-bottom-color: color-mix(in srgb, var(--warning) 30%, var(--border-color));
    background: color-mix(in srgb, var(--warning) 12%, transparent);
}
.pago-avisos--aviso .pago-avisos__ico { color: var(--warning-ink, var(--warning)); }


/* Nombre de un cliente que lleva a su ficha (`window.enlaceCliente`, core.js).
   Se lee como el texto de la celda; el subrayado dice que se puede pulsar. */
.enlace-cliente { color: inherit; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 2px; }
.enlace-cliente:hover, .enlace-cliente:focus-visible { color: var(--primary-ink); text-decoration-color: currentColor; }

/* =====================================================================
   PALETA DE ETIQUETAS (compartida, 2026-10-08)
   =====================================================================
   Etiquetas de Clientes y carpetas de Documentación guardan una CLAVE
   (grafito, azul, verde, ambar, rojo, violeta, turquesa) que aquí se traduce a
   tokens del tema: vale en claro y en oscuro y nada del usuario entra en un
   `style=`. `--c` es el color; `--c-ink`, su tinta para texto y trazos
   (≥ 4,5:1 sobre el papel; donde el tema no tiene una, se oscurece hacia el
   texto fuerte). */
[data-etq-color="grafito"]  { --c: var(--gray-500); --c-ink: var(--text-color); }
[data-etq-color="azul"]     { --c: var(--info);     --c-ink: var(--info-ink); }
[data-etq-color="verde"]    { --c: var(--success);  --c-ink: var(--success-ink); }
[data-etq-color="ambar"]    { --c: var(--warning);  --c-ink: var(--warning-ink); }
[data-etq-color="rojo"]     { --c: var(--danger);   --c-ink: var(--danger-ink); }
[data-etq-color="violeta"]  { --c: var(--violet);   --c-ink: color-mix(in srgb, var(--violet) 45%, var(--text-strong)); }
[data-etq-color="turquesa"] { --c: var(--teal);     --c-ink: color-mix(in srgb, var(--teal) 40%, var(--text-strong)); }

/* Ampliación para las CARPETAS de Documentación (dueño, 2026-10-08: «más
   colores para elegir»): la rampa cromática de base.css (`--rampa-*`, con su
   pareja oscura). Las etiquetas de Clientes siguen con las siete de arriba. */
[data-etq-color="naranja"] { --c: var(--rampa-naranja); --c-ink: color-mix(in srgb, var(--rampa-naranja) 60%, var(--text-strong)); }
[data-etq-color="rosa"]    { --c: var(--rampa-rosa);    --c-ink: color-mix(in srgb, var(--rampa-rosa) 60%, var(--text-strong)); }
[data-etq-color="indigo"]  { --c: var(--rampa-indigo);  --c-ink: color-mix(in srgb, var(--rampa-indigo) 60%, var(--text-strong)); }
[data-etq-color="fucsia"]  { --c: var(--rampa-fucsia);  --c-ink: color-mix(in srgb, var(--rampa-fucsia) 60%, var(--text-strong)); }
[data-etq-color="lima"]    { --c: var(--rampa-lima);    --c-ink: color-mix(in srgb, var(--rampa-lima) 45%, var(--text-strong)); }
[data-etq-color="cian"]    { --c: var(--rampa-cian);    --c-ink: color-mix(in srgb, var(--rampa-cian) 50%, var(--text-strong)); }
[data-etq-color="marron"]  { --c: var(--rampa-marron);  --c-ink: color-mix(in srgb, var(--rampa-marron) 60%, var(--text-strong)); }

/* Muestras de color: círculos del color, el elegido con anillo. */
.cetq-colores { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* Borde del texto fuerte al 55 % (≥ 3:1 en los dos temas): la muestra se distingue del papel aunque el
   color sea claro (ámbar, turquesa); la elegida lleva anillo del texto fuerte. */
.cetq-color {
    width: 1.6rem; height: 1.6rem; border-radius: var(--radius-full); cursor: pointer;
    background: var(--c); border: 2px solid var(--surface-raised);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--text-strong) 55%, transparent);
}
.cetq-color[aria-checked="true"] { box-shadow: 0 0 0 2px var(--surface-raised), 0 0 0 4px var(--text-strong); }
.cetq-color:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }

@media (pointer: coarse) {
    .cetq-color { width: 44px; height: 44px; }
}
