@kaufmann-lab/ui 0.1.8 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,11 +14,11 @@ import { ControlValueAccessor } from '@angular/forms';
14
14
  * reciba un `IconName` (`Button`, `MetricCard`, etc.) — no debería requerir
15
15
  * importar el componente `Icon` completo solo para tipar una prop.
16
16
  */
17
- type IconName = 'activity' | 'alert-circle' | 'alert-octagon' | 'alert-triangle' | 'award' | 'bar-chart' | 'bell' | 'bus' | 'calculator' | 'calendar' | 'car' | 'cart' | 'check' | 'check-circle' | 'check-square' | 'chevron-down' | 'chevron-left' | 'chevron-left-bar' | 'chevron-right' | 'clipboard' | 'clock' | 'credit-card' | 'download' | 'edit' | 'external-link' | 'eye' | 'eye-off' | 'file' | 'filter' | 'gauge' | 'gift' | 'grid' | 'history' | 'inbox' | 'layers' | 'list' | 'map' | 'map-pin' | 'menu' | 'mic' | 'mic-off' | 'more-vertical' | 'pause' | 'phone' | 'play' | 'plus' | 'search' | 'settings' | 'shield-check' | 'sort' | 'star' | 'ticket' | 'tool' | 'trash' | 'truck' | 'upload-cloud' | 'user' | 'user-search' | 'video' | 'x';
17
+ type IconName = 'activity' | 'alert-circle' | 'alert-octagon' | 'alert-triangle' | 'award' | 'bar-chart' | 'bell' | 'bus' | 'calculator' | 'calendar' | 'car' | 'cart' | 'check' | 'check-circle' | 'check-square' | 'chevron-down' | 'chevron-left' | 'chevron-left-bar' | 'chevron-right' | 'clipboard' | 'clock' | 'credit-card' | 'download' | 'edit' | 'external-link' | 'eye' | 'eye-off' | 'file' | 'filter' | 'gauge' | 'gift' | 'grid' | 'history' | 'home' | 'inbox' | 'layers' | 'list' | 'log-out' | 'map' | 'map-pin' | 'menu' | 'mic' | 'mic-off' | 'more-vertical' | 'pause' | 'phone' | 'play' | 'plus' | 'search' | 'settings' | 'shield-check' | 'sort' | 'star' | 'ticket' | 'tool' | 'trash' | 'truck' | 'upload-cloud' | 'user' | 'user-search' | 'video' | 'x';
18
18
 
19
19
  type IconSize = 'sm' | 'md' | 'lg';
