/* ============================================================================
   igp-base.css — CSS propio mínimo del POC Ciclo 15
   Stack: Bootstrap 5.3 + htmx + Alpine.js + Bootstrap Icons.
   Construido encima de Bootstrap 5, NO lo reemplaza.

   Paleta IGP corporativa canon:
     - primario:   #f7ac3b (amarillo logo IGP Lechazo CyL)
     - secundario: #5c6670 (gris pizarra)
   ============================================================================ */

:root {
    /* Override variables BS5 con paleta IGP. */
    --bs-primary: #f7ac3b;
    --bs-primary-rgb: 247, 172, 59;
    --bs-secondary: #5c6670;
    --bs-secondary-rgb: 92, 102, 112;

    /* Custom auxiliares (opacidades primario, heredadas del esquema viejo). */
    --igp-primary: #f7ac3b;
    --igp-primary-60: rgba(247, 172, 59, 0.60);
    --igp-primary-30: rgba(247, 172, 59, 0.30);
    --igp-primary-15: rgba(247, 172, 59, 0.15);
    --igp-primary-10: rgba(247, 172, 59, 0.10);
    --igp-primary-07: rgba(247, 172, 59, 0.07);
    --igp-secondary: #5c6670;

    /* Canon de estados de FORM CONTROLS (ver bloque "Form Controls State Canon"
       más abajo). Variables expuestas para que el sistema de diseño / guía de
       estilo pueda referenciarlas sin hardcodear hex. */
    --igp-form-bg-editable: #ffffff;            /* habilitado normal */
    --igp-form-bg-required: var(--igp-primary-10);  /* habilitado obligatorio */
    --igp-form-border-required: var(--igp-primary-60);
    --igp-form-bg-disabled: #e9ecef;            /* BS5 secondary-bg, canonizado */
    --igp-form-color-disabled: var(--igp-secondary);  /* texto sutil disabled */
    --igp-form-border-default: #ced4da;         /* BS5 input border default */

    /* Tipografía: system-ui stack (cero red, instantáneo, moderno). */
    --bs-body-font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
        "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    --bs-body-font-size: 0.9375rem;
    --bs-body-color: #2c3034;
    --bs-body-bg: #f6f7f9;
    /* BS5 hace que `.form-control` herede `--bs-form-control-bg` con fallback a
       `--bs-body-bg`. Sin esta override los inputs se ven del mismo gris que
       el fondo de página → percepción de "input deshabilitado". Forzamos
       blanco canon vía nuestra variable de canon de form controls. */
    --bs-form-control-bg: var(--igp-form-bg-editable, #ffffff);

    /* ----- Paleta semántica de estado del Volante (Ciclo 15 iter 4) -----
       Tonos calibrados saturación media — visibles sin gritar.
       NO son los danger/warning/success directos de Bootstrap (demasiado vivos).
       Mapeo: 0 = pendiente carga (rojo) · 1 = pendiente descarga (naranja)
              2 = pendiente calificar (amarillo) · 3 = calificado (verde). */
    --igp-status-0: #c0524a;   /* rojo terracota muted (pendiente carga) */
    --igp-status-1: #cf7e3d;   /* naranja muted (pendiente descarga) */
    --igp-status-2: #3f7ba3;   /* azul corporativo (pendiente calificar) — FIX6 F7, alineado con --igp-volante-pend-calificar */
    --igp-status-3: #5e8c54;   /* verde olivo muted (calificado) */

    /* Backgrounds correspondientes (8-10% del color, sobre blanco). */
    --igp-status-0-bg: rgba(192, 82, 74, 0.10);
    --igp-status-1-bg: rgba(207, 126, 61, 0.10);
    --igp-status-2-bg: rgba(63, 123, 163, 0.10);   /* FIX6 F7: azul corp. (era amarillo ocre) */
    --igp-status-3-bg: rgba(94, 140, 84, 0.10);

    /* Azul muted para categorías que no encajan en la paleta de estado del volante
       (p. ej. tipos de entidad como cooperativas). Mismo registro muted que los de estado. */
    --igp-blue: #3f7ba3;
    --igp-blue-bg: rgba(63, 123, 163, 0.10);

    /* ----- Paleta semántica de operaciones financieras (canon §X.x: badges precio) -----
       Disponible cross-ERP para cualquier representación de un PRECIO DE COMPRA o
       PRECIO DE VENTA: badges en DataTables, valores destacados en fichas, KPIs, etc.
       - PURCHASE (compra / coste / gasto)  → azul-marino corporativo.
       - SALE     (venta / ingreso)         → verde IGP.
       - TOTAL    (importe total factura/albarán/pedido, ambos lados) → dorado IGP.
       Mismo registro muted (~10% de fondo) que las paletas semánticas existentes. */
    --igp-price-purchase:    #1e6091;
    --igp-price-purchase-bg: rgba(30, 96, 145, 0.10);
    --igp-price-sale:        #2d8659;
    --igp-price-sale-bg:     rgba(45, 134, 89, 0.10);
    --igp-price-total:       #b8860b;
    --igp-price-total-bg:    rgba(184, 134, 11, 0.10);
}

/* ----- LAYOUT GLOBAL ----- */

body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

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

/* Panel propio anclado al viewport. Sin altura acotada el aside se estiraba con
   el alto del documento: la que scrolleaba era la página entera y el último item
   del menú acababa pegado al borde inferior de la ventana, justo donde el
   navegador pinta la burbuja con la URL del enlace bajo el ratón. */
.igp-sidebar {
    width: 240px;
    flex-shrink: 0;
    align-self: flex-start;
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    background-color: #2a2f36;
    color: #d8dde4;
    overflow-y: auto;
    overscroll-behavior: contain;
    transition: margin-left 0.2s ease;
}

/* Colchón que mantiene el último item fuera de la franja de la burbuja de estado
   del navegador cuando el menú se scrollea hasta el final. */
.igp-sidebar nav {
    padding-bottom: 3rem;
}

.igp-sidebar.collapsed {
    margin-left: -240px;
}

/* Encabezado de sección del sidebar — divisor visual entre dominios
   (Trazabilidad IGP, Comercial, Operativa, Administración). No clicable,
   no desplegable: solo "section header" tipográfico que agrupa los items
   inmediatamente debajo. Patrón canon enterprise sidebar (Asana/Linear/Notion).
   Reorganización sidebar C50 H1 (decisión Dave 2026-06-22). */
.igp-sidebar .igp-sidebar-section-header {
    font-size: 0.6875rem;             /* 11px */
    font-weight: 700;                  /* bold — diferencia clara con peso 400 de items */
    text-transform: uppercase;
    letter-spacing: 0.10em;
    color: rgba(247, 172, 59, 0.65);   /* dorado canon sutil — rompe la paleta gris de items */
    padding: 1.5rem 1.25rem 0.55rem;   /* aire generoso arriba para separar visualmente secciones */
    margin: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.10);  /* línea sutil pero perceptible */
    user-select: none;
    cursor: default;
}
/* Primera sección no necesita separador superior (queda pegado al brand). */
.igp-sidebar .igp-sidebar-section-header:first-of-type {
    border-top: none;
    padding-top: 0.85rem;
}

.igp-sidebar .igp-brand {
    display: block;
    padding: 1rem 1.25rem;
    color: var(--igp-primary);
    font-weight: 700;
    text-decoration: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.igp-sidebar .nav-link {
    display: flex;
    align-items: center;
    color: #d8dde4;
    padding: 0.55rem 1.25rem;
    border-left: 3px solid transparent;
    border-radius: 0;
}

/* Las cabeceras de grupo son <button>: despliegan, no navegan — y así el
   navegador tampoco enseña la burbuja de estado al pasar por ellas. */
.igp-sidebar button.nav-link {
    width: 100%;
    text-align: left;
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
    background-color: transparent;
    border-top: 0;
    border-right: 0;
    border-bottom: 0;
}

.igp-sidebar .nav-link:hover,
.igp-sidebar .nav-link[aria-expanded="true"] {
    color: var(--igp-primary);
    background-color: rgba(255, 255, 255, 0.04);
    border-left-color: var(--igp-primary);
}

/* Item activo del menú lateral (deuda C19 + observación C31, fix Ciclo 32).
   Se aplica al leaf con url_name coincidente — `aria-current="page"` semántico
   + clase `.active` ergonómica. El submenú padre se mantiene expandido con
   `.show` + `aria-expanded="true"` (esta última reusa la regla de hover). */
.igp-sidebar .nav-link.active,
.igp-sidebar .nav-link[aria-current="page"] {
    color: #fff;
    background-color: rgba(247, 172, 59, 0.18); /* tinte primario IGP sutil */
    border-left-color: var(--igp-primary);
    font-weight: 600;
}
.igp-sidebar .submenu .nav-link.active,
.igp-sidebar .submenu .nav-link[aria-current="page"] {
    background-color: rgba(247, 172, 59, 0.14);
}

.igp-sidebar .nav-link i {
    width: 1.25rem;
    text-align: center;
    margin-right: 0.5rem;
    flex-shrink: 0;
}

/* Chevron del final empujado a la derecha del nav-link flex (en lugar de
   float: right, que pierde anclaje y baja al wrappear cuando el texto del
   item es largo — caso "Control de Ganaderías", "Centro de Información"). */
.igp-sidebar .nav-link i.float-end {
    margin-left: auto;
    margin-right: 0;
}

.igp-sidebar .submenu {
    list-style: none;
    padding-left: 0;
    background-color: rgba(0, 0, 0, 0.15);
}

.igp-sidebar .submenu .nav-link {
    padding-left: 2.75rem;
    font-size: 0.875rem;
}

.igp-sidebar .submenu .submenu .nav-link {
    padding-left: 3.75rem;
}

.igp-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.igp-topbar {
    background-color: #fff;
    border-bottom: 1px solid #e3e6ea;
    padding: 0.5rem 1rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.igp-topbar .igp-toggle {
    border: none;
    background: transparent;
    color: var(--igp-secondary);
    font-size: 1.25rem;
    padding: 0.25rem 0.5rem;
    cursor: pointer;
}

.igp-topbar .igp-toggle:hover {
    color: var(--igp-primary);
}

.igp-topbar .igp-user {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.igp-company-link {
    color: var(--igp-secondary);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.9375rem;
    transition: background-color 0.15s ease;
}

.igp-company-link:hover {
    color: var(--igp-primary);
    background-color: var(--igp-primary-07);
    text-decoration: none;
}

.igp-year-badge {
    cursor: pointer;
    font-size: 0.875rem;
    padding: 0.35em 0.6em;
}

.igp-content {
    padding: 1.25rem 1.5rem;
    flex: 1;
    min-height: 0;
}

.igp-footer {
    background-color: #fff;
    border-top: 1px solid #e3e6ea;
    padding: 0.5rem 1rem;
    font-size: 0.8125rem;
    color: var(--igp-secondary);
}

/* ----- BREADCRUMB DEL CONTENT-TITLE ----- */

.igp-page-header {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
    padding-bottom: 0.75rem;
    border-bottom: 2px solid var(--igp-primary-30);
}

.igp-page-header h1 {
    font-size: 1.25rem;
    margin: 0;
    color: var(--igp-secondary);
    font-weight: 600;
}

.igp-page-header h1 i {
    color: var(--igp-primary);
    margin-right: 0.5rem;
}

/* ----- CABECERA DE APARTADO (dashboards / index con paneles KPI) ----- */
/* Subtitulo de seccion para agrupar paneles en "apartados". Mas ligero que
   .igp-page-header (es un nivel por debajo). Icono en ambar, texto en pizarra. */
.igp-section-title {
    display: flex;
    align-items: center;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--igp-secondary);
    margin: 0 0 0.85rem;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--igp-primary-30);
}

.igp-section-title i {
    color: var(--igp-primary);
    margin-right: 0.5rem;
}

/* Título de apartado enlazable (Entidades / Volantes en el index): blindaje contra la
   global a:hover (subraya + tiñe). En hover el texto pasa a ámbar como afordancia de clic. */
.igp-section-title a,
.igp-section-title a:hover,
.igp-section-title a:focus {
    text-decoration: none;
    color: inherit;
}

.igp-section-title a:hover {
    color: var(--igp-primary);
}

/* Logo de empresa en la cabecera de la ficha (company.html). Tamaño contenido. */
.igp-company-logo {
    max-height: 72px;
    max-width: 180px;
    width: auto;
    object-fit: contain;
}

/* Afordancia de clic para spans accionables (p. ej. toggle IVA #changeIntraComVAT). */
.igp-clickable {
    cursor: pointer;
}

/* Campo de fichero (logo) — NO usar form-control sobre el ClearableFileInput (apelotona).
   Separa en líneas legibles el "Actualmente: <enlace>", el checkbox de limpiar y el file input. */
.igp-file-field {
    font-size: 0.875rem;
    color: var(--bs-body-color);
}
.igp-file-field a {
    font-weight: 500;
}
.igp-file-field input[type="checkbox"] {
    margin-left: 0.5rem;
    margin-right: 0.25rem;
    vertical-align: middle;
}
.igp-file-field input[type="file"] {
    display: block;
    width: 100%;
    margin-top: 0.5rem;
    padding: 0.375rem 0.5rem;
    border: 1px solid #ced4da;
    border-radius: 0.375rem;
    background-color: #fff;
    font-size: 0.875rem;
}

/* Input de "etiqueta" editable en los input-group Bloque/Escalera/Piso/Puerta:
   atenuado para distinguirlo del input del número (que va en color normal). */
.igp-label-input {
    color: var(--igp-secondary);
    background-color: #f8f9fa;
}
.igp-label-input::placeholder {
    font-style: italic;
}

/* Árbol de centro padre dentro del modal de centro: acota altura + scroll si es alto. */
#divCentresTreeForm {
    max-height: 300px;
    overflow-y: auto;
}

/* ===== Card de apartado con cabecera (formularios company/centre, maqueta C50). =====
   OJO: .igp-section-title existe global (índice, con flex+border-bottom); aquí se reusa
   dentro de .igp-section-header y se resetean esas props heredadas. */
.igp-section-card {
    background: #fff;
    border: 1px solid #e6e8eb;
    border-radius: 0.5rem;
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
    margin-bottom: 1.25rem;
}
.igp-section-card .igp-section-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.65rem 1rem;
    background: var(--igp-primary-07);
    border-bottom: 1px solid var(--igp-primary-30);
    border-radius: 0.5rem 0.5rem 0 0;
}
.igp-section-card .igp-section-header > i {
    color: var(--igp-primary);
    font-size: 1rem;
}
.igp-section-card .igp-section-header .igp-section-title {
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--igp-secondary);
    margin: 0;
    display: block;       /* reset del .igp-section-title global (flex) */
    padding: 0;           /* reset padding-bottom global */
    border: 0;            /* reset border-bottom global */
}
.igp-section-card .card-body {
    padding: 1.1rem 1.1rem 1.25rem;
}

/* Cabeceras de apartado coloreadas por rol IGP (forms del volante, C50 2/2).
   Por defecto .igp-section-header va en ámbar primary; estos modifiers la tiñen
   con el bg suave del rol y pintan el pictograma con el color pleno del rol —
   Emisión=Ganadero (terracota), Carga=Transportista (ocre), Descarga=Matadero
   (gris), Calificación=Veedor (verde olivo). NO usar primary aquí: el dorado se
   reserva para "modificación manual" en el feed. */
.igp-section-header.igp-role-g { background: var(--igp-role-g-bg); border-bottom-color: rgba(192, 82, 74, 0.30); }
.igp-section-header.igp-role-g > i { color: var(--igp-role-g); }
.igp-section-header.igp-role-t { background: var(--igp-role-t-bg); border-bottom-color: rgba(203, 158, 50, 0.30); }
.igp-section-header.igp-role-t > i { color: var(--igp-role-t); }
.igp-section-header.igp-role-m { background: var(--igp-role-m-bg); border-bottom-color: rgba(108, 116, 128, 0.30); }
.igp-section-header.igp-role-m > i { color: var(--igp-role-m); }
.igp-section-header.igp-role-v { background: var(--igp-role-v-bg); border-bottom-color: rgba(94, 140, 84, 0.30); }
.igp-section-header.igp-role-v > i { color: var(--igp-role-v); }

/* Pieza D FIX ganaderias-canon — respiración de la ficha de ganadería
   (.ganaderia-detail, inyectada en #genericModal). El contenido cuelga directo
   de .igp-section-card sin .card-body, así que el padding se da a los hijos que
   NO son la cabecera (sin reorganizar estructura). Acotado a .ganaderia-detail
   para no afectar al resto de cards del ERP. */
.ganaderia-detail .igp-section-card > :not(.igp-section-header) {
    padding-left: 1.1rem;
    padding-right: 1.1rem;
}
.ganaderia-detail .igp-section-card > .igp-section-header + * {
    padding-top: 0.9rem;
}
.ganaderia-detail .igp-section-card > :last-child {
    padding-bottom: 1.1rem;
}
.ganaderia-detail dl.row {
    row-gap: 6px;
}

/* ============================================================================
   FORM CONTROLS STATE CANON
   ============================================================================
   Tres estados visualmente inequívocos para CUALQUIER control de formulario
   (input, select, textarea, .form-control, .form-select, wrapper select2):

     1. Editable normal      → fondo blanco              · texto normal
     2. Editable obligatorio → fondo ámbar 10%           · borde ámbar 60% · asterisco label
     3. Disabled             → fondo gris #e9ecef        · texto gris sutil --igp-secondary · cursor not-allowed

   Las variables canónicas viven en :root (--igp-form-bg-*, --igp-form-color-*).
   Cualquier override puntual debe respetar estos tres estados.

   Documentado en docs/manuals/10_guia_estilo_visual.md § Form Controls State Canon.
   ============================================================================ */

/* --- Estado 1: editable normal --------------------------------------------
   BS5 hace que `.form-control` y `.form-select` hereden de `--bs-form-control-bg`
   con fallback a `--bs-body-bg`. Como el body del proyecto es gris (#f6f7f9),
   los inputs salían del mismo gris que la página → percepción de "input
   deshabilitado" cuando en realidad eran editables. Forzamos blanco canon
   en una sola regla. La específica de :focus añade halo ámbar pero NO cambia
   el bg (la sensación de "se vuelve blanco al focus" era el halo). */
.form-control,
.form-select {
    background-color: var(--igp-form-bg-editable);
}

/* --- Estado 2: editable obligatorio ----------------------------------------
   Sin !important: igp-base.css carga tras bootstrap, gana por orden cascada. */
.form-control-required {
    background-color: var(--igp-form-bg-required);
    border-color: var(--igp-form-border-required);
}
.form-control-required:focus {
    border-color: var(--igp-primary);
    box-shadow: 0 0 0 0.2rem var(--igp-primary-15);
}
.form-label-required::after {
    content: "\00a0*";
    color: var(--igp-primary);
    font-weight: 700;
}

/* --- Estado 3: disabled uniforme -------------------------------------------
   BS5 ya pinta fondo gris por defecto (var(--bs-secondary-bg) = #e9ecef) pero
   NO uniformiza el color del texto — lo deja al body. El resultado: inputs
   habilitados y deshabilitados se distinguían solo por el fondo, con poca
   diferencia perceptiva. Forzamos también el texto a --igp-secondary para
   que la deshabilitación se lea inequívoca, igual que el rendered de un
   select2 disabled (ya canonizado más abajo).

   Cubrimos tanto los selectores nativos (:disabled) como las clases BS5
   (.form-control:disabled, .form-select:disabled), por si algún día llega
   un control que use la clase sin el atributo. */
input:disabled,
select:disabled,
textarea:disabled,
.form-control:disabled,
.form-select:disabled {
    background-color: var(--igp-form-bg-disabled);
    color: var(--igp-form-color-disabled);
    border-color: var(--igp-form-border-default);
}
/* Placeholder en disabled = igual de tenue que el texto. */
input:disabled::placeholder,
textarea:disabled::placeholder {
    color: var(--igp-form-color-disabled);
    opacity: 0.7;
}

/* (Las reglas de disabled para wrapper select2 viven más abajo en el bloque
    "Estado disabled del wrapper select2" — comparten paleta vía variables.) */

/* Filas de formulario: alinear los controles por abajo. Si una etiqueta larga
   (p. ej. "Código Postal *") salta a 2 líneas en una columna estrecha, los
   inputs de la fila siguen cuadrando en lugar de descolgarse. */
.igp-section-card .card-body .row {
    align-items: flex-end;
}

/* Avisos de vigencia (#GanaderoActivo, #OperadorActivo, #VeedorActivo en
   Form B volante edición) — esconder cuando están vacíos para no robar
   altura en la rejilla cuando no hay nada que avisar. Sin esto, el `<p>`
   vacío dentro de su `<div class="col-12">` rompía el ritmo del `g-3` con
   un hueco visible irregular. Aplica solo dentro de .igp-form para no
   afectar a otros usos del id. */
.igp-form p[id$="Activo"]:empty {
    display: none;
}

/* SweetAlert2 v11: botón de confirmar en el ámbar de marca (uniforme en TODOS
   los swal del ERP, no el azul por defecto de v11). El texto blanco hereda del
   estilo de v11 y casa con los btn-primary de la app. */
.swal2-styled.swal2-confirm {
    background-color: var(--igp-primary) !important;
}
.swal2-styled.swal2-confirm:focus {
    box-shadow: 0 0 0 3px var(--igp-primary-30) !important;
}

/* ===== select2 single homogéneo con los <select> nativos (mismo alto/borde/radio/caret). =====
   select2.min.css carga ANTES que igp-base.css → se puede overridear. El caret es el MISMO
   triángulo relleno #5c6670 que usan los <select> nativos (regla "chevron BS5" de este fichero).
   Cambio ERP-WIDE (homogeneidad buscada).

   ALTURA — usar la MISMA fórmula que `.form-select` BS5 (`calc(1.5em + 0.75rem + 2px)` ≈ 38px)
   en lugar de un height fijo de 38px. Con `38px` fijo se ven filas desniveladas cuando el
   form-select nativo renderiza con descenders ligeramente distintos del Select2 (font-rendering,
   contenido con g/p/j, etc.) — usar la fórmula garantiza identidad pixel a pixel. */
/* Especificidad doble (0,4,0) — fuerza la regla por encima de cualquier override
   residual del select2.min.css base. !important en height/min-height para descartar
   completamente la cascada (Select2 plugin a veces aplica reglas con orden incierto
   tras inicialización JS). */
.select2-container.select2-container--default .select2-selection.select2-selection--single {
    box-sizing: border-box;
    /* OJO: usar rem (no em) — Select2 puede heredar font-size del contexto y dar
       altura distinta a la del .form-select BS5 (que sí fija font-size: 1rem propio).
       1.5rem (24px) + 0.75rem (12px) + 2px border = 38px FIJO independent del contexto. */
    height: calc(1.5rem + 0.75rem + 2px) !important;
    min-height: calc(1.5rem + 0.75rem + 2px) !important;
    font-size: 1rem;
    line-height: 1.5;
    padding: 0.375rem 2rem 0.375rem 0.75rem;
    border: 1px solid #ced4da;
    border-radius: 0.375rem;
    display: flex;
    align-items: center;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%235c6670'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.65rem center;
    background-size: 12px 12px;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
    padding: 0;
    line-height: 1.5;
    color: #212529;
    /* Flex row para que la x del clear (`.select2-selection__clear`) se centre
       verticalmente con el texto seleccionado. El plugin Select2 la deja float:right
       + height:26px por defecto → se desalinea. Con flex + align-items:center
       + reset del float en el clear, la x queda a la misma altura que el texto. */
    display: flex;
    align-items: center;
    width: 100%;
    height: 100%;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
    display: none;
}
/* El tinte de obligatorio (lo añade el JS al .select2-selection__rendered interno):
   tintamos toda la caja vía :has() para que no quede solo el texto. El elemento
   interno `__rendered` lleva también `form-control-required`, pero la regla base
   `.form-control-required { background-color: var(--igp-primary-10); }` le pinta
   un fondo PROPIO igual al del padre. Apilados ambos no se notan, PERO el `padding`
   del padre crea un offset que muestra una "tirita" del rendered interno con
   bordes redondeados sutiles → efecto de "doble capa" feo. Fix: forzar transparent
   en el rendered interno para que sea el padre (`--single`) quien manda el bg.
   Por el mismo motivo el chevron debe quedarse al fondo del PADRE, no del hijo. */
.select2-container--default .select2-selection--single:has(.form-control-required) {
    background-color: var(--igp-primary-10);
    border-color: var(--igp-primary-60);
}
.select2-container--default .select2-selection--single:has(.form-control-required) .select2-selection__rendered.form-control-required {
    background-color: transparent;
    border-color: transparent;
}
/* Cuando el JS añade `form-control-required` DIRECTAMENTE al wrapper visible
   (helper igpSelect2MarkRequired en forms con select2 dentro de AJAX async,
   donde el `:has()` no era fiable), suprimir el halo del :focus heredado de
   la regla .form-control-required:focus arriba. Los wrappers select2 tienen
   tabindex=0 y quedan con focus residual tras autoselect programático,
   pintando un halo ámbar 0.2rem feo sobre el wrapper. Select2 ya tiene su
   indicador propio de focus/dropdown abierto. */
.select2-container--default .select2-selection--single.form-control-required:focus,
.select2-container--default .select2-selection--single.form-control-required:focus-visible {
    box-shadow: none;
}
/* Preservar el chevron en wrappers select2 con form-control-required.
   Regla defensiva — el `background-color` que pinta `.form-control-required`
   en algunos casos hace shorthand-override del `background-image` SVG del
   chevron del wrapper select2 base. Re-aplicamos aquí con specificity alta
   (3 clases vs 1) + !important para garantizar que el triángulo del chevron
   sobrevive sea cual sea el orden de cascada. Mismo SVG que la regla base. */
.select2-container--default .select2-selection--single.form-control-required {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%235c6670'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E") !important;
    background-repeat: no-repeat !important;
    background-position: right 0.65rem center !important;
    background-size: 12px 12px !important;
}

/* Estado disabled del wrapper select2 — paleta canon (--igp-form-*).
   Misma paleta que los inputs nativos disabled (ver "FORM CONTROLS STATE
   CANON" más arriba) para que un select y un select2 deshabilitados se vean
   idénticos. Cubrimos también la combinación disabled + form-control-required
   (que no debería ocurrir tras el guard del helper igpAjaxSelect, pero si
   llega de fuera el disabled MANDA: campo no editable, marca de obligatorio
   sin sentido).

   Specificity: .select2-container--default + .select2-container--disabled +
   .select2-selection--single = (0,3,0), igualada por la base. Usamos
   !important para imponerse sobre el :has(.form-control-required) que es
   (0,4,1). Reforzamos el rendered interno (texto + chevron). */
.select2-container--default.select2-container--disabled .select2-selection--single,
.select2-container--default.select2-container--disabled .select2-selection--single.form-control-required,
.select2-container--default.select2-container--disabled .select2-selection--single:has(.form-control-required) {
    background-color: var(--igp-form-bg-disabled) !important;
    border-color: var(--igp-form-border-default) !important;
    cursor: not-allowed !important;
    opacity: 1;
}
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__rendered,
.select2-container--default.select2-container--disabled .select2-selection--single:has(.form-control-required) .select2-selection__rendered.form-control-required {
    color: var(--igp-form-color-disabled) !important;
    background-color: transparent !important;
}
/* Y el equivalente para <select class="form-select form-control-required"> NATIVO.
   Bootstrap usa una flecha outlined por defecto via CSS variable
   `--bs-form-select-bg-img`, pero el shorthand `background:` del legacy custom_logistics
   también lo wipea. Re-aplicamos el MISMO triángulo del select2 para que todos los
   dropdowns obligatorios del form (select2 y nativos) tengan la misma flecha. */
.form-select.form-control-required {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%235c6670'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E") !important;
    background-repeat: no-repeat !important;
    background-position: right 0.65rem center !important;
    background-size: 12px 12px !important;
}

/* ===== Select2 clear button — ERP-wide (C50).
   Select2 renderiza `<button class="select2-selection__clear">×</button>` DENTRO
   de `.select2-selection__rendered` y por defecto lo pega al texto seleccionado
   sin respiración. Solución: separar la "x" del texto y del chevron con margen
   propio, y hacer visible el hit-area con un chip circular sutil en hover.
   Aplica a single (`--single`) y a items multi (`__choice__remove`). ===== */
.select2-container--default .select2-selection__clear {
    /* Reset del float:right + height:26px del plugin (deja la x baseline).
       Flex-center + order:2 la posiciona a la derecha del texto (después del contenido
       flex del padre `.select2-selection__rendered`) y la centra vertical. margin-left:auto
       la empuja al final; margin-right:10px la separa del chevron. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    float: none !important;
    height: 100%;
    order: 2;
    margin-left: auto;
    margin-right: 10px;
    padding: 0 6px;
    color: var(--igp-secondary);
    font-weight: 700;
    line-height: 1;
    border-radius: 999px;
    background: transparent;
    border: none;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}
/* El span aria-hidden interno (`<span aria-hidden="true">×</span>`) también centrado. */
.select2-container--default .select2-selection__clear > * {
    line-height: 1;
    display: inline-block;
    vertical-align: middle;
}
.select2-container--default .select2-selection__clear:hover,
.select2-container--default .select2-selection__clear:focus-visible {
    background: rgba(192, 82, 74, 0.12);  /* var(--igp-status-0-bg) equivalente */
    color: var(--igp-status-0);
    outline: none;
}
/* Multi: las x de cada chip también con respiración (sin cambiar layout). */
.select2-container--default .select2-selection__choice__remove {
    margin-right: 6px;
    padding: 0 4px;
    border-radius: 999px;
    transition: background 0.15s ease, color 0.15s ease;
}
.select2-container--default .select2-selection__choice__remove:hover,
.select2-container--default .select2-selection__choice__remove:focus-visible {
    background: rgba(192, 82, 74, 0.12);
    color: var(--igp-status-0);
}

/* ===== Logo con miniatura (extiende .igp-file-field existente). ===== */
.igp-file-field .igp-logo-current {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}
.igp-file-field .igp-logo-thumb {
    width: 52px;
    height: 52px;
    object-fit: contain;
    border: 1px solid #e6e8eb;
    border-radius: 0.375rem;
    background: #fff;
    padding: 3px;
}
.igp-file-field .igp-logo-meta a {
    font-weight: 500;
}

/* ----- AJUSTES BOOTSTRAP 5 PARA EL POC ----- */

/* Card del POC = panel del viejo: sombra suave + cabecera neutra. */
.card {
    border: 1px solid #e3e6ea;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.card-header {
    background-color: #f1f3f5;
    border-bottom: 1px solid var(--igp-primary-30);
    font-weight: 600;
    color: var(--igp-secondary);
}

/* Tablas: ligeramente más cómodas que el default BS5. */
.table > :not(caption) > * > * {
    padding: 0.65rem 0.75rem;
}

.table thead th {
    border-bottom: 2px solid var(--igp-primary);
    color: var(--igp-secondary);
    font-weight: 600;
    font-size: 0.875rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.table-hover > tbody > tr:hover > * {
    background-color: var(--igp-primary-07);
}

/* DataTables wrapper: respetar nuestras cabeceras. */
table.dataTable thead th {
    border-bottom: 2px solid var(--igp-primary) !important;
}

/* Formularios: foco en color IGP. */
.form-control:focus,
.form-select:focus {
    border-color: var(--igp-primary);
    box-shadow: 0 0 0 0.2rem var(--igp-primary-30);
}

.form-label {
    font-weight: 500;
    color: var(--igp-secondary);
    margin-bottom: 0.25rem;
}

/* Botones primarios IGP.
   C48: disabled-* vars precalculadas (tinte claro del propio color + texto gris).
   Sin éstas, BS5 caía al disabled azul (#0d6efd) en .btn-primary y .btn-outline-primary
   pese a redefinir el activo a ámbar. Mismo patrón para todas las variantes IGP. */
.btn-primary {
    --bs-btn-bg: var(--igp-primary);
    --bs-btn-border-color: var(--igp-primary);
    --bs-btn-hover-bg: #e69a26;
    --bs-btn-hover-border-color: #e69a26;
    --bs-btn-active-bg: #cf8a20;
    --bs-btn-active-border-color: #cf8a20;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #fef2e0;
    --bs-btn-disabled-border-color: #fcdfb5;
}

.btn-secondary {
    --bs-btn-bg: var(--igp-secondary);
    --bs-btn-border-color: var(--igp-secondary);
    --bs-btn-hover-bg: #4a525a;
    --bs-btn-hover-border-color: #4a525a;
    --bs-btn-active-bg: #3a4148;
    --bs-btn-active-border-color: #3a4148;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #e5e7e8;
    --bs-btn-disabled-border-color: #c1c5c9;
}

.btn-outline-primary {
    --bs-btn-color: var(--igp-primary);
    --bs-btn-border-color: var(--igp-primary);
    --bs-btn-hover-bg: var(--igp-primary);
    --bs-btn-hover-border-color: var(--igp-primary);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--igp-primary);
    --bs-btn-active-border-color: var(--igp-primary);
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #fef2e0;
    --bs-btn-disabled-border-color: #fcdfb5;
}

.btn-outline-secondary {
    --bs-btn-color: var(--igp-secondary);
    --bs-btn-border-color: var(--igp-secondary);
    --bs-btn-hover-bg: var(--igp-secondary);
    --bs-btn-hover-border-color: var(--igp-secondary);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--igp-secondary);
    --bs-btn-active-border-color: var(--igp-secondary);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #e5e7e8;
    --bs-btn-disabled-border-color: #c1c5c9;
}

/* Badge primaria con paleta IGP. */
.badge.text-bg-primary {
    background-color: var(--igp-primary) !important;
    color: #fff !important;
}

/* Botón de acción "ghost" (canon ERP para acciones de icono en cabeceras de
   card / fichas). Fondo transparente con borde sutil, icono mantiene su color
   (`text-primary` dorado / `text-danger` rojo / `text-secondary` gris), hover
   solo cambia el fondo a un gris muy claro. Evita el problema clásico de
   `btn-outline-X` BS5 cuyo hover invierte fondo→color del bordeY texto→#fff,
   resultando en blanco-sobre-color con contraste pobre (caso reportado:
   blanco sobre dorado IGP era ilegible). El icono nunca cambia de color, así
   que siempre se distingue. */
.btn-icon-action {
    background-color: transparent;
    border: 1px solid var(--bs-border-color);
    padding: 0.125rem 0.4rem;
    line-height: 1.4;
    transition: background-color 0.12s ease;
}
.btn-icon-action:hover,
.btn-icon-action:focus {
    background-color: var(--bs-secondary-bg-subtle);
    border-color: var(--bs-border-color);
}
.btn-icon-action:focus { box-shadow: 0 0 0 0.2rem rgba(0, 0, 0, 0.06); }
.btn-icon-action[disabled] { opacity: 0.5; }

/* Links a color IGP. */
a {
    color: var(--igp-primary);
    text-decoration: none;
}

a:hover {
    color: #cf8a20;
    text-decoration: underline;
}

/* Indicador de carga htmx. */
.htmx-indicator {
    display: none;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
    display: inline-block;
}

/* Estado de volante (chips por color). Paleta semántica del proyecto. */
.igp-status {
    display: inline-block;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 500;
    color: #fff;
    white-space: nowrap;
}

.igp-status-0 { background-color: var(--igp-status-0); }
.igp-status-1 { background-color: var(--igp-volante-pend-descarga); }  /* FIX6 F7: ocre volante (desacoplado de --igp-status-1, que mantiene .color-partial naranja ERP-wide) */
.igp-status-2 { background-color: var(--igp-status-2); }
.igp-status-3 { background-color: var(--igp-status-3); }

/* Variantes "copia" — fondo un punto más claro (mix con blanco 55%) + borde punteado
   del color original. Uso canónico: facturas duplicadas (CO/CR/CC) para diferenciar
   visualmente de las originales (OO/OR/OC) manteniendo la familia cromática. C50. */