20
20
  /**
21
- * Ícono de trazo del design system — 60 glyphs (59 de la página 5 + `eye-off`), grilla 24×24,
21
+ * Ícono de trazo del design system — 61 glyphs (59 de la página 5 + `eye-off` y `log-out`), grilla 24×24,
22
22
  * grosor de trazo 1.9px. Referencia un sprite SVG embebido (ver
23
23
  * `icon-sprite.ts`) vía `<use>`, así que no hay peso de red adicional por
24
24
  * cada instancia: el sprite se inyecta al DOM una única vez, la primera
@@ -157,6 +157,71 @@ declare class Alert {
157
157
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Alert, "kf-alert", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["*"], true, never>;
158
158
  }
159
159
 
160
+ /**
161
+ * Layout a pantalla completa para cualquier pantalla de autenticación
162
+ * (login, recuperar contraseña, confirmación de envío) — split marca a la
163
+ * izquierda / contenido libre a la derecha en desktop, formulario a
164
+ * pantalla completa en mobile (el panel de marca completo se oculta, no se
165
+ * apila arriba: en una pantalla angosta el usuario vino a loguearse, no a
166
+ * leer la tagline). Un logo compacto (o wordmark de respaldo) sí se
167
+ * mantiene visible arriba del formulario en mobile — el usuario nunca
168
+ * pierde de vista qué portal está usando, mismo criterio que ya sigue el
169
+ * resto del ecosistema (ver la topbar mobile de `css/otd.css`, que muestra
170
+ * el logo aunque el sidebar completo se oculte).
171
+ *
172
+ * Este componente resuelve el "cascarón" — nunca el formulario en sí. Cada
173
+ * portal arma su propio `<form>` con los componentes de la librería
174
+ * (`kf-input`, `kf-checkbox`, `kf-button`, `kf-alert`) y lo proyecta en el
175
+ * slot por defecto. Es la misma separación de responsabilidad que ya usa
176
+ * `kf-card`: un componente da la estructura visual, el consumidor decide
177
+ * qué va adentro.
178
+ *
179
+ * Antes de este componente, cada portal (LATAM primero, ahora OTD) hubiera
180
+ * tenido que reimplementar este mismo split desde cero — la librería
181
+ * había pospuesto esta abstracción a propósito hasta tener un segundo
182
+ * consumidor real que confirmara qué parte es realmente compartida (el
183
+ * cascarón) y qué parte es de cada portal (el formulario). OTD es ese
184
+ * segundo consumidor.
185
+ *
186
+ * Ningún input tiene valor por defecto propio de ningún portal — `brand`
187
+ * es obligatorio (`input.required`) y el resto de props de marca son
188
+ * opcionales sin fallback hardcodeado, precisamente para que cada
189
+ * consumidor (LATAM, OTD, Star, o el ERP de Asistencia en Ruta con sus
190
+ * propios temas) traiga su propio logo/tagline sin que la librería asuma
191
+ * cuál.
192
+ *
193
+ * <kf-auth-layout
194
+ * brand="Mi Portal"
195
+ * brandLogoSrc="images/mi-portal-logo-white.png"
196
+ * brandLogoMobileSrc="images/mi-portal-logo-black.png"
197
+ * brandTagline="Frase corta de marca, propia de cada portal."
198
+ * >
199
+ * <form (ngSubmit)="onSubmit()">
200
+ * <kf-input label="Correo" type="email" />
201
+ * <kf-input label="Contraseña" type="password" toggleable />
202
+ * <kf-button type="submit">Ingresar</kf-button>
203
+ * </form>
204
+ * </kf-auth-layout>
205
+ */
206
+ declare class AuthLayout {
207
+ /** Nombre de marca/portal, usado como wordmark de respaldo si no hay ningún logo, y como `alt` de ambos logos. */
208
+ readonly brand: _angular_core.InputSignal<string>;
209
+ /** Logo en blanco para el panel oscuro de desktop — si no se pasa, se muestra `brand()` como wordmark tipográfico. */
210
+ readonly brandLogoSrc: _angular_core.InputSignal<string | undefined>;
211
+ /** Frase corta bajo el logo — opcional, cada portal decide su propio texto de marca. */
212
+ readonly brandTagline: _angular_core.InputSignal<string | undefined>;
213
+ /**
214
+ * Logo compacto para mobile, sobre el fondo claro del panel de contenido
215
+ * — normalmente la variante oscura/negra del mismo logo, ya que
216
+ * `brandLogoSrc` suele ser la variante blanca pensada para el gradiente
217
+ * oscuro. Si no se pasa, reutiliza `brandLogoSrc`; si tampoco hay logo en
218
+ * absoluto, cae al wordmark tipográfico igual que en desktop.
219
+ */
220
+ readonly brandLogoMobileSrc: _angular_core.InputSignal<string | undefined>;
221
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthLayout, never>;
222
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AuthLayout, "kf-auth-layout", never, { "brand": { "alias": "brand"; "required": true; "isSignal": true; }; "brandLogoSrc": { "alias": "brandLogoSrc"; "required": false; "isSignal": true; }; "brandTagline": { "alias": "brandTagline"; "required": false; "isSignal": true; }; "brandLogoMobileSrc": { "alias": "brandLogoMobileSrc"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
223
+ }
224
+
160
225
  /**
161
226
  * Avatar de usuario: muestra una foto (`src`) o, si no hay foto, las
162
227
  * iniciales (`initials`) sobre un fondo con gradiente de marca. `round`
@@ -206,7 +271,7 @@ declare class Avatar {
206
271
  * no existe un `-strong` para secondary todavía — usa el color base).
207
272
  */
208
273
  declare class Badge {
209
- readonly tone: _angular_core.InputSignal<"error" | "info" | "ok" | "warn" | "neutral" | "service">;
274
+ readonly tone: _angular_core.InputSignal<"info" | "ok" | "warn" | "error" | "neutral" | "service">;
210
275
  readonly dot: _angular_core.InputSignalWithTransform<boolean, unknown>;
211
276
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Badge, never>;
212
277
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Badge, "kf-badge", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
@@ -393,7 +458,7 @@ declare class Checkbox implements ControlValueAccessor {
393
458
  * de superficie/borde/texto-muted más cercanos).
394
459
  */
395
460
  declare class Chip {
396
- readonly tone: _angular_core.InputSignal<"error" | "ok" | "warn" | "neutral">;
461
+ readonly tone: _angular_core.InputSignal<"ok" | "warn" | "error" | "neutral">;
397
462
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Chip, never>;
398
463
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Chip, "kf-chip", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
399
464
  }
@@ -621,84 +686,209 @@ declare class Divider {
621
686
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Divider, "kf-divider", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
622
687
  }
623
688
 
689
+ type DrawerSide = 'left' | 'right';
690
+ /**
691
+ * Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
692
+ * ni qué es sesión, solo da tres lugares para proyectar contenido
693
+ * (`[kf-drawer-header]`, default, `[kf-drawer-footer]`) y resuelve lo que
694
+ * cualquier drawer necesita resolver igual sin importar qué haya adentro:
695
+ * overlay con blur que cierra al click, `Escape`, atrapado de foco real y
696
+ * devolución de foco al cerrar.
697
+ *
698
+ * Extraído de `PortalHeader`, que hasta acá reimplementaba su propio drawer
699
+ * embebido (navegación hardcodeada + slot único al final). Cualquier
700
+ * consumidor que necesite un panel lateral — no solo `PortalHeader` — puede
701
+ * usar este componente directamente.
702
+ *
703
+ * `open` es two-way, mismo patrón que `Dropdown`/`Modal`: el consumidor
704
+ * decide qué dispara la apertura (un botón de hamburguesa, por ejemplo) y
705
+ * qué hace con el cierre.
706
+ *
707
+ * Foco: al abrir, se crea un `FocusTrap` del CDK sobre el panel (la misma
708
+ * pieza que usa Angular Material para `mat-sidenav`/`mat-dialog`) — Tab y
709
+ * Shift+Tab ciclan solo entre los elementos focuseables del drawer, sin
710
+ * escapar al contenido de fondo. Al cerrar, el foco vuelve al elemento que
711
+ * tenía el foco antes de abrir (guardado en `document.activeElement`), no
712
+ * se pierde en el `<body>`.
713
+ *
714
+ * `[kf-drawer-header]` tiene fondo oscuro propio (`--kf-color-tertiary-deep`),
715
+ * distinto del body (`--kf-color-white`) — pensado para contener
716
+ * `kf-drawer-session`, que asume ese fondo y no lo trae él mismo (ver
717
+ * `DrawerSession.ts`). Si el slot queda vacío, el contenedor colapsa
718
+ * (`display: none`) y no deja una franja oscura sin contenido.
719
+ *
720
+ * <kf-drawer [(open)]="menuOpen" ariaLabel="Menú de navegación">
721
+ * <div kf-drawer-header>
722
+ * <kf-drawer-session name="Tomás Soto" email="tomas@kaufmann.com" (logoutClick)="onLogout()" />
723
+ * </div>
724
+ * <a href="/">Home</a>
725
+ * <a href="/vehiculos">Vehículos</a>
726
+ * </kf-drawer>
727
+ */
728
+ declare class Drawer {
729
+ /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
730
+ readonly open: _angular_core.ModelSignal<boolean>;
731
+ /** Borde de anclaje del panel. `left` (default) es la convención dominante
732
+ * (Material/iOS/Android): el botón que abre el drawer y el propio panel
733
+ * quedan del mismo lado. */
734
+ readonly side: _angular_core.InputSignal<DrawerSide>;
735
+ /** `aria-label` del `<nav>` interno — describe el propósito del panel para
736
+ * lectores de pantalla (ej. "Menú de navegación"). */
737
+ readonly ariaLabel: _angular_core.InputSignal<string>;
738
+ private readonly panelRef;
739
+ private readonly focusTrapFactory;
740
+ private readonly document;
741
+ private focusTrap;
742
+ private lastFocusedElement;
743
+ constructor();
744
+ /** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
745
+ protected onEscape(): void;
746
+ private getOrCreateFocusTrap;
747
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Drawer, never>;
748
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Drawer, "kf-drawer", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "side": { "alias": "side"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["[kf-drawer-header]", "*", "[kf-drawer-footer]"], true, never>;
749
+ }
750
+
751
+ /**
752
+ * Botón de cerrar sesión, pensado para proyectarse dentro de
753
+ * `[kf-drawer-footer]` de `kf-drawer`, o junto a `kf-drawer-session` dentro
754
+ * de `[kf-sidebar-footer]` de `kf-sidebar-shell` — separado de
755
+ * `kf-drawer-session` (que solo muestra identidad: brand, avatar, nombre,
756
+ * email), no anidado dentro de ella.
757
+ *
758
+ * Reproduce el mismo botón que antes vivía embebido en `DrawerSession`:
759
+ * ícono `log-out` + label, color `--kf-color-error` con hover en
760
+ * `--kf-color-error-soft` y foco visible con `--kf-shadow-focus`. Se separó
761
+ * en un componente propio porque identidad y logout son responsabilidades
762
+ * distintas — `kf-drawer-session` no debería saber que existe un botón de
763
+ * cerrar sesión, y el footer del drawer ya tiene su propio padding/borde
764
+ * superior (ver `Drawer.ts`) que reemplaza el borde que antes traía el botón
765
+ * mismo.
766
+ *
767
+ * No sabe nada de autenticación real — solo emite `logoutClick` cuando el
768
+ * consumidor debe cerrar la sesión real.
769
+ *
770
+ * `collapsed` oculta el label y centra el ícono, exponiéndolo vía `title`
771
+ * — mismo criterio que `SidebarNavItem.collapsed` y
772
+ * `DrawerSession.collapsed`, para cuando este botón vive dentro de un
773
+ * `kf-sidebar-shell` colapsado.
774
+ *
775
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
776
+ */
777
+ declare class DrawerLogout {
778
+ /** Label del botón, en español neutro por defecto. Input opcional por si
779
+ * algún consumidor futuro necesita otro texto. */
780
+ readonly label: _angular_core.InputSignal<string>;
781
+ /** Oculta el label y centra el ícono — ver comentario de la clase. */
782
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
783
+ readonly logoutClick: _angular_core.OutputEmitterRef<void>;
784
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerLogout, never>;
785
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DrawerLogout, "kf-drawer-logout", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; }, { "logoutClick": "logoutClick"; }, never, never, true, never>;
786
+ }
787
+
788
+ /**
789
+ * Bloque de identidad para `[kf-drawer-header]` de `kf-drawer` o
790
+ * `[kf-sidebar-footer]` de `kf-sidebar-shell`: avatar + nombre + email,
791
+ * brand opcional arriba. Solo identidad — el logout vive aparte, en
792
+ * `kf-drawer-logout`.
793
+ *
794
+ * `collapsed` oculta brand/nombre/email y deja solo el avatar (para un
795
+ * `kf-sidebar-shell` colapsado). `interactive` convierte la tarjeta en un
796
+ * botón real que emite `cardClick`, para cuando la identidad es el
797
+ * disparador de un menú de acciones (ej. envuelta en `kf-dropdown`).
798
+ *
799
+ * <kf-dropdown [(open)]="menuOpen">
800
+ * <kf-drawer-session interactive name="Juan Pérez" email="juan@empresa.com" (cardClick)="menuOpen.set(!menuOpen())" />
801
+ * <kf-dropdown-panel variant="dark">
802
+ * <kf-dropdown-item icon="log-out" tone="error" (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
803
+ * </kf-dropdown-panel>
804
+ * </kf-dropdown>
805
+ */
806
+ declare class DrawerSession {
807
+ readonly name: _angular_core.InputSignal<string>;
808
+ readonly email: _angular_core.InputSignal<string>;
809
+ readonly initials: _angular_core.InputSignal<string>;
810
+ readonly avatarUrl: _angular_core.InputSignal<string>;
811
+ /** Label superior opcional, ej. "Portal Kaufmann LATAM". */
812
+ readonly brand: _angular_core.InputSignal<string>;
813
+ /** Oculta brand/nombre/email y centra el avatar — ver comentario de la clase. */
814
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
815
+ /** Convierte la tarjeta en un botón real con ícono de kebab — ver comentario de la clase. */
816
+ readonly interactive: _angular_core.InputSignalWithTransform<boolean, unknown>;
817
+ /** Se emite al clickear la tarjeta, solo cuando `interactive` es `true`. */
818
+ readonly cardClick: _angular_core.OutputEmitterRef<void>;
819
+ /** Si no se pasa `initials`, se derivan de `name`: primera letra de cada
820
+ * una de las primeras 2 palabras, en mayúscula. */
821
+ protected readonly displayInitials: _angular_core.Signal<string>;
822
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerSession, never>;
823
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DrawerSession, "kf-drawer-session", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "email": { "alias": "email"; "required": false; "isSignal": true; }; "initials": { "alias": "initials"; "required": false; "isSignal": true; }; "avatarUrl": { "alias": "avatarUrl"; "required": false; "isSignal": true; }; "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; }, { "cardClick": "cardClick"; }, never, never, true, never>;
824
+ }
825
+
624
826
  type DropdownItemTone = 'neutral' | 'error';
625
827
  /**
626
828
  * Ítem de acción dentro de un `kf-dropdown-panel`. Es un `<button>` nativo
627
- * por dentro (no un `<a>`): las acciones de un menú de este tipo disparan
628
- * comportamiento en la pantalla actual (cerrar sesión, ejecutar un
629
- * comando), no navegan a otra URL.
829
+ * (no un `<a>`): dispara comportamiento en la pantalla actual, no navega.
630
830
  *
631
- * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
831
+ * `icon` es opcional — sin él, el ítem es solo texto.
832
+ *
833
+ * <kf-dropdown-item icon="log-out" (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
632
834
  */
633
835
  declare class DropdownItem {
634
836
  readonly tone: _angular_core.InputSignal<DropdownItemTone>;
837
+ /** Ícono opcional al inicio de la fila. */
838
+ readonly icon: _angular_core.InputSignal<IconName | undefined>;
635
839
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownItem, never>;
636
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownItem, "kf-dropdown-item", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
840
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownItem, "kf-dropdown-item", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
637
841
  }
842
+ type DropdownPanelVariant = 'solid' | 'dark';
638
843
  /**
639
844
  * Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
640
- * (`<ng-template>` por dentro, capturada vía `TemplateRef`) — `Dropdown` la
641
- * conecta al overlay real cuando `open` pasa a `true`, no la renderiza en su
642
- * propio lugar del DOM. Esto es lo que permite que el panel aparezca pegado
643
- * al host de `Dropdown` sin importar dónde esté ese host en la página.
644
- *
645
- * <kf-dropdown [(open)]="menuOpen">
646
- * <kf-dropdown-panel>
647
- * <kf-dropdown-item (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
648
- * </kf-dropdown-panel>
649
- * </kf-dropdown>
845
+ * (`<ng-template>`, capturada vía `TemplateRef`) — `Dropdown` la conecta al
846
+ * overlay real cuando `open` pasa a `true`, no la renderiza en su propio
847
+ * lugar del DOM. Esto permite que el panel aparezca pegado al host de
848
+ * `Dropdown` sin importar dónde esté ese host en la página.
849
+ *
850
+ * `variant`: `'solid'` (default, fondo blanco) o `'dark'` (fondo sólido
851
+ * oscuro, mismo tono que `[kf-drawer-header]`) para cuando el trigger vive
852
+ * sobre una superficie oscura de marca.
853
+ *
854
+ * `dark` es sólida a propósito, no semitransparente: el CDK Overlay
855
+ * reposiciona el panel según dónde entra en el viewport y puede terminar
856
+ * cayendo sobre fondo blanco en vez de sobre el rail oscuro — un color
857
+ * sólido se ve intencional sin importar dónde lo posicione el CDK.
650
858
  */
651
859
  declare class DropdownPanel {
860
+ readonly variant: _angular_core.InputSignal<DropdownPanelVariant>;
652
861
  readonly templateRef: _angular_core.Signal<TemplateRef<any>>;
653
862
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownPanel, never>;
654
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, {}, {}, never, ["*"], true, never>;
863
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
655
864
  }
656
865
  /**
657
866
  * Menú de acciones flotante, conectado a un elemento disparador vía
658
867
  * `@angular/cdk/overlay` — la misma pieza que usa Angular Material para
659
- * `mat-menu`/`mat-select`. Resuelve el patrón que antes se reimplementaba a
660
- * mano en cada consumidor (ver el session-menu de `web-portal-latam-frontend`,
661
- * ~80 líneas de HTML/TS/SCSS para toggle + click-afuera + Escape + animación
662
- * de entrada, acopladas a una clase interna no pública de `PortalHeader`).
663
- *
664
- * Se apoya en el CDK en vez de un `position: absolute` casero (como sí hace
665
- * `SelectMenu`, que puede permitírselo porque su trigger vive siempre dentro
666
- * del propio componente) porque acá el disparador real puede vivir en OTRO
667
- * componente por completo — ej. el avatar dentro de `kf-portal-header` — y
668
- * el panel tiene que aparecer pegado a ESE elemento sin importar dónde el
669
- * consumidor ubique al `Dropdown` en su HTML. El Overlay CDK calcula esa
670
- * posición (con fallback si no entra en pantalla) en vez de que cada
671
- * consumidor lo recalcule a mano.
672
- *
673
- * `open` es two-way, igual que `Alert`/`Modal`: el consumidor decide qué
674
- * dispara la apertura (puede ser un `(click)` en un botón propio, o un
675
- * evento de otro componente como `PortalHeader.sessionClick`) — `Dropdown`
676
- * en sí no renderiza ningún trigger, su propio host es el punto de anclaje
677
- * visual del panel.
868
+ * `mat-menu`/`mat-select`.
678
869
  *
679
- * <kf-dropdown [(open)]="menuOpen">
680
- * <kf-dropdown-panel>
681
- * <div class="mi-header">Sesión iniciada como {{ email() }}</div>
682
- * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
683
- * </kf-dropdown-panel>
684
- * </kf-dropdown>
870
+ * Se apoya en el CDK en vez de un `position: absolute` casero (a diferencia
871
+ * de `SelectMenu`, que puede permitírselo porque su trigger vive siempre
872
+ * dentro del propio componente) porque acá el disparador real puede vivir
873
+ * en OTRO componente por completo — ej. el avatar dentro de
874
+ * `kf-portal-header` — y el panel tiene que aparecer pegado a ESE elemento
875
+ * sin importar dónde el consumidor ubique al `Dropdown` en su HTML.
876
+ *
877
+ * `open` es two-way: el consumidor decide qué dispara la apertura —
878
+ * `Dropdown` en sí no renderiza ningún trigger, su propio host es el punto
879
+ * de anclaje visual del panel.
880
+ *
881
+ * <kf-dropdown [(open)]="menuOpen"><kf-dropdown-panel>...</kf-dropdown-panel></kf-dropdown>
685
882
  */
686
883
  declare class Dropdown {
687
884
  /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
688
885
  readonly open: _angular_core.ModelSignal<boolean>;
689
886
  /**
690
887
  * Elemento al que se conecta el overlay, si es distinto del propio host
691
- * de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es visualmente
692
- * el disparador (caso normal: el consumidor arma su propio botón como
693
- * contenido, y `Dropdown` envuelve ese contenido). Cuando el disparador
694
- * real vive dentro de OTRO componente que no se puede envolver sin
695
- * ensanchar el área de click a algo más grande que el botón (ej. el
696
- * avatar de sesión dentro de `PortalHeader`, que convive con logo y
697
- * navegación en el mismo host), `anchor` permite apuntar el overlay a la
698
- * referencia real de ese componente en vez de al host vacío de
699
- * `Dropdown`.
700
- *
701
- * <kf-dropdown [(open)]="menuOpen" [anchor]="headerRef()">
888
+ * de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es el
889
+ * disparador. Útil cuando el disparador real vive dentro de OTRO
890
+ * componente que no se puede envolver sin ensanchar el área de click
891
+ * (ej. el avatar de sesión dentro de `PortalHeader`).
702
892
  */
703
893
  readonly anchor: _angular_core.InputSignal<ElementRef<HTMLElement> | undefined>;
704
894
  private readonly panel;
@@ -707,18 +897,41 @@ declare class Dropdown {
707
897
  private readonly viewContainerRef;
708
898
  private readonly destroyRef;
709
899
  private overlayRef;
900
+ /**
901
+ * Suscripciones a `outsidePointerEvents()`/`keydownEvents()` del overlay
902
+ * ACTUALMENTE adjunto. `getOrCreateOverlayRef()` reutiliza el mismo
903
+ * `overlayRef` entre aperturas (no lo recrea cada vez) — sin esto,
904
+ * `attach()` se resuscribía a esos observables en cada apertura sin
905
+ * cancelar la suscripción de la vez anterior, acumulando N suscripciones
906
+ * activas sobre el mismo overlay persistente. Eso causaba el bug real de
907
+ * "clicks seguidos": al abrir de nuevo, el propio pointerdown que originó
908
+ * la apertura llegaba a más de una suscripción acumulada, disparando
909
+ * `open.set(false)` de forma errática en el mismo tick (patrón observado:
910
+ * abre cada 2 clicks, cierra cada 1). Se cancelan explícitamente en
911
+ * `detach()`, antes de la siguiente `attach()`.
912
+ */
913
+ private overlaySubscription;
710
914
  constructor();
711
915
  /**
712
916
  * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo
713
- * ve teclas presionadas con el foco DENTRO del pane del overlay. Si el
714
- * usuario abre el menú y presiona Escape con el foco en otro lado de la
715
- * página (caso común — el foco puede seguir en el botón disparador, que
716
- * vive fuera del overlay), `keydownEvents()` nunca dispara. Este listener
717
- * a nivel de documento cubre ese caso; el de `keydownEvents()` sigue
718
- * activo para cuando el foco sí está dentro del panel.
917
+ * ve teclas con el foco DENTRO del panel. Si el foco queda en el botón
918
+ * disparador (caso común), `keydownEvents()` nunca dispara — este
919
+ * listener a nivel de documento cubre ese caso.
719
920
  */
720
921
  onDocumentEscape(): void;
721
922
  private attach;
923
+ /**
924
+ * El trigger real (el `anchor`, o el propio host si no se pasó ninguno —
925
+ * ver comentario de la clase) vive FUERA del overlay a propósito: el
926
+ * overlay recién se crea/adjunta cuando `open` pasa a `true`, así que el
927
+ * CDK no tiene forma de saber que ese elemento externo "es parte" de su
928
+ * disparador. Sin este filtro, el mismo `pointerdown` que abre el menú
929
+ * (el trigger no es hijo del overlay) también llega a
930
+ * `outsidePointerEvents()`, cerrando el panel en el mismo gesto que lo
931
+ * abrió — bug real reproducido con Playwright: "abre y cierra" en
932
+ * clicks consecutivos rápidos sobre la tarjeta de sesión del sidebar.
933
+ */
934
+ private isEventOnTrigger;
722
935
  private detach;
723
936
  private getOrCreateOverlayRef;
724
937
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Dropdown, never>;
@@ -881,6 +1094,21 @@ interface FolderTabOption {
881
1094
  label: string;
882
1095
  icon?: IconName;
883
1096
  }
1097
+ /**
1098
+ * Marca el contenido que `kf-folder-tabs` debe proyectar dentro de su
1099
+ * panel. Sin lógica propia — existe solo como token de tipo para que
1100
+ * `contentChild(FolderTabsPanel)` pueda detectar si el consumidor proyectó
1101
+ * algo (mismo mecanismo que `kf-dropdown` usa con `contentChild(DropdownPanel)`;
1102
+ * acá no hace falta un componente completo, alcanza con una directiva de
1103
+ * atributo porque el panel no necesita comportamiento propio, solo marcar
1104
+ * su presencia).
1105
+ *
1106
+ * <div kfFolderTabsPanel>...</div>
1107
+ */
1108
+ declare class FolderTabsPanel {
1109
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabsPanel, never>;
1110
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FolderTabsPanel, "[kfFolderTabsPanel]", never, {}, {}, never, never, true, never>;
1111
+ }
884
1112
  /**
885
1113
  * Tabs con forma de "carpeta": el borde superior queda redondeado y el borde
886
1114
  * inferior de la pestaña activa se funde visualmente con el panel de
@@ -891,20 +1119,93 @@ interface FolderTabOption {
891
1119
  * `--active` se mueve de un botón a otro, y eso vuelve a disparar el keyframe
892
1120
  * sin tocar JS.
893
1121
  *
894
- * El consumidor es responsable de renderizar el panel de contenido debajo;
895
- * este componente solo resuelve la fila de pestañas y emite `valueChange`.
896
- *
1122
+ * El panel de contenido es un `<ng-content>` OPCIONAL — si el consumidor no
1123
+ * proyecta nada, `kf-folder-tabs` renderiza solo la fila de pestañas (mismo
1124
+ * comportamiento que antes de que existiera este slot, detectado con
1125
+ * `contentChild()` sin `.required`, igual patrón que usa `kf-dropdown` para
1126
+ * saber si hay un `kf-dropdown-panel` proyectado). Si SÍ proyecta contenido,
1127
+ * se envuelve en un panel con la esquina superior-izquierda recta (nace ahí
1128
+ * la primera pestaña, siempre pegada al borde izquierdo de la fila — un
1129
+ * radio ahí competiría visualmente con el propio borde redondeado de esa
1130
+ * pestaña) y las otras tres redondeadas. El `border-bottom` de 2px de la
1131
+ * pestaña activa (ver `.kf-folder-tabs__tab--active` más abajo) es la señal
1132
+ * visual de conexión con el panel para CUALQUIER pestaña activa, sin
1133
+ * necesitar mover ni medir nada.
1134
+ *
1135
+ * El borde izquierdo de toda la fila (y por lo tanto de la esquina recta
1136
+ * del panel) depende de que ninguna pestaña se corra al cambiar de estado
1137
+ * — ver `transform-origin: bottom left` en `.kf-folder-tabs__tab`: con el
1138
+ * origen centrado (como estaba antes), el `scale()` de la pestaña inactiva
1139
+ * encogía el borde izquierdo unos px, y ese corrimiento sub-pixel es lo que
1140
+ * hacía que este mismo diseño (esquina fija) se viera "roto" cuando la
1141
+ * pestaña activa no era la primera. Con el origen anclado a la izquierda,
1142
+ * el corrimiento desaparece y la esquina fija vuelve a ser una solución
1143
+ * válida y simple — sin necesitar medir la pestaña activa por JS.
1144
+ *
1145
+ * Antes de este slot, el patrón era que el consumidor pusiera su propia
1146
+ * card como HERMANO de `kf-folder-tabs` y anulara a mano el radio superior
1147
+ * completo (`border-top-left-radius: 0`, `border-top-right-radius: 0` — ver
1148
+ * el caso original de `web-portal-latam-frontend`). Encapsular esto acá
1149
+ * evita que cada consumidor repita esas reglas a mano.
1150
+ *
1151
+ * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
1152
+ * <div kfFolderTabsPanel>contenido de la pestaña activa</div>
1153
+ * </kf-folder-tabs>
1154
+ *
1155
+ * <!-- o, sin contenido, como antes: -->
897
1156
  * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event" />
898
1157
  */
899
1158
  declare class FolderTabs {
900
1159
  readonly options: _angular_core.InputSignal<FolderTabOption[]>;
901
1160
  readonly value: _angular_core.InputSignal<string>;
902
1161
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
1162
+ /**
1163
+ * Detecta si el consumidor proyectó contenido marcado con
1164
+ * `kfFolderTabsPanel` — sin `.required`, así que es `undefined`
1165
+ * cuando no hay nada proyectado (mismo patrón que usa `kf-dropdown` con
1166
+ * `contentChild(DropdownPanel)` para saber si hay un panel que
1167
+ * attachear). `hasPanel()` decide si el wrapper del panel participa del
1168
+ * layout (`display: contents` si no).
1169
+ */
1170
+ private readonly projectedPanel;
1171
+ protected readonly hasPanel: _angular_core.Signal<boolean>;
903
1172
  select(value: string): void;
904
1173
  /** Navegación estándar de ARIA tabs: flechas mueven el foco y activan la pestaña. */
905
1174
  onKeydown(event: KeyboardEvent, currentValue: string): void;
906
1175
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabs, never>;
907
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FolderTabs, "kf-folder-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1176
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FolderTabs, "kf-folder-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, ["projectedPanel"], ["[kfFolderTabsPanel]"], true, never>;
1177
+ }
1178
+
1179
+ /** Un link del pie — legales, contacto, soporte. */
1180
+ interface FooterLink {
1181
+ label: string;
1182
+ href?: string;
1183
+ }
1184
+ /**
1185
+ * Pie de página general del portal (página 38): logo + copyright a la
1186
+ * izquierda, links legales/soporte empujados a la derecha. Logo y copyright
1187
+ * son inputs (cada portal del ecosistema — LATAM, OTD, Star — trae su
1188
+ * propia marca y texto legal); `links` es la lista de accesos, típicamente
1189
+ * placeholders hasta que existan destinos reales.
1190
+ *
1191
+ * <kf-footer
1192
+ * logoSrc="images/logo-kaufmann-dark.png"
1193
+ * logoAlt="Grupo Kaufmann"
1194
+ * copyright="© 2026 Kaufmann LATAM · Todos los derechos reservados"
1195
+ * [links]="[
1196
+ * { label: 'Políticas de privacidad' },
1197
+ * { label: 'Contacto' },
1198
+ * { label: 'Sugerencias y reclamos' },
1199
+ * ]"
1200
+ * />
1201
+ */
1202
+ declare class Footer {
1203
+ readonly logoSrc: _angular_core.InputSignal<string | undefined>;
1204
+ readonly logoAlt: _angular_core.InputSignal<string>;
1205
+ readonly copyright: _angular_core.InputSignal<string>;
1206
+ readonly links: _angular_core.InputSignal<FooterLink[]>;
1207
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Footer, never>;
1208
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Footer, "kf-footer", never, { "logoSrc": { "alias": "logoSrc"; "required": false; "isSignal": true; }; "logoAlt": { "alias": "logoAlt"; "required": false; "isSignal": true; }; "copyright": { "alias": "copyright"; "required": true; "isSignal": true; }; "links": { "alias": "links"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
908
1209
  }
909
1210
 
910
1211
  /**
@@ -1109,18 +1410,25 @@ interface MultiSelectOption {
1109
1410
  label: string;
1110
1411
  }
1111
1412
  /**
1112
- * Dropdown de selección múltiple con checkboxes internos, siguiendo el mismo
1113
- * patrón de panel flotante que `SelectMenu` (panel con `position: absolute`
1114
- * sobre el host, sin Angular CDK Overlay, que no es dependencia del proyecto).
1115
- * Se abre y cierra por click en el disparador, y se cierra al hacer click
1116
- * afuera o al presionar Escape.
1117
- *
1118
- * A diferencia de `SelectMenu`, el disparador no muestra un único valor sino
1119
- * los nombres seleccionados junto a un badge circular con el conteo (página
1120
- * 12: "Sucursal Norte, Lima Centro" + badge "2"), y cada opción del panel se
1121
- * marca/desmarca con un checkbox sin cerrar el panel. Incluye una opción
1122
- * "Todas" al inicio de la lista, con estado `indeterminate` cuando hay
1123
- * selección parcial — el mismo patrón de "seleccionar todos" de `Checkbox`.
1413
+ * Dropdown de selección múltiple con checkboxes internos. El panel flotante
1414
+ * se conecta al trigger vía `@angular/cdk/overlay` — el mismo mecanismo que
1415
+ * `Select` (ver `../select/Select.ts`) y `Dropdown` (`../dropdown/Dropdown.ts`).
1416
+ *
1417
+ * Hasta la `0.1.9` este panel vivía con `position: absolute` manual sobre el
1418
+ * propio host, sin depender del CDK Overlay. Se migró para unificar el
1419
+ * mecanismo de posicionamiento/cierre con el resto de los dropdowns flotantes
1420
+ * de la librería (4 posiciones de fallback, `withViewportMargin`, `withPush`,
1421
+ * cierre por `outsidePointerEvents()`/`keydownEvents()` en vez de un
1422
+ * `@HostListener('document:click')` manual).
1423
+ *
1424
+ * A diferencia de `Select`, el disparador no muestra un único valor sino los
1425
+ * nombres seleccionados junto a un badge circular con el conteo, y cada
1426
+ * opción del panel se marca/desmarca con un checkbox sin cerrar el panel.
1427
+ * Incluye una opción "Todas" al inicio de la lista, con estado
1428
+ * `indeterminate` cuando hay selección parcial — el mismo patrón de
1429
+ * "seleccionar todos" de `Checkbox`. "Todas" nunca se filtra por el buscador:
1430
+ * queda siempre visible y sigue actuando sobre el universo COMPLETO de
1431
+ * `options()`, no sobre el subconjunto filtrado (ver JSDoc de `toggleAll()`).
1124
1432
  *
1125
1433
  * <kf-multi-select
1126
1434
  * label="Sucursales"
@@ -1139,14 +1447,27 @@ declare class MultiSelect {
1139
1447
  readonly values: _angular_core.InputSignal<string[]>;
1140
1448
  readonly placeholder: _angular_core.InputSignal<string>;
1141
1449
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1450
+ /**
1451
+ * Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
1452
+ * se activa solo cuando `options().length` supera `searchThreshold()`. Si
1453
+ * el consumidor define `true`/`false` explícitamente, ese valor manda por
1454
+ * sobre el umbral automático. Mismo criterio que `Select`.
1455
+ */
1456
+ readonly searchable: _angular_core.InputSignal<boolean | undefined>;
1457
+ readonly searchThreshold: _angular_core.InputSignal<number>;
1142
1458
  readonly valuesChange: _angular_core.OutputEmitterRef<string[]>;
1143
1459
  private readonly instanceId;
1144
1460
  readonly triggerId: string;
1145
1461
  readonly listboxId: string;
1146
1462
  readonly isOpen: _angular_core.WritableSignal<boolean>;
1463
+ readonly searchQuery: _angular_core.WritableSignal<string>;
1464
+ readonly activeIndex: _angular_core.WritableSignal<number>;
1147
1465
  readonly selectedCount: _angular_core.Signal<number>;
1148
1466
  readonly allSelected: _angular_core.Signal<boolean>;
1149
1467
  readonly isPartiallySelected: _angular_core.Signal<boolean>;
1468
+ readonly showSearch: _angular_core.Signal<boolean>;
1469
+ /** Opciones visibles tras aplicar el filtro de búsqueda ("Todas" nunca se filtra). */
1470
+ readonly filteredOptions: _angular_core.Signal<MultiSelectOption[]>;
1150
1471
  /**
1151
1472
  * Texto del disparador: placeholder si no hay selección, o los nombres
1152
1473
  * seleccionados unidos por coma (el conteo va aparte, en el badge). Con
@@ -1154,18 +1475,88 @@ declare class MultiSelect {
1154
1475
  * corta el texto — el badge de conteo siempre queda visible al lado.
1155
1476
  */
1156
1477
  readonly summaryText: _angular_core.Signal<string>;
1157
- private readonly host;
1478
+ private readonly triggerRef;
1479
+ private readonly panelTemplate;
1480
+ private readonly searchInputRef;
1481
+ private readonly overlayService;
1482
+ private readonly viewContainerRef;
1483
+ private readonly destroyRef;
1484
+ private overlayRef;
1485
+ constructor();
1158
1486
  isSelected(value: string): boolean;
1159
1487
  toggle(): void;
1160
1488
  toggleOption(value: string): void;
1161
- /** Alterna entre "todas seleccionadas" y "ninguna seleccionada". */
1489
+ /**
1490
+ * Alterna entre "todas seleccionadas" y "ninguna seleccionada". Actúa
1491
+ * siempre sobre el universo COMPLETO de `options()`, no sobre
1492
+ * `filteredOptions()` — con un buscador activo, "Todas" no se filtra ni
1493
+ * cambia de significado: seguir marcando/desmarcando únicamente lo visible
1494
+ * sería sorprendente para quien ya tenía otras opciones (no visibles en el
1495
+ * filtro actual) seleccionadas de antes.
1496
+ */
1162
1497
  toggleAll(): void;
1163
- onDocumentClick(event: MouseEvent): void;
1498
+ onSearchInput(event: Event): void;
1499
+ /** Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp simple, sin wraparound. */
1500
+ moveActive(delta: number): void;
1501
+ toggleActive(): void;
1164
1502
  onEscape(): void;
1503
+ private attach;
1504
+ private detach;
1505
+ private getOrCreateOverlayRef;
1165
1506
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MultiSelect, never>;
1166
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MultiSelect, "kf-multi-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "valuesChange": "valuesChange"; }, never, never, true, never>;
1507
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MultiSelect, "kf-multi-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; }, { "valuesChange": "valuesChange"; }, never, never, true, never>;
1508
+ }
1509
+
1510
+ type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
1511
+ type TextVariant = 'title-xxl' | 'title-xl' | 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption';
1512
+ type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
1513
+ /**
1514
+ * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
1515
+ * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
1516
+ * ve). Antes de este componente, cada pantalla que consume la librería
1517
+ * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
1518
+ * (título de página, subtítulo, texto secundario) — auditoría sobre
1519
+ * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
1520
+ * home sin ningún paso de escala en común. `variant` resuelve eso contra
1521
+ * la escala de `tokens/base.css` (`--kf-font-size-*`).
1522
+ *
1523
+ * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
1524
+ * escape hatch de color libre. Si un caso necesita un color que `tone` no
1525
+ * cubre, es una discusión de design system, no una prop nueva acá.
1526
+ *
1527
+ * `title-xl`/`title-xxl` son un tier aparte de `title-lg|md|sm`: no vienen
1528
+ * de la auditoría de contenido de página (esa cerró en 2.125rem), son para
1529
+ * títulos protagonistas de pantalla completa (ej. bienvenida del home vía
1530
+ * `kf-page-header[size=xl|xxl]`) — ver tokens/base.css.
1531
+ *
1532
+ * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
1533
+ * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
1534
+ * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
1535
+ */
1536
+ declare class Text {
1537
+ /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
1538
+ readonly as: _angular_core.InputSignal<TextAs>;
1539
+ readonly variant: _angular_core.InputSignal<TextVariant>;
1540
+ readonly tone: _angular_core.InputSignal<TextTone>;
1541
+ /**
1542
+ * Copia de `as` para uso dentro del template: `as` es palabra reservada
1543
+ * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
1544
+ * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
1545
+ * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
1546
+ */
1547
+ protected readonly tag: _angular_core.Signal<TextAs>;
1548
+ protected hostClass(): string;
1549
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
1550
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Text, "kf-text", never, { "as": { "alias": "as"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1167
1551
  }
1168
1552
 
1553
+ /**
1554
+ * Tamaño del título — `md` es el tamaño de siempre (no rompe a nadie que no
1555
+ * lo especifique). Cada valor mapea 1:1 a una variante `title-*` de
1556
+ * `kf-text` (ver `Text.ts`); `xl`/`xxl` son el tier "hero" de esa escala,
1557
+ * pensado para casos de un título más protagonista (ej. bienvenida del home).
1558
+ */
1559
+ type PageHeaderTitleSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
1169
1560
  /**
1170
1561
  * Bloque de título al tope de una página: chip de ícono opcional, título (h1),
1171
1562
  * subtítulo opcional y un área de acciones opcional proyectada vía
@@ -1177,13 +1568,17 @@ declare class MultiSelect {
1177
1568
  * </kf-page-header>
1178
1569
  *
1179
1570
  * <kf-page-header icon="settings" title="Configuración" />
1571
+ *
1572
+ * <kf-page-header size="xl" title="Bienvenido de nuevo" />
1180
1573
  */
1181
1574
  declare class PageHeader {
1182
1575
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
1183
1576
  readonly title: _angular_core.InputSignal<string>;
1184
1577
  readonly subtitle: _angular_core.InputSignal<string | undefined>;
1578
+ readonly size: _angular_core.InputSignal<PageHeaderTitleSize>;
1579
+ protected readonly titleVariant: _angular_core.Signal<TextVariant>;
1185
1580
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PageHeader, never>;
1186
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PageHeader, "kf-page-header", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1581
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PageHeader, "kf-page-header", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1187
1582
  }
1188
1583
 
1189
1584
  /** Un número de página clickeable o un separador "…" no interactivo. */
@@ -1229,6 +1624,13 @@ declare class Pagination {
1229
1624
  * <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos">
1230
1625
  * <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
1231
1626
  * </kf-panel>
1627
+ *
1628
+ * El host y el `kf-card` interno son flex column con `flex:1` de punta a
1629
+ * punta — si un grid/flex externo estira `kf-panel` a la altura de un
1630
+ * hermano más alto, el contenido proyectado hereda esa altura en vez de
1631
+ * dejar un hueco vacío bajo el `kf-card`. Sin esa presión externa (altura
1632
+ * "auto" del contenedor), `flex:1` no tiene espacio extra que repartir y el
1633
+ * panel se comporta igual que antes.
1232
1634
  */
1233
1635
  declare class Panel {
1234
1636
  readonly title: _angular_core.InputSignal<string>;
@@ -1238,11 +1640,14 @@ declare class Panel {
1238
1640
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Panel, "kf-panel", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1239
1641
  }
1240
1642
 
1241
- /** Un ítem de navegación de la barra de marca. */
1643
+ /** Un ítem de navegación de la barra de marca. `icon` es opcional y solo se
1644
+ * usa en el drawer mobile (columna, ver template) — el `<nav>` horizontal de
1645
+ * escritorio se mantiene solo texto, sin ícono ni chevron. */
1242
1646
  interface PortalHeaderNavItem {
1243
1647
  label: string;
1244
1648
  href: string;
1245
1649
  active?: boolean;
1650
+ icon?: IconName;
1246
1651
  }
1247
1652
  /** Una acción de la franja de administrador (ej. "Consultar deuda clientes"). */
1248
1653
  interface PortalHeaderAdminAction {
@@ -1258,12 +1663,51 @@ interface PortalHeaderAdminAction {
1258
1663
  * Es distinto de `kf-page-header`/`kf-page-head` (título de una vista
1259
1664
  * interna) — este es el chrome global del portal, se monta una sola vez.
1260
1665
  *
1666
+ * `:host` es `position: sticky; top: 0` — el header queda pegado arriba al
1667
+ * hacer scroll. No es `fixed`: como no se superpone al contenido en el
1668
+ * primer render, no hace falta que el consumidor compense con
1669
+ * `padding-top`.
1670
+ *
1671
+ * Bajo los 768px, el `<nav>` deja de mostrarse en línea junto al logo y
1672
+ * colapsa detrás de un botón de menú (ícono `menu`/`x`) que despliega un
1673
+ * `kf-drawer` (panel lateral genérico, ver `Drawer.ts`) anclado al borde
1674
+ * IZQUIERDO — mismo lado que el botón que lo abre, convención dominante
1675
+ * Material/iOS/Android — con los mismos `navItems` en columna dentro del
1676
+ * body (fondo claro) de ese drawer.
1677
+ *
1678
+ * `PortalHeader` expone dos slots propios pensados para que el consumidor
1679
+ * proyecte contenido que en escritorio vive en otro lugar:
1680
+ * - `<ng-content select="[kf-portal-header-extra]">`: identidad de sesión
1681
+ * (ej. `kf-drawer-session`). Cae en `[kf-drawer-header]` de `kf-drawer` —
1682
+ * el único contenedor con fondo oscuro (`--kf-color-tertiary-deep`, ver
1683
+ * `Drawer.ts`) que `kf-drawer-session` espera como fondo (no trae uno
1684
+ * propio, ver `DrawerSession.ts`).
1685
+ * - `<ng-content select="[kf-portal-header-footer]">`: acciones separadas del
1686
+ * resto (ej. `kf-drawer-logout`). Cae en `[kf-drawer-footer]` de
1687
+ * `kf-drawer`, mismo criterio de "no reimplementar nada del drawer": el
1688
+ * footer ya trae su propio padding/separador (ver `Drawer.ts`).
1689
+ *
1690
+ * `PortalHeader` no sabe qué es ninguno de los dos contenidos ni cómo se
1691
+ * abren, solo les da el lugar correcto dentro del drawer ya desplegado.
1692
+ * `kf-drawer` resuelve overlay con blur, cierre con click-afuera/`Escape`,
1693
+ * atrapado de foco real y devolución de foco al cerrar — `PortalHeader` no
1694
+ * reimplementa nada de eso, solo controla su `open` desde el botón de
1695
+ * hamburguesa.
1696
+ *
1261
1697
  * <kf-portal-header
1262
1698
  * [isAdmin]="true"
1263
1699
  * [adminActions]="[{ label: 'Consultar deuda clientes', href: '/deuda' }, { label: 'Cambiar DNI', href: '/dni' }]"
1264
- * [navItems]="[{ label: 'Home', href: '/', active: true }, { label: 'Vehículos', href: '/vehiculos' }]"
1700
+ * [navItems]="[{ label: 'Home', href: '/', active: true, icon: 'grid' }, { label: 'Vehículos', href: '/vehiculos', icon: 'truck' }]"
1265
1701
  * userInitials="TS"
1266
- * />
1702
+ * >
1703
+ * <kf-drawer-session
1704
+ * kf-portal-header-extra
1705
+ * brand="Portal Kaufmann LATAM"
1706
+ * name="Tomás Soto"
1707
+ * email="tomas.soto@kaufmann.com"
1708
+ * />
1709
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
1710
+ * </kf-portal-header>
1267
1711
  */
1268
1712
  declare class PortalHeader {
1269
1713
  readonly brand: _angular_core.InputSignal<string>;
@@ -1272,8 +1716,23 @@ declare class PortalHeader {
1272
1716
  readonly navItems: _angular_core.InputSignal<PortalHeaderNavItem[]>;
1273
1717
  readonly userInitials: _angular_core.InputSignal<string>;
1274
1718
  readonly sessionClick: _angular_core.OutputEmitterRef<void>;
1719
+ /** Visibilidad del drawer de navegación mobile (bajo los 768px). Se pasa a
1720
+ * `kf-drawer` como `[(open)]` — `PortalHeader` ya no implementa overlay,
1721
+ * `Escape` ni atrapado de foco por su cuenta, eso vive en `Drawer`. */
1722
+ protected readonly mobileMenuOpen: _angular_core.WritableSignal<boolean>;
1723
+ /**
1724
+ * `ElementRef` real del botón de sesión (el avatar en la esquina
1725
+ * derecha), no de todo el header. Pensado para anclar un `kf-dropdown`
1726
+ * externo — sin esto, el único `ElementRef` disponible desde afuera es el
1727
+ * del host de `PortalHeader`, que ocupa el ancho completo de la pantalla
1728
+ * (logo + nav + sesión), y el overlay del CDK termina "pegado" a la
1729
+ * esquina del header entero en vez de al botón que el usuario clickeó.
1730
+ *
1731
+ * <kf-dropdown [(open)]="menuOpen" [anchor]="header().sessionButtonRef()">
1732
+ */
1733
+ readonly sessionButtonRef: _angular_core.Signal<ElementRef<any>>;
1275
1734
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PortalHeader, never>;
1276
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "isAdmin": { "alias": "isAdmin"; "required": false; "isSignal": true; }; "adminActions": { "alias": "adminActions"; "required": false; "isSignal": true; }; "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "userInitials": { "alias": "userInitials"; "required": true; "isSignal": true; }; }, { "sessionClick": "sessionClick"; }, never, never, true, never>;
1735
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "isAdmin": { "alias": "isAdmin"; "required": false; "isSignal": true; }; "adminActions": { "alias": "adminActions"; "required": false; "isSignal": true; }; "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "userInitials": { "alias": "userInitials"; "required": true; "isSignal": true; }; }, { "sessionClick": "sessionClick"; }, never, ["[kf-portal-header-extra]", "[kf-portal-header-footer]"], true, never>;
1277
1736
  }
1278
1737
 
1279
1738
  /**
@@ -1287,7 +1746,12 @@ declare class PortalHeader {
1287
1746
  * invitación + las acciones como un bloque compuesto propio del dominio
1288
1747
  * "taller", no un placeholder de una sola pieza.
1289
1748
  *
1290
- * <kf-reserve-block emptyLabel="No tienes reservas de taller"
1749
+ * `grow` estira el panel de invitación para llenar el espacio vertical
1750
+ * disponible — solo cuando el consumidor comparte fila con una tarjeta más
1751
+ * alta (ej. junto a "Total de vehículos"); en un lugar sin esa presión
1752
+ * (home, debajo del carousel) se deja en su alto natural.
1753
+ *
1754
+ * <kf-reserve-block emptyLabel="No tienes reservas de taller" grow
1291
1755
  * title="¿Necesitas mantención?" description="Tenemos la mejor red de talleres."
1292
1756
  * actionLabel="Agenda tu reserva aquí" actionHref="/taller/agendar">
1293
1757
  * <kf-button actions icon="calendar">Reservar hora</kf-button>
@@ -1300,8 +1764,10 @@ declare class ReserveBlock {
1300
1764
  readonly description: _angular_core.InputSignal<string | undefined>;
1301
1765
  readonly actionLabel: _angular_core.InputSignal<string | undefined>;
1302
1766
  readonly actionHref: _angular_core.InputSignal<string | undefined>;
1767
+ /** La invitación se estira para llenar la tarjeta (ej. junto a "Total de vehículos"). */
1768
+ readonly grow: _angular_core.InputSignalWithTransform<boolean, unknown>;
1303
1769
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ReserveBlock, never>;
1304
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ReserveBlock, "kf-reserve-block", never, { "emptyLabel": { "alias": "emptyLabel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1770
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ReserveBlock, "kf-reserve-block", never, { "emptyLabel": { "alias": "emptyLabel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; "grow": { "alias": "grow"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1305
1771
  }
1306
1772
 
1307
1773
  /**
@@ -1402,16 +1868,30 @@ declare class Select {
1402
1868
  readonly value: _angular_core.InputSignal<string | undefined>;
1403
1869
  readonly placeholder: _angular_core.InputSignal<string>;
1404
1870
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1871
+ /**
1872
+ * Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
1873
+ * se activa solo cuando `options().length` supera `searchThreshold()`. Si
1874
+ * el consumidor define `true`/`false` explícitamente, ese valor manda por
1875
+ * sobre el umbral automático.
1876
+ */
1877
+ readonly searchable: _angular_core.InputSignal<boolean | undefined>;
1878
+ readonly searchThreshold: _angular_core.InputSignal<number>;
1405
1879
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
1406
1880
  private readonly instanceId;
1407
1881
  readonly triggerId: string;
1408
1882
  readonly listboxId: string;
1409
1883
  readonly isOpen: _angular_core.WritableSignal<boolean>;
1884
+ readonly searchQuery: _angular_core.WritableSignal<string>;
1885
+ readonly activeIndex: _angular_core.WritableSignal<number>;
1410
1886
  /** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
1411
1887
  readonly normalizedOptions: _angular_core.Signal<SelectOption[]>;
1412
1888
  readonly selectedOption: _angular_core.Signal<SelectOption | undefined>;
1889
+ readonly showSearch: _angular_core.Signal<boolean>;
1890
+ /** Opciones visibles tras aplicar el filtro de búsqueda (case/acento-insensitive). */
1891
+ readonly filteredOptions: _angular_core.Signal<SelectOption[]>;
1413
1892
  private readonly triggerRef;
1414
1893
  private readonly panelTemplate;
1894
+ private readonly searchInputRef;
1415
1895
  private readonly overlayService;
1416
1896
  private readonly viewContainerRef;
1417
1897
  private readonly destroyRef;
@@ -1426,11 +1906,107 @@ declare class Select {
1426
1906
  onDocumentEscape(): void;
1427
1907
  toggle(): void;
1428
1908
  select(option: SelectOption): void;
1909
+ onSearchInput(event: Event): void;
1910
+ /**
1911
+ * Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp
1912
+ * simple (sin wraparound): llegar al final o al principio de la lista se
1913
+ * queda ahí en vez de dar la vuelta — es el comportamiento más predecible
1914
+ * cuando se combina con un buscador que puede reducir la lista a 1 opción.
1915
+ */
1916
+ moveActive(delta: number): void;
1917
+ selectActive(): void;
1429
1918
  private attach;
1430
1919
  private detach;
1431
1920
  private getOrCreateOverlayRef;
1432
1921
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Select, never>;
1433
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1922
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1923
+ }
1924
+
1925
+ /** Tipo que consume `kf-sidebar-shell.navItems`. `icon` es requerido: en el
1926
+ * sidebar persistente el ícono es parte permanente del diseño. */
1927
+ interface SidebarNavItemData {
1928
+ icon: IconName;
1929
+ label: string;
1930
+ href: string;
1931
+ active?: boolean;
1932
+ }
1933
+ /**
1934
+ * Ítem de navegación de `kf-sidebar-shell` — ícono + label en fila, estado
1935
+ * activo, sin agrupación. Es el equivalente vertical/persistente del `<a>`
1936
+ * que arma `kf-portal-header` para su `<nav>` horizontal.
1937
+ *
1938
+ * `collapsed` se recibe como input explícito (no vía `:host-context`): el
1939
+ * componente no necesita saber cómo funciona el estado de colapso del
1940
+ * shell, solo qué hacer cuando `collapsed` es `true`.
1941
+ *
1942
+ * No sabe nada de routing — emite `href` como cualquier link y deja que el
1943
+ * consumidor intercepte el click para integrarlo con el Router.
1944
+ */
1945
+ declare class SidebarNavItem {
1946
+ readonly icon: _angular_core.InputSignal<IconName>;
1947
+ readonly label: _angular_core.InputSignal<string>;
1948
+ readonly href: _angular_core.InputSignal<string>;
1949
+ readonly active: _angular_core.InputSignalWithTransform<boolean, unknown>;
1950
+ /** Oculta el label y centra el ícono — lo controla `kf-sidebar-shell` según su propio estado de colapso. */
1951
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
1952
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarNavItem, never>;
1953
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarNavItem, "kf-sidebar-nav-item", never, { "icon": { "alias": "icon"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "href": { "alias": "href"; "required": true; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1954
+ }
1955
+
1956
+ /**
1957
+ * Shell de layout con navegación lateral persistente — el complemento
1958
+ * vertical de `kf-portal-header` (navbar horizontal). Pensado para
1959
+ * portales tipo "aplicación interna" (Portal OTD, Servicio Star).
1960
+ *
1961
+ * `navItems` se recibe como array de datos (no como contenido proyectado):
1962
+ * el componente no sabe nada de routing, emite `navItemClick` con el ítem
1963
+ * clickeado y deja que el consumidor decida cómo navegar.
1964
+ *
1965
+ * Slots: `[kf-sidebar-brand]` (logo del rail expandido, visible en desktop
1966
+ * y en el panel mobile — es el MISMO rail), `[kf-sidebar-brand-collapsed]`
1967
+ * (reemplazo opcional cuando el rail está colapsado en desktop),
1968
+ * `[kf-sidebar-brand-mobile]` (logo de la topbar angosta de mobile, fondo
1969
+ * claro) y `[kf-sidebar-footer]` (tarjeta de usuario / acciones al pie).
1970
+ *
1971
+ * Botón de colapso: vive como último ítem de la propia lista de
1972
+ * navegación, no flotando en el borde del rail — mismo criterio de
1973
+ * tap-target grande y predecible que el resto de los ítems.
1974
+ *
1975
+ * Responsive: bajo los 900px el sidebar NO se reemplaza por un panel
1976
+ * distinto — es el MISMO rail el que pasa a `position: fixed` fuera de la
1977
+ * pantalla (`translateX(-100%)`) y se desliza a la vista (`translateX(0)`)
1978
+ * sobre un backdrop cuando `mobileOpen` es `true`. Una topbar angosta con
1979
+ * botón de hamburguesa reemplaza al rail en el flujo del documento
1980
+ * mientras el panel está cerrado.
1981
+ *
1982
+ * A propósito NO reutiliza `kf-drawer` (el panel lateral genérico de la
1983
+ * librería): `kf-drawer` tiene su propio fondo claro fijo y duplicaría el
1984
+ * árbol de navegación en un segundo lugar del DOM. Bug de contraste ya
1985
+ * corregido en el historial: los `kf-sidebar-nav-item`, con texto claro
1986
+ * pensado para el gradiente oscuro, quedaban invisibles sobre el fondo
1987
+ * blanco de `kf-drawer`. Acá hay un solo rail, un solo árbol de nav, que
1988
+ * cambia de posición según el viewport.
1989
+ */
1990
+ declare class SidebarShell {
1991
+ /** Ítems del menú del rail. */
1992
+ readonly navItems: _angular_core.InputSignal<SidebarNavItemData[]>;
1993
+ /** Estado de colapso del rail en desktop, two-way. Sin efecto bajo los 900px (ver estilos). */
1994
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
1995
+ /** Visibilidad del rail deslizado como panel en mobile, two-way. */
1996
+ readonly mobileOpen: _angular_core.ModelSignal<boolean>;
1997
+ /** Se emite al clickear un ítem — el consumidor decide cómo navegar (ej. `router.navigateByUrl`). */
1998
+ readonly navItemClick: _angular_core.OutputEmitterRef<SidebarNavItemData>;
1999
+ /** `true` bajo los 900px — sin esto, un rail dejado colapsado en desktop
2000
+ * seguía ocultando el label de cada ítem al abrir el panel en mobile. */
2001
+ private readonly isMobileViewport;
2002
+ /** Estado de colapso que efectivamente se le pasa a cada `kf-sidebar-nav-item`
2003
+ * y al propio botón de colapso. */
2004
+ protected readonly itemsCollapsed: _angular_core.Signal<boolean>;
2005
+ /** Cierra el panel mobile con Escape — mismo criterio que `Dropdown`/`Modal`/`Drawer`. */
2006
+ protected onEscape(): void;
2007
+ protected onNavItemClick(event: Event, item: SidebarNavItemData): void;
2008
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarShell, never>;
2009
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarShell, "kf-sidebar-shell", never, { "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "mobileOpen": { "alias": "mobileOpen"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "mobileOpen": "mobileOpenChange"; "navItemClick": "navItemClick"; }, never, ["[kf-sidebar-brand]", "[kf-sidebar-brand-collapsed]", "[kf-sidebar-footer]", "[kf-sidebar-brand-mobile]"], true, never>;
1434
2010
  }
1435
2011
 
1436
2012
  /** Forma del placeholder. */
@@ -1522,7 +2098,7 @@ declare class StatTile {
1522
2098
  * <kf-status-pill tone="error" dot>Fuera de servicio</kf-status-pill>
1523
2099
  */
1524
2100
  declare class StatusPill {
1525
- readonly tone: _angular_core.InputSignal<"error" | "ok" | "warn" | "blue">;
2101
+ readonly tone: _angular_core.InputSignal<"ok" | "warn" | "error" | "blue">;
1526
2102
  readonly dot: _angular_core.InputSignalWithTransform<boolean, unknown>;
1527
2103
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatusPill, never>;
1528
2104
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatusPill, "kf-status-pill", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
@@ -1561,44 +2137,6 @@ declare class Tabs {
1561
2137
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Tabs, "kf-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1562
2138
  }
1563
2139
 
1564
- type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
1565
- type TextVariant = 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption';
1566
- type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
1567
- /**
1568
- * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
1569
- * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
1570
- * ve). Antes de este componente, cada pantalla que consume la librería
1571
- * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
1572
- * (título de página, subtítulo, texto secundario) — auditoría sobre
1573
- * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
1574
- * home sin ningún paso de escala en común. `variant` resuelve eso contra
1575
- * la escala de `tokens/base.css` (`--kf-font-size-*`).
1576
- *
1577
- * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
1578
- * escape hatch de color libre. Si un caso necesita un color que `tone` no
1579
- * cubre, es una discusión de design system, no una prop nueva acá.
1580
- *
1581
- * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
1582
- * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
1583
- * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
1584
- */
1585
- declare class Text {
1586
- /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
1587
- readonly as: _angular_core.InputSignal<TextAs>;
1588
- readonly variant: _angular_core.InputSignal<TextVariant>;
1589
- readonly tone: _angular_core.InputSignal<TextTone>;
1590
- /**
1591
- * Copia de `as` para uso dentro del template: `as` es palabra reservada
1592
- * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
1593
- * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
1594
- * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
1595
- */
1596
- protected readonly tag: _angular_core.Signal<TextAs>;
1597
- protected hostClass(): string;
1598
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
1599
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Text, "kf-text", never, { "as": { "alias": "as"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1600
- }
1601
-
1602
2140
  /**
1603
2141
  * Visual tone of a toast. Maps to the state tokens in
1604
2142
  * projects/ui/src/tokens/base.css. `loading` has no auto-dismiss and shows an
@@ -1696,6 +2234,34 @@ declare class ToastHost {
1696
2234
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ToastHost, "kf-toast-host", never, {}, {}, never, never, true, never>;
1697
2235
  }
1698
2236
 
2237
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
2238
+ /**
2239
+ * Tooltip de hover/focus — reemplaza el atributo `title` nativo del
2240
+ * navegador (lento en aparecer, sin estilo, inconsistente entre
2241
+ * navegadores/SO) por un panel propio, mismo mecanismo de
2242
+ * `@angular/cdk/overlay` que ya usa `Dropdown`.
2243
+ *
2244
+ * Pensado para el caso "ícono sin label visible necesita comunicar qué
2245
+ * es" (ej. `kf-sidebar-nav-item` colapsado). No es two-way: a diferencia
2246
+ * de `Dropdown`, esto se abre/cierra solo con hover/focus, nunca por click.
2247
+ *
2248
+ * <a [kfTooltip]="label()" kfTooltipPosition="right">…</a>
2249
+ */
2250
+ declare class TooltipDirective {
2251
+ text: string;
2252
+ kfTooltipPosition: TooltipPosition;
2253
+ private readonly host;
2254
+ private readonly overlayService;
2255
+ private readonly viewContainerRef;
2256
+ private readonly injector;
2257
+ private overlayRef;
2258
+ protected show(): void;
2259
+ protected hide(): void;
2260
+ private positionsFor;
2261
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TooltipDirective, never>;
2262
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TooltipDirective, "[kfTooltip]", never, { "text": { "alias": "kfTooltip"; "required": false; }; "kfTooltipPosition": { "alias": "kfTooltipPosition"; "required": false; }; }, {}, never, never, true, never>;
2263
+ }
2264
+
1699
2265
  /**
1700
2266
  * Estado puntual mostrado junto a la identidad del vehículo (ej. "En ruta",
1701
2267
  * "Mantenimiento", "Fuera de servicio"). `tone` define el color semántico:
@@ -1754,5 +2320,5 @@ declare class ViewToggle {
1754
2320
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ViewToggle, "kf-view-toggle", never, { "view": { "alias": "view"; "required": true; "isSignal": true; }; }, { "viewChange": "viewChange"; }, never, never, true, never>;
1755
2321
  }
1756
2322
 
1757
- export { ActionTile, AdminBadge, Alert, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, VehicleHeader, ViewToggle };
1758
- export type { AlertTone, BreadcrumbItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, DataTableCardRole, DataTableColumn, DataTableView, DividerOrientation, DropdownItemTone, FolderTabOption, IconName, IconSize, LinkVariant, MultiSelectOption, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, SegmentedOption, SkeletonVariant, SpinnerSize, TabOption, TextAs, TextTone, TextVariant, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, VehicleHeaderStatus, ViewToggleOption };
2323
+ export { ActionTile, AdminBadge, Alert, AuthLayout, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Drawer, DrawerLogout, DrawerSession, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, FolderTabsPanel, Footer, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, SidebarNavItem, SidebarShell, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, TooltipDirective, VehicleHeader, ViewToggle };
2324
+ export type { AlertTone, BreadcrumbItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, DataTableCardRole, DataTableColumn, DataTableView, DividerOrientation, DrawerSide, DropdownItemTone, DropdownPanelVariant, FolderTabOption, FooterLink, IconName, IconSize, LinkVariant, MultiSelectOption, PageHeaderTitleSize, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, SegmentedOption, SidebarNavItemData, SkeletonVariant, SpinnerSize, TabOption, TextAs, TextTone, TextVariant, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, TooltipPosition, VehicleHeaderStatus, ViewToggleOption };