.igp-status-copy-0,
.igp-status-copy-1,
.igp-status-copy-2,
.igp-status-copy-3 {
    color: #fff;
    border: 1.5px dashed transparent;
    background-clip: padding-box;
}
.igp-status-copy-0 {
    background-color: color-mix(in srgb, var(--igp-status-0) 55%, #fff);
    border-color: var(--igp-status-0);
}
.igp-status-copy-1 {
    background-color: color-mix(in srgb, var(--igp-volante-pend-descarga) 55%, #fff);
    border-color: var(--igp-volante-pend-descarga);
}
.igp-status-copy-2 {
    background-color: color-mix(in srgb, var(--igp-status-2) 55%, #fff);
    border-color: var(--igp-status-2);
}
.igp-status-copy-3 {
    background-color: color-mix(in srgb, var(--igp-status-3) 55%, #fff);
    border-color: var(--igp-status-3);
}

/* Avisos de la campana y de la bandeja (app notificaciones).

   1. Bootstrap pinta `.dropdown-item:active` con el azul de menu seleccionado, y
      como la navegacion tarda, el aviso pulsado se queda azul entero. Aqui el item
      no es una opcion de menu: es una tarjeta que lleva a otro sitio.
   2. Franja izquierda con el color del estado del volante al que mira el aviso, y
      punto del mismo color en la tabla. Reutilizan la paleta semantica de
      `.igp-status-*`, para que un aviso y su volante hablen del mismo color. */
.igp-notif-item {
    border-left: 4px solid transparent;
    transition: background-color .12s ease-in-out;
}

/* Sin volante detras -mensajes del Consejo, eliminaciones- el gris de Bootstrap,
   pero marcado al pulsar: el de hover casi no se ve sobre el fondo del desplegable. */
.igp-notif-item.dropdown-item:focus,
.igp-notif-item.dropdown-item:hover {
    color: inherit;
    background-color: #f1f3f5;
}

.igp-notif-item.dropdown-item:active,
.igp-notif-item.dropdown-item.active {
    color: inherit;
    background-color: #dde1e5;
}

.igp-notif-estado-0 { border-left-color: var(--igp-status-0); }
.igp-notif-estado-1 { border-left-color: var(--igp-volante-pend-descarga); }
.igp-notif-estado-2 { border-left-color: var(--igp-status-2); }
.igp-notif-estado-3 { border-left-color: var(--igp-status-3); }

/* Con volante detras, el aviso se tine de SU color: un roce apenas lo insinua y el
   clic lo deja claro, sin salirse de la paleta del estado. */
.igp-notif-estado-0.dropdown-item:focus,
.igp-notif-estado-0.dropdown-item:hover { background-color: color-mix(in srgb, var(--igp-status-0) 12%, #fff); }
.igp-notif-estado-1.dropdown-item:focus,
.igp-notif-estado-1.dropdown-item:hover { background-color: color-mix(in srgb, var(--igp-volante-pend-descarga) 12%, #fff); }
.igp-notif-estado-2.dropdown-item:focus,
.igp-notif-estado-2.dropdown-item:hover { background-color: color-mix(in srgb, var(--igp-status-2) 12%, #fff); }
.igp-notif-estado-3.dropdown-item:focus,
.igp-notif-estado-3.dropdown-item:hover { background-color: color-mix(in srgb, var(--igp-status-3) 12%, #fff); }

.igp-notif-estado-0.dropdown-item:active,
.igp-notif-estado-0.dropdown-item.active { background-color: color-mix(in srgb, var(--igp-status-0) 30%, #fff); }
.igp-notif-estado-1.dropdown-item:active,
.igp-notif-estado-1.dropdown-item.active { background-color: color-mix(in srgb, var(--igp-volante-pend-descarga) 30%, #fff); }
.igp-notif-estado-2.dropdown-item:active,
.igp-notif-estado-2.dropdown-item.active { background-color: color-mix(in srgb, var(--igp-status-2) 30%, #fff); }
.igp-notif-estado-3.dropdown-item:active,
.igp-notif-estado-3.dropdown-item.active { background-color: color-mix(in srgb, var(--igp-status-3) 30%, #fff); }

/* Punto de estado de la bandeja. El color lo pone `.igp-status-N`. */
.igp-notif-dot {
    display: inline-block;
    width: 0.55rem;
    height: 0.55rem;
    margin-right: 0.4rem;
    border-radius: 50%;
    vertical-align: middle;
}

/* Overrides para las clases del stack viejo emitidas por subtemplates AJAX
   (`volantes_table.html`, `volante_detail.html` legacy, etc.) — armonizar con la
   paleta semántica IGP para que los iconos de la columna "Estado" del DataTable
   y otros indicadores hereden el mismo lenguaje cromático. */
.color-pend-carga      { color: var(--igp-status-0) !important; }
.color-pend-descarga   { color: var(--igp-volante-pend-descarga) !important; }
.color-pend-calificar  { color: var(--igp-status-2) !important; }
.color-calificado      { color: var(--igp-status-3) !important; }
.color-times,
.color-pending         { color: var(--igp-status-0) !important; }
.color-check,
.color-complete        { color: var(--igp-status-3) !important; }
.color-partial         { color: var(--igp-status-2) !important; }

/* Login page wrapper. */
.igp-login-wrapper {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #f6f7f9 0%, var(--igp-primary-10) 100%);
    padding: 2rem 1rem;
}

.igp-login-card {
    width: 100%;
    max-width: 380px;
}

.igp-login-card .card-header {
    text-align: center;
    background-color: var(--igp-primary);
    color: #fff;
    padding: 1.25rem 1rem;
    border-bottom: none;
}

.igp-login-card .card-header h1 {
    margin: 0;
    font-size: 1.25rem;
    color: #fff;
}

/* Responsive: sidebar oculto en móvil por defecto, se abre con offcanvas-like. */
@media (max-width: 767.98px) {
    .igp-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        height: auto;
        z-index: 1040;
    }
    .igp-sidebar:not(.open) {
        margin-left: -240px;
    }
}

/* ----- KPIs del listado de Volantes (sustituyen los panel-pend-* del stack viejo) ----- */

.igp-kpi {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 0.875rem 1rem;
    background-color: #fff;
    border: 1px solid #e3e6ea;
    border-left: 4px solid var(--igp-kpi-color, var(--igp-secondary));
    border-radius: 6px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
    user-select: none;
    height: 100%;
}

.igp-kpi:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.06);
}

.igp-kpi:focus-visible {
    outline: 2px solid var(--igp-primary);
    outline-offset: 2px;
}

.igp-kpi-active {
    background-color: var(--igp-kpi-bg, var(--igp-primary-07));
    border-left-width: 6px;
}

.igp-kpi-icon {
    font-size: 1.875rem;
    line-height: 1;
    color: var(--igp-kpi-color, var(--igp-secondary));
    flex-shrink: 0;
    width: 2.25rem;
    text-align: center;
    opacity: 0.85;
}

.igp-kpi-body {
    flex: 1;
    min-width: 0;
}

.igp-kpi-title {
    font-size: 0.8125rem;
    color: var(--igp-secondary);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.125rem;
    line-height: 1.2;
}

.igp-kpi-number {
    font-size: 1.625rem;
    font-weight: 600;
    color: var(--igp-kpi-color, var(--bs-body-color));
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
    margin-bottom: 0.125rem;
}

.igp-kpi-sub {
    font-size: 0.72rem;
    color: var(--bs-body-color);
    line-height: 1.3;
    /* Grid 2x2 forzado: 4 sub-stats se acomodan en 2 columnas × 2 filas. */
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 8px;
    row-gap: 2px;
    align-items: baseline;
    margin-top: 4px;
}
/* Pareja "número + label" en el sub-stats. Cada celda del grid contiene una pareja
   que no se parte (white-space: nowrap). */
.igp-kpi-sub .kpi-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.igp-kpi-sub strong {
    font-variant-numeric: tabular-nums;
    color: var(--igp-secondary);
}

.igp-kpi-divider {
    color: #c4cad1;
    margin: 0;
    display: none; /* Sustituido por gap del flex; sin divisor visual ya. */
}

/* Lista compacta mes → importe dentro de un KPI (p. ej. "pendiente de registro de
   cobro" por mes). Grid de 2 columnas ajustado al contenido (width:max-content) para
   que el importe quede pegado al nombre del mes, no separado al ancho de la card. */
.igp-kpi-monthlist {
    display: grid;
    grid-template-columns: auto auto;
    column-gap: 1.25rem;
    row-gap: 2px;
    width: max-content;
    max-width: 100%;
    margin-top: 2px;
    font-size: 0.82rem;
}
.igp-kpi-monthlist strong { font-variant-numeric: tabular-nums; }

/* Plan de vencimientos dentro de la ficha de un vencimiento: filas
   fecha · importe · estado; la fila del vencimiento en curso se resalta. */
.igp-inst-plan-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.5rem;
    border-radius: 4px;
    font-size: 0.85rem;
}
.igp-inst-plan-row + .igp-inst-plan-row { margin-top: 1px; }
.igp-inst-plan-row.is-current {
    background: var(--igp-primary-07, rgba(198, 156, 74, 0.10));
    font-weight: 600;
}

/* Colvis "Columnas Visibles": el layout multi-columna (column-count) de DataTables
   monta las etiquetas largas ("Veedor / Ganadería", "Pedido de Venta") sobre la
   columna vecina, porque cada columna tiene un ancho fijo estrecho. Forzamos una
   sola columna (lista vertical): el ancho del desplegable sobra para cualquier
   etiqueta y las opciones nunca se solapan. Se conserva el centrado de DataTables. */
div.dt-button-collection.two-column > :last-child,
div.dt-button-collection.three-column > :last-child,
div.dt-button-collection.four-column > :last-child {
    column-count: 1 !important;
}
div.dt-button-collection .dt-button {
    white-space: normal;
}

/* Paleta semántica de estados (alineada con --igp-status-* + .igp-status-*). */
.igp-kpi-0 { --igp-kpi-color: var(--igp-status-0); --igp-kpi-bg: var(--igp-status-0-bg); }
.igp-kpi-1 { --igp-kpi-color: var(--igp-volante-pend-descarga); --igp-kpi-bg: var(--igp-volante-pend-descarga-bg); }  /* FIX6 F7: ocre volante */
.igp-kpi-2 { --igp-kpi-color: var(--igp-status-2); --igp-kpi-bg: var(--igp-status-2-bg); }
.igp-kpi-3 { --igp-kpi-color: var(--igp-status-3); --igp-kpi-bg: var(--igp-status-3-bg); }
/* Categórico (no de estado): azul muted, p. ej. cooperativas en el panel de entidades. */
.igp-kpi-blue { --igp-kpi-color: var(--igp-blue); --igp-kpi-bg: var(--igp-blue-bg); }

/* Paleta canónica de actores IGP (FIX5) — KPIs que representan a uno de los 5
   actores usan estas clases, NO las .igp-kpi-0/1/2/3/blue (legacy, reservadas
   para estados/categorías no-actor). */
.igp-kpi-role-g { --igp-kpi-color: var(--igp-role-g); --igp-kpi-bg: var(--igp-role-g-bg); }
.igp-kpi-role-t { --igp-kpi-color: var(--igp-role-t); --igp-kpi-bg: var(--igp-role-t-bg); }
.igp-kpi-role-c { --igp-kpi-color: var(--igp-role-c); --igp-kpi-bg: var(--igp-role-c-bg); }
.igp-kpi-role-o { --igp-kpi-color: var(--igp-role-o); --igp-kpi-bg: var(--igp-role-o-bg); }
.igp-kpi-role-v { --igp-kpi-color: var(--igp-role-v); --igp-kpi-bg: var(--igp-role-v-bg); }
.igp-kpi-role-m { --igp-kpi-color: var(--igp-role-m); --igp-kpi-bg: var(--igp-role-m-bg); }

/* KPI informativo (no accionable): mismo look que .igp-kpi pero sin afordancia de clic. */
.igp-kpi-static { cursor: default; }
.igp-kpi-static:hover { transform: none; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); }

/* ----- Ficha de detalle / perfil (estándar tipográfico C50). Reutilizable en cualquier
   pantalla de detalle: título de sección en mayúsculas sutiles + pares etiqueta/valor +
   chip de cabecera. ----- */
.igp-detail-title {
    font-size: 0.8rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--igp-secondary);
    margin: 0;
}
.igp-field { margin-bottom: 0.55rem; }
.igp-field:last-child { margin-bottom: 0; }
.igp-field-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: #8a9099;
    margin-bottom: 1px;
}
.igp-field-label i { color: var(--igp-secondary); margin-right: 0.3rem; }
.igp-field-value { font-size: 0.9rem; }
.igp-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    background-color: var(--igp-primary-15);
    color: #9a6f2f;
    border-radius: 6px;
    padding: 3px 10px;
}
.igp-chip i { color: var(--igp-primary); }

/* ----- Ficha de documento (compra/venta) C50 6/6 — partes De/A, pestañas boxed
   y tabla de totales. Formaliza el CSS scoped que nació en purchases/order_detail
   (referencia viva); reusable ERP-wide en las 6 fichas de documento. ----- */
.igp-party-card {
    height: 100%;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 0.5rem;
    padding: 0.6rem 0.75rem;
    background: #fff;
    transition: box-shadow 0.15s;
}
.igp-party-card:hover { box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.08); }
.igp-party-role {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    font-weight: 700;
    color: var(--igp-primary);
    margin-bottom: 0.1rem;
}
.igp-party-name { font-weight: 600; line-height: 1.2; }
.igp-party-card .igp-field { margin-bottom: 0.2rem; }

/* Pestañas de ficha de documento: la barra + el panel forman UNA caja que
   encapsula la pestaña activa con su contenido (solapa de carpeta, mismo mecanismo
   que `.igp-tabs-subframe` pero en blanco). El `<ul>` NO dibuja línea base propia
   (`border-bottom: 0`); el marco lo da el `.tab-content` con borde completo — se
   repone el borde superior que los templates traen como `border-top-0` —, y la
   pestaña activa se engancha: borde arriba/lados + fondo blanco y su borde inferior
   blanco corta el borde superior del panel bajo ella (continuidad). Las inactivas
   descansan sobre esa línea. NO volver a poner `border-bottom` en el `<ul>` ni
   dejar el panel sin borde superior (lección: deja la activa "flotando"). */
.igp-doc-tabs.nav-tabs { border-bottom: 0; margin-bottom: 0; }
.igp-doc-tabs + .tab-content { border-top: 1px solid var(--bs-border-color) !important; margin-top: -1px; }
.igp-doc-tabs .nav-link {
    margin-bottom: -1px;
    margin-right: 1px;
    background-color: #f1f3f5;  /* fondo gris de la inactiva (canon §4: inactiva gris / activa blanca) — la distingue en cualquier contenedor */
    border-color: var(--bs-border-color) var(--bs-border-color) transparent;
}
.igp-doc-tabs .nav-link:not(.active):hover { background-color: #e9ecef; }  /* se oscurece al pasar el ratón */
.igp-doc-tabs .nav-link.active {
    background-color: #fff;
    border-color: var(--bs-nav-tabs-border-color) var(--bs-nav-tabs-border-color) #fff;
    box-shadow: inset 0 2px 0 var(--igp-primary);
    position: relative;
    z-index: 1;
}

/* Drag-and-drop reorder de filas (Sortable.js) — canon reorden ERP-wide. El handle
   `.drag-handle` (icono bi-grip-vertical) va en la 1ª columna; la fila fantasma
   durante el arrastre se atenúa. */
.drag-handle { cursor: grab; user-select: none; }
tr.sortable-ghost { opacity: 0.4; background: #f8f9fa; }

/* Tabla de totales de documento: etiquetas muted a la izquierda, importes a la
   derecha (tabular-nums), subtotal Base con borde, Total dorado con borde superior. */
.igp-totals { width: 100%; font-variant-numeric: tabular-nums; }
.igp-totals td { padding: 0.35rem 0.4rem; vertical-align: middle; }
.igp-totals .lbl { color: var(--igp-secondary); }
.igp-totals .amt { text-align: right; white-space: nowrap; }
.igp-totals .minor td {
    color: var(--igp-secondary);
    font-size: 0.88rem;
    padding-top: 0.05rem;
    padding-bottom: 0.05rem;
}
.igp-totals .sub td { border-top: 1px solid var(--bs-border-color); font-weight: 600; }
.igp-totals .grand td {
    border-top: 2px solid var(--igp-primary);
    font-weight: 700;
    font-size: 1.2rem;
    color: var(--igp-price-total);
    padding-top: 0.5rem;
}

/* KPI accionable como enlace (panel de entidades): blindaje contra la global a:hover
   (subraya + tiñe de ambar). Conserva el look de tarjeta y el hover-lift de .igp-kpi. */
a.igp-kpi,
a.igp-kpi:hover,
a.igp-kpi:focus {
    text-decoration: none;
    color: inherit;
}

/* ----- DataTable jerarquía tipográfica + hover (refinamiento Ciclo 15 iter 2). -----
   DT 2.x (bundle vendorizado en Ciclo 49) emite `.dt-container` como wrapper —
   no `.dataTables_wrapper`. Selectores actualizados a la nomenclatura `.dt-*`.
   ---------------------------------------------------------------------------- */

/* DataTable wrapper en su conjunto. */
.dt-container {
    font-size: 0.875rem;
}

/* Headers: mayúsculas, peso 600, letterspacing, color secundario. */
table.dataTable thead th,
table.dataTable thead td,
.table thead th {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--igp-secondary);
    border-bottom: 2px solid var(--igp-primary) !important;
    padding: 0.75rem 0.75rem;
    background-color: #fafbfc;
}

/* Filas del body: padding cómodo, separador sutil, numbers tabular. */
table.dataTable tbody td,
.table tbody td {
    padding: 0.65rem 0.75rem;
    border-bottom: 1px solid #eef0f3;
    vertical-align: middle;
    font-variant-numeric: tabular-nums;
}

/* Texto plano del body en color más suave que el negro absoluto, mantiene jerarquía. */
table.dataTable tbody {
    color: #3c4147;
}

/* Hover sutil: ya teníamos var(--igp-primary-07) — reforzamos sólo el shadow del row. */
table.dataTable.hover tbody tr:hover,
table.dataTable.display tbody tr:hover,
.table-hover > tbody > tr:hover > * {
    background-color: var(--igp-primary-07);
}

/* Filtros / paginación / info: tipografía más discreta. */
.dt-container .dt-search,
.dt-container .dt-length,
.dt-container .dt-info,
.dt-container .dt-paging {
    font-size: 0.8125rem;
    color: var(--igp-secondary);
}

.dt-container .dt-search input,
.dt-container .dt-length select {
    border: 1px solid #d4d8dd;
    border-radius: 4px;
    padding: 0.25rem 0.5rem;
}

.dt-container .dt-search input:focus,
.dt-container .dt-length select:focus {
    border-color: var(--igp-primary);
    box-shadow: 0 0 0 0.15rem var(--igp-primary-30);
    outline: none;
}

/* Acordeón estilizado (paleta IGP en el botón expandido). */
.accordion-button:not(.collapsed) {
    background-color: var(--igp-primary-10);
    color: var(--igp-secondary);
    box-shadow: inset 0 -1px 0 var(--igp-primary-30);
}

.accordion-button:focus {
    border-color: var(--igp-primary);
    box-shadow: 0 0 0 0.15rem var(--igp-primary-30);
}

/* ----- DataTable loading: overlay BS5 propio (Ciclo 15 iter 4). -----
   Sustituye el "Procesando..." default de DataTables (puntitos + texto) por un
   overlay translúcido con spinner-border BS5 en color primario IGP. El handler
   `processing.dt` en base_modern.html sigue inyectando `.igp-dt-overlay` en
   `settings.nTableWrapper` (= el `.dt-container` en DT 2.x).
   ------------------------------------------------------------------------- */

.dt-container {
    position: relative;
}

/* Ocultar el indicador default de DataTables — lo sustituye `.igp-dt-overlay`. */
.dt-container .dt-processing {
    display: none !important;
}

.igp-dt-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;                        /* alto: encima de cualquier z-index interno del DT */
    background-color: rgba(255, 255, 255, 0.72);
    backdrop-filter: blur(1px);
    -webkit-backdrop-filter: blur(1px);
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: auto;                 /* bloquea clicks subyacentes */
    cursor: wait;
    border-radius: inherit;
}

.igp-dt-overlay-spinner {
    /* CSS self-contained: no depende de `.spinner-border` BS5. Border-color
       explícito para no depender de currentColor (que en iter 4 fallaba).
       Iter 7: agrandado + box-shadow ligera para garantizar visibilidad sobre
       cualquier fondo (la opacidad translúcida del overlay no destacaba bien
       el spinner antes). */
    width: 3.5rem;
    height: 3.5rem;
    border: 0.4em solid rgba(247, 172, 59, 0.22);
    border-top-color: var(--igp-primary);
    border-right-color: var(--igp-primary);
    border-radius: 50%;
    animation: igp-overlay-spin 0.65s linear infinite;
    box-sizing: border-box;
    display: block;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
    /* En cada inserción del overlay limpio (handler re-inyecta), la animación
       arranca desde 0 — sin esto un overlay re-mostrado podía no animar bien. */
    will-change: transform;
}

@keyframes igp-overlay-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* ----- Spacing entre botones de acción dentro del DataTable. ----- */

table.dataTable td .btn + .btn,
table.dataTable td button + button {
    margin-left: 0.25rem;
}

table.dataTable td .btn,
table.dataTable td button.btn {
    line-height: 1;
}

/* Botones circle-xs del stack viejo: respiración + alineado a iconos BS Icons. */
table.dataTable td .btn-circle-xs,
table.dataTable td button.btn-circle-xs {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border-radius: 50%;
    background-color: var(--igp-secondary) !important;
    color: #fff !important;
    border: 1px solid var(--igp-secondary) !important;
    transition: background-color 0.18s ease, border-color 0.18s ease;
    box-shadow: none !important;
}

/* Hover sutil: solo cambio de fondo (paleta semántica por tipo de acción).
   Sin transform, sin lift, sin box-shadow llamativo. Iteración 5. */
table.dataTable td .btn-circle-xs:hover,
table.dataTable td button.btn-circle-xs:hover {
    background-color: var(--igp-primary) !important;
    border-color: var(--igp-primary) !important;
}

table.dataTable td .btn-circle-xs:focus,
table.dataTable td button.btn-circle-xs:focus,
table.dataTable td .btn-circle-xs:focus-visible,
table.dataTable td button.btn-circle-xs:focus-visible {
    outline: none !important;
    box-shadow: 0 0 0 2px rgba(247, 172, 59, 0.45) !important;
}

table.dataTable td .btn-circle-xs i,
table.dataTable td button.btn-circle-xs i {
    font-size: 0.78rem;
    line-height: 1;
}

/* Paleta semántica por tipo de acción — hover sólo cambia background-color. */

table.dataTable td button[name="aceptarCargaVolante"]:hover,
table.dataTable td button[name="calificarVolante"]:hover {
    background-color: var(--igp-status-3) !important;
    border-color: var(--igp-status-3) !important;
}

table.dataTable td button[name="descargarCargaVolante"]:hover,
table.dataTable td button[name="transferirCargaVolante"]:hover {
    background-color: var(--igp-volante-pend-descarga) !important;
    border-color: var(--igp-volante-pend-descarga) !important;
}

table.dataTable td button[name="updateVolante"]:hover {
    background-color: var(--igp-status-2) !important;
    border-color: var(--igp-status-2) !important;
}

table.dataTable td button[name="delVolante"]:hover,
table.dataTable td button[name="noCalificarVolante"]:hover {
    background-color: var(--igp-status-0) !important;
    border-color: var(--igp-status-0) !important;
}

table.dataTable td button[name="viewVolante"]:hover {
    background-color: var(--igp-primary) !important;
    border-color: var(--igp-primary) !important;
}

/* Variante red del subtemplate (btn-erp-red): hereda rojo en estado base. */
table.dataTable td .btn-erp-red {
    background-color: var(--igp-status-0) !important;
    border-color: var(--igp-status-0) !important;
}

/* ----- Layout DT 2.x (estándar IGP § 18.3, Ciclo 49). -----
   DT 2.x emite el layout vía `<div class="dt-layout-row"><div class="dt-layout-cell
   dt-layout-start">…</div><div class="dt-layout-cell dt-layout-end">…</div></div>`
   por cada fila. El default global en base_modern.html define:
     · top1 (fila más arriba): buttons (start) · search (end)
     · top  (fila junto a la tabla, arriba): info (start) · pageLength + paging (end)
     · bottom (fila bajo la tabla): info (start) · paging (end)
   Estilamos esa estructura directamente — sin hacks tipo `.top` legacy del
   adaptador BS3/BS4. -------------------------------------------------------- */

.dt-container .dt-layout-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.75rem;
    margin-bottom: 0.5rem;
}

.dt-container .dt-layout-row:last-of-type {
    margin-top: 0.5rem;
    margin-bottom: 0;
}

.dt-container .dt-layout-cell {
    padding: 0;
}

.dt-container .dt-layout-cell.dt-layout-start {
    flex: 1 1 auto;
    text-align: left;
}

.dt-container .dt-layout-cell.dt-layout-end {
    flex: 0 1 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.75rem;
    justify-content: flex-end;
}

/* Botonera Buttons (dt-buttons): respeta el flujo flex pero sin margen vertical residual. */
.dt-container .dt-buttons {
    margin: 0 !important;
}

/* Length + paging en la misma celda end de la fila top → ambos inline. */
.dt-container .dt-length,
.dt-container .dt-length label {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8125rem;
    color: var(--igp-secondary);
    white-space: nowrap;
}

/* ----- Paginador DT 2.x BS5 — estilo B (Ciclo 49 § 18.3). -----
   La integración BS5 nativa emite `.pagination .page-item > .page-link` dentro
   de `.dt-paging`. Reestilamos sobre ese markup BS5 puro: números sobrios,
   hover gris claro, activo relleno ámbar IGP, disabled como § 18.1.
   Defensa contra la global `a:hover { ... }` legacy: los .page-link son <a>
   → `text-decoration: none !important` (lección § 18.2 reincidente). ---- */

.dt-container .dt-paging {
    display: flex;
    justify-content: flex-end;
    margin: 0;
}

.dt-container .dt-paging .pagination {
    margin: 0;
    --bs-pagination-padding-x: 0.55rem;
    --bs-pagination-padding-y: 0.25rem;
    --bs-pagination-font-size: 0.8125rem;
    --bs-pagination-color: #5c6670;
    --bs-pagination-bg: #fff;
    --bs-pagination-border-color: #dde1e6;
    --bs-pagination-border-radius: 5px;
    --bs-pagination-hover-color: #5c6670;
    --bs-pagination-hover-bg: #f0f1f3;
    --bs-pagination-hover-border-color: #dde1e6;
    --bs-pagination-focus-color: #5c6670;
    --bs-pagination-focus-bg: #f0f1f3;
    --bs-pagination-focus-box-shadow: 0 0 0 0.15rem rgba(247, 172, 59, 0.30);
    --bs-pagination-active-color: #fff;
    --bs-pagination-active-bg: #f7ac3b;
    --bs-pagination-active-border-color: #f7ac3b;
    --bs-pagination-disabled-color: #b0b6bc;
    --bs-pagination-disabled-bg: #fef2e0;
    --bs-pagination-disabled-border-color: #fcdfb5;
}

.dt-container .dt-paging .pagination .page-item {
    margin: 0 1px;
}

.dt-container .dt-paging .page-link {
    min-width: 1.875rem;
    height: 1.875rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 500;
    line-height: 1;
    /* Blindaje § 18.2: la global `a:hover` legacy subraya/tiñe los <a>. */
    text-decoration: none !important;
}

.dt-container .dt-paging .page-link:hover,
.dt-container .dt-paging .page-link:focus {
    text-decoration: none !important;
}

.dt-container .dt-paging .page-item.active .page-link,
.dt-container .dt-paging .page-item.active .page-link:hover,
.dt-container .dt-paging .page-item.active .page-link:focus {
    color: #fff !important;
    background-color: #f7ac3b !important;
    border-color: #f7ac3b !important;
    font-weight: 600;
}

.dt-container .dt-paging .page-item.disabled .page-link {
    cursor: not-allowed;
}

/* ----- Selectores con chevron BS5 (form-control y form-select heredados). ----- */

select.form-control,
select.form-select,
.dt-container .dt-length select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%235c6670'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.65rem center;
    background-size: 12px 12px;
    padding-right: 2rem;
}

.input-group select.form-control {
    background-position: right 0.65rem center;
}

/* Iconos en <option> de selects RETIRADOS en iter 6 (estética inconsistente —
   los glyphs FontAwesome renderizados nativamente en el dropdown nativo no
   convencían a Dave). Los unicodes `&#xf111;` del subtemplate `volantes_table.html`
   se renderizarán como caracteres "no-glyph" (cuadrados) en el dropdown, pero
   el texto Pendiente de carga/etc. queda legible. Para retirarlos completamente
   habría que tocar el subtemplate — fuera de scope POC. */

/* ----- Acordeón filtros (heredado del tema legacy) — refinar. ----- */

.accordion-filters {
    margin: 0 0 1rem 0 !important;
    background: #fff;
    border: 1px solid #e3e6ea;
    border-radius: 6px;
    overflow: hidden;
}

.accordion-filters .panel-default {
    border: none;
    margin: 0;
    box-shadow: none;
    background: transparent;
}

.accordion-filters .panel-heading,
.accordion-filters .panel-heading-filters {
    background-color: #fafbfc !important;
    border-bottom: 1px solid var(--igp-primary-30) !important;
    padding: 0.6rem 1rem !important;
    cursor: pointer;
    user-select: none;
}

.accordion-filters .panel-heading h5,
.accordion-filters .panel-heading-filters h5 {
    margin: 0;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--igp-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.accordion-filters .panel-heading .indicator_filters,
.accordion-filters .panel-heading-filters .indicator_filters {
    margin-right: 0.5rem;
    color: var(--igp-primary);
}

/* Compat BS3 → BS5 para `.collapse.in` heredado del subtemplate: gestionado
   por handler JS en base_modern.html que normaliza `.in` → `.show` post-render
   (BS5 native) + shim `$.fn.collapse` para que `$('#x').collapse('toggle')` siga
   funcionando. El override CSS `.in { display: block !important }` de iter 5 se
   retira en iter 6 porque rompía el toggle (siempre visible). */

/* Varios subtemplates `_table.html` (volantes_table.html, ganaderias_table.html,
   etc.) aplican `style="margin-bottom: 20px;"` inline al div del collapse
   (`#collapseFiltersVolantes{{rol}}`, `#collapseFiltersGanaderias`, ...).
   Sobra — la sección filtros ya tiene su propio margin via .accordion-filters.
   Override con !important porque es style inline. Selector generalizado
   (Ciclo 16) para cubrir todos los `collapseFilters*` del proyecto. */
[id^="collapseFilters"] {
    margin-bottom: 0 !important;
}

.accordion-filters .panel-body {
    padding: 1.25rem !important;
    background: #fff;
    /* Flex-wrap con breakpoints explícitos. Sustituye el grid `auto-fit` de iter 4
       que no se imponía a las BS5 .col-md-* del subtemplate (`width: 50%` ganaba
       sobre `width: auto` del reset sin `!important`). Iter 5: !important everywhere. */
    display: flex !important;
    flex-wrap: wrap;
    gap: 1rem 1.25rem;
    align-items: flex-end;
}

/* Hijo wrapper que rompe el reparto 1/4 del panel-body: ocupa una fila completa.
   Dentro de él se puede usar layout normal (BS5 row/col, flex, lo que sea).
   Útil para agrupar bloques semánticamente distintos (KPIs / acciones / filtros). */
.accordion-filters .panel-body > .filters-row {
    flex: 1 0 100% !important;
    width: 100% !important;
    max-width: 100% !important;
}

/* Badge de % de tasa de sacrificio en la columna del listado de Ganaderías.
   Color primario IGP (ámbar) con texto un poco más grande que el resto de
   celdas para destacar la métrica. */
.badge-tasa-sacrif {
    display: inline-block;
    background: var(--igp-primary);
    color: #fff;
    font-weight: 600;
    font-size: 0.95rem;
    padding: 0.15rem 0.55rem;
    border-radius: 0.375rem;
    line-height: 1.3;
}

/* C50 H1 — Tasa de sacrificio como barra horizontal porcentual (sustituye
   al badge plano). Segmento dorado .seg-tasa proporcional al % real (tope
   visual al 100% — para >100 la barra queda llena). El texto del %
   numérico (con coma decimal es-ES) va al lado/encima. Patrón ligero,
   no flex container — el wrap externo .tasa-sacrif-wrap decide
   alineación (text-end típico en cells dt-right). */
.tasa-sacrif-wrap {
    display: inline-block;
    min-width: 90px;
    line-height: 1.2;
}
.tasa-sacrif-wrap .tasa-sacrif-num {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--igp-secondary);
}
.tasa-sacrif-bar {
    display: block;
    width: 100%;
    height: 8px;
    border-radius: 4px;
    overflow: hidden;
    background: #eef0f2;
    margin-top: 3px;
}
.tasa-sacrif-bar .seg-tasa {
    display: block;
    height: 100%;
    background: var(--igp-primary);
}

/* Grupo de checkboxes filtro inline (ganaderias_table filtros adicionales:
   Traspasadas / SIN Inspección / Riesgo). Forzar inline + gap para que
   los 3 caigan en la misma línea con espacio. */
.igp-filter-checks {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1.25rem;
    align-items: center;
    padding-top: 0.25rem;
}
.igp-filter-checks .form-check-inline {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
}
.igp-filter-checks .form-check-inline .form-check-input {
    margin: 0;
    cursor: pointer;
}

/* Variante .is-inline de .user-cell-list usada por window.renderUsersCell
   en los 5 listados maestros: los badges de rol pegados al email (sin
   flex-grow del mail que los empuja al final). Conserva el formato
   columna entre usuarios. */
.user-cell-list.is-inline .user-cell-mail {
    flex-grow: 0;
}
.user-cell-list.is-inline .user-cell-item {
    gap: 6px;
}

/* Filtro Estado canon de los 5 listados maestros de rol (radio buttons
   TODOS / ACTIVOS / BAJAS). Padding/gap para que las 3 opciones respiren
   y no se apelmacen contra el label. */
.igp-filter-status .form-label {
    margin-bottom: 0.5rem;
    font-weight: 500;
}
.igp-filter-status .radio-inline {
    margin-right: 1.25rem;
    padding: 0.2rem 0;
    cursor: pointer;
    display: inline-block;
}
.igp-filter-status .radio-inline input[type="radio"] {
    margin-right: 0.5rem;
    vertical-align: middle;
    cursor: pointer;
}
.igp-filter-status .radio-inline span {
    vertical-align: middle;
}

.accordion-filters .panel-body > [class*="col-"] {
    /* Cada filtro toma 1/4 del ancho del row en desktop; los breakpoints abajo
       lo escalan. !important en TODO para superar las reglas BS5 .col-md-*. */
    flex: 0 1 calc(25% - 1rem) !important;
    width: auto !important;
    max-width: none !important;
    min-width: 180px !important;
    padding: 0 !important;
    margin: 0 !important;
}

@media (max-width: 1199.98px) {
    .accordion-filters .panel-body > [class*="col-"] {
        flex: 0 1 calc(33.333% - 1rem) !important;  /* 3 cols laptop */
    }
}

@media (max-width: 767.98px) {
    .accordion-filters .panel-body > [class*="col-"] {
        flex: 0 1 calc(50% - 1rem) !important;      /* 2 cols tablet */
    }
}

@media (max-width: 575.98px) {
    .accordion-filters .panel-body > [class*="col-"] {
        flex: 1 1 100% !important;                  /* 1 col móvil */
    }
}

/* Wrapper anidado de filtros: cuando el subtemplate agrupa varios filtros
   relacionados dentro de un `col-*` adicional (caso `ganaderias_table.html`:
   `<div class="col-xs-12 col-md-12 col-lg-5 no-pad">` con 4 sub-`col-*` de
   checks + input). Lo tratamos como sub-flex container para que sus hijos
   `col-*` no hereden los width BS5 sobre un padre `display: block`. Detección
   con `:has()` (soporte universal browsers modernos desde 2023). */
.accordion-filters .panel-body > [class*="col-"]:has(> [class*="col-"]) {
    display: flex !important;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    align-items: flex-end;
    padding: 0 !important;
    flex: 1 1 auto !important;
    min-width: 220px !important;
}

.accordion-filters .panel-body > [class*="col-"]:has(> [class*="col-"]) > [class*="col-"] {
    flex: 0 1 auto !important;
    width: auto !important;
    max-width: none !important;
    min-width: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
}

.accordion-filters .panel-collapse {
    background: transparent;
}

/* Mantén form-control de BS3 dentro de filtros mirando BS5. */
.accordion-filters .form-control,
.accordion-filters .control-label {
    font-size: 0.875rem;
}

.accordion-filters .control-label {
    display: block;
    color: var(--igp-secondary);
    font-weight: 500;
    margin-bottom: 0.3rem;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.accordion-filters .input-group {
    width: 100%;
}

.accordion-filters .input-group-addon {
    display: inline-flex;
    align-items: center;
    padding: 0.375rem 0.65rem;
    background-color: #f1f3f5;
    border: 1px solid #ced4da;
    border-left: none;
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
    color: var(--igp-secondary);
}

/* ----- Timeline horizontal del log de acciones del volante.
   Las clases las emite `volante_time_line_log.html` (subtemplate AJAX).
   Replica el layout antiguo: burbujas circulares conectadas por línea horizontal.
   ----- */

.timeline.timeline-horizontal {
    list-style: none;
    padding: 2rem 1rem 1.5rem 1rem;
    margin: 0;
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    position: relative;
    gap: 0;
}

.timeline.timeline-horizontal::before {
    content: '';
    position: absolute;
    top: 2.75rem;
    left: 2rem;
    right: 2rem;
    height: 2px;
    background: linear-gradient(to right, var(--igp-primary-30) 0%, var(--igp-primary-30) 100%);
    z-index: 0;
}

.timeline.timeline-horizontal .timeline-item {
    position: relative;
    flex: 1 0 200px;
    max-width: 280px;
    padding: 0 0.5rem;
    text-align: center;
    z-index: 1;
}

.timeline.timeline-horizontal .timeline-badge {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background-color: var(--igp-secondary);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 0.5rem;
    box-shadow: 0 0 0 4px #fff, 0 2px 6px rgba(0, 0, 0, 0.1);
    font-size: 1rem;
}

.timeline.timeline-horizontal .timeline-badge.danger   { background-color: #dc3545; }
.timeline.timeline-horizontal .timeline-badge.warning  { background-color: #fd7e14; }
.timeline.timeline-horizontal .timeline-badge.primary  { background-color: var(--igp-primary); color: #fff; }
.timeline.timeline-horizontal .timeline-badge.success  { background-color: #198754; }

.timeline.timeline-horizontal .timeline-panel {
    background: #fff;
    border: 1px solid #e3e6ea;
    border-radius: 6px;
    padding: 0.65rem 0.75rem;
    text-align: left;
    font-size: 0.8125rem;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
}

.timeline.timeline-horizontal .timeline-title {
    margin: 0 0 0.25rem 0;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--igp-secondary);
}

.timeline.timeline-horizontal .timeline-title small {
    font-size: 0.6875rem;
    color: var(--bs-secondary-color);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-left: 0.25rem;
}

.timeline.timeline-horizontal .timeline-heading p {
    margin: 0 0 0.2rem 0;
    line-height: 1.3;
}

.timeline.timeline-horizontal .timeline-body {
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    border-top: 1px dashed #eef0f3;
    color: #5a6068;
    font-size: 0.75rem;
    line-height: 1.4;
}

.timeline.timeline-horizontal .timeline-body p {
    margin: 0 0 0.15rem 0;
}

/* ----- dl horizontal IGP — usado en volante_detail.html para clave-valor consistente. ----- */

.dl-igp {
    display: grid;
    grid-template-columns: minmax(110px, 38%) 1fr;
    column-gap: 0.75rem;
    row-gap: 0.35rem;
    margin: 0;
    font-size: 0.875rem;
}

.dl-igp dt {
    color: var(--igp-secondary);
    font-weight: 500;
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    align-self: center;
}

.dl-igp dd {
    margin: 0;
    color: #2c3034;
    font-weight: 500;
    word-break: break-word;
}

.dl-igp dd em,
.dl-igp dd b {
    color: var(--igp-secondary);
}

/* ----- .dl-igp-rows / .dl-igp-row — variante de dl-igp con separador unificado
   por fila. Pensado para cards densas donde un valor ocupa varias líneas (p.ej.
   tarifa activa con N proveedores) y el `.dl-igp` estándar (con dt/dd como items
   independientes en grid) genera bordes desalineados — label corto vs valor
   largo terminan en alturas distintas y los `border-bottom` aplicados a dt/dd
   no se pueden unificar a nivel "fila implícita".

   El truco: cada concepto va en un `<div class="dl-igp-row">` que tiene su
   propio grid interno (label izda · valor dcha) y un único border-bottom abajo
   del wrapper — alineado y limpio.

   Uso típico:
       <div class="dl-igp-rows">
           <div class="dl-igp-row">
               <div class="label">PROVEEDOR PRINCIPAL</div>
               <div class="value">GRAFICOLOR PALENCIA, S.L.</div>
           </div>
           <div class="dl-igp-row">
               <div class="label">TARIFA ACTIVA</div>
               <div class="value">… contenido multi-línea …</div>
           </div>
       </div>

   Tipografía idéntica a .dl-igp para mantener consistencia visual. Idempotente:
   última fila sin border ni padding extra. */
.dl-igp-rows {
    display: flex;
    flex-direction: column;
    margin: 0;
    font-size: 0.875rem;
}
.dl-igp-row {
    display: grid;
    grid-template-columns: minmax(110px, 38%) 1fr;
    column-gap: 0.75rem;
    padding-bottom: 0.85rem;
    margin-bottom: 0.85rem;
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.dl-igp-row:last-child {
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom: 0;
}
.dl-igp-row > .label {
    color: var(--igp-secondary);
    font-weight: 500;
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    align-self: start;
    padding-top: 0.1rem;
}
.dl-igp-row > .value {
    color: #2c3034;
    font-weight: 500;
    word-break: break-word;
}
.dl-igp-row > .value em,
.dl-igp-row > .value b {
    color: var(--igp-secondary);
}

/* ===== Vista simplificada del volante — FLUJO OPERATIVO =====
   4 cards en fila (1×4 en lg, 2×2 < lg). Cada card representa un hito del
   flujo físico (Origen / Transporte / Matadero / Calificación), con el
   color canon del rol responsable. Card "pending" cuando la fase aún no
   ha ocurrido — opacidad reducida + icono reloj en el foot. */
.igp-flow-row {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.75rem;
}
@media (max-width: 991.98px) {
    .igp-flow-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 575.98px) {
    .igp-flow-row { grid-template-columns: 1fr; }
}
.igp-flow-card {
    background: #fff;
    border: 1px solid #e6e8eb;
    border-left: 3px solid var(--igp-card-accent, #d4d6d8);
    border-radius: 0.5rem;
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.igp-flow-card--g { --igp-card-accent: var(--igp-role-g); }
.igp-flow-card--t { --igp-card-accent: var(--igp-role-t); }
.igp-flow-card--m { --igp-card-accent: var(--igp-role-m); }
.igp-flow-card--v { --igp-card-accent: var(--igp-role-v); }
.igp-flow-card--pending {
    background: repeating-linear-gradient(135deg, #fafbfc, #fafbfc 6px, #f3f4f6 6px, #f3f4f6 12px);
}
.igp-flow-card--pending .igp-flow-actor,
.igp-flow-card--pending .igp-flow-meta { opacity: 0.55; }

.igp-flow-card-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.7rem;
    background: var(--igp-primary-07);
    border-bottom: 1px solid var(--igp-primary-15);
}
.igp-flow-card-head > i {
    color: var(--igp-card-accent);
    font-size: 1rem;
}
.igp-flow-card-title {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--igp-secondary);
}
.igp-flow-card-link {
    margin-left: auto;
    color: var(--igp-secondary);
    font-size: 0.85rem;
    text-decoration: none;
    padding: 0.1rem 0.3rem;
    border-radius: 3px;
    transition: color 0.15s, background-color 0.15s;
}
.igp-flow-card-link:hover {
    color: var(--igp-primary);
    background-color: var(--igp-primary-10);
}
.igp-flow-card-body {
    padding: 0.65rem 0.7rem;
    font-size: 0.8125rem;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.igp-flow-actor {
    font-weight: 600;
    color: #1f2937;
    /* Sin truncado — las cards tienen espacio de sobra y los nombres son
       razón social legible. Si en algún caso extremo no cabe en una sola
       línea, envuelve naturalmente. */
}
.igp-flow-meta {
    font-size: 0.7625rem;
    color: var(--igp-secondary);
    display: flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.igp-flow-meta > i {
    color: var(--igp-card-accent);
    flex-shrink: 0;
}
.igp-flow-meta-key {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--igp-secondary);
    opacity: 0.75;
}
/* Contacto compacto: teléfono / móvil / email en una sola línea con
   separación. Si no caben envuelve a varias líneas con flex-wrap. */
.igp-flow-contact {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.6rem;
    overflow: visible;
    white-space: normal;
}
.igp-flow-contact > span {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.7rem;
}
.igp-flow-contact > span > i {
    color: var(--igp-card-accent);
    font-size: 0.75rem;
}
.igp-flow-empty {
    font-size: 0.7625rem;
    color: var(--igp-secondary);
    font-style: italic;
}
.igp-flow-card-foot {
    padding: 0.45rem 0.7rem;
    background: #fafbfc;
    border-top: 1px solid #eef0f3;
    font-size: 0.72rem;
    color: var(--igp-secondary);
    font-weight: 500;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
/* Modificador --multi: dos sub-pasos visibles separados por línea sutil
   (Card Transporte: Carga + Descarga). */
.igp-flow-card-foot--multi .igp-flow-step + .igp-flow-step {
    padding-top: 0.25rem;
    border-top: 1px dashed #eef0f3;
}
.igp-flow-step {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.25rem 0.4rem;
}
.igp-flow-step > i {
    color: var(--igp-card-accent);
}
.igp-flow-step.text-muted > i { color: inherit; }
.igp-flow-dur {
    color: #1f2937;
    font-style: normal;
    font-weight: 600;
}
/* Tag con el autor de la acción — dot circular con la letra del rol IGP
   (G/T/M/V) coloreado canon, y nombre del usuario al lado. Se muestra
   junto a la fecha de cada hito que tiene log identificado. */
.igp-flow-actor-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;              /* respira entre dot rol y nombre */
    margin-left: auto;        /* empuja el autor al final del step */
    color: #1f2937;
    font-weight: 500;
    font-size: 0.72rem;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.igp-role-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    color: #fff;
    font-size: 0.62rem;
    font-weight: 700;
    flex-shrink: 0;
}
.igp-role-dot--g { background: var(--igp-role-g); }
.igp-role-dot--t { background: var(--igp-role-t); }
.igp-role-dot--c { background: var(--igp-role-c); }
.igp-role-dot--o { background: var(--igp-role-o); }
.igp-role-dot--m { background: var(--igp-role-m); }
.igp-role-dot--v { background: var(--igp-role-v); }

/* ===== Vista simplificada del volante — CADENA COMERCIAL (chips) =====
   Flujo horizontal de propietarios del documento, leído de izquierda a
   derecha. Cada chip lleva el color de rol canon y representa un actor;
   las flechas marcan transiciones (las que vienen de un traspaso usan
   icono bi-arrow-left-right en lugar de bi-arrow-right). En estrecho
   los chips envuelven a la siguiente línea. */
.igp-chain {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 0.4rem;
}
/* Mini-card de la cadena comercial — header con icono+nombre+rol y bloque
   inferior con dirección+contacto. Mantiene el aspecto de chip clicable
   pero gana sustancia para que el operador IGP tenga la info básica del
   actor sin tener que abrir la ficha completa. */
.igp-chain-node {
    display: inline-flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.5rem 0.7rem;
    background: #fff;
    border: 1px solid var(--igp-chain-accent, #d4d6d8);
    border-left: 3px solid var(--igp-chain-accent, #d4d6d8);
    border-radius: 0.4rem;
    font-size: 0.8125rem;
    min-width: 0;
    max-width: 320px;
    text-decoration: none;
    transition: box-shadow 0.15s, transform 0.15s;
}
.igp-chain-node:hover {
    box-shadow: 0 2px 6px rgba(16, 24, 40, 0.08);
    transform: translateY(-1px);
    text-decoration: none;
}
.igp-chain-node-top {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.5rem;
    align-items: center;
}
.igp-chain-node-top > i {
    font-size: 1.05rem;
    color: var(--igp-chain-accent);
    align-self: center;
}
.igp-chain-label {
    font-weight: 600;
    color: #1f2937;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.15;
    max-width: 240px;
}
.igp-chain-role {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--igp-secondary);
    font-weight: 500;
    line-height: 1.1;
}
.igp-chain-node-info {
    border-top: 1px dashed #eef0f3;
    padding-top: 0.3rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 0.7rem;
    color: var(--igp-secondary);
}
.igp-chain-node-info > div {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.igp-chain-node-info i {
    color: var(--igp-chain-accent);
    flex-shrink: 0;
}
.igp-chain-node--g { --igp-chain-accent: var(--igp-role-g); }
.igp-chain-node--c { --igp-chain-accent: var(--igp-role-c); }
.igp-chain-node--o { --igp-chain-accent: var(--igp-role-o); }
.igp-chain-arrow {
    color: var(--igp-secondary);
    font-size: 1.1rem;
    display: inline-flex;
    align-items: center;
}
/* Variante de flecha cuando hay traspaso: icono azul + fecha + autor
   apilados verticalmente con respiro entre líneas. */
.igp-chain-arrow--traspaso {
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    line-height: 1;
    padding: 0.2rem 0.4rem;
}
.igp-chain-arrow-date {
    font-size: 0.6875rem;
    color: var(--igp-secondary);    /* sin color especial — info secundaria */
    font-weight: 500;
    white-space: nowrap;
}
/* Autor del traspaso — dot del rol + nombre, debajo de la fecha. Mismo
   tamaño y look que `.igp-flow-actor-tag` de las cards superiores para
   que el operador IGP perciba todos los autores con la misma jerarquía. */
.igp-chain-arrow-author {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;                    /* respira entre dot rol y nombre */
    font-size: 0.72rem;             /* igualado con .igp-flow-actor-tag */
    color: #1f2937;
    font-weight: 500;
    white-space: nowrap;
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.igp-chain-traspaso {
    color: #2f8fc4;          /* azul canon traspaso (igual que badge en DT) */
}

/* ===== Vista DETALLADA del volante (`volante_detail.html`) =====
   Layout 2 columnas: izquierda feed cronológico de eventos, derecha
   resumen de calificación + tabs con mapa/timeline/series/etc. */
.igp-volante-detail {
    font-size: 0.875rem;
}
.igp-volante-detail-grid {
    display: grid;
    /* Traza media-estrecha (290-410px) tras feedback Dave — un pelín más de
       respiro para que las cabeceras no compriman, sin restarle protagonismo
       al panel derecho con resumen + tabs (mapa, timeline, etc.). */
    grid-template-columns: minmax(290px, 410px) minmax(0, 1fr);
    gap: 1rem;
}
@media (max-width: 991.98px) {
    .igp-volante-detail-grid { grid-template-columns: 1fr; }
}
/* Permitir que el popover lateral (.igp-feed-extra) desborde hacia la derecha
   sobre el panel de resumen/tabs sin recortarse. La section-card que envuelve
   el feed NO debe clipping (Bootstrap card lleva overflow:hidden por defecto
   en algunos contextos). */
.igp-volante-detail-grid > .igp-section-card { overflow: visible; }
.igp-volante-detail-grid > .igp-section-card > .card-body { overflow: visible; }

/* Acciones del header (botones Editar/Calificar/etc.) */
.igp-volante-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

/* Feed cronológico — secuencia vertical de eventos del volante.
   Cada item tiene:
     · marker (icono coloreado por rol) + conector vertical hacia el siguiente
     · body con título + dot rol + fecha + actor con info + autor del log
     · gap (tiempo transcurrido al siguiente evento), inline tras el contenido */
.igp-feed {
    position: relative;
}
.igp-feed-item {
    display: grid;
    grid-template-columns: 36px 1fr;
    column-gap: 0.75rem;
    padding-bottom: 0.6rem;
}
.igp-feed-item:last-child {
    padding-bottom: 0;
}
.igp-feed-marker {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.igp-feed-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--igp-feed-accent, #888);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.95rem;
    box-shadow: 0 1px 2px rgba(0,0,0,0.12);
    flex-shrink: 0;
    z-index: 2;
}
.igp-feed-connector {
    flex: 1 1 auto;
    width: 2px;
    background: #e1e3e7;
    margin-top: 2px;
    min-height: 24px;
}
.igp-feed-item--rol-g { --igp-feed-accent: var(--igp-role-g); }
.igp-feed-item--rol-t { --igp-feed-accent: var(--igp-role-t); }
.igp-feed-item--rol-c { --igp-feed-accent: var(--igp-role-c); }
.igp-feed-item--rol-o { --igp-feed-accent: var(--igp-role-o); }
.igp-feed-item--rol-m { --igp-feed-accent: var(--igp-role-m); }
.igp-feed-item--rol-v { --igp-feed-accent: var(--igp-role-v); }
/* Modificación manual (staff IGP) — no es un rol del flujo, color primary
   dorado para distinguirlo claramente del resto sin invadir paletas de rol. */
.igp-feed-item--rol-modify { --igp-feed-accent: var(--igp-primary); }

.igp-feed-body {
    position: relative;        /* anclaje del popover lateral .igp-feed-extra */
    background: #fafbfc;
    border: 1px solid #eef0f3;
    border-left: 3px solid var(--igp-feed-accent, #d4d6d8);
    border-radius: 0.4rem;
    padding: 0.5rem 0.75rem 0.55rem;
    transition: box-shadow 0.18s ease-out;
    cursor: default;
}
.igp-feed-body:hover {
    box-shadow: 0 2px 8px rgba(16, 24, 40, 0.08);
}
/* Variante "simple" — para cards sin popover ni pin (kind=modify). Sin
   cursor:pointer ni hover-shadow para que no parezca clicable. */
.igp-feed-body--simple { cursor: default !important; }
.igp-feed-body--simple:hover { box-shadow: none; }
/* Popover lateral — info extendida al hover.
   En lugar de expandir la propia card hacia abajo (lo que movía los puntos
   donde el ratón ya estaba haciendo hover y causaba flapping), la
   información secundaria se materializa como una TARJETA flotante a la
   derecha de la card padre. Se posiciona con `left: 100%` para que arranque
   pegada a su borde derecho, con z-index alto para superponerse al panel
   de resumen+tabs sin alterar el layout original. Adaptable al contenido
   (max-content) con caps razonables. Visible al hover/focus de la card padre. */
.igp-feed-extra {
    position: absolute;
    top: -1px;                  /* alineada con el borde superior de la card padre */
    left: calc(100% + 0.5rem);  /* hueco visual entre card padre y popover */
    z-index: 1050;              /* > nav-tabs (~1) y sobre tab-content de la derecha */
    width: max-content;         /* adaptable al contenido */
    min-width: 220px;
    max-width: 340px;
    background: #fff;
    border: 1px solid #e6e8eb;
    border-left: 3px solid var(--igp-feed-accent, #d4d6d8);
    border-radius: 0.4rem;
    padding: 0.6rem 0.75rem;
    box-shadow: 0 6px 18px rgba(16, 24, 40, 0.12);
    /* Estado oculto: invisible + sin clicks + desplazada hacia la izquierda
       (genera el efecto "slide-in" al aparecer). */
    opacity: 0;
    visibility: hidden;
    transform: translateX(-6px);
    transition: opacity 0.18s ease-out, transform 0.20s ease-out, visibility 0s linear 0.20s;
    pointer-events: none;
}
.igp-feed-body:hover .igp-feed-extra,
.igp-feed-body:focus-within .igp-feed-extra,
.igp-feed-body.is-pinned .igp-feed-extra,
.igp-feed-extra:hover {
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
    transition: opacity 0.20s ease-out, transform 0.22s ease-out, visibility 0s;
    /* Cuando el popover es visible debe recibir eventos de ratón
       (click, selección de texto, hover de enlaces). Si se queda en
       pointer-events:none los clicks atraviesan al tab debajo y ni
       siquiera se puede seleccionar texto — bug encontrado en V10. */
    pointer-events: auto;
}
/* Bridge invisible que cubre el GAP entre la card padre y el popover.
   Sin esto, al mover el ratón hacia el popover se cruza una zona vacía,
   se pierde el :hover de la card y el popover se oculta antes de
   alcanzarlo. La pseudo-tira (0.6rem) extiende el hit-area del popover
   hacia la izquierda, manteniéndolo "hovered" durante el viaje. */
.igp-feed-extra::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: -0.6rem;
    width: 0.6rem;
}
/* Chincheta visual — esquina superior derecha de la card, parcialmente
   FUERA del rectángulo para sugerir un alfiler físico. Círculo blanco con
   sombra ligera (eleva el icono sobre el fondo). En reposo: gris tenue.
   Hover: presencia + ligera elevación. Pinned: tintado del color del rol +
   inclinación más pronunciada como "clavado". Sin necesidad de tooltip,
   el pictograma sólo ya invita al click. */
.igp-feed-pin-hint {
    position: absolute;
    top: -8px;
    right: -8px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: #ffffff;
    border: 1px solid #e6e8eb;
    box-shadow: 0 1px 3px rgba(16, 24, 40, 0.10);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    color: rgba(92, 102, 112, 0.55);
    transition: color 0.18s ease-out, transform 0.20s ease-out, box-shadow 0.18s ease-out;
    pointer-events: none;
    z-index: 2;
}
.igp-feed-pin-hint i { line-height: 1; transform: rotate(35deg); transition: transform 0.20s ease-out; }
.igp-feed-body { cursor: pointer; }
.igp-feed-body:hover .igp-feed-pin-hint {
    color: var(--igp-feed-accent);
    box-shadow: 0 2px 6px rgba(16, 24, 40, 0.18);
}
.igp-feed-body.is-pinned .igp-feed-pin-hint {
    color: var(--igp-feed-accent);
    background: var(--igp-feed-accent);
    border-color: var(--igp-feed-accent);
    box-shadow: 0 2px 8px rgba(16, 24, 40, 0.22);
}
.igp-feed-body.is-pinned .igp-feed-pin-hint i {
    color: #fff;
    transform: rotate(0deg);  /* "clavado" derecho */
}
/* Mini-header dentro del popover indicando estado pinned. Sólo visible
   cuando la card padre tiene la clase `.is-pinned`. */
.igp-feed-extra-pin {
    display: none;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.7rem;
    color: var(--igp-feed-accent);
    margin-bottom: 0.4rem;
    padding-bottom: 0.35rem;
    border-bottom: 1px dashed #eef0f3;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    cursor: pointer;
}
.igp-feed-body.is-pinned .igp-feed-extra-pin { display: flex; }
.igp-feed-extra-pin i { font-size: 0.8rem; }
.igp-feed-extra-pin-label { color: var(--igp-secondary); font-weight: 500; }
/* Cap de altura con scroll interno por si el contenido fuese muy largo. */
.igp-feed-extra { max-height: 60vh; overflow-y: auto; }
.igp-feed-extra-row {
    font-size: 0.78rem;
    color: var(--igp-secondary);
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.25rem;
    white-space: nowrap;
}
/* Enlaces dentro del popover (tel:, mailto:, cod. explotación → modal).
   Heredan el color base del row + subrayado discreto en hover. Color
   del acento al hover para alinearse con el rol de la card padre. */
.igp-feed-link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dashed transparent;
    transition: color 0.15s ease-out, border-color 0.15s ease-out;
}
.igp-feed-link:hover,
.igp-feed-link:focus {
    color: var(--igp-feed-accent);
    border-bottom-color: var(--igp-feed-accent);
    text-decoration: none;
}
.igp-feed-cod-link { cursor: pointer; }
.igp-feed-extra-row:last-child { margin-bottom: 0; }
.igp-feed-extra-row i {
    color: var(--igp-feed-accent);
    flex-shrink: 0;
    width: 1rem;
    text-align: center;
}
/* En móvil/colapsado el popover lateral no tiene espacio a la derecha
   sin tapar la card padre — degradamos a expansión vertical clásica. */
@media (max-width: 991.98px) {
    .igp-feed-extra {
        position: static;
        opacity: 1; visibility: visible; transform: none;
        margin-top: 0.4rem;
        max-width: 100%; width: 100%;
        box-shadow: none;
        background: rgba(0,0,0,0.02);
    }
}
.igp-feed-head {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 0.25rem;
}
.igp-feed-title {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    color: var(--igp-secondary);
    line-height: 1.4;
}
.igp-feed-meta-right {
    margin-left: auto;
    text-align: right;
    font-size: 0.7rem;
    color: var(--igp-secondary);
    line-height: 1.25;
}
.igp-feed-date {
    font-weight: 500;
}
.igp-feed-author {
    font-size: 0.68rem;
    color: var(--igp-secondary);
    margin-top: 1px;
    white-space: nowrap;
}
.igp-feed-content {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.igp-feed-actor {
    font-weight: 600;
    color: #1f2937;
    font-size: 0.85rem;
}
.igp-feed-actor-meta {
    font-size: 0.7625rem;
    color: var(--igp-secondary);
}
.igp-feed-actor-meta i {
    color: var(--igp-feed-accent);
}
.igp-feed-gap {
    margin-top: 0.4rem;
    padding-top: 0.3rem;
    border-top: 1px dashed #eef0f3;
    font-size: 0.7rem;
    display: flex;
    align-items: center;
    gap: 0.3rem;
}
/* Segunda línea (acumulado desde Emisión) — sin nuevo borde superior y
   con margen reducido para que se entienda como complemento de la
   primera, no como hito independiente. */
.igp-feed-gap.igp-feed-gap--cumulative {
    margin-top: 0.1rem;
    padding-top: 0;
    border-top: none;
}
/* Etiqueta "desde {hito}" — italics y muy tenue, complementa el número. */
.igp-feed-gap-from {
    font-style: italic;
    opacity: 0.78;
    font-size: 0.68rem;
}

/* Tabs vista detallada — canon ERP `.nav .nav-tabs`. Mantengo aquí solo
   pequeños ajustes de gap/iconos que no rompen el resto del ERP. */
.igp-volante-detail .nav-tabs .nav-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
/* Tab Descalificaciones — tinte semántico rojo cuando está activa para
   coherencia con la barra apilada del resumen y los badges del propio
   contenido. El badge `badge-d` ya lleva el rojo canon, solo afinamos
   tamaño/peso aquí. La línea inferior del tab activo se pinta del mismo
   rojo en lugar del primary IGP. */
.igp-volante-detail .nav-tabs .nav-link.igp-tab-descalif.active,
.igp-volante-detail .nav-tabs .nav-link.igp-tab-descalif.active:hover {
    border-bottom-color: var(--igp-status-0);
    color: var(--igp-status-0);
}
.igp-volante-detail .nav-tabs .nav-link.igp-tab-descalif .badge-calif {
    font-size: 0.7rem;
    padding: 0.18rem 0.45rem;
    line-height: 1;
    font-weight: 600;
}

/* Mapa Leaflet en el tab Mapa */
.igp-volante-map {
    height: 420px;
    border-radius: 0.4rem;
    overflow: hidden;
    border: 1px solid #e6e8eb;
}
/* Marcadores personalizados (divIcon) — burbujita coloreada por rol con
   pictograma Bootstrap dentro. Modificador `--action` para acciones del
   dispositivo (sólido) y `--entity` para fichas (semitransparente + borde
   dashed). Color por --pin-color (custom property inline). */
.igp-map-pin-wrapper { background: transparent !important; border: none !important; }
.igp-map-pin {
    width: 34px; height: 34px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #fff;
    font-size: 1rem;
    box-shadow: 0 2px 6px rgba(16, 24, 40, 0.30);
    transition: transform 0.18s ease-out, box-shadow 0.18s ease-out, filter 0.18s ease-out;
    cursor: pointer;
}
.igp-map-pin--action {
    background: var(--pin-color, #888);
    border: 3px solid #fff;
}
.igp-map-pin--entity {
    background: var(--pin-color, #888);
    border: 2px dashed #fff;
    opacity: 0.78;
}
.igp-map-pin--entity:hover { opacity: 1; }
.igp-map-pin:hover,
.igp-map-pin.is-feed-highlight {
    transform: scale(1.25);
    box-shadow: 0 4px 12px rgba(16, 24, 40, 0.45);
    filter: brightness(1.1);
    z-index: 1000;
}
/* Cross-highlight inverso: feed-item resaltado cuando el ratón está
   sobre el marker del mapa. Tinte suave del color rol en el fondo (sin
   tocar bordes — la card ya tiene su border-left del color). Eleva
   ligeramente con shadow para indicar foco sin marco extra. */
.igp-feed-item.is-map-highlight .igp-feed-body {
    background: color-mix(in srgb, var(--igp-feed-accent, #d4d6d8) 12%, #fff);
    box-shadow: 0 2px 10px rgba(16, 24, 40, 0.10);
}
@supports not (background: color-mix(in srgb, red, white)) {
    /* Fallback (engines sin color-mix): tinte estático muy suave por rol. */
    .igp-feed-item--rol-g.is-map-highlight .igp-feed-body { background: rgba(192, 82, 74, 0.10); }
    .igp-feed-item--rol-t.is-map-highlight .igp-feed-body { background: rgba(203, 158, 50, 0.10); }
    .igp-feed-item--rol-c.is-map-highlight .igp-feed-body { background: rgba(70, 184, 196, 0.10); }
    .igp-feed-item--rol-o.is-map-highlight .igp-feed-body { background: rgba(125, 91, 166, 0.10); }
    .igp-feed-item--rol-m.is-map-highlight .igp-feed-body { background: rgba(108, 116, 128, 0.12); }
    .igp-feed-item--rol-v.is-map-highlight .igp-feed-body { background: rgba(94, 140, 84, 0.10); }
}

/* Icono "abrir ficha entidad" junto al nombre del actor en cada card del
   feed. Pequeño, gris tenue por defecto; al hover de la card gana
   protagonismo con el color del rol. Abre en nueva pestaña. */
.igp-feed-entity-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 0.4rem;
    width: 18px;
    height: 18px;
    border-radius: 4px;
    color: var(--igp-secondary);
    opacity: 0.55;
    font-size: 0.78rem;
    line-height: 1;
    text-decoration: none;
    transition: opacity 0.15s ease-out, color 0.15s ease-out, background 0.15s ease-out;
}
.igp-feed-body:hover .igp-feed-entity-link,
.igp-feed-body.is-pinned .igp-feed-entity-link {
    opacity: 1;
    color: var(--igp-feed-accent);
}
.igp-feed-entity-link:hover {
    background: rgba(0, 0, 0, 0.05);
    color: var(--igp-feed-accent) !important;
    opacity: 1 !important;
}

/* Indicador de inconsistencia temporal en card del feed — mismo icono
   ⚠ canon que la leyenda del timeline. Pequeño, naranja, con cursor:help. */
.igp-feed-inconsist {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 0.25rem;
    color: var(--igp-status-1);   /* naranja muted canon (Pendiente descarga / Inconsistencia) */
    font-size: 0.85rem;
    line-height: 1;
    cursor: help;
}

/* Badge warning en nav-link de la tab Línea de tiempo cuando hay
   inconsistencias temporales — el icono ⚠ aparece a la derecha del label. */
.igp-volante-detail .nav-tabs .nav-link.igp-tab-warning .igp-tab-warning-icon {
    color: var(--igp-status-1);
    font-size: 0.85rem;
    line-height: 1;
}
.igp-volante-detail .nav-tabs .nav-link.igp-tab-warning.active,
.igp-volante-detail .nav-tabs .nav-link.igp-tab-warning.active:hover {
    border-bottom-color: var(--igp-status-1);
}

/* Botón custom Leaflet "Centrar mapa" — replica el estilo de leaflet-bar
   con tipografía Bootstrap Icons centrada. Pegado a los botones zoom. */
.leaflet-control.igp-map-reset {
    display: flex !important;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    line-height: 30px;
    color: #5c6670;
    font-size: 0.95rem;
    text-decoration: none;
    background: #fff;
}
.leaflet-control.igp-map-reset:hover {
    background: #f4f4f4;
    color: var(--igp-primary);
}
.leaflet-control.igp-map-reset i { line-height: 1; }

/* Listas de series etiquetadas/vitolas con rangos */
.igp-section-title-mini {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--igp-secondary);
    font-weight: 600;
    margin-bottom: 0.5rem;
}
.igp-section-title-mini i {
    color: var(--igp-primary);
    margin-right: 0.25rem;
}
.igp-series-list {
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
    font-size: 0.8rem;
}
.igp-series-list > li { margin-bottom: 0.4rem; }
.igp-series-list ul {
    list-style: none;
    padding-left: 1rem;
    margin: 0.15rem 0 0;
    font-size: 0.75rem;
    color: var(--igp-secondary);
}
.igp-series-list ul li {
    padding: 0.1rem 0;
}
/* Tab Etiquetas y Vitolas — tipografía un punto mayor que el resto del panel
   (Dave: "subir un punto el tamaño de los textos aquí"). Aplica a la lista
   de series y a sus rangos. */
.igp-etiqvit-pane .igp-series-list { font-size: 0.875rem; }
.igp-etiqvit-pane .igp-series-list ul { font-size: 0.825rem; }
/* Sección "Asignadas individuales" — chips por etiqueta/vitola con × para
   desasociar inline. Pensados para densidad alta (16-200 chips) con scroll
   interno controlado. */
.igp-etiqvit-rangos-title,
.igp-etiqvit-individual-title {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--igp-secondary);
    font-weight: 600;
    margin-bottom: 0.45rem;
}
.igp-etiqvit-individual { border-top: 1px dashed #eef0f3; padding-top: 0.75rem; }
.igp-etiqvit-chips {
    list-style: none;
    padding-left: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    max-height: 240px;
    overflow-y: auto;
    padding-right: 0.25rem;
}
.igp-etiqvit-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    background: #fff;
    border: 1px solid #e6e8eb;
    border-radius: 0.4rem;
    padding: 0.18rem 0.35rem 0.18rem 0.5rem;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    transition: border-color 0.15s ease-out, background 0.15s ease-out;
}
.igp-etiqvit-chip:hover {
    border-color: var(--igp-primary-30);
    background: var(--igp-primary-07);
}
.igp-etiqvit-chip-series {
    background: var(--igp-primary-15);
    color: #5a4318;
    font-weight: 600;
    border-radius: 0.25rem;
    padding: 0 0.35rem;
    font-size: 0.72rem;
    letter-spacing: 0.02em;
}
.igp-etiqvit-chip-serial { color: #1f2937; font-weight: 500; }
.igp-etiqvit-chip-remove {
    appearance: none;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 50%;
    width: 18px;
    height: 18px;
    line-height: 1;
    padding: 0;
    color: var(--igp-secondary);
    cursor: pointer;
    transition: background 0.12s ease-out, color 0.12s ease-out, border-color 0.12s ease-out;
}
.igp-etiqvit-chip-remove:hover {
    background: #fef2f2;
    color: #b14238;
    border-color: #fecaca;
}
.igp-etiqvit-chip-remove i { font-size: 0.85rem; pointer-events: none; }
/* Botón "+ Asociar" con badge de huecos.
   C50: inline-flex + align-items center centran verticalmente el badge interno.
   Fix definitivo del "cap-space" residual: badge con altura fija (1.35em) + padding
   solo horizontal + line-height:1 + inline-flex interno → el número queda centrado
   pixel-perfect dentro del rectángulo del badge, y el badge mismo se centra vs el
   texto del botón por align-items del padre. Sin este truco el font-metrics de BS5
   deja más aire debajo del texto que arriba. */
.igp-etiqvit-add {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.78rem;
}
.igp-etiqvit-add .badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* BS5 aplica `.btn .badge { position: relative; top: -1px }` que sube el badge
       1px respecto a la baseline del texto. Neutralizado con `top: 0` (mantiene
       position: relative para que top surta efecto). */
    position: relative;
    top: 0;
    height: 1.25em;
    padding: 0 7px;
    line-height: 1;
    font-weight: 500;
    font-size: 0.7rem;
}

/* Modal Asociar etiquetas/vitolas — fix legibilidad del badge .igp-series-badge
   dentro del dropdown del select2 cuando la opción está :hover (highlighted).
   Por defecto select2 pinta el fondo azul/púrpura sobre el badge ámbar y la
   serie pierde contraste. Forzamos en hover el badge a fondo blanco con texto
   marrón canon para que se lea claramente sobre cualquier highlight. */
.select2-results__option--highlighted .igp-series-badge,
.select2-results__option[aria-selected="true"] .igp-series-badge {
    background: #ffffff !important;
    color: #5a4318 !important;
    border-color: #ffffff !important;
}
/* Badge identificador de serie — variante azul corporativa para no chocar
   con los rangos numéricos. Más visual que el `<strong>` plano. */
.igp-series-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    background: var(--igp-primary-15);
    color: #5a4318;
    border: 1px solid var(--igp-primary-30);
    padding: 0.15rem 0.5rem;
    border-radius: 0.3rem;
    font-weight: 600;
    font-size: 0.85rem;
    letter-spacing: 0.01em;
}
.igp-series-badge i { font-size: 0.75rem; opacity: 0.7; }

/* Lista de motivos de descalificación con badge rojo canon `badge-d`
   (mismo que el DT de volantes en la celda Rendimientos). Más visual que
   la tabla — cada fila respira y los números resaltan en rojo. */
.igp-descalif-list {
    list-style: none;
    padding-left: 0;
    margin: 0 auto;            /* centrar la lista dentro de su contenedor */
    font-size: 0.85rem;
    width: max-content;        /* contraer al contenido — no estirar */
    max-width: 100%;
    min-width: 220px;
}
.igp-descalif-list > li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.25rem;
    padding: 0.4rem 0.75rem;
    border-bottom: 1px dashed #eef0f3;
}
.igp-descalif-list > li:last-child { border-bottom: none; }
.igp-descalif-list .igp-descalif-motivo {
    color: #1f2937;
    flex: 1 1 auto;
    white-space: nowrap;       /* sin truncar — la lista es max-content */
}
.igp-descalif-list .igp-descalif-total {
    background: var(--igp-primary-07);
    border-top: 1px solid #e6e8eb;
    border-radius: 0.3rem;
    margin-top: 0.25rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}
/* Sustituir el badge rojo cuando el valor es cero/vacío por un guión sutil:
   el rojo enfático sólo tiene sentido si hay cabezas descalificadas reales. */
.igp-descalif-list .badge-calif:empty,
.igp-descalif-list .badge-calif.is-zero {
    background: transparent !important;
    color: var(--igp-secondary) !important;
    font-weight: 500;
    border: 1px dashed #d4d6d8;
}
.igp-descalif-list .badge-calif.is-zero::before { content: "—"; }

/* ===== Resumen de calificación — barra apilada proporcional =====
   Sobre BD real (86.880 volantes finalizados) Trasladadas y Clasificadas
   coinciden en el 99.97% → los 4 KPIs anteriores eran redundantes en la
   práctica. Aquí mostramos un único total en la cabecera y la composición
   real (etiquetadas, no etiq, descalif, no calif) como segmentos
   proporcionales con sus cifras y porcentajes debajo. Casos anómalos
   (discrepancia entre suma y trasladadas) se marcan en la cabecera. */
.igp-resumen-calif-total {
    font-size: 0.85rem;
    color: var(--igp-secondary);
    font-weight: 500;
}
.igp-resumen-calif-total strong { color: #1f2937; }
.igp-badge-mismatch {
    background: var(--igp-status-0);
    color: #fff;
    font-size: 0.7rem;
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    margin-left: 0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

.igp-calif-bar {
    display: flex;
    height: 36px;
    border-radius: 6px;
    overflow: hidden;
    box-shadow: 0 1px 2px rgba(0,0,0,0.04);
    margin-bottom: 0.65rem;
}
.igp-calif-seg {
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-weight: 600;
    font-size: 0.85rem;
    cursor: help;
    min-width: 32px;
    transition: filter 0.15s;
}
.igp-calif-seg:hover {
    filter: brightness(1.08);
}
.igp-calif-seg + .igp-calif-seg {
    border-left: 1px solid rgba(255, 255, 255, 0.4);
}
.igp-calif-seg-n {
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
}
/* Mapeo canon — DEBE coincidir con `.bar-distribution .seg-*` del DT
   volantes/sacrificios y con `.badge-ce/ne/d` del listado. Ver bloque
   `bar-distribution` más abajo. */
.igp-calif-seg--etiq     { background: var(--igp-volante-calificado); }   /* verde olivo (V) — etiq IGP */
.igp-calif-seg--noetiq   { background: var(--igp-secondary); }            /* GRIS — aptas sin etiqueta */
.igp-calif-seg--descalif { background: var(--igp-role-g); }               /* rojo terracota (G) — no aptas */
.igp-calif-seg--nocalif  { background: #8b7355; }                         /* marrón propio — caso excepcional */

.igp-calif-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    font-size: 0.78rem;
    color: var(--igp-secondary);
}
.igp-calif-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.igp-calif-legend-item strong { color: #1f2937; }
.igp-calif-legend-item .swatch {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 3px;
}
.igp-calif-pct {
    font-style: normal;
    color: var(--igp-secondary);
    opacity: 0.85;
}

/* ----- Modal "Más info" del DataTable de volantes (`volante_detail_resumen.html`).
   Cargado vía AJAX en `#genericModalBody` desde `volantes_table.html:1396`. El título
   del modal lo fija el handler externo (línea 1402); este bloque viste el cuerpo. ----- */

.igp-resumen {
    font-size: 0.875rem;
}

.igp-resumen-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem 0.85rem;
    padding: 0.55rem 0.85rem;
    border-radius: 6px;
    background: var(--igp-status-0-bg);
    border-left: 3px solid var(--igp-status-0);
}

.igp-resumen-header--0 { background: var(--igp-status-0-bg); border-left-color: var(--igp-status-0); }
.igp-resumen-header--1 { background: var(--igp-volante-pend-descarga-bg); border-left-color: var(--igp-volante-pend-descarga); }
.igp-resumen-header--2 { background: var(--igp-status-2-bg); border-left-color: var(--igp-status-2); }
.igp-resumen-header--3 { background: var(--igp-status-3-bg); border-left-color: var(--igp-status-3); }

.igp-resumen-header .igp-status {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.75rem;
}

.igp-resumen-issues {
    font-size: 0.8125rem;
    color: var(--igp-secondary);
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

/* Badge de referencia del volante en el header — replica el render canon
   del DT volantes (col 1) con letra coloreada según origen (M/W/H/Z). */
.igp-resumen-ref {
    display: inline-flex;
    align-items: center;
    font-size: 0.875rem;
    font-weight: 600;
    margin-right: 0.5rem;
}
.igp-resumen-ref .ref-rest {
    color: #1f2937;
    margin-left: 0.15rem;
}

/* Razas en el header: bloque protagonista con icono ovejita + uno o más
   chips compactos con nombre de raza + nº de cabezas. Ocupa el espacio
   vacío entre el badge de estado y los links de la derecha. */
.igp-resumen-razas {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: 0.5rem;
    flex-wrap: wrap;
}
/* Razas e Incidencias en cabecera del volante — chip canon ovalado con:
     · icono dentro (ovejita para razas, warning/check para incidencias)
     · label en bold
     · número en círculo coloreado
   Paleta por defecto: primary IGP (ámbar/dorado). Variantes con override:
     · `.igp-resumen-issue-item--open` (rojo status-0, hay incidencias abiertas)
     · `.igp-resumen-issue-item--closed` (verde status-3, todas cerradas) */
.igp-resumen-razas {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.igp-resumen-raza-item {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    background: rgba(255, 255, 255, 0.6);
    border: 1px solid var(--igp-primary);
    border-radius: 999px;
    padding: 0.15rem 0.55rem 0.15rem 0.45rem;
    font-size: 0.78rem;
    color: #1f2937;
    margin-left: 0.4rem;
}
.igp-resumen-raza-icon {
    color: var(--igp-primary);
    font-size: 1rem;
    line-height: 1;
}
.igp-resumen-raza-item > i { color: var(--igp-primary); line-height: 1; }
.igp-resumen-raza-n {
    background: var(--igp-primary);
    color: #fff;
    border-radius: 999px;
    padding: 0 0.5rem;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.5;
    min-width: 1.5rem;
    text-align: center;
}
/* Variante incidencias — colores semánticos (rojo abierta / verde cerrada). */
.igp-resumen-issue-item--open {
    border-color: var(--igp-status-0);
    background: var(--igp-status-0-bg);
}
.igp-resumen-issue-item--open > i,
.igp-resumen-issue-item--open .igp-resumen-raza-icon { color: var(--igp-status-0); }
.igp-resumen-issue-item--open .igp-resumen-raza-n { background: var(--igp-status-0); }
.igp-resumen-issue-item--closed {
    border-color: var(--igp-status-3);
    background: var(--igp-status-3-bg);
}
.igp-resumen-issue-item--closed > i,
.igp-resumen-issue-item--closed .igp-resumen-raza-icon { color: var(--igp-status-3); }
.igp-resumen-issue-item--closed .igp-resumen-raza-n { background: var(--igp-status-3); }

.igp-resumen-open {
    margin-left: auto;
    font-size: 0.8125rem;
    color: var(--igp-secondary);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.igp-resumen-open:hover {
    color: var(--igp-primary);
}

.igp-resumen-card {
    border: 1px solid #e3e6ea;
    border-radius: 6px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
}

.igp-resumen-card > .card-header {
    background-color: #f7f8fa;
    padding: 0.45rem 0.75rem;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--igp-secondary);
    border-bottom: 1px solid #e3e6ea;
}

.igp-resumen-card > .card-body {
    padding: 0.85rem;
}

.igp-resumen-subhead {
    color: var(--igp-secondary);
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    font-weight: 600;
    border-top: 1px dashed #eef0f3;
    padding-top: 0.6rem;
    margin: 0.5rem 0 0.4rem 0;
}

.igp-resumen-traspasos {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.8125rem;
}

.igp-resumen-traspasos li {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.35rem 0;
}

.igp-resumen-traspasos li + li {
    border-top: 1px dashed #eef0f3;
}

.igp-resumen-traspaso-date {
    color: var(--igp-secondary);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    min-width: 9rem;
}

.igp-resumen-traspaso-flow {
    flex: 1 1 auto;
}

.igp-resumen-traspaso-flow small {
    color: var(--igp-secondary);
    font-size: 0.7rem;
}

.igp-resumen-section {
    margin-top: 1.25rem;
}

.igp-resumen-section-title {
    color: var(--igp-secondary);
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    font-weight: 600;
    border-bottom: 1px solid #e3e6ea;
    padding-bottom: 0.4rem;
    margin: 0 0 0.75rem 0;
}

.igp-resumen-totales {
    font-size: 0.8125rem;
    margin-bottom: 0;
}

.igp-resumen-totales thead th {
    background-color: #f7f8fa;
    color: var(--igp-secondary);
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.5rem;
    border-bottom: 1px solid #dee2e6;
    vertical-align: middle;
}

.igp-resumen-totales tbody td {
    padding: 0.5rem;
    vertical-align: middle;
}

.igp-resumen-totales-label {
    text-align: left;
}

/* ===== Timeline canon §10.x del volante (`_volante_timeline.html`) =====
   Representa las 4 fases del ciclo de vida (pendiente carga · transporte ·
   en matadero · pendiente calificación) como barras horizontales coloreadas
   apiladas verticalmente, con dots circulares para hitos (Emisión, Carga,
   Descarga, Matanza, Calificación) y eventos puntuales del log (modificaciones
   manuales, transferencias). Sin SVG: cada barra es un `<div>` con
   posicionamiento absoluto y porcentajes pre-calculados en el view. */
.igp-timeline {
    --tl-track-height: 28px;
    --tl-row-gap: 6px;
    position: relative;
    padding: 0 4px;
}
.igp-timeline-axis {
    /* La `height` se inyecta como inline style desde el template — calculada
       en el view según número real de filas de milestones, número de fases
       y existencia de eventos. Evita reservar espacio vertical para tracks
       vacíos en volantes en estado temprano (p. ej. 'Pendiente de carga'). */
    position: relative;
    background: repeating-linear-gradient(to right,
        transparent 0,
        transparent calc(10% - 1px),
        rgba(0,0,0,0.04) calc(10% - 1px),
        rgba(0,0,0,0.04) 10%);
    border-radius: 6px;
    margin: 0 28px 0.5rem;        /* respiro lateral para que los milestones */
                                  /* de los extremos no se corten contra el card */
}
.igp-timeline-phase {
    position: absolute;
    height: var(--tl-track-height);
    /* Min-width visual: las fases muy cortas se compactan a ~32px (solo icono).
       Sin texto visible, basta con que el icono quepa con padding mínimo —
       gana mucho espacio para barras pequeñas sobre escalas largas. */
    min-width: 32px;
    border-radius: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 0.5rem;
    color: #fff;
    font-size: 0.95rem;
    box-shadow: 0 1px 2px rgba(0,0,0,0.12);
    cursor: help;                    /* no enlaza — sólo muestra tooltip */
}
.igp-timeline-phase i {
    line-height: 1;
}
/* Modificador de inconsistencia — borde rojo + icono ⚠ junto al icono base. */
.igp-timeline-phase--inconsistent {
    box-shadow: 0 0 0 2px var(--igp-status-0), 0 1px 2px rgba(0,0,0,0.12);
}
.igp-timeline-phase--inconsistent .bi-exclamation-triangle-fill {
    color: #fff;
    font-size: 0.85rem;
}
/* Cada fase recibe su `top` inline desde el template (calculado en el view
   según el nº de filas reales de milestones y su orden en la lista). Aquí
   sólo asignamos el color de fondo por clase semántica. */
.igp-timeline-phase.phase-pendiente-carga { background: #d4a017; }
.igp-timeline-phase.phase-transporte      { background: #2f8fc4; }
.igp-timeline-phase.phase-en-matadero     { background: #c97e3a; }
.igp-timeline-phase.phase-pendiente-calif { background: #6a9a3f; }

/* Hitos canon (Emisión / Carga / Descarga / Matanza / Calificación) — dot
   con etiqueta. Hasta 4 filas verticales escalonadas para evitar solape
   cuando varios hitos caen muy cerca en el eje X. La fila la asigna el view
   con un algoritmo greedy de proximidad (ver views.detail_volante). */
.igp-timeline-milestone {
    position: absolute;
    transform: translateX(-50%);
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--igp-primary);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.18);
    cursor: help;                    /* no enlaza — sólo muestra tooltip */
    z-index: 3;
}
/* Filas verticales escalonadas — separación 46px (28 dot + 14 label + 4
   margen) para que el label de una fila no se solape con el dot/label de
   la siguiente cuando dos hitos están en la misma posición X. Renuncio al
   zigzag (label arriba/abajo) porque cuando hitos compartían X exacto el
   label de la fila impar se montaba con el dot de la fila par. */
.igp-timeline-milestone--row-0 { top: 4px;   }
.igp-timeline-milestone--row-1 { top: 50px;  }
.igp-timeline-milestone--row-2 { top: 96px;  }
.igp-timeline-milestone--row-3 { top: 142px; }

/* Label canon — siempre justo debajo del dot, fuera del área de los otros. */
.igp-timeline-milestone-label {
    position: absolute;
    top: 30px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.65rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--igp-secondary);
    white-space: nowrap;
    background: rgba(255, 255, 255, 0.85);   /* halo para legibilidad sobre el axis */
    padding: 1px 4px;
    border-radius: 3px;
}

/* Eventos puntuales del log (modificaciones manuales, transferencias)
   como dots sobre la fila inferior, por debajo de las fases. */
.igp-timeline-event {
    /* `top` inline desde el template (calculado tras la zona de fases). */
    position: absolute;
    transform: translateX(-50%);
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.18);
    cursor: help;                    /* no enlaza — sólo muestra tooltip */
    z-index: 3;
}
.igp-timeline-event.event-modify   { background: var(--igp-primary); color: #fff; }
.igp-timeline-event.event-transfer { background: #2f8fc4; color: #fff; }

/* Ticks intermedios del eje X — entre t0 y tn. Cada tick es una raya vertical
   corta sobre el axis con su fecha/hora debajo. El paso entre ticks lo decide
   el view según rango total (1h / 6h / 12h / 1d / 7d). */
.igp-timeline-ticks {
    position: relative;
    height: 22px;
    margin: 0 28px;        /* alinea con el respiro lateral de .igp-timeline-axis */
}
.igp-timeline-tick {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    width: 1px;
    height: 6px;
    background: rgba(0, 0, 0, 0.25);
}
.igp-timeline-tick-label {
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.65rem;
    color: var(--igp-secondary);
    white-space: nowrap;
}

.igp-timeline-scale {
    display: flex;
    justify-content: space-between;
    font-size: 0.72rem;
    color: var(--igp-secondary);
    margin: 0 28px;
    padding-top: 0.25rem;
    border-top: 1px dashed #eef0f3;
    font-weight: 600;
}

.igp-timeline-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    margin-top: 0.6rem;
    padding-top: 0.6rem;
    border-top: 1px dashed #eef0f3;
    font-size: 0.72rem;
    color: var(--igp-secondary);
}
.igp-timeline-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.igp-timeline-legend-item .swatch {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 4px;
}
.igp-timeline-legend-item .swatch.phase-pendiente-carga { background: #d4a017; }
.igp-timeline-legend-item .swatch.phase-transporte      { background: #2f8fc4; }
.igp-timeline-legend-item .swatch.phase-en-matadero     { background: #c97e3a; }
.igp-timeline-legend-item .swatch.phase-pendiente-calif { background: #6a9a3f; }
.igp-timeline-legend-item .swatch.event-modify          { background: var(--igp-primary); border-radius: 50%; width: 12px; height: 12px; }

/* ----- Botones header de pantalla con paleta IGP semántica -----
   Variantes para acciones destacadas (carga masiva admin, exportación, etc.)
   en cabeceras de pantalla. La paleta BS5 default (`btn-danger`, `btn-warning`)
   choca con la paleta IGP muted (§ 3 F5).

   Implementación: definir las variables `--bs-btn-*` que BS5.3 usa internamente
   para `.btn:hover / :active / :focus / :disabled`. Así BS5 gestiona el cascade
   sin que tengamos que escribir selectores `:hover` / `:active` propios — los
   nuestros perdían specificity contra `.btn:hover` BS5 y dejaban el fondo unset
   (botón en blanco con texto blanco, ilegible).

   Hex darkenings calculados sobre `--igp-status-0` = #c0524a (R192 G82 B74):
     - hover (×0.82) → #9d433d
     - active (×0.70) → #863934
   focus-shadow-rgb apunta a `--igp-primary` (#f7ac3b → 247,172,59). */
.btn-igp-danger {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--igp-status-0);
    --bs-btn-border-color: var(--igp-status-0);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #9d433d;
    --bs-btn-hover-border-color: #9d433d;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #863934;
    --bs-btn-active-border-color: #863934;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #f5e3e2;
    --bs-btn-disabled-border-color: #e7bdba;
}

.btn-outline-igp-danger {
    --bs-btn-color: var(--igp-status-0);
    --bs-btn-border-color: var(--igp-status-0);
    --bs-btn-hover-bg: var(--igp-status-0);
    --bs-btn-hover-border-color: var(--igp-status-0);
    --bs-btn-hover-color: #fff;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-bg: var(--igp-status-0);
    --bs-btn-active-border-color: var(--igp-status-0);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #f5e3e2;
    --bs-btn-disabled-border-color: #e7bdba;
}

/* Variante para acciones positivas (descarga, exportación, confirmación). Mismo
   patrón que `.btn-igp-danger` pero leyendo `--igp-status-3` = #5e8c54.
   Hex darkenings calculados:
     - hover (×0.82) → #4d7345
     - active (×0.70) → #42623b */
.btn-igp-success {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--igp-status-3);
    --bs-btn-border-color: var(--igp-status-3);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #4d7345;
    --bs-btn-hover-border-color: #4d7345;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #42623b;
    --bs-btn-active-border-color: #42623b;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #e5ede4;
    --bs-btn-disabled-border-color: #c2d3be;
}

.btn-outline-igp-success {
    --bs-btn-color: var(--igp-status-3);
    --bs-btn-border-color: var(--igp-status-3);
    --bs-btn-hover-bg: var(--igp-status-3);
    --bs-btn-hover-border-color: var(--igp-status-3);
    --bs-btn-hover-color: #fff;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-bg: var(--igp-status-3);
    --bs-btn-active-border-color: var(--igp-status-3);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #e5ede4;
    --bs-btn-disabled-border-color: #c2d3be;
}

/* =========================================================================
   C48 — Variantes de color nuevas (definidas, NO aplicadas todavía).
   Ampliación de gama para el pulido (F5 § 18): turquesa, púrpura muted y
   light/gris claro. Misma estructura `--bs-btn-*` que el resto de variantes
   IGP. Las clases no se aplican a ningún botón en este ciclo (funcionalidad
   congelada) — se reservan para futuras necesidades de gama.

   Hex base (calibrados con el logo IGP):
     - igp-blue   #46b8c4  hover #3997a1  disabled #e1f3f6 / #b9e4e9
     - igp-purple #7d5ba6  hover #674b88  disabled #eae5f1 / #cec1dd
     - light      #eef0f2  hover #e2e5e9  borde #d6dadf  (texto oscuro;
                   outline en gris #ced4da/#6c757d porque sobre fondo claro
                   un outline blanco quedaría invisible)
   ========================================================================= */

.btn-igp-blue {
    --bs-btn-color: #fff;
    --bs-btn-bg: #46b8c4;
    --bs-btn-border-color: #46b8c4;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #3997a1;
    --bs-btn-hover-border-color: #3997a1;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #3997a1;
    --bs-btn-active-border-color: #3997a1;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #e1f3f6;
    --bs-btn-disabled-border-color: #b9e4e9;
}

.btn-outline-igp-blue {
    --bs-btn-color: #46b8c4;
    --bs-btn-border-color: #46b8c4;
    --bs-btn-hover-bg: #46b8c4;
    --bs-btn-hover-border-color: #46b8c4;
    --bs-btn-hover-color: #fff;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-bg: #46b8c4;
    --bs-btn-active-border-color: #46b8c4;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #e1f3f6;
    --bs-btn-disabled-border-color: #b9e4e9;
}

.btn-igp-purple {
    --bs-btn-color: #fff;
    --bs-btn-bg: #7d5ba6;
    --bs-btn-border-color: #7d5ba6;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #674b88;
    --bs-btn-hover-border-color: #674b88;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #674b88;
    --bs-btn-active-border-color: #674b88;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #eae5f1;
    --bs-btn-disabled-border-color: #cec1dd;
}

.btn-outline-igp-purple {
    --bs-btn-color: #7d5ba6;
    --bs-btn-border-color: #7d5ba6;
    --bs-btn-hover-bg: #7d5ba6;
    --bs-btn-hover-border-color: #7d5ba6;
    --bs-btn-hover-color: #fff;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-bg: #7d5ba6;
    --bs-btn-active-border-color: #7d5ba6;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #eae5f1;
    --bs-btn-disabled-border-color: #cec1dd;
}

.btn-light {
    --bs-btn-color: #2c3034;
    --bs-btn-bg: #eef0f2;
    --bs-btn-border-color: #d6dadf;
    --bs-btn-hover-color: #2c3034;
    --bs-btn-hover-bg: #e2e5e9;
    --bs-btn-hover-border-color: #c9ced4;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #2c3034;
    --bs-btn-active-bg: #e2e5e9;
    --bs-btn-active-border-color: #c9ced4;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #f4f5f6;
    --bs-btn-disabled-border-color: #e6e9ec;
}

.btn-outline-light {
    --bs-btn-color: #6c757d;
    --bs-btn-border-color: #ced4da;
    --bs-btn-hover-bg: #e2e5e9;
    --bs-btn-hover-border-color: #c9ced4;
    --bs-btn-hover-color: #2c3034;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-bg: #e2e5e9;
    --bs-btn-active-border-color: #c9ced4;
    --bs-btn-active-color: #2c3034;
    --bs-btn-disabled-color: #b0b6bc;
    --bs-btn-disabled-bg: #f4f5f6;
    --bs-btn-disabled-border-color: #e6e9ec;
}

/* =========================================================================
   Sub-ciclo C34 (F5 § 15): alias paleta legacy `btn-erp*` → semántica IGP BS5.

   `base_modern.html` NO carga `custom.css`. Los subtemplates AJAX BS3 que se
   inyectan en wrappers modernizados (C15-C33) siguen usando clases legacy
   `.btn-erp*` heredadas, que sin alias quedaban sin estilo de fondo (texto
   blanco sobre fondo transparente, ilegible). Estos alias mapean a la paleta
   IGP semántica § 7 F5 usando el patrón CSS vars `--bs-btn-*` (idéntico a
   `.btn-igp-danger` / `.btn-igp-success`):

       .btn-erp          → --igp-secondary  (#5c6670 gris pizarra)
                           Continuidad: en legacy `.btn-erp { background:
                           var(--second-color-erp) }` y `--second-color-erp`
                           es exactamente `#5c6670` ≡ `--igp-secondary`.
       .btn-erp-red      → --igp-status-0   (#c0524a rojo terracota muted)
       .btn-erp-green    → --igp-status-3   (#5e8c54 verde olivo muted)
       .btn-erp-greenw   → --igp-status-3   (variante histórica; código
                                              muerto en HTML — solo en
                                              comentarios Django, pero alias
                                              preservado por defensa).
       .btn-default-erp  → circular + --igp-secondary (preserva shape 25×25
                                                       redondo legacy).

   La lógica JS de toggle de clase (`companies.html`, `staff/employees.html`,
   `warehouse/locations.html`, `sales/forms/order_line_form.html` `#id_cost`,
   etc.) funciona transparente: el JS añade/quita la clase, el estilo nuevo
   se aplica vía estas vars. */

.btn-erp {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--igp-secondary);
    --bs-btn-border-color: var(--igp-secondary);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #4a5258;
    --bs-btn-hover-border-color: #4a5258;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #3e4449;
    --bs-btn-active-border-color: #3e4449;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--igp-secondary);
    --bs-btn-disabled-border-color: var(--igp-secondary);
}

.btn-erp-red {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--igp-status-0);
    --bs-btn-border-color: var(--igp-status-0);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #9d433d;
    --bs-btn-hover-border-color: #9d433d;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #863934;
    --bs-btn-active-border-color: #863934;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--igp-status-0);
    --bs-btn-disabled-border-color: var(--igp-status-0);
}

.btn-erp-green,
.btn-erp-greenw {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--igp-status-3);
    --bs-btn-border-color: var(--igp-status-3);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #4d7345;
    --bs-btn-hover-border-color: #4d7345;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #42623b;
    --bs-btn-active-border-color: #42623b;
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--igp-status-3);
    --bs-btn-disabled-border-color: var(--igp-status-3);
}

/* Botón circular pequeño (add/del icon inline en cards y headers). Preserva
   shape legacy (25×25, border-radius 50%) y aplica color secundario IGP. */
.btn-default-erp {
    width: 25px;
    height: 25px;
    padding: 5px 0;
    border-radius: 50%;
    text-align: center;
    font-size: 10px;
    line-height: 1.428571429;
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--igp-secondary);
    --bs-btn-border-color: var(--igp-secondary);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #4a5258;
    --bs-btn-hover-border-color: #4a5258;
    --bs-btn-focus-shadow-rgb: 247, 172, 59;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #3e4449;
    --bs-btn-active-border-color: #3e4449;
}

/* State class de selección manual usada junto a `.btn-erp` en algunos
   toggles legacy. Mismo color que el estado active. */
.btn-erp-selected {
    --bs-btn-bg: #3e4449;
    --bs-btn-border-color: #3e4449;
    --bs-btn-color: #fff;
}

/* Override defensivo (histórico): el CSS legacy del proyecto (custom_*.css,
   erradicado en C50) tenía reglas globales sobre `a:focus` y `a:hover` con
   `background-color: white !important` que rompían cualquier `<a class="btn">`
   BS5. Esta regla restaura el comportamiento BS5 estándar para `<a class="btn">`
   leyendo las CSS vars `--bs-btn-*` que cada variante define.

   Mantenida tras la erradicación como red defensiva por si en el futuro
   reaparece algún custom CSS con reglas globales sobre `a:hover/focus`. Coste
   cero al no haber legacy actual. */
a.btn:hover,
a.btn:focus,
a.btn:focus-visible {
    background-color: var(--bs-btn-hover-bg, var(--bs-btn-bg)) !important;
    color: var(--bs-btn-hover-color, var(--bs-btn-color)) !important;
    border-color: var(--bs-btn-hover-border-color, var(--bs-btn-border-color)) !important;
    text-decoration: none !important;
}
a.btn:active,
a.btn.active {
    background-color: var(--bs-btn-active-bg, var(--bs-btn-bg)) !important;
    color: var(--bs-btn-active-color, var(--bs-btn-color)) !important;
    border-color: var(--bs-btn-active-border-color, var(--bs-btn-border-color)) !important;
}

/* C48 (F5 § 18) — opacidad disabled fija a 1.
   El look del disabled se controla con los colores precalculados de cada variante
   (`--bs-btn-disabled-bg / -border-color / -color` en cada `.btn-*`), no atenuando
   con la opacidad 0.65 que BS5 aplica por defecto. Sustituye al bloque interino
   con `color-mix()` que se usó como puente — ahora los tintes son hex precalculados
   por variante (ver F5 § 18). Específicas (.btn-primary, .btn-igp-*, .btn-erp-*,
   etc.) heredan esta opacidad y aportan sus colores propios. */
.btn {
    --bs-btn-disabled-opacity: 1;
}

/* C48 (F5 § 18) — cursor "no permitido" en controles realmente deshabilitados.
   BS5 fija `pointer-events: none` en `.btn:disabled` (y `.btn.disabled`), lo que
   impide que el cursor `not-allowed` se vea. Lo invertimos a `auto` SÓLO en los
   selectores que matchean controles nativos con el atributo `disabled`
   (`.btn:disabled`, `:disabled` en inputs/selects/textareas/form-control/form-select):
   el clic sigue bloqueado por el atributo nativo, el cursor sí aparece.

   Importante: NO se incluye `.btn.disabled` ni los `<a class="btn ... disabled">`
   del switchboard. Esos anchors no tienen pseudo `:disabled` y dependen
   explícitamente de `pointer-events: none` para no ser clicables. Si les diéramos
   `pointer-events: auto`, se volverían clicables. Se quedan sin cursor especial. */
.btn:disabled,
.form-control:disabled,
.form-select:disabled,
input:disabled,
select:disabled,
textarea:disabled {
    cursor: not-allowed;
    pointer-events: auto;
}

/* =========================================================================
   C48 (F5 § 18.2) — Pestañas de contenido `.nav-tabs` con paleta IGP.

   Estándar "Boxed IGP" (aprobado por Dave): mantiene la caja blanca BS5 del
   tab activo y añade un acento ámbar superior. Sustituye al fallback azul
   `#0d6efd` de BS5 en las inactivas — `igp-base.css` sólo estilizaba el
   sidebar (`.igp-sidebar .nav-link`), no los tabs de contenido.

   Scope estricto: SÓLO `.nav-tabs`. No afecta a `.igp-sidebar .nav-link`
   ni a otros componentes `.nav` (pills, navbar, etc.) — esos se mantienen
   con sus paletas propias. El acento ámbar se aplica con
   `box-shadow: inset 0 2px 0 #f7ac3b` en lugar de `border-top-width: 2px`
   para evitar el desplazamiento de 1 px que tendría el tab activo respecto
   a los inactivos. Hex precalculados (sin `color-mix()`).

   El `[+]` que algunos tabs llevan al lado es un `btn-outline-primary
   btn-sm`, ya cubierto por F5 § 18.1 (estándar de botones C48). No se toca. */

.nav-tabs {
    --bs-nav-link-color: #5c6670;
    --bs-nav-link-hover-color: #2c3034;
    --bs-nav-link-disabled-color: #b0b6bc;
    --bs-nav-tabs-link-active-color: #2c3034;
}

/* Blindaje contra la regla global `a:hover` (igp-base.css ~356): los tabs son
   `<a>` y heredaban subrayado + color ámbar en hover. Forzamos sin subrayado en
   todos los estados y el color de hover del estándar. */
.nav-tabs .nav-link,
.nav-tabs .nav-link:hover,
.nav-tabs .nav-link:focus {
    text-decoration: none;
}

.nav-tabs .nav-link:not(.active):not(.disabled):hover {
    color: #2c3034;
    background-color: #f0f1f3;
}

.nav-tabs .nav-link.active {
    font-weight: 500;
    box-shadow: inset 0 2px 0 #f7ac3b;
}

/* Algunos tabs llevan un botón `[+]` (btn-sm) DENTRO del propio `.nav-link`
   (p. ej. config General). Con su padding por defecto el botón es más alto que
   el texto y, por el `align-items: stretch` de BS5, las pestañas de solo texto
   se estiraban dejando un hueco entre el texto y la línea divisoria. Compactamos
   el botón embebido para que no supere la altura del texto del tab → todas las
   pestañas quedan a la misma altura y a ras de la línea. */
.nav-tabs .nav-link .btn {
    --bs-btn-padding-y: 0;
    line-height: 1.2;
    vertical-align: middle;
}

/* FIX7 F2 — badge ámbar de contador en sub-pestañas [icono][nº][texto]. */
.igp-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 18px;
    padding: 0 6px;
    border-radius: 9px;
    background: var(--igp-primary);
    color: #fff;
    font-size: .72rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: .2px;
    margin: 0 6px 0 4px;
}

/* -------------------------------------------------------------------------
   C50 (F5 § 18.2.1) — Pestañas verticales `.igp-nav-vertical`.

   Extensión aditiva del estándar § 18.2: misma paleta exacta (gris pizarra
   inactivo, caja blanca activa, acento ámbar #f7ac3b, hover #f0f1f3). Sólo
   cambia la orientación del acento (ámbar a la IZQUIERDA con
   `box-shadow: inset 2px 0 0` en vez de arriba) y el radio (esquina izquierda
   redondeada, derecha plana para "fundirse" con el panel de contenido).

   Caso de uso: sub-secciones del mismo nivel que no caben cómodas en barra
   horizontal (5+ pestañas). Ejemplo vivo: Config → Contabilidad → Cuentas
   Contables de la Empresa (Customer/Bank/Product/Supplier/Centre/Tax).

   Scope estricto: SÓLO `.nav-tabs.flex-column.igp-nav-vertical`, no pisa el
   `.nav-tabs` horizontal de nivel 1 del mismo wrapper. */
.nav-tabs.flex-column.igp-nav-vertical {
    border-bottom: none;
    border-right: 1px solid var(--bs-border-color);
    min-width: 200px;
    flex-shrink: 0;
}

.nav-tabs.flex-column.igp-nav-vertical .nav-link {
    display: flex;
    align-items: center;
    text-align: left;
    min-height: 48px;
    background-color: #f1f3f5;  /* fondo gris de la inactiva (igual que la horizontal) */
    margin-right: -1px;
    margin-bottom: 1px;
    border: 1px solid var(--bs-border-color);  /* contorno sutil de la inactiva; cara derecha libre (el panel aporta el borde) */
    border-right: none;
    border-radius: 0.375rem 0 0 0.375rem;
}

/* Acento ámbar a la izquierda (en vez de arriba). La cara derecha del tab
   activo pasa a blanco para "fundirse" con el panel de contenido. */
.nav-tabs.flex-column.igp-nav-vertical .nav-link.active {
    background-color: #fff;
    border-color: var(--bs-border-color) #fff var(--bs-border-color) var(--bs-border-color);
    box-shadow: inset 2px 0 0 #f7ac3b;
}
.nav-tabs.flex-column.igp-nav-vertical .nav-link:not(.active):hover { background-color: #e9ecef; }

/* El `[+]` embebido se empuja al extremo derecho del nav-link (el label lleva
   `.flex-grow-1` en el markup). */
.nav-tabs.flex-column.igp-nav-vertical .nav-link .btn {
    flex-shrink: 0;
}

/* § 18.2.1 VARIANTE (FIX2 F5) — icono grande arriba + label debajo (2 líneas).
   Aditiva sobre `.igp-nav-vertical`: misma paleta + acento ámbar izquierdo. Opt-in
   por clase modificadora; Contabilidad mantiene el canon horizontal-en-línea. */
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top {
    min-width: 110px;  /* FIX3 F6: sobreescribe el 200px del canon (icono domina, label corto) */
}
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top .nav-link {
    flex-direction: column;
    align-items: center;
    gap: 4px;
    text-align: center;
    min-height: 64px;
    padding: .65rem .85rem;
}
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top .nav-link .v-icon {
    font-size: 1.6rem;
    margin: 0;
    line-height: 1;
}
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top .nav-link .v-label {
    font-size: .8rem;
    font-weight: 500;
    line-height: 1.15;
}

/* FIX6 F5 — Pestañas verticales de la ficha de entidad: panel unificado.
   La pestaña activa con fondo blanco + acento ámbar + sombra sutil, y el
   .tab-content también blanco con borde (sin borde izquierdo, pegado a las
   pestañas) forman un solo card. Scope ESTRICTO a la variante icon-top
   (solo ficha entidad) — NO toca el canon .igp-nav-vertical base de
   Config → Contabilidad. */
/* Cierra el gap horizontal entre la columna de pestañas y el panel: anula el
   margin-right del `me-3` (BS, !important) para que la pestaña activa quede
   pegada al .tab-content y muerda su borde izquierdo (la cara derecha blanca de
   la activa + el `margin-right: -1px` base solapan el borde-derecho del nav).
   El aire interior del panel lo da el `ps-3` del .tab-content (padding, no gap). */
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top {
    margin-right: 0 !important;
}
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top .nav-link.active {
    background: #fff;
    box-shadow: inset 2px 0 0 var(--igp-entity-tab-accent, #f7ac3b), 0 1px 3px rgba(0, 0, 0, .05);
}

/* Acento por rol en la pestaña activa de la ficha de entidad (G T C O V).
   Cada button con clase `.igp-entity-tab-<letra>` aporta su color canónico
   --igp-role-* a la variable --igp-entity-tab-accent que consume la regla
   .active de arriba. Si no está la clase, fallback al ámbar IGP. */
.nav-link.igp-entity-tab-g { --igp-entity-tab-accent: var(--igp-role-g); }
.nav-link.igp-entity-tab-t { --igp-entity-tab-accent: var(--igp-role-t); }
.nav-link.igp-entity-tab-c { --igp-entity-tab-accent: var(--igp-role-c); }
.nav-link.igp-entity-tab-o { --igp-entity-tab-accent: var(--igp-role-o); }
.nav-link.igp-entity-tab-v { --igp-entity-tab-accent: var(--igp-role-v); }
.nav-tabs.flex-column.igp-nav-vertical.igp-nav-vertical-icon-top .nav-link:not(.active):hover {
    background: rgba(255, 255, 255, .5);
}
/* Igualar alturas de la columna de pestañas y el panel (en vez de
   `align-items-start`, que las deja con alturas naturales independientes):
   - contenido más alto → el nav se estira y su border-right (divisor) recorre
     toda la altura, enmarcando el panel por completo;
   - contenido más bajo → el panel se estira hasta la altura de las pestañas
     (alto mínimo = extensión de las pestañas, sin número mágico). */
.igp-entity-perfil {
    align-items: stretch;
}
/* Marco del .tab-content de la ficha de entidad — SOLO el primer nivel
   (hijo directo de .igp-entity-perfil, el contenedor de los 5 tab-panes
   de rol G/T/C/O/V). El selector descendente original ".igp-entity-perfil
   .tab-content" pintaba el marco también en los .tab-content anidados de
   las sub-pestañas dentro de cada rol (veedor.html, ganadero.html, etc.),
   lo que dejaba el contenido visualmente pegado al borde derecho del
   marco interno. El selector > restringe a hijo directo. */
.igp-entity-perfil > .tab-content {
    background: #fff;
    border: 1px solid var(--bs-border-color);
    border-left: none;
    border-radius: 0 4px 4px 0;
}

/* FIX2 F6 — bug de scroll horizontal al cargar el fragment AJAX de una pestaña.
   El `.tab-content.flex-grow-1` es flex item con `min-width: auto` por defecto, lo
   que impide encoger por debajo del content-size y provoca overflow en el primer
   render (antes de que el flex negocie ancho con el `.igp-nav-vertical` sibling).
   Acotado al contexto de la ficha de entidad para no regresionar otras pantallas. */
.igp-entity-perfil > .tab-content.flex-grow-1 { min-width: 0; }

/* ===== Sub-tabs canon anidadas — .igp-tabs-subframe =====
   Patrón canon para sub-tabs HORIZONTALES dentro de un .tab-pane. Se usa típicamente
   anidado dentro de tabs principales (sean horizontales o verticales `.igp-entity-perfil`).

   ALTERNANCIA padre/hijo: el panel padre suele ser blanco (canon vertical). Para
   diferenciar visualmente el nivel anidado, el sub-panel + la sub-pestaña activa usan
   un GRIS SUAVE (#f1f3f5) — mismo registro que las cabeceras de grupo del DT. Tabs algo
   más pequeñas (font 0.875rem, padding compacto) para jerarquía visual.

   Uso típico:
       <div class="igp-tabs-subframe">
           <ul class="nav nav-tabs">...</ul>
           <div class="tab-content">...</div>
       </div>
*/
.igp-tabs-subframe > .nav-tabs > .nav-item > .nav-link,
.igp-tabs-subframe > ul.nav-tabs > .nav-link {
    font-size: 0.875rem;
    padding: 0.4rem 0.85rem;
    /* Inversa: el panel + la activa van en GRIS; las inactivas van en BLANCO con
       contorno visible para leerse como "no seleccionadas" (bottom transparent para
       no dibujar línea contra el panel). Simétrico al contorno de .igp-doc-tabs. */
    background-color: #fff;
    border-color: var(--bs-border-color) var(--bs-border-color) transparent;
    /* Separación de 1px entre pestañas. NOTA: el canon NO está factorizado en una
       regla común a toda .nav-link del ERP; cada variante (.igp-doc-tabs, subframe,
       vertical) declara la suya, por eso hay que replicarla aquí. */
    margin-right: 1px;
}
.igp-tabs-subframe > .nav-tabs > .nav-item > .nav-link:not(.active):hover,
.igp-tabs-subframe > ul.nav-tabs > .nav-link:not(.active):hover {
    background-color: #e9ecef;
}
.igp-tabs-subframe > .nav-tabs {
    margin-bottom: 0;
    /* Sin border-bottom del ul: la línea de 1px del canon BS5 asoma en los tramos
       sin pestaña activa y produce una separación visible entre pestaña y contenedor
       cuando ambos comparten fondo. La pestaña activa ya define sus propios bordes
       up/left/right + bottom del color del panel (invisible). */
    border-bottom: 0;
}
.igp-tabs-subframe > .nav-tabs > .nav-item > .nav-link.active,
.igp-tabs-subframe > ul.nav-tabs > .nav-link.active {
    background-color: #f1f3f5;
    border-color: var(--bs-border-color) var(--bs-border-color) #f1f3f5;
}
.igp-tabs-subframe > .tab-content {
    background-color: #f1f3f5;
    border: 1px solid var(--bs-border-color);
    border-top: 0;
    border-radius: 0 0 0.375rem 0.375rem;
    padding: 1rem;
}

/* -------------------------------------------------------------------------
   C50 — Badge de datos en tablas (p. ej. número de cuenta contable, rol G/O).
   Tamaño intermedio: el badge BS5 por defecto (~0.75em) queda pequeño junto al
   texto de la celda, y `fs-6` (1rem) resulta grande. BS5 no tiene escalón entre
   medias, así que se fija aquí. */
.igp-badge-table {
    font-size: 0.85rem;
}

/* C50 — Token de código en patrones de series (%s, %i, %Y, %y). Usado en las
   leyendas y celdas Pattern de invoice_series_table y product_series_table.
   El badge BS5 por defecto (~0.75em) queda enano en la cabecera y junto al
   patrón; un escalón da legibilidad sin romper la celda. */
.igp-pattern-token {
    font-size: 0.875em;
}

/* C50 — Paleta semántica del ciclo del libro (entrada → salida → utilización).
   Usada en cabeceras agrupadoras del localizador (libros_localizador_table).
   Familia propia (NO mezclar con --first-color-warehouse/sales/logistics
   legacy, que cruzaba dominios y daba bajo contraste blanco/op50). Cada fase
   tiene un par (fondo saturado para fila agrupadora con texto blanco / fondo
   claro para fila columnas con texto oscuro), ambos contrastes verificados. */
:root {
    --igp-libro-entrada:        #1d4ed8;   /* azul saturado — texto blanco */
    --igp-libro-entrada-soft:   #dbeafe;   /* azul claro — texto oscuro */
    --igp-libro-entrada-fg:     #1e3a8a;   /* navy para fondo soft */
    --igp-libro-salida:         #047857;   /* verde saturado — texto blanco */
    --igp-libro-salida-soft:    #d1fae5;
    --igp-libro-salida-fg:      #065f46;
    --igp-libro-uso:            #b45309;   /* ámbar saturado — texto blanco */
    --igp-libro-uso-soft:       #fef3c7;
    --igp-libro-uso-fg:         #78350f;
}

.igp-libro-localizador thead th {
    border-bottom: 1px solid rgba(0,0,0,0.06);
    font-weight: 600;
}
.igp-libro-localizador thead .igp-libro-group {
    color: #fff;
    text-align: center;
    border-bottom: none;
}
.igp-libro-localizador thead .igp-libro-group.entrada { background-color: var(--igp-libro-entrada); }
.igp-libro-localizador thead .igp-libro-group.salida  { background-color: var(--igp-libro-salida); }
.igp-libro-localizador thead .igp-libro-group.uso     { background-color: var(--igp-libro-uso); }

.igp-libro-localizador thead .igp-libro-col.entrada { background-color: var(--igp-libro-entrada-soft); color: var(--igp-libro-entrada-fg); }
.igp-libro-localizador thead .igp-libro-col.salida  { background-color: var(--igp-libro-salida-soft);  color: var(--igp-libro-salida-fg); }
.igp-libro-localizador thead .igp-libro-col.uso     { background-color: var(--igp-libro-uso-soft);     color: var(--igp-libro-uso-fg); }

/* =========================================================================
   Forms migrados al patrón § 11 F5 — estilos de validación.

   Convención: los fragments migrados se marcan con `class="igp-form"`. El
   shim `formAjaxSubmit` (base_modern.html) detecta errores por presencia de
   `.errorlist` en el HTML re-rendered, por lo que NO se sustituye por
   `is-invalid` / `invalid-feedback` BS5 puro (rompería el shim). En su lugar
   se mantiene la salida `{{ form.X.errors|safe }}` (que renderiza
   `<ul class="errorlist">`) y se estiliza acotada al patrón `.igp-form`.

   Decisión consciente: estilo BS5-idiomático sin tocar el shim ni los views.
   ========================================================================= */

.igp-form .errorlist {
    list-style: none;
    padding: 0;
    margin: 0.25rem 0 0 0;
    font-size: 0.8125rem;
    color: var(--igp-status-0);
}
.igp-form .errorlist li {
    margin: 0;
    padding: 0;
    line-height: 1.4;
}

/* non_field_errors se renderiza al inicio del form (antes del primer campo).
   Card de error compacto que destaca sin ocupar mucha altura. */
.igp-form > .errorlist {
    background: var(--igp-status-0-bg);
    border-left: 3px solid var(--igp-status-0);
    padding: 0.5rem 0.75rem;
    margin: 0 0 1rem 0;
    border-radius: 4px;
    font-size: 0.875rem;
}

/* El fragment se inyecta en .modal-body del genericModal/genericMiniModal
   (base_modern.html). El modal padre BS5 NO tiene .modal-footer propio para
   estos modales → el footer del form vive en `<div class="d-flex justify-end mt-3">`
   dentro del propio form. Nada que estilizar aquí, BS5 default basta. */

/* Encabezados de sección para forms largos single-page (§ 11.y F5).
   Sustituye al wizard jQuery Steps con `<h3>` + `<fieldset>` por una división
   visual ligera dentro del mismo scroll: divider sutil, iconografía bi-*,
   tipografía discreta. Mantiene la jerarquía sin imponer navegación por pasos. */
.igp-form .igp-form-section {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--bs-body-color);
    text-transform: uppercase;
    letter-spacing: 0.025em;
    padding-bottom: 0.5rem;
    margin-bottom: 0.75rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.igp-form .igp-form-section i {
    margin-right: 0.4rem;
}

/* =========================================================================
   § 14 F5 — Densidad visual BS5 calibrada al ERP (Ciclo 32)

   Fragmentos AJAX heredados que cargan dentro de wrappers BS5 (sales/order,
   purchases/order, manufacturing/order, sales/invoice, purchases/invoice,
   sales/delivery_note, purchases/delivery_note) usan clases BS3 legacy de
   `custom.css` (`.detail-menu-btns`, `.btn-group-vertical`, `.table_totales`,
   `.detail-pad*`). Como `base_modern.html` no carga `custom.css`, esas clases
   carecen de overrides y el chrome BS5 default deja: tabs apilados en lugar
   de fila horizontal compacta, totales con padding 0.5rem (Margen+Beneficio
   caen fuera del fold), cabeceras grandes. Estas reglas restauran la densidad
   calibrada del ERP sin tocar los subtemplates (política § 4.2 perpetua).

   Patrón replicable: cualquier nueva clase legacy que necesite override va
   bajo esta sección y se documenta en F5 § 14.
   ========================================================================= */

/* --- Shim BS3 `col-xs-*` (removido en BS5) acotado al detail document fragment.
   El subtemplate emite `col-xs-3` para fila de 4 KPIs (Ref / Fecha / Customer
   Order Number / Received Date) y `col-xs-6 col-sm-3` para fila de totales
   arriba. Sin shim, los elementos pierden width y se apilan. --- */
.detail-menu-container .col-xs-1  { flex: 0 0 auto; width: 8.3333%;  }
.detail-menu-container .col-xs-2  { flex: 0 0 auto; width: 16.6666%; }
.detail-menu-container .col-xs-3  { flex: 0 0 auto; width: 25%;      }
.detail-menu-container .col-xs-4  { flex: 0 0 auto; width: 33.3333%; }
.detail-menu-container .col-xs-6  { flex: 0 0 auto; width: 50%;      }
.detail-menu-container .col-xs-12 { flex: 0 0 auto; width: 100%;     }

/* --- Cabecera del detail document fragment (refs, fechas, totales arriba). --- */
.detail-menu-container {
    background-color: #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
    border-radius: 6px;
    margin: 0;
}
.detail-menu-container h4 {
    font-weight: 500;
    color: #2c3034;
    font-size: 1rem;
    margin: 0;
    line-height: 1.3;
}
.detail-menu-container h5 {
    font-weight: 600;
    color: var(--igp-secondary, #6c757d);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin: 0 0 0.15rem 0;
}
.detail-details {
    min-height: 0;
}
.detail-pad {
    padding: 0.6rem 0.75rem;
}
.detail-pad2 {
    padding: 0.25rem 0.5rem;
}
.detail-pad-10 {
    padding: 0.4rem 0;
}
.overview h5 {
    font-weight: 600 !important;
    margin-top: 0;
}

/* --- Botoneras tab (5 botones acción) — restaurar fila horizontal compacta. --- */
.detail-menu-btns {
    padding: 0;
}
.detail-menu-btns > .btn-group-vertical,
.detail-menu-btns > #responsive {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: wrap;
    gap: 0.25rem;
    padding-bottom: 0.5rem;
    width: 100%;
}
.detail-menu-btns > .btn-group-vertical > .btn,
.detail-menu-btns > .btn-group-vertical > a.btn,
.detail-menu-btns > #responsive > .btn,
.detail-menu-btns > #responsive > a.btn {
    flex: 1 1 0;
    width: auto !important;        /* anula style="width: N%" inline legacy */
    max-width: none;
    min-width: 0;
    padding: 0.4rem 0.4rem;
    border-radius: 6px !important;
    background-color: #fff !important;
    border: 1px solid var(--igp-status-3, #5e8c54) !important;
    color: var(--igp-status-3, #5e8c54) !important;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.detail-menu-btns > .btn-group-vertical > a.btn:hover,
.detail-menu-btns > #responsive > a.btn:hover {
    background-color: var(--igp-status-3, #5e8c54) !important;
    color: #fff !important;
}
.detail-menu-btns > .btn-group-vertical > a.btn.active,
.detail-menu-btns > #responsive > a.btn.active {
    background-color: var(--igp-status-3, #5e8c54) !important;
    color: #fff !important;
    box-shadow: none;
}
/* fa-3x dentro de tabs es desmesurado en BS5; reescalar a tamaño contenido. */
.detail-menu-btns > .btn-group-vertical > a.btn .fa,
.detail-menu-btns > .btn-group-vertical > a.btn .bi,
.detail-menu-btns > #responsive > a.btn .fa,
.detail-menu-btns > #responsive > a.btn .bi {
    font-size: 1.35rem;
    line-height: 1;
}

/* --- Contenido del tab activo (paneles tab0A..tab6A). --- */
.detail-menu {
    padding: 0.75rem !important;
}
.detail-menu div.detail-menu-content:not(.active) {
    display: none;
}

/* --- Bloque totales — restaurar densidad legacy (todo entra sin scroll). --- */
.table.table_totales,
table.table_totales {
    margin-bottom: 0;
    font-size: 0.78rem;
}
.table.table_totales > tbody > tr,
table.table_totales > tbody > tr {
    font-weight: 600;
    text-align: right;
}
.table.table_totales > tbody > tr > td,
table.table_totales > tbody > tr > td {
    padding: 0.15rem 0.35rem !important;
    font-size: 0.78rem;
    vertical-align: middle;
    line-height: 1.4;
    border-bottom-width: 0;
    border-color: transparent;
}
/* botones inline de add/edit/delete dentro de las filas de totales. */
.table.table_totales .btn,
table.table_totales .btn {
    padding: 0 0.3rem;
    font-size: 0.75rem;
    line-height: 1.4;
}

/* --- dl-horizontal-profile usado en el tab Associations. --- */
.dl-horizontal-profile {
    font-size: 0.85rem;
    margin: 0;
}
.dl-horizontal-profile dt {
    color: var(--igp-secondary, #6c757d);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-weight: 600;
    margin-top: 0.4rem;
}
.dl-horizontal-profile dd {
    margin-left: 0;
    color: #2c3034;
}

/* --- Shim global BS3 `col-xs-*` (removido en BS5).
   964 usos en fragments AJAX heredados. Cuando un fragment se inyecta dentro
   de un wrapper migrado, las columnas pierden width y se apilan. BS5 no
   tiene breakpoint xs (mobile-first arranca en col-*); mapeamos a width fijo
   estilo BS3 sin media queries — coherente con el rol original `xs = always`. --- */
.col-xs-1  { flex: 0 0 auto; width: 8.3333%;  }
.col-xs-2  { flex: 0 0 auto; width: 16.6666%; }
.col-xs-3  { flex: 0 0 auto; width: 25%;      }
.col-xs-4  { flex: 0 0 auto; width: 33.3333%; }
.col-xs-5  { flex: 0 0 auto; width: 41.6666%; }
.col-xs-6  { flex: 0 0 auto; width: 50%;      }
.col-xs-7  { flex: 0 0 auto; width: 58.3333%; }
.col-xs-8  { flex: 0 0 auto; width: 66.6666%; }
.col-xs-9  { flex: 0 0 auto; width: 75%;      }
.col-xs-10 { flex: 0 0 auto; width: 83.3333%; }
.col-xs-11 { flex: 0 0 auto; width: 91.6666%; }
.col-xs-12 { flex: 0 0 auto; width: 100%;     }

/* --- Paleta semántica heredada `color-times/pending/partial/complete`.
   Utility classes BS3 usadas como icono de estado en fragments AJAX
   (`<i class="fa color-times">`). Mapeo a la paleta `--igp-status-*` ya
   consolidada en F5 § 3. Aplicación segura: clases sólo aplican color. --- */
.color-times,
.color-pending {
    color: var(--igp-status-0, #c0524a) !important;
}
.color-partial {
    color: var(--igp-status-1, #cf7e3d) !important;
}
.color-pend-calif {
    color: var(--igp-status-2, #cba12e) !important;
}
.color-complete,
.color-calificado {
    color: var(--igp-status-3, #5e8c54) !important;
}

/* --- fa-3x dentro de un `.btn` queda desmesurado en BS5; en BS3 con custom.css
   el override `.btn-group-vertical>a` con padding 11px lo absorbía. Reescalar
   defensivamente sólo dentro de botones (no afecta usos legítimos en headers). --- */
.btn .fa.fa-3x,
.btn .fa.fa-4x,
.btn .fa.fa-5x {
    font-size: 1.5rem;
    line-height: 1;
}

/* =========================================================================
   § 14.2 F5 — Acordeones legacy BS3 `panel-group / panel-default / panel-heading
   / panel-collapse / panel-body` directos en wrappers migrados (Ciclo 41 D6)

   `products/product.html` es el único wrapper migrado tras C40 que renderiza
   markup BS3 `panel-*` directamente en su cuerpo (acordeones compras/ventas/
   almacén + outer `#divPanelProduct.panel.panel-default.panel-shadow`). El
   resto de callsites de `panel-*` viven en fragmentos AJAX `*_table.html`
   bajo política § 4.2 perpetua (extienden `base.html` legacy, no se tocan).

   Sin override, BS5 ignora `.panel*` y los acordeones renderizan sin chrome
   (sin borde, sin fondo, sin separación), aunque los handlers JS siguen
   funcionando (BS5 collapse + `.show/.in` shim de `base_modern.html`).

   Estrategia: override CSS-only acotado a la jerarquía `panel-group >
   panel-default > panel-heading | panel-collapse` y al outer wrapper
   `#divPanelProduct`. NO afecta `.accordion-filters` (cubierto en § 14
   con scope propio) ni fragments AJAX inyectados por `.load()` (no usan
   `.panel-group` envoltorio).
   ========================================================================= */

#divPanelProduct.panel.panel-default {
    background: #fff;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
    margin-bottom: 1rem;
}
#divPanelProduct > .panel-body {
    padding: 1rem;
}

.panel-group {
    margin-bottom: 1rem;
}
.panel-group > .panel-default {
    background: #fff;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
    margin-bottom: 0.75rem;
    overflow: hidden;
}
.panel-group > .panel-default > .panel-heading {
    background-color: #fafbfc;
    border-bottom: 1px solid var(--igp-primary-30, #dee2e6);
    padding: 0.6rem 1rem;
    cursor: pointer;
    user-select: none;
}
.panel-group > .panel-default > .panel-heading .panel-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--igp-secondary, #2c3034);
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.panel-group > .panel-default > .panel-heading .panel-title .indicator_filters {
    color: var(--igp-primary, #5e8c54);
}
.panel-group > .panel-default > .panel-collapse {
    background: #fff;
}

/* =============================================================================
   Iconos canónicos de actores IGP — §18.6 (post-C50 pulido Usuarios)
   Ganadero: icono custom oveja (mask-image + currentColor = comportamiento
   idéntico a <i class="bi bi-*">).
   ============================================================================= */
.igp-icon-sheep {
    display: inline-block;
    width: 1em; height: 1em;
    vertical-align: -0.125em;
    background-color: currentColor;
    -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='4.5' cy='8.5' r='2.3'/><circle cx='7' cy='8' r='2.6'/><circle cx='9.5' cy='8.5' r='2.3'/><circle cx='11.6' cy='6.7' r='1.7'/><ellipse cx='10.6' cy='5.4' rx='.45' ry='.8' transform='rotate(-25 10.6 5.4)'/><rect x='3.9' y='10.5' width='.9' height='2.9' rx='.3'/><rect x='6.6' y='10.5' width='.9' height='2.9' rx='.3'/><rect x='9.3' y='10.5' width='.9' height='2.9' rx='.3'/></svg>");
            mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='4.5' cy='8.5' r='2.3'/><circle cx='7' cy='8' r='2.6'/><circle cx='9.5' cy='8.5' r='2.3'/><circle cx='11.6' cy='6.7' r='1.7'/><ellipse cx='10.6' cy='5.4' rx='.45' ry='.8' transform='rotate(-25 10.6 5.4)'/><rect x='3.9' y='10.5' width='.9' height='2.9' rx='.3'/><rect x='6.6' y='10.5' width='.9' height='2.9' rx='.3'/><rect x='9.3' y='10.5' width='.9' height='2.9' rx='.3'/></svg>");
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center;  mask-position: center;
    -webkit-mask-size: contain;     mask-size: contain;
}

/* =============================================================================
   Variantes de chip de estado/tipo para fichas de detalle (§ ficha usuario).
   Base .igp-chip ya en línea ~773.
   ============================================================================= */
.igp-chip-success { background-color: rgba(94, 140, 84, 0.15); color: #3e6037; }
.igp-chip-success i { color: var(--igp-status-3); }
.igp-chip-danger  { background-color: rgba(192, 82, 74, 0.15);  color: #8a3934; }
.igp-chip-danger  i { color: var(--igp-status-0); }
.igp-chip-blue    { background-color: rgba(70, 184, 196, 0.15); color: #2f7a82; }
.igp-chip-blue    i { color: var(--igp-blue); }

/* =============================================================================
   Componentes específicos de la ficha de usuario.
   ============================================================================= */

/* Avatar circular en cabecera de ficha. */
.igp-user-avatar {
    width: 88px; height: 88px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid #e6e8eb;
    background: #fff;
}

/* Lista de roles IGP (pares icono-cuadradito + actor + entidad). */
.igp-role-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.25rem;
    border-bottom: 1px dashed #eef0f2;
    font-size: 0.9rem;
}
.igp-role-row:last-child { border-bottom: 0; }
.igp-role-row .igp-role-icon {
    width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--igp-primary-10);
    color: var(--igp-primary);
    border-radius: 6px;
    font-size: 1rem;
    flex-shrink: 0;
}
.igp-role-row .igp-role-label {
    color: var(--igp-secondary);
    font-weight: 500;
    min-width: 110px;
}
.igp-role-row .igp-role-entity { color: #212529; }
.igp-role-row.empty { color: #8a9099; font-style: italic; font-size: 0.85rem; }

/* Tabla informativa de dispositivos asociados. */
.igp-device-table { font-size: 0.85rem; margin: 0; }
.igp-device-table th {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: #8a9099;
    background: #fafbfc;
    font-weight: 600;
}
.igp-device-table td { vertical-align: middle; }
.igp-device-table .device-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
    color: #5c6670;
}

/* Modales de ancho grande — cascada responsiva al estilo Bootstrap
   Uso: añadir la clase al .modal-dialog  (igual que modal-lg / modal-xl).
   Bootstrap aplica max-width: var(--bs-modal-width) desde ≥576px;
   por debajo de esa anchura el diálogo ocupa el ancho del viewport. */

/* modal-1300: target 1300px en xxl (≥1400px) */
.modal-1300 { --bs-modal-width: 800px; }
@media (min-width: 1200px) { .modal-1300 { --bs-modal-width: 1140px; } }
@media (min-width: 1400px) { .modal-1300 { --bs-modal-width: 1300px; } }

/* modal-1500: usado por forms densos (editar Entidad → 4 igp-section-card en 2×2
   + partial de dirección con mapa Leaflet). En viewport <1600px el modal ocupa
   95vw (siempre generoso, con margen mínimo); en viewports mayores se queda
   fijo en 1500px máximo. Antes los breakpoints (800→1140→1400→1500) dejaban
   el modal a 1140-1400px en pantallas medias, donde el layout no respiraba. */
.modal-1500 { --bs-modal-width: 95vw; }
@media (min-width: 1600px) { .modal-1500 { --bs-modal-width: 1500px; } }

/* modal-1900: target 1900px en pantallas ≥2000px. Usado por la ficha del
   lote del libro de matadero (cabecera 3 columnas + DT con 15 columnas
   necesita más superficie que el modal-1500 por defecto). */
.modal-1900 { --bs-modal-width: 800px; }
@media (min-width: 1200px) { .modal-1900 { --bs-modal-width: 1140px; } }
@media (min-width: 1400px) { .modal-1900 { --bs-modal-width: 1400px; } }
@media (min-width: 1600px) { .modal-1900 { --bs-modal-width: 1600px; } }
@media (min-width: 1800px) { .modal-1900 { --bs-modal-width: 1800px; } }
@media (min-width: 2000px) { .modal-1900 { --bs-modal-width: 1900px; } }

/* Cabecera del genericModal coloreada por rol IGP. La clase se aplica al
   #genericModal desde el JS al abrir la ficha del actor correspondiente
   (igpOpenMataderoModal añade .igp-modal-role-m, etc.) y se limpia en
   hidden.bs.modal. Centraliza el estilo ERP-wide para que la ficha del
   matadero (o cualquier otro rol futuro) tenga su cabecera coloreada
   independientemente del origen de la apertura — DT, listado, vistas de
   ganadero/veedor con mataderos relacionados, etc. */
.igp-modal-role-m .modal-header {
    background-color: var(--igp-role-m);
    color: #fff;
    border-bottom: 5px solid #e5e5e5;
    border-radius: 6px 6px 0 0;
}
.igp-modal-role-m .modal-header .btn-close { filter: brightness(0) invert(1); }

/* =========================================================================
   C50 (F5 § 18.8 / FIX5 paleta canónica de actores) — Paleta canónica de los
   5 actores IGP + paleta canónica de los 4 estados del volante.

   Portado del mockup canónico `docs/styleguide/actores_palette.html`
   (Dave OK 2026-06-04). Estos valores son hex LITERALES (no aliases a
   --igp-status-*) para que la familia --igp-role-* evolucione de forma
   independiente de la paleta legacy de estado --igp-status-* (que se mantiene
   intacta para pantallas de volante aún no auditadas — FIX5 § 3).

   Mapeo de rol → color canónico (FIX5):
     G (Ganadero)     → terracota     #c0524a
     T (Transportista)→ ocre amarillo #cb9e32  (CAMBIA desde el alias #cf7e3d
                                                 legacy, para alejarlo del rojo G)
     C (Cooperativa)  → turquesa      #46b8c4
     O (Operador)     → morado        #7d5ba6
     V (Veedor)       → verde olivo   #5e8c54

   Estados del volante (asociados semánticamente al actor que más interviene
   en cada fase): carga = G · descarga = T · calificar = azul corp. propio ·
   calificado = V.
   ========================================================================= */
:root {
    /* === Paleta canónica de actores IGP === */
    --igp-role-g: #c0524a;   /* terracota — Ganadero */
    --igp-role-t: #cb9e32;   /* ocre amarillo — Transportista */
    --igp-role-c: #46b8c4;   /* turquesa — Cooperativa */
    --igp-role-o: #7d5ba6;   /* morado — Operador */
    --igp-role-v: #5e8c54;   /* verde olivo — Veedor */
    --igp-role-m: #6c7480;   /* gris-azulado — Matadero (no actor IGP, pero pinta como tal en la pestaña Ganadero, FIX7) */

    --igp-role-g-bg: rgba(192, 82, 74, 0.10);
    --igp-role-t-bg: rgba(203, 158, 50, 0.10);
    --igp-role-c-bg: rgba(70, 184, 196, 0.10);
    --igp-role-o-bg: rgba(125, 91, 166, 0.10);
    --igp-role-v-bg: rgba(94, 140, 84, 0.10);
    --igp-role-m-bg: rgba(108, 116, 128, 0.10);

    /* === Paleta canónica de estados del volante === */
    --igp-volante-pend-carga:     var(--igp-role-g);   /* = #c0524a */
    --igp-volante-pend-descarga:  var(--igp-role-t);   /* = #cb9e32 */
    --igp-volante-pend-calificar: #3f7ba3;             /* azul corporativo, propio */
    --igp-volante-calificado:     var(--igp-role-v);   /* = #5e8c54 */

    --igp-volante-pend-carga-bg:     var(--igp-role-g-bg);
    --igp-volante-pend-descarga-bg:  var(--igp-role-t-bg);
    --igp-volante-pend-calificar-bg: rgba(63, 123, 163, 0.10);
    --igp-volante-calificado-bg:     var(--igp-role-v-bg);
}

/* ---- Cabecera plana de gran impacto ---- */
.igp-entity-hero {
    background: #fff;
    border: 1px solid var(--bs-border-color);
    border-radius: 10px;
    overflow: hidden;
    margin-bottom: 18px;
}
.igp-entity-hero-title {
    padding: 12px 18px 10px;
    background: linear-gradient(180deg, #fcfcfd 0%, #fff 100%);
    border-bottom: 1px solid var(--bs-border-color);
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
}
.igp-entity-hero-name {
    font-size: 1.4rem;
    font-weight: 600;
    color: var(--bs-body-color);
    line-height: 1.1;
    margin: 0;
    letter-spacing: .2px;
}
.igp-entity-hero-meta {
    display: inline-flex;
    gap: 6px;
    flex-wrap: wrap;
    align-items: center;
}
.igp-entity-hero-meta .hero-divider {
    height: 18px;
    width: 1px;
    background: var(--bs-border-color);
    margin: 0 4px;
}
.igp-entity-hero-actions {
    margin-left: auto;
    display: flex;
    gap: 6px;
    align-items: center;
}

/* ---- Badges de la cabecera (persona / estado / rol) ----
   Modificadores propios; el `.badge` base de BS5 sólo aporta layout. */
.badge-person {
    background: #eef0f2;
    color: #5c6670;
    text-transform: none;
    letter-spacing: .3px;
}
.badge-active {
    background: var(--igp-status-3-bg);
    color: var(--igp-status-3);
    border: 1px solid rgba(94, 140, 84, .3);
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.badge-inactive {
    background: var(--igp-status-0-bg);
    color: var(--igp-status-0);
    border: 1px solid rgba(192, 82, 74, .3);
}
.badge-role {
    background: #fff;
    border: 1px solid var(--bs-border-color);
    color: var(--bs-body-color);
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: .22rem .55rem;
}
.badge-role .role-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    font-size: .7rem;
    font-weight: 600;
    color: #fff;
    line-height: 1;
}
.badge-role.g .role-icon { background: var(--igp-role-g); }
.badge-role.t .role-icon { background: var(--igp-role-t); }
.badge-role.c .role-icon { background: var(--igp-role-c); }
.badge-role.o .role-icon { background: var(--igp-role-o); }
.badge-role.v .role-icon { background: var(--igp-role-v); }

/* ---- Grid responsive 4-col (xl) / 2-col (md) / 1-col (xs-sm) ---- */
.igp-entity-hero-body {
    padding: 16px 18px;
    display: grid;
    gap: 16px 18px;
    grid-template-columns: repeat(12, minmax(0, 1fr));
}
.hero-col {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    grid-column: span 12;
}

/* FIX2 F3 — cada hero-col es una card delimitada con header + body. */
.hero-section-card {
    gap: 0;
    background: #fff;
    border: 1px solid var(--bs-border-color);
    border-radius: 8px;
    overflow: hidden;
}
.hero-section-header {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: .5rem .75rem;
    border-bottom: 1px solid var(--bs-border-color);
    background: #fcfcfd;
}
.hero-section-header > i { color: #5c6670; font-size: .85rem; }
.hero-section-title {
    font-size: .7rem;
    font-weight: 600;
    color: #5c6670;
    text-transform: uppercase;
    letter-spacing: .4px;
}
.hero-section-header .kpi-tag { font-size: .65rem; padding: 0 6px; }
.hero-section-header .mini-action { width: 20px; height: 20px; }
.hero-section-body { padding: .65rem .9rem; }
@media (min-width: 768px) {
    .hero-col { grid-column: span 6; }
}
@media (min-width: 1200px) {
    .hero-col { grid-column: span 3; }
}
.hero-col-label {
    font-size: .7rem;
    font-weight: 600;
    color: #5c6670;
    text-transform: uppercase;
    letter-spacing: .4px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 2px;
    flex-wrap: wrap;
}
.hero-col-label .kpi-tag {
    font-size: .65rem;
    padding: 0 6px;
}

/* ---- Lista clave/valor compacta dentro de las columnas ---- */
.igp-entity-hero .kv {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 12px;
    line-height: 1.4;
    margin: 0;
}
.igp-entity-hero .kv dt {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .3px;
    color: #5c6670;
    font-weight: 500;
    align-self: center;
}
.igp-entity-hero .kv dd {
    margin: 0;
    font-size: .88rem;
    color: var(--bs-body-color);
    font-weight: 500;
    overflow-wrap: anywhere;
}
.kv dd .icon-check, .icon-check { color: var(--igp-status-3); }
.kv dd .icon-x, .icon-x { color: var(--igp-status-0); }
.igp-entity-hero .kv dd a {
    color: var(--bs-body-color);
    text-decoration: none;
    border-bottom: 1px dashed var(--bs-border-color);
}
.igp-entity-hero .kv dd a:hover { color: #f7ac3b; border-bottom-color: #f7ac3b; }
.toggle-inline {
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    line-height: 1;
}

/* ---- Acción mini (+) ---- */
.mini-action {
    border: 1px solid #f7ac3b;
    color: #f7ac3b;
    background: transparent;
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    cursor: pointer;
    font-size: .7rem;
    padding: 0;
    line-height: 1;
}
.mini-action:hover { background: rgba(247, 172, 59, .1); }

/* ---- Users — lista completa con scroll vertical (FIX2 F4 / FIX3 F4) ----
   max-height ≈ 5 filas (cada li ~40px: u-main + u-name + padding); scrollbar
   solo cuando hay overflow real (overflow-y: auto), tematizada en ámbar. */
.user-list {
    list-style: none;
    padding: 0 4px 0 0;
    margin: 0;
    font-size: .82rem;
    line-height: 1.35;
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-height: 200px;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: #f7ac3b #f0f1f3;
}
.user-list::-webkit-scrollbar { width: 6px; }
.user-list::-webkit-scrollbar-track { background: #f0f1f3; border-radius: 3px; }
.user-list::-webkit-scrollbar-thumb { background: #f7ac3b; border-radius: 3px; }
.user-list li {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 6px;
    padding: 3px 0;
    border-bottom: 1px dashed var(--bs-border-color);
    align-items: center;
}
.user-list li:last-of-type { border-bottom: none; }
.user-list .u-main { display: flex; align-items: center; gap: 5px; min-width: 0; }
.user-list .u-mail {
    font-weight: 500;
    color: var(--bs-body-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .8rem;
    min-width: 0;
}
.user-list .u-tags { display: inline-flex; gap: 2px; flex-shrink: 0; }
.user-list .u-tag {
    font-size: .6rem;
    font-weight: 700;
    color: #fff;
    width: 16px;
    height: 16px;
    border-radius: 50%;   /* FIX5: pictograma circular canónico (antes 14×14 radius 3px) */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.user-list .u-tag.c { background: var(--igp-role-c); }
.user-list .u-tag.o { background: var(--igp-role-o); }
.user-list .u-tag.g { background: var(--igp-role-g); }
.user-list .u-tag.t { background: var(--igp-role-t); }
.user-list .u-tag.v { background: var(--igp-role-v); }
.user-list .u-name {
    grid-column: 1 / -1;
    color: #5c6670;
    font-size: .74rem;
    font-style: italic;
    text-transform: uppercase;   /* FIX6 F4: versales en nombres de usuario */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.user-list .u-acts { display: inline-flex; gap: 1px; flex-shrink: 0; }
.user-list .u-act {
    background: transparent;
    border: none;
    color: #5c6670;
    cursor: pointer;
    padding: 1px 3px;
    font-size: .78rem;
    border-radius: 3px;
    line-height: 1;
}
.user-list .u-act:hover { background: #f0f1f3; color: var(--bs-body-color); }
.user-list .u-act.danger:hover { color: var(--igp-status-0); }
.user-list-more {
    margin-top: 4px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: .78rem;
    color: #f7ac3b;
    font-weight: 500;
    cursor: pointer;
    background: transparent;
    border: none;
    padding: 0;
    text-decoration: none;
}
.user-list-more:hover { text-decoration: underline; }

/* ---- Notas con truncado + expansor inline ---- */
.notes-inline {
    font-size: .8rem;
    color: var(--bs-body-color);
    line-height: 1.4;
    background: #fafbfc;
    border-left: 2px solid #f7ac3b;
    padding: 6px 10px;
    border-radius: 0 4px 4px 0;
    margin-top: 4px;
}
.notes-inline .notes-label {
    display: block;
    font-size: .65rem;
    color: #5c6670;
    text-transform: uppercase;
    letter-spacing: .3px;
    font-weight: 600;
    margin-bottom: 2px;
}
.notes-inline .notes-truncated { display: block; }
.notes-inline .notes-expand {
    margin-top: 3px;
    background: transparent;
    border: none;
    color: #f7ac3b;
    font-size: .7rem;
    font-weight: 500;
    cursor: pointer;
    padding: 0;
    letter-spacing: .2px;
}
.notes-inline .notes-expand:hover { text-decoration: underline; }

/* ---- Badges circulares de rol en breadcrumb + role-cards de forms ---- */
.role-icon-bc {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;   /* FIX5: circular canónico (antes 4px cuadrado-redondeado) */
    font-size: .75rem;
    color: #fff;
    font-weight: 600;
    line-height: 1;
    margin-left: 4px;
}
.role-icon-bc.ri-c { background: var(--igp-role-c); }
.role-icon-bc.ri-o { background: var(--igp-role-o); }
.role-icon-bc.ri-g { background: var(--igp-role-g); }
.role-icon-bc.ri-t { background: var(--igp-role-t); }
.role-icon-bc.ri-v { background: var(--igp-role-v); }

/* ---- KPI tag (contador pequeño) ---- */
.kpi-tag {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 10px;
    font-size: .72rem;
    background: #e8eef4;
    color: #5c6670;
}

/* =========================================================================
   Cabecera de rol + secciones dentro de los fragments *_detail.html
   (cargados por AJAX en cada pestaña vertical de la ficha de entidad).
   ========================================================================= */
/* FIX3 F7 — cabecera de rol compacta horizontal (meta inline + acciones). */
.role-panel-header {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--bs-border-color);
    margin-bottom: 20px;
    flex-wrap: wrap;
}
.role-panel-meta {
    display: flex;
    align-items: center;
    gap: 4px 18px;
    flex-wrap: wrap;
    font-size: .9rem;
    color: var(--bs-body-color);
}
.role-panel-meta span { white-space: nowrap; }
.role-panel-meta strong {
    color: #5c6670;
    font-weight: 600;
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .3px;
    margin-right: 4px;
}
.role-panel-meta em {
    color: #5c6670;
    font-style: italic;
    font-size: .82rem;
}
.role-panel-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

/* FIX7 F1 — Cabecera V2 de la pestaña Ganadero (par label/valor tipografiado).
   Modificador `role-panel-header-v2` para NO regresionar la `.role-panel-header`
   compartida por los detail de los otros 4 roles (fuera de scope de FIX7). */
.role-panel-header.role-panel-header-v2 {
    padding: 14px 18px;
    background: #fafbfc;
    border: 1px solid #e3e6ea;
    border-radius: 6px;
}
.role-panel-meta-v2 { display: flex; gap: 32px; flex-wrap: wrap; align-items: baseline; }
.role-panel-meta-v2 .meta-pair { display: flex; flex-direction: column; gap: 1px; }
.role-panel-meta-v2 .meta-pair .k { font-size: .7rem; color: var(--igp-secondary); text-transform: uppercase; letter-spacing: .6px; font-weight: 600; }
.role-panel-meta-v2 .meta-pair .v { font-size: 1rem; color: #1f2428; font-weight: 600; }
.role-panel-meta-v2 .meta-pair .v em { font-style: normal; color: #7a8089; font-size: .86em; font-weight: 400; margin-left: 4px; }
.role-panel-meta-v2 .meta-pair.is-baja .v { color: var(--igp-status-0); }

/* FIX7 F1 — aire entre la cabecera/actores y los KPIs de volante de la pestaña Ganadero. */
#divContentPanelsGanadero { margin-top: 28px; }

/* FIX7 F3 — Cards de actores asociados (pestaña Ganadero). Variante autocontenida
   .igp-actor-card: NO hereda el header ámbar de .igp-section-card global. */
.actors-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin-top: 0; margin-bottom: 20px; }
@media (max-width: 1100px) { .actors-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px)  { .actors-grid { grid-template-columns: 1fr; } }

.igp-actor-card {
    background: #fff;
    border: 1px solid #e3e6ea;
    border-radius: 10px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .03);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.igp-actor-card .igp-section-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 14px;
    background: #fafbfc;
    border-bottom: 1px solid #e3e6ea;
}
.igp-actor-card .role-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--role-color);
    color: #fff;
    flex-shrink: 0;
}
.igp-actor-card .role-pill i { font-size: .85em; color: #fff; }
.igp-actor-card .igp-section-header .igp-section-title {
    flex: 1;
    font-size: .92rem;
    font-weight: 600;
    color: #1f2428;
    text-transform: none;
    letter-spacing: 0;
    margin: 0;
    padding: 0;
    border: 0;
    display: block;
}
.igp-actor-card .igp-section-count {
    font-size: .78rem;
    color: var(--igp-secondary);
    background: #eef0f2;
    padding: 2px 8px;
    border-radius: 10px;
    font-weight: 600;
}
.igp-actor-card .actor-list { list-style: none; padding: 6px 0; margin: 0; flex: 1; }
.igp-actor-card .actor-list li {
    padding: 6px 14px;
    font-size: .86rem;
    color: #3a4046;
    display: flex;
    align-items: center;
    gap: 8px;
    border-bottom: 1px solid #f1f3f5;
}
.igp-actor-card .actor-list li:last-child { border-bottom: none; }
.igp-actor-card .actor-list li:hover { background: #fafbfc; }
.igp-actor-card .actor-list li i { font-size: .86em; color: var(--igp-secondary); opacity: .6; }
.igp-actor-card .actor-list li.muted { color: #9098a1; font-style: italic; padding: 14px; text-align: center; justify-content: center; }
.igp-actor-card .section-footer {
    padding: 9px 14px;
    border-top: 1px solid #f1f3f5;
    background: #fafbfc;
    font-size: .82rem;
    text-align: center;
}
.igp-actor-card .section-footer a { color: var(--igp-blue); text-decoration: none; font-weight: 600; }
.igp-actor-card .section-footer a:hover { text-decoration: underline; }

.igp-actor-card.role-g { --role-color: var(--igp-role-g); }
.igp-actor-card.role-t { --role-color: var(--igp-role-t); }
.igp-actor-card.role-c { --role-color: var(--igp-role-c); }
.igp-actor-card.role-o { --role-color: var(--igp-role-o); }
.igp-actor-card.role-m { --role-color: var(--igp-role-m); }
.igp-actor-card.role-v { --role-color: var(--igp-role-v); }

/* FIX7 F5/F8 — badges canónicos en DT Volantes / Informes Sacrificios + leyenda. */
.badge-ref {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 4px; font-size: .74rem; font-weight: 700;
    color: #fff; margin-right: 6px; vertical-align: middle;
}
.badge-ref-m { background: var(--igp-role-o); }
.badge-ref-w { background: var(--igp-role-c); }
.ref-rest { color: var(--igp-secondary); font-size: .86rem; }

.badge-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 26px; padding: 2px 9px; border-radius: 12px;
    background: var(--igp-primary); color: #fff; font-size: .82rem; font-weight: 600;
    margin-left: 6px;
}
/* Badges de "estado de vida" para series (activa / parada / sin uso). El color
   de la barra de progreso lo aporta la propia .progress-bar (bg-success / bg-info
   / bg-warning / bg-danger). El badge solo refleja si la serie sigue viva.
   - activa: emisión en los últimos 12 meses.
   - parada: se ha usado pero hace > 12 meses.
   - sin uso: nunca se ha emitido nada con esa serie. */
.igp-badge-estado-activa, .igp-badge-estado-parada, .igp-badge-estado-sin-uso {
    display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 12px;
    font-size: .72rem; font-weight: 600;
}
.igp-badge-estado-activa  { background: rgba(40, 167, 69, 0.15); color: #1e7e34; }
.igp-badge-estado-parada  { background: rgba(108, 117, 125, 0.18); color: #495057; }
.igp-badge-estado-sin-uso { background: rgba(108, 117, 125, 0.08); color: #6c757d; }

/* ===== Cabeceras DT con icono apilado sobre texto =====
   `.igp-th-stack` apila el `<i>` en la 1ª línea y el `<span>` con texto en
   la 2ª línea, todo centrado. Se aplica como hijo directo de cada `<th>`
   en el thead/tfoot del DT volantes (y futuras), garantizando alineación
   vertical y horizontal consistente. */
.igp-th-stack {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    line-height: 1.1;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--igp-secondary);
    width: 100%;
}
.igp-th-stack > i {
    font-size: 0.95rem;
    color: var(--igp-primary);
    line-height: 1;
}
.igp-th-stack > span {
    white-space: nowrap;
}
/* DT por defecto deja los th alineados a la izquierda; con .igp-th-stack
   queremos todo centrado tanto el contenido del th como las celdas con tinte. */
table.dataTable thead th:has(> .igp-th-stack),
table.dataTable tfoot th:has(> .igp-th-stack) {
    text-align: center;
    vertical-align: middle;
}

/* ===== Tinte sutil de columna por rol canon (DT volantes) =====
   Aplicado vía className en columnDef (th + tfoot th + td) — robusto frente
   a columnas ocultas y reordenamientos. Colores hex sólidos (sin alpha)
   para no penalizar el compositing del browser. Cada hex es el blend del
   color canon sobre blanco al ~10%.

   Mapping rol → cols del DT volantes (orden actual rol→fecha):
     G  rojo terracota   #FCEFEE   Ganadero · Explotación · Emisión
     T  ocre amarillo    #FBF3DA   Transportista · Carga
     C  turquesa         #E7F7F8   Cooperativa · Traspasos
     O  morado           #F1ECF5   Operador
     M  gris azulado     #F1F2F3   Descarga · Matadero · Matanza
     V  verde olivo      #EDF3EB   Veedor · Calificación
*/
table.dataTable .igp-col-rol-g { background-color: #FCEFEE; }
table.dataTable .igp-col-rol-t { background-color: #FBF3DA; }
table.dataTable .igp-col-rol-c { background-color: #E7F7F8; }
table.dataTable .igp-col-rol-o { background-color: #F1ECF5; }
table.dataTable .igp-col-rol-m { background-color: #F1F2F3; }
table.dataTable .igp-col-rol-v { background-color: #EDF3EB; }

/* Icono de la cabecera coloreado con el color CANON del rol — sustituye
   al ámbar por defecto de `.igp-th-stack > i` para que cada th transmita
   visualmente su pertenencia al actor. */
table.dataTable .igp-col-rol-g .igp-th-stack > i { color: var(--igp-role-g); }
table.dataTable .igp-col-rol-t .igp-th-stack > i { color: var(--igp-role-t); }
table.dataTable .igp-col-rol-c .igp-th-stack > i { color: var(--igp-role-c); }
table.dataTable .igp-col-rol-o .igp-th-stack > i { color: var(--igp-role-o); }
table.dataTable .igp-col-rol-m .igp-th-stack > i { color: var(--igp-role-m); }
table.dataTable .igp-col-rol-v .igp-th-stack > i { color: var(--igp-role-v); }

/* ===== Badge azul canon para marcar volante con traspaso/s en col Operador.
   Mismo azul que `.igp-timeline-event.event-transfer` para coherencia visual. */
.igp-badge-traspaso {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #2f8fc4;
    color: #fff;
    font-size: 0.7rem;
    box-shadow: 0 1px 2px rgba(0,0,0,0.12);
}

/* ===== Botones de acción en DataTables — `.igp-dt-actions` =====
   Patrón canon (guía §3.x) para la columna de acciones de un DataTable.
   El wrapper es un grid 2D de 4 columnas × 32px FIJOS con gap uniforme. Cada
   botón hijo se fuerza a 32px × 32px con el icono centrado, garantizando:
   - misma posición horizontal de cada columna en TODAS las filas (alineación
     vertical perfecta aunque las filas tengan distinto nº de botones);
   - misma posición x entre la 1ª y la 2ª línea cuando el wrap salta (5-7 botones);
   - botones cuadrados independientemente del ancho intrínseco del icono.
   Se aplica en `createdCell` de la columna de acciones envolviendo el HTML
   resultante: `<div class="igp-dt-actions">…</div>`. */
.igp-dt-actions {
    display: grid;
    grid-template-columns: repeat(4, 32px);
    gap: 4px;
    width: 140px;
    margin: 0 auto;
    place-items: center;
}
.igp-dt-actions > .btn {
    width: 32px;
    height: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* DT core inyecta `table.dataTable td .btn + .btn { margin-left:.25rem }`
       que desplaza cada botón ≠ primero dentro de su celda y rompe la
       alineación entre líneas. El `gap` del grid ya gestiona la separación. */
    margin: 0 !important;
}

/* ===== Badges de precio (canon §X.x: operaciones financieras) =====
   Convención visual cross-ERP para resaltar PRECIOS:
   - `.badge-price-purchase` → precio de COMPRA (gasto/coste). Azul-marino corporativo
     (`--igp-price-purchase`).
   - `.badge-price-sale`     → precio de VENTA (ingreso). Verde IGP
     (`--igp-price-sale`).
   Tamaño medio canon (`font-size: 0.85rem`, padding compacto). Pensado para celdas
   de DataTables, pero válido en fichas, KPIs, tooltips, listados — cualquier
   sitio donde un PRECIO necesite jerarquía visual frente al texto plano.

   Uso típico (template):
       <span class="badge-price badge-price-purchase">12,345 €/u.</span>
       <span class="badge-price badge-price-sale">14,500 €/u.</span>

   Ordenable numérico en DataTables: el `data` function debe devolver el float raw
   cuando `type==='sort' || type==='type'`, y el HTML del badge cuando `type==='display'`.
   El backend envía dos campos por precio (ej. `purchase_average_price` HTML +
   `purchase_average_price_num` float).

   Para mantener la jerarquía si conviven con otros badges (rol, estado), `badge-price`
   ya impone `white-space: nowrap` y `tabular-nums` — los precios no rompen línea
   y los dígitos quedan alineados verticalmente entre filas. */
.badge-price {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 500;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    white-space: nowrap;
}
.badge-price-purchase {
    background-color: var(--igp-price-purchase-bg);
    color: var(--igp-price-purchase);
    border: 1px solid var(--igp-price-purchase);
}
.badge-price-sale {
    background-color: var(--igp-price-sale-bg);
    color: var(--igp-price-sale);
    border: 1px solid var(--igp-price-sale);
}
/* Dorado — para IMPORTES TOTALES (facturas, albaranes, pedidos: ambos lados).
   Semántica cross-ERP: "este número es EL total, el más importante de la fila".
   Introducido C50 6/6 tras cierre del hilo de facturas venta — Dave pidió más
   protagonismo a la col Total en los 6 DTs compras+ventas. Peso semibold + font
   ligeramente mayor para reforzar la jerarquía. */
.badge-price-total {
    background-color: var(--igp-price-total-bg);
    color: var(--igp-price-total);
    border: 1px solid var(--igp-price-total);
    font-weight: 600;
    font-size: 0.9rem;
}
/* Modifier para valor 0 — apaga el color semántico (compra/venta) y deja gris muted.
   Semánticamente "no hay precio significativo" — se aplica encima de la variante base:
       <span class="badge-price badge-price-purchase badge-price-zero">0,000 €/u.</span>
   El selector .badge-price.badge-price-zero gana especificidad sobre las variantes. */
.badge-price.badge-price-zero {
    background-color: rgba(108, 117, 125, 0.08);
    color: #adb5bd;
    border-color: #ced4da;
}

/* ===== Filtros canon — altura uniforme en .filters-row =====
   Forzamos la misma altura para form-select-sm, form-control-sm, input-group y
   select2 dentro de un `.filters-row`. Sin este override:
   - el input nativo + input-group-text del daterangepicker queda más alto que
     un select2 form-select-sm, generando filas visualmente desalineadas;
   - select2 a veces no respeta el form-select-sm si el script de inicialización
     no llega antes del primer render. */
.filters-row .form-control-sm,
.filters-row .form-select-sm,
.filters-row .input-group-sm > .form-control,
.filters-row .input-group-sm > .form-select,
.filters-row .input-group-sm > .input-group-text,
.filters-row .select2-container--default .select2-selection--single {
    height: calc(1.5em + 0.5rem + 2px) !important;
    min-height: calc(1.5em + 0.5rem + 2px) !important;
}
.filters-row .select2-container--default .select2-selection--single .select2-selection__rendered {
    line-height: calc(1.5em + 0.5rem) !important;
}
.filters-row .select2-container--default .select2-selection--single .select2-selection__arrow {
    height: calc(1.5em + 0.5rem) !important;
}

/* ===== Forms IGP — altura uniforme en `.igp-form` =====
   Misma técnica que `.filters-row` (arriba). Sin esto, en un form con mezcla
   de form-control-sm + form-select-sm + select2 + input-group-sm, las alturas
   no cuadran (input nativo más alto que select2, input-group con icono más
   alto que form-select-sm aislado, etc.) y la rejilla se ve "saltando".

   Cuidado: `calc(1.5em + 0.5rem + 2px)` usa `em` que se calcula contra el
   font-size del propio elemento. En form-control-sm el font-size es 0.875rem
   (heredado de BS5), así que 1.5em = 21px → altura ≈ 31px. En el wrapper
   select2, el font-size se hereda como 1rem (no es `-sm` por sí mismo), así
   que 1.5em = 24px → altura ≈ 34px (3px más alto, visible). Fix: forzamos
   font-size 0.875rem en el wrapper select2 para que el `em` cuadre con los
   inputs sm. Idem en el rendered span y la flecha. */
.igp-form .form-control-sm,
.igp-form .form-select-sm,
.igp-form .input-group-sm > .form-control,
.igp-form .input-group-sm > .form-select,
.igp-form .input-group-sm > .input-group-text,
.igp-form .select2-container--default .select2-selection--single {
    height: calc(1.5em + 0.5rem + 2px) !important;
    min-height: calc(1.5em + 0.5rem + 2px) !important;
}
.igp-form .select2-container--default .select2-selection--single {
    font-size: 0.875rem !important;
}
.igp-form .select2-container--default .select2-selection--single .select2-selection__rendered {
    font-size: 0.875rem !important;
    line-height: calc(1.5em + 0.5rem) !important;
}
.igp-form .select2-container--default .select2-selection--single .select2-selection__arrow {
    height: calc(1.5em + 0.5rem) !important;
}

/* ===== Tabla "Cabezas por raza" del form crear Volante =====
   Tabla canon más legible: filas con altura cómoda, header con tinte sutil
   del rol G (ganadero), input centrado con tipografía grande y peso bold
   para que el número sea el protagonista. Fila TOTAL diferenciada.
   Responsive: 100% en md y abajo, 50% centrada en lg+ (modal-1300 deja
   sobra horizontal de sobra y la tabla con 2 columnas se ve muy estirada
   ocupando todo el ancho). */
.igp-cabezas-table {
    margin-bottom: 0;
    width: 100%;
}
@media (min-width: 992px) {
    .igp-cabezas-table {
        width: 50%;
        margin-left: auto;
        margin-right: auto;
    }
}
.igp-cabezas-table thead th {
    background: var(--igp-primary);
    color: #fff;
    text-transform: uppercase;
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    font-weight: 700;
    text-align: center;
    padding: 0.5rem 0.75rem;
    /* !important porque BS5 `.table-bordered > * > *` (specificity 0,2,2) pinta
       los borders inter-cell con `--bs-border-color` (gris claro) y aún con
       bg colored se asomaba 1px en la unión vertical de cells del thead
       ("despuntes" visibles en la franja superior). Igualando TODAS las
       aristas al color del bg, los separators desaparecen. */
    border-color: var(--igp-primary) !important;
    /* La tabla razas/cabezas usa el DORADO PRIMARIO canon (decisión C50 H1 —
       no rol Ganadero — para no competir cromáticamente con la tabla de
       Calificación verde y para que el ámbar canon enmarque el dato base
       del volante). El `border-bottom-color` se iguala al bg para tapar el
       subrayado dorado de la regla `.table thead th` global. */
    border-bottom-color: var(--igp-primary) !important;
}
/* BS5 `.table-bordered > * > tr` aplica `border-width: 1px 0` al ROW (no solo
   al cell). El `<tr>` del thead hereda `--bs-border-color` (gris) y su border
   horizontal se asoma 1px sobre el bg coloreado donde se cruza con los
   border-right/left verticales de los `<th>` (pixel rendering quirk en
   `border-collapse: collapse`). Igualamos el color del `<tr>` para neutralizar. */
.igp-cabezas-table thead tr {
    border-color: var(--igp-primary) !important;
}
.igp-cabezas-table tbody td {
    vertical-align: middle;
    padding: 0.5rem 0.75rem;
}
.igp-cabezas-table tbody tr td:first-child {
    font-weight: 500;
    color: #1f2937;
    text-align: center;
}
.igp-cabezas-table .igp-cabezas-input {
    text-align: center;
    font-weight: 600;
    font-size: 1.0625rem !important;     /* mayor que el resto del form */
    height: calc(2em + 0.5rem + 2px) !important;
    color: #1f2937;
    border-color: #dbe1e8;
    max-width: 180px;
    margin: 0 auto;
    background: #fff;
}
.igp-cabezas-table .igp-cabezas-input:focus {
    border-color: var(--igp-primary);
    box-shadow: 0 0 0 0.18rem var(--igp-primary-15);
}
.igp-cabezas-table tfoot tr.igp-cabezas-total {
    background: var(--igp-primary-07);
}
.igp-cabezas-table tfoot tr.igp-cabezas-total td {
    border-top: 2px solid var(--igp-primary);
    padding: 0.5rem 0.75rem;
    font-weight: 700;
    color: var(--igp-primary);
}
.igp-cabezas-table tfoot tr.igp-cabezas-total td:first-child {
    text-align: end;
    text-transform: uppercase;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
}
.igp-cabezas-table tfoot tr.igp-cabezas-total td:last-child {
    text-align: center;
    font-size: 1.0625rem;
}
.igp-cabezas-title {
    color: var(--igp-primary) !important;
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.igp-cabezas-title .igp-icon-sheep {
    width: 1.1em;
    height: 1.1em;
}

/* ===== Tabla "Totales de calificación" del form edición Volante (Form B) =====
   Hermana de .igp-cabezas-table pero en gama verde V (rol Veedor). Misma
   estructura visual (header coloreado, fila TOTAL canon, input centrado).
   Tinte verde olivo para separarla semánticamente del rojo G de Emisión.
   Tamaño de inputs respeta el ritmo `form-control-sm` del resto del form
   (Dave V: no importar el tamaño grande de igp-cabezas-input — el chart
   interactivo va a ser el protagonista de Calificación en B3). */
.igp-calif-table {
    margin-bottom: 0;
    width: 100%;
}
@media (min-width: 992px) {
    .igp-calif-table {
        width: 75%;
        margin-left: auto;
        margin-right: auto;
    }
}
.igp-calif-table thead th {
    background: var(--igp-role-v);
    color: #fff;
    text-transform: uppercase;
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    font-weight: 700;
    text-align: center;
    padding: 0.5rem 0.75rem;
    /* !important — ver nota en .igp-cabezas-table thead th. Sin esto, los
       separators verticales BS5 de table-bordered se asoman 1px sobre el bg
       verde en la franja superior del header. */
    border-color: var(--igp-role-v) !important;
    /* Idem .igp-cabezas-table — sin subrayado dorado en headers coloreados. */
    border-bottom-color: var(--igp-role-v) !important;
}
/* Idem .igp-cabezas-table thead tr — neutralizar el border-top horizontal
   del `<tr>` que se asoma sobre el bg coloreado en la unión de cells. */
.igp-calif-table thead tr {
    border-color: var(--igp-role-v) !important;
}
.igp-calif-table tbody td {
    vertical-align: middle;
    padding: 0.5rem 0.75rem;
}
/* Anchura por columna: priorizar la columna de MOTIVOS (col 3 lógica) que tiene
   los nombres largos ("Peso menor de 5,5 Kg. (4,5Kg)", "Cobertura grasa renal
   menor 50%") y rompía a dos líneas. Damos más anchura penalizando col 1
   (Cumple/Descalif labels, más cortas) y mantenemos cols 2 y 4 estrechas para
   los inputs centrados. */
.igp-calif-table colgroup col.igp-calif-col-1 { width: 20%; }
.igp-calif-table colgroup col.igp-calif-col-2 { width: 15%; }
.igp-calif-table colgroup col.igp-calif-col-3 { width: 50%; }
.igp-calif-table colgroup col.igp-calif-col-4 { width: 15%; }
.igp-calif-table { table-layout: fixed; }
.igp-calif-table tbody tr td:first-child {
    font-weight: 500;
    color: #1f2937;
}
.igp-calif-table .igp-calif-input {
    text-align: center;
    font-weight: 600;
    color: #1f2937;
    border-color: #dbe1e8;
    max-width: 140px;
    margin: 0 auto;
    background: #fff;
}
.igp-calif-table .igp-calif-input:focus {
    border-color: var(--igp-role-v);
    box-shadow: 0 0 0 0.18rem rgba(94, 140, 84, 0.18);
}
.igp-calif-table tfoot tr.igp-calif-total {
    background: rgba(94, 140, 84, 0.08);
}
.igp-calif-table tfoot tr.igp-calif-total td {
    border-top: 2px solid var(--igp-role-v);
    padding: 0.5rem 0.75rem;
    font-weight: 700;
    color: var(--igp-role-v);
}
.igp-calif-table tfoot tr.igp-calif-total td:first-child {
    text-transform: uppercase;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
}
.igp-calif-table tfoot tr.igp-calif-total td:last-child {
    text-align: center;
    font-size: 1.0625rem;
}
.igp-calif-title {
    color: var(--igp-role-v) !important;
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.igp-calif-title i { font-size: 0.95rem; }

/* Variante DESCALIFICACIÓN — canon rojo `--igp-role-g` (rojo terracota, el
   rojo canon que usan los badge-d en el resto del ERP). Tratamiento SUTIL:
   tinte 10% del rojo (`--igp-role-g-bg`) + texto del propio rojo + borde
   izquierdo gordo del color. Mucho más legible que el sólido blanco-sobre-rojo,
   que era cantoso. Se aplica a las celdas que llevan `.igp-calif-zone-descalif`
   o a las tablas con modificador `--descalif`. */
.igp-calif-table--descalif thead th,
.igp-calif-table .igp-calif-zone-descalif {
    background: var(--igp-role-g-bg) !important;
    color: var(--igp-role-g) !important;
    font-weight: 700;
    /* Borde izquierdo del MISMO grosor que el resto (1px de .table-bordered),
       solo cambia el color al rol — ancla visualmente la zona sin saltar a 3px. */
    border-left-color: var(--igp-role-g) !important;
}
/* Variante CUMPLE PLIEGO — gemela de descalif pero en verde V. Mismas reglas
   de tratamiento sutil (tinte 10% + texto color rol + borde izquierdo grueso)
   pero apuntando a `--igp-role-v` (verde olivo, calificación). Las dos zonas
   semánticas de la tabla quedan así visualmente espejadas: verde=cumple,
   rojo=descalif, ambas igual de legibles, ambas igual de sutiles. */
.igp-calif-table .igp-calif-zone-cumple {
    background: var(--igp-role-v-bg) !important;
    color: var(--igp-role-v) !important;
    font-weight: 700;
    /* Mismo tratamiento que la zona descalif: 1px del color rol, sin saltar. */
    border-left-color: var(--igp-role-v) !important;
}
.igp-calif-table .igp-calif-input.is-descalif:focus {
    border-color: var(--igp-role-g);
    box-shadow: 0 0 0 0.18rem rgba(192, 82, 74, 0.18);
}
.igp-calif-title--descalif {
    color: var(--igp-role-g) !important;
}

/* ===== Chip de estado "cuadre" para forms con totales auto-computados =====
   Indica si los totales tecleados cuadran con el esperado:
     · --ok    → verde V, "Cuadra (N)"
     · --falta → rojo G, "Faltan N"
     · --sobra → rojo G, "Sobran N"
     · --info  → gris secundario, "Sin datos" / placeholder neutro
   Diseñado para acompañar a una barra apilada `.igp-calif-bar` interactiva
   (form B totales calificación). Estética pill, padding amplio, icono delante. */
.igp-cuadre-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.28rem 0.7rem;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 600;
    border: 1px solid transparent;
    line-height: 1;
    white-space: nowrap;
}
.igp-cuadre-chip i { font-size: 0.85rem; }
.igp-cuadre-chip--ok {
    background: var(--igp-role-v-bg);
    color: var(--igp-role-v);
    border-color: var(--igp-role-v);
}
.igp-cuadre-chip--falta,
.igp-cuadre-chip--sobra {
    background: var(--igp-role-g-bg);
    color: var(--igp-role-g);
    border-color: var(--igp-role-g);
}
.igp-cuadre-chip--info {
    background: rgba(0, 0, 0, 0.04);
    color: var(--igp-secondary);
    border-color: rgba(0, 0, 0, 0.15);
}

/* Override scope: cualquier chip cuadre que viva DENTRO de la zona descalif
   de la tabla calificación (la fila bar de distribución motivos) usa fondo
   BLANCO. La zona ya tiene su propio tinte terracota --igp-role-g-bg, y un
   chip con tinte verde encima rompía la lectura de "zona descalif". El
   blanco lo deja como pieza neutra que solo destaca por su borde y texto
   (verde para ok, terracota para falta/sobra). Aplica a las 4 variantes. */
.igp-calif-zone-descalif .igp-cuadre-chip {
    background: #fff;
}

/* Strip read-only canon — bloques informativos encapsulados dentro de un
   form activo (p. ej. Etiquetas/Vitolas en form B Calificación, cuya edición
   real vive en otro tab). Fondo gris muy sutil + separador vertical interno
   para distinguirlo de los inputs editables sin que pese visualmente. Diseño
   inline (head con icono + label + badge; body con valores en una sola línea
   separados por · ) para minimizar altura. */
.igp-readonly-strip {
    background: rgba(0, 0, 0, 0.025);
    border: 1px solid rgba(0, 0, 0, 0.09);
    border-radius: 0.375rem;
    padding: 0.45rem 0.6rem;
}
.igp-readonly-strip .igp-readonly-cell {
    padding: 0 0.6rem;
}
.igp-readonly-strip .igp-readonly-cell + .igp-readonly-cell {
    border-left: 1px solid rgba(0, 0, 0, 0.09);
}
@media (max-width: 575.98px) {
    /* col-sm-6 colapsa a col-12 en xs; el border-left queda mal cuando los
       cells se apilan. Lo sustituimos por un border-top sutil. */
    .igp-readonly-strip .igp-readonly-cell + .igp-readonly-cell {
        border-left: none;
        border-top: 1px solid rgba(0, 0, 0, 0.09);
        padding-top: 0.4rem;
        margin-top: 0.4rem;
    }
}
.igp-readonly-strip .igp-readonly-head {
    font-size: 0.72rem;
    text-transform: uppercase;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--igp-secondary);
    margin-bottom: 0.15rem;
    line-height: 1.2;
}
.igp-readonly-strip .igp-readonly-head i {
    color: var(--igp-primary);
}
.igp-readonly-strip .igp-readonly-badge {
    display: inline-block;
    background: var(--igp-primary);
    color: #fff;
    border-radius: 999px;
    padding: 0.05rem 0.45rem;
    font-size: 0.7rem;
    margin-left: 0.2rem;
    line-height: 1.2;
    font-weight: 700;
}
.igp-readonly-strip .igp-readonly-body {
    font-size: 0.78rem;
    line-height: 1.35;
    color: var(--igp-text, #333);
}
.igp-readonly-strip .igp-readonly-series {
    white-space: nowrap;
}

/* BS5 .is-invalid sobre `<input type="number">` aplica padding-right + icono
   SVG de error que aplasta/desplaza los spinners nativos del navegador (visible
   al usuario como descolocación durante la edición). En este proyecto el
   feedback de error ya lo da el chip cuadre lateral + el borde rojo, así que
   suprimimos el icono de validación BS5 sólo para number inputs (el resto de
   inputs conserva el icono como en BS5 base). */
.igp-form input[type="number"].is-invalid,
.igp-form input[type="number"].is-invalid:focus,
input[type="number"].is-invalid,
input[type="number"].is-invalid:focus {
    padding-right: 0.5rem;
    background-image: none;
}

/* Visualización barra apilada interactiva (form B calificación). Misma base
   que `.igp-calif-bar` reusada de volante_detail.html pero más fina (28px)
   para no abrumar el form, y con segmentos que se ocultan vía display:none
   cuando flex=0 (de otra forma BS5 dejaría un sliver visible). */
.igp-calif-viz {
    margin-top: 0.5rem;
}
.igp-calif-viz .igp-calif-bar {
    height: 28px;
    background: rgba(0, 0, 0, 0.03);    /* visible aunque ningún segmento */
}
.igp-calif-viz .igp-calif-seg-n {
    font-size: 0.78rem;
}

/* ===== Badge canónico por rol IGP (.igp-badge-role-{g,t,c,o,m,v}) =====
   Sustituto canon de `text-bg-warning`/`text-bg-success` cuando el badge
   representa SEMÁNTICAMENTE un actor IGP (Ganadero, Transportista,
   Cooperativa, Operador, Matadero, Veedor). Misma forma BS5 (badge pill)
   pero color corporativo en lugar de paleta genérica Bootstrap. */
.igp-badge-role {
    display: inline-block;
    padding: 0.35em 0.65em;
    font-size: 0.75em;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    text-align: center;
    white-space: nowrap;
    vertical-align: baseline;
    border-radius: 0.375rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.igp-badge-role-g { background: var(--igp-role-g); }
.igp-badge-role-t { background: var(--igp-role-t); }
.igp-badge-role-c { background: var(--igp-role-c); }
.igp-badge-role-o { background: var(--igp-role-o); }
.igp-badge-role-m { background: var(--igp-role-m); }
.igp-badge-role-v { background: var(--igp-role-v); }
/* Variantes coloreadas por rol IGP (paleta canon `--igp-role-*`). Reutilizables
   en cualquier badge-count donde el dominio del número represente un actor
   IGP (ej. listado Mataderos: Volantes en color rol-m del matadero, Operadores
   en color rol-o del operador). */
.badge-count.badge-count-role-g { background: var(--igp-role-g); }
.badge-count.badge-count-role-t { background: var(--igp-role-t); }
.badge-count.badge-count-role-c { background: var(--igp-role-c); }
.badge-count.badge-count-role-o { background: var(--igp-role-o); }
.badge-count.badge-count-role-v { background: var(--igp-role-v); }
.badge-count.badge-count-role-m { background: var(--igp-role-m); }

.badge-calif {
    display: inline-block; padding: 2px 8px; border-radius: 12px;
    font-size: .74rem; font-weight: 600; margin-right: 4px;
}
.badge-ce { background: var(--igp-volante-calificado); color: #fff; }
/* FIX4 Frente C — E y V condicionales: gris sutil cuando cuadran con CE (caso
   esperado), rojo sutil (.badge-mismatch) cuando divergen. CE mantiene su verde. */
.badge-e, .badge-v { background: #e9ecef; color: var(--igp-secondary); }
.badge-e.badge-mismatch, .badge-v.badge-mismatch {
    background: rgba(192, 82, 74, .15); color: var(--igp-role-g);
}
/* FIX5 — NE (no etiquetadas, gris oscuro) y D (descalificadas, rojo) en la cuenta
   de calificación (Rendimientos). Heredan la forma pill de .badge-calif. */
.badge-ne { background: var(--igp-secondary); color: #fff; }
.badge-d  { background: var(--igp-role-g);  color: #fff; }
.badge-aptos { background: var(--igp-blue); color: #fff; }
/* C50 H1 — Badges adicionales del Certificado Anual.
   badge-total: agregado bruto (gris-azul neutro, sin connotación).
   badge-nc: cabezas no calificadas (gris-marrón apagado). */
.badge-total { background: var(--igp-blue); color: #fff; }
.badge-nc    { background: #8b7355; color: #fff; }
.calif-line2 { margin-top: 3px; }

/* FIX vitolas-etiquetas-canon — badge dorado de Serie (resumen Vitolas/Etiquetas).
   Forma cuadrado-redondeado (4px, canon § badges); fondo = oro de marca
   (--igp-primary = #f7ac3b). La serie main lleva una estrella delante
   (bi-star-fill) dentro del badge. */
.badge-igp-serie {
    display: inline-block; padding: 2px 10px; border-radius: 4px;
    font-size: .76rem; font-weight: 700; letter-spacing: .02em;
    background: var(--igp-primary); color: #fff; white-space: nowrap;
}
.badge-igp-serie .bi { font-size: .68rem; }

/* C50 — celda de rango de vitolas/etiquetas en el DT de pedidos de venta.
   Layout: [badge serie] [rango numérico enmarcado] (cantidad). El rango va
   solo con los NÚMEROS, sin repetir el prefijo de la serie (que ya se muestra
   como badge dorado a la izquierda). Familia monospace + borde sutil ámbar. */
.igp-sn-line {
    display: flex; align-items: center; gap: 6px;
    padding: 2px 0; white-space: nowrap; font-size: .82rem;
}
.igp-sn-line + .igp-sn-line { margin-top: 2px; }
.igp-sn-range {
    display: inline-block; padding: 1px 8px;
    border: 1px solid var(--igp-primary-30);
    border-radius: 4px; background: #fffbf3;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: .78rem; letter-spacing: .02em; color: var(--igp-secondary);
}
.igp-sn-qty { color: var(--igp-secondary); font-size: .78rem; }

/* Variante grande del rango S/N para fichas de detalle donde el rango fabricado
   es protagonista (p. ej. pestaña "S/N impresos" de la orden de fabricación).
   Aditiva y scoped: solo escala lo que envuelva `.igp-sn-lines-lg`. */
.igp-sn-lines-lg .igp-sn-line { font-size: .95rem; padding: 3px 0; gap: 8px; }
.igp-sn-lines-lg .badge-igp-serie { font-size: .86rem; padding: 3px 12px; }
.igp-sn-lines-lg .badge-igp-serie .bi { font-size: .78rem; }
.igp-sn-lines-lg .igp-sn-range { font-size: .9rem; padding: 2px 10px; }
.igp-sn-lines-lg .igp-sn-qty { font-size: .86rem; }

/* Badge verde de pieza individual (vitola/etiqueta) en los DT "NO utilizadas".
   Misma forma cuadrado-redondeado que .badge-igp-serie; fondo = verde calificado
   de marca (--igp-volante-calificado = #5e8c54). */
.badge-igp-pieza {
    display: inline-block; padding: 2px 10px; border-radius: 4px;
    font-size: .76rem; font-weight: 700; letter-spacing: .02em;
    background: var(--igp-volante-calificado); color: #fff; white-space: nowrap;
}
/* Modificadores de estado de la pieza (reutilizan la familia roja terracota ya
   canon en .badge-estrop/.badge-extrav/.badge-anulada). Verde base = libre. */
.badge-igp-pieza.is-estrop  { background: #c0524a; }   /* estropeada (= --igp-role-g) */
.badge-igp-pieza.is-extrav  { background: #d97757; }   /* extraviada */
.badge-igp-pieza.is-anulada { background: #e89a85; }   /* anulada */

/* Celda de lista relacional en los DT maestros (cooperativas/operadores/
   mataderos de un transportista o veedor). Cada item una entrada, enlazable a
   la ficha de la entidad. Canon FIX listados-maestros (Pieza D). */
.igp-cell-list {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: 0.875rem;
}
.igp-cell-list li {
    line-height: 1.4;
    padding: 1px 0;
}
/* Los enlaces dentro de .igp-cell-list heredan del `a` canónico global
   (color: var(--igp-primary), subrayado en hover). Sin overrides locales
   — el canon de link se aplica ERP-wide. */

/* 3 badges "no aptas" (familia roja terracota, distinguibles entre sí). Color-only:
   se combinan con .badge-calif (forma pill). Clicables si >0 (.badge-rangos-link)
   → abren #igpRangosModal con el desglose de rangos. */
.badge-estrop  { background: #c0524a; color: #fff; }   /* terracota oscuro (= --igp-role-g) */
.badge-extrav  { background: #d97757; color: #fff; }   /* terracota medio */
.badge-anulada { background: #e89a85; color: #fff; }   /* terracota claro */
.badge-rangos-link { cursor: pointer; }
.badge-rangos-link:hover { filter: brightness(0.92); }

.badge-incidencias {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 22px; padding: 1px 8px; border-radius: 12px;
    background: var(--igp-role-g); color: #fff; font-size: .74rem; font-weight: 700;
}

.dt-legend {
    display: flex; gap: 18px; flex-wrap: wrap; padding: 10px 14px;
    background: #fafbfc; border-radius: 6px; margin: 10px 0 12px;
    font-size: .82rem; color: var(--igp-secondary);
}
.dt-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.dt-legend-item .legend-swatch { display: inline-block; width: 14px; height: 14px; border-radius: 3px; }

/* Variante con acción a la derecha (ej: leyenda + botón "Añadir Ganadería"). */
.dt-legend.dt-legend-with-action {
    display: flex; justify-content: space-between; align-items: center; gap: 14px;
}
.dt-legend.dt-legend-with-action .dt-legend-items {
    display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
}

/* FIX2 (post-FIX7) — pulido DT Ganaderías / Libros / Volantes / Sacrificios.
   Los badges de color reutilizan la forma de .badge-calif / .badge-ref (sólo
   aportan fondo); no se duplica geometría. */
/* CANTIDADES (números) → forma pill (border-radius alto), incluso en leyendas.
   ETIQUETAS/IDENTIFICADORES (CE/E/V, M/W/X, Estado) → forma rect con borde
   suavizado, ya cubierta por .badge-calif/.badge-ref/.badge-estado (4px). */
.badge-cabezas-m { background: var(--igp-blue);    color: #fff; border-radius: 12px; padding: 1px 8px; }
.badge-cabezas-h { background: var(--igp-role-o);  color: #fff; border-radius: 12px; padding: 1px 8px; }
/* Sufijo M/H del badge de cabañas: el número domina, la letra va sutil. */
.badge-cabezas-suffix { font-size: 0.7em; font-weight: 500; opacity: 0.8; margin-left: 1px; }

/* Min-width de las columnas CABAÑAS / HISTÓRICO CABAÑAS del DT Ganaderías
   para que la entry caiga en formato "raza+badges en una línea, fecha debajo". */
#ganaderiasData thead th:nth-child(8),
#ganaderiasData thead th:nth-child(9) { min-width: 180px; }

/* Header de la celda CABAÑAS con el botón addCabanna integrado. */
.cabannas-header { margin-bottom: 6px; }

/* Cabaña entry — estructura estable a cualquier ancho de columna.
   Línea 1: raza + badges M/H (flex con wrap). Línea 2: fecha muted. */
.cabanna-entry {
    display: block;
    margin-bottom: 6px;
    line-height: 1.3;
}
.cabanna-entry:last-child { margin-bottom: 0; }
/* Layout estable 2 líneas: raza en línea propia, badges juntos abajo.
   Predecible a cualquier ancho de columna (no flex-wrap arbitrario por
   longitud del número del badge). */
.cabanna-entry .cabanna-main {
    display: inline-flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 6px;
}
.cabanna-entry .cabanna-main > strong {
    font-weight: 500;
}
.cabanna-entry .cabanna-main > .badge-calif {
    display: inline-block;
}
.cabanna-entry .cabanna-meta {
    color: var(--igp-secondary);
    font-size: .72rem;
    margin-top: 1px;
}
/* Panel histórico cabañas en la ficha de Ganadería — separación visible entre
   filas (border-bottom + padding) para que las entries no se peguen entre sí.
   No afecta a otras superficies que usan .cabanna-entry (DataTables ganaderías,
   PDFs documentales). */
.cabanna-history .cabanna-entry {
    padding: 6px 4px;
    border-bottom: 1px solid var(--bs-border-color-translucent);
    margin-bottom: 0;
}
.cabanna-history .cabanna-entry:last-child {
    border-bottom: none;
}
/* Badges de cabezas compactados dentro de la entry para que no fuercen ancho. */
.cabanna-entry .badge-cabezas-m,
.cabanna-entry .badge-cabezas-h {
    padding: 1px 6px;
    font-size: .7rem;
    line-height: 1.4;
    margin-right: 0;
    white-space: nowrap;
}
/* Cabaña con acciones: iconos en línea propia (sin saltos), entry debajo.
   Stack vertical → predictable a cualquier ancho de columna. */
.cabanna-line {
    display: block;
    margin-bottom: 8px;
}
.cabanna-line:last-child { margin-bottom: 0; }
.cabanna-line .dt-actions-inline {
    display: flex;
    flex-wrap: nowrap;
    gap: 6px;
    margin-bottom: 4px;
}
.cabanna-line .cabanna-entry { margin-bottom: 0; }

.badge-libros-libres         { background: var(--igp-volante-calificado); color: #fff; border-radius: 12px; padding: 1px 8px; }
.badge-libros-utilizadas     { background: var(--igp-role-g);             color: #fff; border-radius: 12px; padding: 1px 8px; }
.badge-libros-deshabilitadas { background: var(--igp-blue);               color: #fff; border-radius: 12px; padding: 1px 8px; }

.badge-ref-hoja { background: var(--igp-primary); color: #fff; }

/* Badge cuadrado de estado del volante (icono blanco dentro). 4 estados canónicos
   VOLANTE_STATUS_CHOICES + variante muted para finalizado-no-calificado. */
.badge-estado {
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 24px; border-radius: 4px; color: #fff;
}
.badge-estado-0 { background: var(--igp-volante-pend-carga); }
.badge-estado-1 { background: var(--igp-volante-pend-descarga); }
.badge-estado-2 { background: var(--igp-volante-pend-calificar); }
.badge-estado-3 { background: var(--igp-volante-calificado); }
.badge-estado-nc { background: var(--igp-secondary); }

/* Mini-barra horizontal de reparto proporcional (libros: libre/usada/deshab;
   volantes: cabezas CE/CNE/DES). Los segmentos llevan width inline calculado. */
.bar-distribution { display: flex; width: 120px; height: 8px; border-radius: 4px; overflow: hidden; background: #eef0f2; }
.bar-distribution > span { display: block; height: 100%; }
.bar-distribution .seg-libres        { background: var(--igp-volante-calificado); }
.bar-distribution .seg-utilizadas    { background: var(--igp-role-g); }
.bar-distribution .seg-deshabilitadas{ background: var(--igp-blue); }
/* Mapeo canon de colores de la composición de la calificación —
   se replica en DT volantes/sacrificios (.bar-distribution) y en la vista
   simple del volante (.igp-calif-seg--*). DEBEN coincidir entre vistas.
     · CE  Cabezas etiquetadas      → verde olivo (V)
     · CNE Cabezas no etiquetadas  → GRIS (--igp-secondary)
     · DES Descalificadas           → rojo terracota (G)
     · NC  No calificadas           → marrón propio (caso excepcional)        */
.bar-distribution .seg-ce  { background: var(--igp-volante-calificado); }
.bar-distribution .seg-cne { background: var(--igp-secondary); }
.bar-distribution .seg-des { background: var(--igp-role-g); }
.bar-distribution .seg-nc  { background: #8b7355; }
/* FIX vitolas-etiquetas-canon — rendimiento Vitolas/Etiquetas: libres (neutro azul)
   · utilizadas (verde) · resto rojo (estropeadas + extraviadas + anuladas). */
.bar-distribution .seg-rend-free { background: var(--igp-blue); }
.bar-distribution .seg-rend-use  { background: var(--igp-volante-calificado); }
.bar-distribution .seg-rend-rest { background: var(--igp-role-g); }
.bar-distribution-wrap { display: inline-flex; flex-direction: column; gap: 3px; }
.bar-distribution-pct { font-size: .7rem; color: var(--igp-secondary); line-height: 1; }

/* Leyenda agrupada por concepto (sub-bloques con título muted, separador vertical). */
.dt-legend-group { display: flex; flex-direction: column; gap: 4px; padding-right: 18px; border-right: 1px solid #eef0f2; }
.dt-legend-group:last-child { border-right: none; padding-right: 0; }
.dt-legend-group-title { font-size: .7rem; text-transform: uppercase; letter-spacing: .5px; color: var(--igp-secondary); font-weight: 600; }
.dt-legend-group-items { display: flex; gap: 14px; flex-wrap: wrap; }

/* Acciones DT en stack horizontal (no apiladas). Scoped a las celdas de acción:
   los iconos .edit y los btn-circle-xs van en fila con respiración, sin tocar el
   markup ni los handlers (que cuelgan de `td div` / `button` por name/id). */
.dt-actions-inline { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }

.panel-section { margin-bottom: 18px; }
.panel-section > .panel-section-title {
    font-size: .78rem;
    text-transform: uppercase;
    color: #5c6670;
    font-weight: 600;
    letter-spacing: .4px;
    margin: 0 0 8px;
    display: flex;
    align-items: center;
    gap: 6px;
}
.panel-section .count-pill {
    font-size: .7rem;
    background: rgba(70, 184, 196, .15);
    color: var(--igp-role-c);
    padding: 1px 6px;
    border-radius: 8px;
    font-weight: 600;
}
.panel-section .panel-section-title .mini-action {
    margin-left: auto;
    width: 20px;
    height: 20px;
    font-size: .75rem;
}

/* ---- Mini-DataTable compacta adaptativa (§ 18.3 reducido) ----
   La visibilidad de buscador / paginador / info se decide en el init JS
   según el nº de filas (≤3 sin toolbar, 4-10 sólo buscador, >10 completo).
   Esta clase sólo aporta la compactación visual (padding + font). */
.mini-dt-compact table.dataTable thead th,
.mini-dt-compact table.dataTable tbody td {
    padding: .3rem .5rem;
    font-size: .85rem;
}
.mini-dt-compact table.dataTable thead th {
    text-transform: uppercase;
    font-size: .72rem;
    letter-spacing: .3px;
    color: #5c6670;
}
.mini-dt-compact .dt-search input,
.mini-dt-compact .dt-input {
    font-size: .8rem;
    padding: .2rem .5rem;
}
.mini-dt-compact .dt-info { font-size: .78rem; color: #5c6670; }

/* =========================================================================
   Columna ROLES / USERS del listado de Entidades (entities_table.html).
   ========================================================================= */
.badge-role-table {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 5px;
    cursor: pointer;
    margin: 0 1px;
    vertical-align: middle;
}
.badge-role-table .role-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    font-size: .72rem;
    font-weight: 700;
    color: #fff;
    line-height: 1;
}
.badge-role-table.g .role-icon { background: var(--igp-role-g); }
.badge-role-table.t .role-icon { background: var(--igp-role-t); }
.badge-role-table.c .role-icon { background: var(--igp-role-c); }
.badge-role-table.o .role-icon { background: var(--igp-role-o); }
.badge-role-table.v .role-icon { background: var(--igp-role-v); }
.badge-role-table:hover { background: #f0f1f3; }

.user-cell-list { display: flex; flex-direction: column; gap: 2px; }
.user-cell-item {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: .82rem;
    line-height: 1.3;
}
.user-cell-mail { flex-grow: 1; min-width: 0; overflow-wrap: anywhere; }
.user-cell-act {
    background: transparent;
    border: none;
    color: #5c6670;
    cursor: pointer;
    padding: 0 3px;
    font-size: .8rem;
    border-radius: 3px;
    line-height: 1;
}
.user-cell-act:hover { background: #f0f1f3; color: var(--bs-body-color); }
.user-cell-act.danger:hover { color: var(--igp-status-0); }

/* FIX2 F1 — [+] como chip (mismo alto que badges/iconos) + tags de rol por usuario. */
.badge-role-table.badge-role-add {
    border: 1px dashed var(--amber, #f7ac3b);
    color: var(--amber, #f7ac3b);
    background: transparent;
    font-size: .8rem;
}
.badge-role-table.badge-role-add:hover { background: rgba(247, 172, 59, .1); }

.user-cell-addrow { margin-bottom: 2px; }
.user-cell-add {
    border: 1px dashed var(--amber, #f7ac3b);
    color: var(--amber, #f7ac3b);
    border-radius: 4px;
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.user-cell-add:hover { background: rgba(247, 172, 59, .1); color: var(--amber, #f7ac3b); }

.user-cell-roltags { display: inline-flex; gap: 2px; flex-shrink: 0; }
.user-cell-roltag {
    font-size: .6rem;
    font-weight: 700;
    color: #fff;
    width: 16px;
    height: 16px;
    border-radius: 50%;   /* FIX5: pictograma circular canónico (antes 14×14 radius 3px) */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.user-cell-roltag.c { background: var(--igp-role-c); }
.user-cell-roltag.g { background: var(--igp-role-g); }
.user-cell-roltag.o { background: var(--igp-role-o); }
.user-cell-roltag.t { background: var(--igp-role-t); }
.user-cell-roltag.v { background: var(--igp-role-v); }

/* =========================================================================
   FIX3 — retoques smoke 2.
   ========================================================================= */

/* F1 — Roles del listado SIEMPRE en una línea (no apilar al haber 2+ roles). */
.role-cell {
    display: inline-flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 3px;
}

/* F2 — Modal "Añadir Rol": role-cards encapsuladas (check en header + body
   colapsable). Borde ámbar cuando la card está activa: vía :has() (navegadores
   modernos) y vía .active (fallback JS que el handler togglea en cada change). */
.role-card {
    border: 1px solid var(--bs-border-color);
    border-radius: 8px;
    margin-bottom: 12px;
    overflow: hidden;
    transition: border-color .2s;
}
.role-card-header {
    padding: 10px 14px;
    background: #fcfcfd;
    border-bottom: 1px solid transparent;
}
.role-card:has(.form-check-input:checked),
.role-card.active {
    border-color: #f7ac3b;
}
.role-card:has(.form-check-input:checked) .role-card-header,
.role-card.active .role-card-header {
    background: #fef4e0;
    border-bottom-color: var(--bs-border-color);
}
.role-card-body {
    padding: 14px;
    background: #fff;
}
.role-tip-inline {
    background: #eef6f8;
    border-left: 3px solid var(--igp-role-c);
    padding: 8px 12px;
    border-radius: 0 4px 4px 0;
    font-size: .85rem;
    color: var(--bs-body-color);
    display: flex;
    align-items: flex-start;
    gap: 8px;
}
.role-tip-inline .bi {
    color: var(--igp-role-c);
    flex-shrink: 0;
    margin-top: 2px;
}

/* F5 — permitir wrap en los dt de la cabecera (literales largos a 2 líneas). */
.hero-section-body .kv dt {
    white-space: normal;
    line-height: 1.25;
}

/* =========================================================================
   FIX4 F3 — form de entidad: Identificación / Contacto responsive.
   <1100px apilados (fila completa cada uno); ≥1100px Identificación 55%
   (main) / Contacto 45% (side). Tras pulido C50 H1 (modal subió a 1500),
   el reparto se equilibra a 55/45 para que Contacto respire con sus 3
   campos teléfono/móvil/fax. No es grid Bootstrap estándar.
   ========================================================================= */
.entity-form-cols {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
@media (min-width: 1100px) {
    .entity-form-cols {
        flex-direction: row;
        gap: 1.5rem;
    }
    .entity-form-cols .entity-form-col-side {
        flex: 0 0 45%;
        min-width: 0;
    }
    .entity-form-cols .entity-form-col-main {
        flex: 1;
        min-width: 0;
    }
}

/* =========================================================================
   FIX4 F5 — relación asociada: lista plana inline (count ≤ 10).
   Cuando count > 10 se usa un botón que abre la mini-DataTable en modal.
   ========================================================================= */
.item-list {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: .85rem;
}
.item-list li {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: .35rem .25rem;
    border-bottom: 1px solid var(--bs-border-color);
}
.item-list li:last-child { border-bottom: none; }
.item-list .item-main { font-weight: 500; color: var(--bs-body-color); overflow-wrap: anywhere; }
.item-list .item-code {
    font-size: .78rem;
    color: #5c6670;
    background: #f0f1f3;
    border-radius: 4px;
    padding: 0 6px;
    white-space: nowrap;
}
.item-list .item-extra { color: #5c6670; font-size: .8rem; }
.item-list .item-action {
    margin-left: auto;
    flex-shrink: 0;
    color: #5c6670;
    text-decoration: none;
}
.item-list .item-action:hover { color: #f7ac3b; }
.item-list .item-empty { color: #5c6670; font-style: italic; padding: .35rem .25rem; }

/* =========================================================================
   FIX H1 entidades — pestaña Operador (§ 18.10b layout A4 + § 18.10c CRUD).
   Clases NUEVAS al final del fichero: no tocan el canon § 18.10 base.
   ========================================================================= */

/* § 18.10b — agrupación semántica: sub-rows con título de grupo. */
.group-row { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.group-row:last-child { margin-bottom: 0; }
.group-row-title {
    font-size: .72rem;
    color: var(--igp-secondary);
    text-transform: uppercase;
    letter-spacing: .6px;
    font-weight: 600;
    margin-bottom: 4px;
}
.group-row .actors-grid { margin-top: 0; }  /* el aire lo pone .group-row, no el grid */
.role-panel-header + .group-row { margin-top: 18px; }  /* aire tras la cabecera V2 */
.actors-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.actors-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 1100px) {
    .actors-grid-3, .actors-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .actors-grid-3, .actors-grid-2 { grid-template-columns: 1fr; }
}

/* Pills de cards no-actor de la pestaña Operador (Inspecciones / Series /
   Shipper Contracts). Mismo mecanismo --role-color que .role-g/.role-t/… */
.igp-actor-card.role-blue  { --role-color: var(--igp-blue); }
.igp-actor-card.role-amber { --role-color: var(--igp-primary); }
.igp-actor-card.role-gray  { --role-color: var(--igp-secondary); }

/* Botón + del header de card (registrar visita / nueva serie). */
.igp-actor-card .igp-section-add {
    width: 26px;
    height: 26px;
    border-radius: 6px;
    border: 1px solid #d6d9dd;
    background: #fff;
    color: var(--igp-secondary);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .85em;
    flex-shrink: 0;
}
.igp-actor-card .igp-section-add:hover { background: #f1f3f5; color: #1f2428; }

/* Items con CRUD inline (Series Etiquetas, Shipper Contracts). */
.igp-actor-card .item-with-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 14px;
    font-size: .86rem;
    border-bottom: 1px solid #f1f3f5;
}
.igp-actor-card .item-with-actions:last-child { border-bottom: none; }
.igp-actor-card .item-with-actions:hover { background: #fafbfc; }

/* ============================================================================
   AIR DATEPICKER — TEMA CANON IGP
   ============================================================================
   Tema sobre Air Datepicker 3.5 (vanilla, sin jQuery/Moment). Sustituye al
   bloque "DATEPICKERS CANON (C50 — Opción D)" que revestía los dropdowns BS3
   deprecated (Eonasdan datetimepicker + el daterangepicker, ya retirados del
   bundle y del árbol static). Air Datepicker expone variables `--adp-*` que aquí
   se redefinen a la paleta dorada `--igp-primary`; el resto es la sidebar de
   presets propia (`.igp-dp-presets`, inyectada por igp-date-helpers.js) y la
   botonera Apply/Cancel canon. Un solo selector de fecha en todo el ERP.
   ============================================================================ */
.air-datepicker {
    --adp-font-family: var(--bs-body-font-family);
    --adp-color: #1f2428;
    --adp-color-secondary: var(--igp-secondary);
    --adp-accent-color: var(--igp-primary);
    --adp-color-current-date: var(--igp-primary);
    --adp-border-color: #e6e8eb;
    --adp-border-color-inner: #eef0f2;
    --adp-border-radius: 0.5rem;
    --adp-background-color-hover: var(--igp-primary-15);
    --adp-background-color-active: var(--igp-primary-30);
    --adp-cell-border-radius: 0.25rem;
    --adp-cell-background-color-selected: var(--igp-primary);
    --adp-cell-background-color-selected-hover: var(--igp-primary-60);
    --adp-cell-background-color-in-range: var(--igp-primary-15);
    --adp-cell-background-color-in-range-hover: var(--igp-primary-30);
    --adp-day-name-color: var(--igp-primary);
    --adp-nav-arrow-color: var(--igp-secondary);
    --adp-nav-color-secondary: var(--igp-secondary);
    --adp-btn-color: var(--igp-primary);
    --adp-btn-color-hover: var(--igp-primary);
    --adp-time-track-color: #dde1e6;
    --adp-time-track-color-hover: var(--igp-primary-60);
    --adp-z-index: 1090;
    box-shadow: 0 4px 16px rgba(16, 24, 40, 0.12);
    /* En modales scrollable el calendario se ancla a `.modal-dialog`, que hereda
       `pointer-events:none` de `.modal` (BS5); solo `.modal-content` lo restaura.
       Sin esto las celdas no reciben el puntero: los clics/hover atraviesan el
       calendario y los recibe el contenido del form que hay detrás. */
    pointer-events: auto;
}
/* Día de hoy — borde ámbar canon en lugar del subrayado azul default. */
.air-datepicker-cell.-current- {
    color: var(--igp-primary);
    font-weight: 700;
}
.air-datepicker-cell.-current-.-selected- {
    color: #fff;
}
/* Start / end del rango: esquinas asimétricas (look daterangepicker legacy). */
.air-datepicker-cell.-range-from- {
    border-radius: 0.25rem 0 0 0.25rem;
    border-color: var(--igp-primary);
}
.air-datepicker-cell.-range-to- {
    border-radius: 0 0.25rem 0.25rem 0;
    border-color: var(--igp-primary);
}

/* --- Sidebar de presets (inyectada por igp-date-helpers.js en modos range) --
   Air Datepicker YA es `display:grid` con `--adp-grid-areas: "nav" "body"
   "timepicker" "buttons"` (1 columna) y sus hijos se colocan con `grid-area`.
   Para añadir la sidebar nos integramos en SU grid: redefinimos la variable de
   áreas con una columna `presets` a la izquierda (NO imponemos un grid propio,
   que solaparía el calendario). El puntero (`--pointer`) va position:absolute,
   fuera del flujo grid. */
.air-datepicker.-igp-with-presets- {
    --adp-grid-areas: "presets nav" "presets body" "presets timepicker" "presets buttons";
    grid-template-columns: auto 1fr;
    width: auto;
}
.igp-dp-presets {
    grid-area: presets;
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 132px;
    padding: 0.35rem;
    border-right: 1px solid var(--igp-primary-30);
    background: var(--igp-primary-07);
    overflow-y: auto;
}
.igp-dp-preset {
    text-align: left;
    border: none;
    background: transparent;
    color: #1f2428;
    font-size: 0.8rem;
    padding: 0.3rem 0.5rem;
    border-radius: 0.25rem;
    cursor: pointer;
    white-space: nowrap;
}
.igp-dp-preset:hover {
    background: var(--igp-primary-15);
    color: var(--igp-primary);
}
/* Botonera Apply / Cancel canon al pie del picker range. */
.air-datepicker--buttons {
    border-top: 1px solid var(--igp-primary-30);
    display: flex;
    gap: 0.4rem;
    padding: 0.4rem;
}
.air-datepicker--buttons .air-datepicker-button {
    flex: 0 0 auto;
}
.item-with-actions .item-body { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.item-with-actions .item-main { font-weight: 500; color: #1f2428; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.item-with-actions .item-sub { font-size: .74rem; color: var(--igp-secondary); }
.item-with-actions .item-actions { display: flex; gap: 4px; flex-shrink: 0; }

/* Botón-icono compacto de acción por fila (inline y modal CRUD). */
.btn-icon {
    width: 24px;
    height: 24px;
    border-radius: 5px;
    border: 1px solid #dde1e6;
    background: #fff;
    color: var(--igp-secondary);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: .78em;
    padding: 0;
}
.btn-icon:hover { background: #f1f3f5; color: #1f2428; }
.btn-icon.danger:hover {
    background: rgba(192, 82, 74, .12);
    color: var(--igp-role-g);
    border-color: var(--igp-role-g);
}

/* Card Inspecciones — 2 bloques fijos Última / Próxima visita (no es lista). */
.igp-actor-card .visit-block { padding: 10px 14px; border-bottom: 1px solid #f1f3f5; }
.igp-actor-card .visit-block:last-child { border-bottom: none; }
.igp-actor-card .visit-label {
    font-size: .7rem;
    color: var(--igp-secondary);
    text-transform: uppercase;
    letter-spacing: .6px;
    font-weight: 600;
    margin-bottom: 3px;
}
.igp-actor-card .visit-value { font-size: .95rem; color: #1f2428; font-weight: 500; display: flex; align-items: center; gap: 8px; }
.igp-actor-card .visit-value .actions { margin-left: auto; display: flex; gap: 4px; }
.igp-actor-card .visit-empty { color: #9098a1; font-style: italic; }
.igp-actor-card .visit-next { background: #fafbfc; }

/* § 18.10c — modal CRUD: toolbar (búsqueda propia + botón add) sobre la tabla. */
.crud-modal-toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 0 0 12px;
}
.crud-modal-toolbar input { max-width: 240px; }
.crud-modal-toolbar .btn-add {
    background: var(--igp-primary);
    color: #fff;
    border: none;
    border-radius: 5px;
    padding: 5px 12px;
    font-size: .84rem;
    cursor: pointer;
    white-space: nowrap;
}
.crud-modal-toolbar .btn-add:hover { background: #e09418; }
.row-actions { display: flex; gap: 4px; justify-content: flex-end; }
