@kaufmann-lab/ui 0.1.9 → 0.1.13

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.
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { AfterContentInit, TemplateRef, ElementRef, AfterViewInit } from '@angular/core';
2
+ import { AfterContentInit, TemplateRef, Signal, ElementRef, AfterViewInit, InjectionToken, Provider } from '@angular/core';
3
3
  import { ControlValueAccessor } from '@angular/forms';
4
4
 
5
5
  /**
@@ -13,12 +13,36 @@ import { ControlValueAccessor } from '@angular/forms';
13
13
  * comparten `Icon`, `icons/index.ts` (registro) y cualquier componente que
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
+ *
17
+ * `minus`, `maximize`, `minimize`, `crosshair` y `navigation` son una
18
+ * extensión propia (mismo criterio que `eye-off`): página 5 del PDF no las
19
+ * incluye, pero los controles flotantes de `kf-map` (zoom, fullscreen,
20
+ * recenter, follow) los necesitan y ninguno tiene un glyph equivalente ya
21
+ * en el set — antes de agregarlos se confirmó que no había forma de resolver
22
+ * esas 5 acciones con un ícono ya existente.
23
+ *
24
+ * `message-circle`, `monitor`, `zap` y `pin` son otra extensión propia, mismo
25
+ * criterio: el selector de canal de entrada de ticket (Llamada/WhatsApp/
26
+ * Portal/API·agente) de `apps/operations` necesita "burbuja de chat",
27
+ * "pantalla" y "rayo" y ninguno tiene glyph equivalente en el set (`phone`
28
+ * ya cubre "Llamada", no hizo falta agregarlo). `pin` es la acción de
29
+ * fijar/soltar un subheader — se confirmó que no alcanza con reusar
30
+ * `map-pin`, que en este mismo dominio representa ubicación geográfica real
31
+ * de vehículos; reusarlo para "fijar" generaría ambigüedad con los markers
32
+ * de mapa.
16
33
  */
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';
34
+ 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' | 'crosshair' | 'download' | 'edit' | 'external-link' | 'eye' | 'eye-off' | 'file' | 'filter' | 'gauge' | 'gift' | 'grid' | 'history' | 'home' | 'inbox' | 'layers' | 'list' | 'log-out' | 'map' | 'map-pin' | 'maximize' | 'menu' | 'message-circle' | 'mic' | 'mic-off' | 'minimize' | 'minus' | 'monitor' | 'more-vertical' | 'navigation' | 'pause' | 'phone' | 'pin' | 'play' | 'plus' | 'search' | 'settings' | 'shield-check' | 'sort' | 'star' | 'ticket' | 'tool' | 'trash' | 'truck' | 'upload-cloud' | 'user' | 'user-search' | 'video' | 'x' | 'zap';
18
35
 
19
36
  type IconSize = 'sm' | 'md' | 'lg';
37
+ /** Ángulos de rotación soportados por `rotate` — cubre los casos reales del
38
+ * design system (un chevron que apunta en 4 direcciones posibles según
39
+ * estado, ej. orden ascendente/descendente o un panel abierto/cerrado);
40
+ * no un `number` libre, para no habilitar ángulos arbitrarios sin caso de
41
+ * uso ni animación pensada. */
42
+ type IconRotation = 0 | 90 | 180 | 270;
20
43
  /**
21
- * Ícono de trazo del design system — 60 glyphs (59 de la página 5 + `eye-off`), grilla 24×24,
44
+ * Ícono de trazo del design system — 71 glyphs (59 de la página 5 + extensiones
45
+ * propias documentadas en `icon-name.ts`), grilla 24×24,
22
46
  * grosor de trazo 1.9px. Referencia un sprite SVG embebido (ver
23
47
  * `icon-sprite.ts`) vía `<use>`, así que no hay peso de red adicional por
24
48
  * cada instancia: el sprite se inyecta al DOM una única vez, la primera
@@ -29,17 +53,36 @@ type IconSize = 'sm' | 'md' | 'lg';
29
53
  * sigue siempre `currentColor` — se hereda del texto del elemento
30
54
  * contenedor, nunca se fija en el propio ícono.
31
55
  *
56
+ * `rotate` gira el mismo glyph en vez de requerir un ícono aparte por
57
+ * dirección — útil para un chevron que representa un estado con más de una
58
+ * orientación (ej. orden ascendente/descendente de una columna, un panel
59
+ * abierto/cerrado). La rotación transiciona con la misma duración que el
60
+ * resto de las transiciones cortas del design system.
61
+ *
32
62
  * <kf-icon name="alert-triangle" size="sm" />
63
+ * <kf-icon name="chevron-down" [rotate]="isOpen() ? 180 : 0" />
33
64
  * <kf-button icon="calendar">Reservar hora</kf-button> <!-- ver Button -->
34
65
  */
35
66
  declare class Icon {
36
67
  readonly name: _angular_core.InputSignal<IconName>;
37
68
  readonly size: _angular_core.InputSignal<IconSize>;
69
+ readonly rotate: _angular_core.InputSignal<IconRotation>;
70
+ /**
71
+ * Rellena el glyph (`fill: currentColor`) en vez de dejarlo solo de trazo.
72
+ * Pensado para un estado activo/marcado de un ícono usado como toggle (ej.
73
+ * "fijar" un panel) — un cambio de COLOR únicamente puede no distinguirse
74
+ * lo suficiente en marcas cuyo `--kf-color-secondary` queda cerca del color
75
+ * de texto por default; rellenar el área cambia más píxeles y se percibe
76
+ * con más contraste aunque el tono sea similar. No es el default del
77
+ * design system (sigue siendo de trazo, página 6 del PDF) — es opt-in por
78
+ * componente para ese caso puntual.
79
+ */
80
+ readonly filled: _angular_core.InputSignalWithTransform<boolean, unknown>;
38
81
  private readonly document;
39
82
  protected readonly pixelSize: _angular_core.Signal<string>;
40
83
  constructor();
41
84
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Icon, never>;
42
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Icon, "kf-icon", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
85
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Icon, "kf-icon", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "rotate": { "alias": "rotate"; "required": false; "isSignal": true; }; "filled": { "alias": "filled"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
43
86
  }
44
87
 
45
88
  /**
@@ -81,47 +124,73 @@ declare class AdminBadge {
81
124
  }
82
125
 
83
126
  /**
84
- * Wraps a native `<button>`, so keyboard handling, focus and form submission
85
- * keep working. The cost of the wrapper is forwarding `disabled` and `type` by
86
- * hand: as attributes on a custom element they have no native effect.
127
+ * Envuelve un `<button>` nativo: mantiene teclado, foco y submit de formulario.
128
+ * Como es un elemento personalizado, ningún atributo ARIA puesto en el host
129
+ * llega al `<button>` interno — por eso `disabled`, `type`, `ariaLabel`,
130
+ * `ariaExpanded` y `ariaPressed` se reenvían a mano.
87
131
  *
88
- * Five variants (page 9 of the design system): `primary` (main action, blue),
89
- * `secondary` (service actions, orange), `ghost` (low-emphasis, no fill),
90
- * `outline` (medium-emphasis, bordered) and `danger` (destructive
91
- * confirmations — red, see the Confirmation Modal pattern on page 30). Three
92
- * sizes (`sm`/`md`/`lg`). `icon` renders a leading glyph (`kf-icon`) before
93
- * the projected label; `block` stretches the button to the full width of its
94
- * container.
132
+ * Seis variantes: `primary`, `secondary`, `ghost`, `outline`, `danger`,
133
+ * `surface`. Tres tamaños (`sm`/`md`/`lg`). `icon` agrega un glifo antes del
134
+ * label proyectado; `block` estira el botón a todo el ancho de su contenedor.
95
135
  *
96
- * `(click)` is not forwarded — it bubbles from the inner button to the host.
136
+ * `surface` es la variante neutra pensada para flotar SOBRE contenido
137
+ * variable (ej. los controles de `kf-map` sobre un mapa que cambia de tema:
138
+ * calles, satélite, oscuro) — fondo sólido de `--kf-color-surface` con
139
+ * sombra, en vez de depender de contraste contra lo que haya debajo (que
140
+ * `ghost`/`outline`, transparentes, no pueden garantizar ahí).
97
141
  *
98
- * `loading` replaces the projected content with `kf-spinner` and forces
99
- * `disabled` — for the common "Ingresando…"/"Enviando…" submit pattern,
100
- * which before this prop existed as plain text swaps with no visual
101
- * indicator (see `web-portal-latam-frontend`'s login submit buttons).
142
+ * `shape="circle"` vuelve el botón cuadrado con esquina `pill` (mismo ancho
143
+ * y alto, sin padding horizontal extra) — pensado para uso solo-ícono
144
+ * (`icon` sin contenido proyectado): combinarlo con un label visible
145
+ * descentra el ícono, no es el caso de uso.
102
146
  *
103
- * <kf-button (click)="save()">Save</kf-button>
104
- * <kf-button variant="ghost" disabled>Cancel</kf-button>
105
- * <kf-button variant="danger" (click)="remove()">Delete</kf-button>
106
- * <kf-button size="sm" icon="calendar" block>Reservar hora</kf-button>
147
+ * `(click)` no se reenvía: burbujea del botón interno al host solo.
148
+ *
149
+ * `loading` reemplaza el contenido proyectado por `kf-spinner` y fuerza
150
+ * `disabled`.
151
+ *
152
+ * Botón solo-ícono (sin label visible): pasar `ariaLabel` en vez de dejar el
153
+ * botón sin nombre accesible.
154
+ *
155
+ * <kf-button (click)="save()">Guardar</kf-button>
156
+ * <kf-button variant="danger" (click)="remove()">Eliminar</kf-button>
157
+ * <kf-button size="sm" icon="external-link" ariaLabel="Expandir" (click)="expand()" />
107
158
  * <kf-button type="submit" [loading]="submitting()">Ingresar</kf-button>
159
+ * <kf-button variant="surface" shape="circle" icon="plus" ariaLabel="Acercar" [ariaPressed]="false" />
108
160
  */
109
161
  declare class Button {
110
- readonly variant: _angular_core.InputSignal<"primary" | "secondary" | "ghost" | "outline" | "danger">;
162
+ readonly variant: _angular_core.InputSignal<"primary" | "secondary" | "ghost" | "outline" | "danger" | "surface">;
111
163
  readonly size: _angular_core.InputSignal<"sm" | "md" | "lg">;
164
+ readonly shape: _angular_core.InputSignal<"rect" | "circle">;
112
165
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
113
- /** Matches the native default: submits when inside a `<form>`. */
166
+ /** Igual al default nativo: hace submit dentro de un `<form>`. */
114
167
  readonly type: _angular_core.InputSignal<"button" | "submit" | "reset">;
115
- /** Leading icon, rendered via `kf-icon` before the projected label. */
168
+ /** Ícono líder, renderizado con `kf-icon` antes del label proyectado. */
116
169
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
117
- /** Stretches the button to the full width of its container. */
170
+ /** Estira el botón a todo el ancho de su contenedor. */
118
171
  readonly block: _angular_core.InputSignalWithTransform<boolean, unknown>;
119
- /** Replaces the projected content with `kf-spinner` and forces `disabled`. */
172
+ /** Reemplaza el contenido proyectado por `kf-spinner` y fuerza `disabled`. */
120
173
  readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
121
- /** `ghost`/`outline` have no solid background — the default spinner ring already reads fine there. */
174
+ /** Nombre accesible del botón interno — obligatorio quien no proyecta
175
+ * ningún texto visible (botón solo-ícono), porque el host no lo reenvía. */
176
+ readonly ariaLabel: _angular_core.InputSignal<string | undefined>;
177
+ /** Estado expandido/colapsado para un botón toggle — mismo motivo que
178
+ * `ariaLabel`, el host no reenvía atributos ARIA al `<button>` interno. */
179
+ readonly ariaExpanded: _angular_core.InputSignal<boolean | undefined>;
180
+ /** Estado presionado/no presionado para un botón toggle real (ej. "seguir
181
+ * vehículo" activo/inactivo) — distinto semánticamente de `ariaExpanded`
182
+ * (que es para mostrar/ocultar un panel), mismo motivo de reenvío manual.
183
+ * Con `icon`, presionado (`true`) rellena el glyph (`kf-icon[filled]`) en
184
+ * vez de solo cambiar de color — un toggle solo-ícono necesita esa señal
185
+ * más fuerte para leerse activo, y así ningún consumidor tiene que
186
+ * acordarse de pedirlo aparte. */
187
+ readonly ariaPressed: _angular_core.InputSignal<boolean | undefined>;
188
+ /** `ghost`/`outline`/`surface` no tienen fondo oscuro sólido — el anillo
189
+ * del spinner por defecto ya se lee bien ahí sin variante propia
190
+ * (`surface` es fondo claro con texto oscuro, mismo motivo que `outline`). */
122
191
  protected readonly hasSolidFill: _angular_core.Signal<boolean>;
123
192
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Button, never>;
124
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Button, "kf-button", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "block": { "alias": "block"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
193
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Button, "kf-button", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "block": { "alias": "block"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaExpanded": { "alias": "ariaExpanded"; "required": false; "isSignal": true; }; "ariaPressed": { "alias": "ariaPressed"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
125
194
  }
126
195
 
127
196
  type AlertTone = 'info' | 'ok' | 'warn' | 'error';
@@ -134,10 +203,6 @@ type AlertTone = 'info' | 'ok' | 'warn' | 'error';
134
203
  * deja de renderizar. El consumidor puede volver a abrirlo poniéndolo en
135
204
  * `true` (ej. cuando la validación falla otra vez).
136
205
  *
137
- * ponytail: los glifos van inline en vez de venir de un componente Icon —
138
- * son 4 paths fijos, uno por tono, y no existe todavía `kf-icon` en la
139
- * librería. Cuando exista, se reemplaza este `@switch` por `<kf-icon>`.
140
- *
141
206
  * <kf-alert tone="warn" title="SOAT por vencer">Vence el 15/08.</kf-alert>
142
207
  * <kf-alert tone="ok" [(open)]="saved" dismissible>Pago registrado.</kf-alert>
143
208
  */
@@ -153,10 +218,139 @@ declare class Alert {
153
218
  * Los avisos informativos se anuncian sin cortar lo que se esté leyendo.
154
219
  */
155
220
  protected readonly ariaRole: _angular_core.Signal<"alert" | "status">;
221
+ protected readonly toneIcon: _angular_core.Signal<IconName>;
156
222
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Alert, never>;
157
223
  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
224
  }
159
225
 
226
+ type AlertRowTone = 'warn' | 'error';
227
+ /**
228
+ * Fila clickeable de una lista de pendientes/alertas: ícono circular con
229
+ * tono semántico, texto de una línea, chevron al final. Pensada para
230
+ * agrupar varias dentro de un `kf-card` (ej. "Tus pendientes" del
231
+ * dashboard) — no incluye el contenedor en sí.
232
+ *
233
+ * <kf-alert-row icon="file" tone="warn" text="2 cotizaciones esperan tu aprobación" (rowClick)="onNavigate('cotizaciones')" />
234
+ */
235
+ declare class AlertRow {
236
+ readonly icon: _angular_core.InputSignal<IconName>;
237
+ readonly tone: _angular_core.InputSignal<AlertRowTone>;
238
+ readonly text: _angular_core.InputSignal<string>;
239
+ readonly rowClick: _angular_core.OutputEmitterRef<void>;
240
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AlertRow, never>;
241
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AlertRow, "kf-alert-row", never, { "icon": { "alias": "icon"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "text": { "alias": "text"; "required": true; "isSignal": true; }; }, { "rowClick": "rowClick"; }, never, never, true, never>;
242
+ }
243
+
244
+ /**
245
+ * Marca el contenido que va en el slot de SSO de `kf-auth-layout` (típico:
246
+ * un `kf-sso-button`, o varios si el ecosistema suma más de un proveedor).
247
+ * `AuthLayout` la detecta vía `contentChild` para decidir si renderiza el
248
+ * separador "o" entre el bloque de SSO y el formulario tradicional
249
+ * proyectado en el slot por defecto — sin este marcador, `AuthLayout`
250
+ * tendría que adivinar si hubo proyección leyendo el DOM a mano.
251
+ *
252
+ * <kf-auth-layout brand="Mi Portal">
253
+ * <div kfAuthLayoutSso>
254
+ * <kf-sso-button provider="microsoft" (click)="loginWithMicrosoft()" />
255
+ * </div>
256
+ * <form ...>...</form>
257
+ * </kf-auth-layout>
258
+ */
259
+ declare class AuthLayoutSso {
260
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthLayoutSso, never>;
261
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AuthLayoutSso, "[kfAuthLayoutSso]", never, {}, {}, never, never, true, never>;
262
+ }
263
+
264
+ type AuthLayoutBackground = 'plain' | 'gradient' | 'pattern';
265
+ /**
266
+ * Layout a pantalla completa para pantallas de autenticación (login,
267
+ * recuperar contraseña, confirmación de envío) — panel de marca a la
268
+ * izquierda, contenido libre a la derecha en desktop; en mobile el panel de
269
+ * marca se oculta y un logo compacto queda visible arriba del formulario.
270
+ *
271
+ * Resuelve solo el "cascarón": cada portal arma su propio `<form>` con los
272
+ * componentes de la librería y lo proyecta en el slot por defecto.
273
+ *
274
+ * <kf-auth-layout
275
+ * brand="Mi Portal"
276
+ * brandLogoSrc="images/mi-portal-logo-white.png"
277
+ * brandLogoMobileSrc="images/mi-portal-logo-black.png"
278
+ * brandTagline="Frase corta de marca, propia de cada portal."
279
+ * >
280
+ * <form (ngSubmit)="onSubmit()">
281
+ * <kf-input label="Correo" type="email" />
282
+ * <kf-input label="Contraseña" type="password" toggleable />
283
+ * <kf-button type="submit">Ingresar</kf-button>
284
+ * </form>
285
+ * </kf-auth-layout>
286
+ *
287
+ * `backgroundVariant` controla la superficie del panel derecho:
288
+ * `'plain'` (fondo liso, comportamiento histórico), `'gradient'` (default —
289
+ * degradé sutil derivado de `--kf-color-primary`/`--kf-color-surface` vía
290
+ * `color-mix()`, ningún color hardcodeado) o `'pattern'` (el mismo degradé
291
+ * más una textura de puntos, también resuelta con tokens). Los tres se
292
+ * resuelven contra los tokens de tema ACTIVOS — cambiar de tema
293
+ * (`andes-maq`, `divemotor-star`, `kaufmann-ar`, futuros) no requiere tocar
294
+ * este componente ni pasarle un color a mano.
295
+ *
296
+ * `card`, en `false` por defecto (comportamiento histórico: el contenido
297
+ * proyectado flota sin superficie propia), envuelve el slot por defecto con
298
+ * la misma superficie de `kf-card` (fondo, borde, sombra, radio) para que
299
+ * el formulario deje de verse suelto sobre el fondo del panel.
300
+ *
301
+ * `title`/`subtitle` renderizan un encabezado de bienvenida DENTRO de la
302
+ * card, antes que cualquier otro contenido (jerarquía ya resuelta:
303
+ * `title-lg` + `body-muted`, ver `Text.ts`) — así ningún consumidor tiene
304
+ * que adivinar la variante de `kf-text` correcta ni terminar con un
305
+ * "Iniciar sesión" repetido y suelto dentro de su propio `<form>`. Sin
306
+ * `title`, no se renderiza ningún encabezado (comportamiento histórico).
307
+ *
308
+ * El slot `[kfAuthLayoutSso]` (ver `AuthLayoutSso`) es para el patrón
309
+ * "SSO primero, credenciales como alternativa secundaria" — típicamente un
310
+ * `kf-sso-button`. Cuando hay contenido ahí, `AuthLayout` resuelve el
311
+ * separador "o" automáticamente entre ese bloque y el contenido por
312
+ * defecto; sin ese slot, nada cambia frente al comportamiento actual.
313
+ *
314
+ * <kf-auth-layout
315
+ * brand="Mi Portal"
316
+ * backgroundVariant="pattern"
317
+ * card
318
+ * title="Iniciar sesión"
319
+ * subtitle="Usa tu cuenta corporativa o ingresa con contraseña"
320
+ * >
321
+ * <div kfAuthLayoutSso>
322
+ * <kf-sso-button provider="microsoft" (click)="loginWithMicrosoft()" />
323
+ * </div>
324
+ * <form (ngSubmit)="onSubmit()">
325
+ * <kf-input label="Correo" type="email" />
326
+ * <kf-input label="Contraseña" type="password" toggleable />
327
+ * <kf-button type="submit">Ingresar</kf-button>
328
+ * </form>
329
+ * </kf-auth-layout>
330
+ */
331
+ declare class AuthLayout {
332
+ /** Nombre de marca/portal — wordmark de respaldo si no hay logo, y `alt` de ambos logos. */
333
+ readonly brand: _angular_core.InputSignal<string>;
334
+ /** Logo en blanco para el panel oscuro de desktop. Sin esto, se muestra `brand()` como wordmark. */
335
+ readonly brandLogoSrc: _angular_core.InputSignal<string | undefined>;
336
+ /** Frase corta bajo el logo. */
337
+ readonly brandTagline: _angular_core.InputSignal<string | undefined>;
338
+ /** Logo compacto para mobile, sobre fondo claro. Sin esto, reutiliza `brandLogoSrc`. */
339
+ readonly brandLogoMobileSrc: _angular_core.InputSignal<string | undefined>;
340
+ /** Superficie del panel derecho. Default `'gradient'`; `'plain'` preserva el fondo liso histórico. */
341
+ readonly backgroundVariant: _angular_core.InputSignal<AuthLayoutBackground>;
342
+ /** Envuelve el contenido proyectado con la superficie de `kf-card` (fondo, borde, sombra). Default `false` — preserva el comportamiento histórico. */
343
+ readonly card: _angular_core.InputSignalWithTransform<boolean, unknown>;
344
+ /** Título del encabezado dentro de la card (`title-lg`). Sin este input, no se renderiza ningún encabezado. */
345
+ readonly title: _angular_core.InputSignal<string | undefined>;
346
+ /** Subtítulo del encabezado (`body-muted`), debajo de `title`. Sin efecto si `title` no está presente. */
347
+ readonly subtitle: _angular_core.InputSignal<string | undefined>;
348
+ /** Presencia de contenido en el slot `[kfAuthLayoutSso]` — controla si se renderiza el separador "o". */
349
+ protected readonly ssoContent: _angular_core.Signal<AuthLayoutSso | undefined>;
350
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthLayout, never>;
351
+ 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; }; "backgroundVariant": { "alias": "backgroundVariant"; "required": false; "isSignal": true; }; "card": { "alias": "card"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; }, {}, ["ssoContent"], ["[kfAuthLayoutSso]", "*"], true, never>;
352
+ }
353
+
160
354
  /**
161
355
  * Avatar de usuario: muestra una foto (`src`) o, si no hay foto, las
162
356
  * iniciales (`initials`) sobre un fondo con gradiente de marca. `round`
@@ -169,10 +363,6 @@ declare class Alert {
169
363
  * <kf-avatar initials="JD" />
170
364
  * <kf-avatar src="https://…/foto.jpg" [round]="false" />
171
365
  * <kf-avatar initials="TS" pill />
172
- *
173
- * Pendiente: si en el futuro se agrega un ícono de fallback (persona
174
- * genérica) para cuando no hay ni `src` ni `initials`, depende del
175
- * componente Icon / sprite SVG que todavía no existe en esta librería.
176
366
  */
177
367
  declare class Avatar {
178
368
  readonly initials: _angular_core.InputSignal<string | undefined>;
@@ -187,29 +377,24 @@ declare class Avatar {
187
377
  }
188
378
 
189
379
  /**
190
- * Pill chica de fondo suave (soft) para un conteo, tag o estado liviano
191
- * puntual (no un proceso en vivo — para eso existe `kf-status-pill`, que usa
192
- * borde en vez de relleno). El color de fondo/texto/punto depende de `tone`.
193
- *
194
- * El PDF (página 10) fija seis tonos con fondo soft y texto del tono fuerte
195
- * — nunca fondo saturado con texto blanco, que es más apto para un botón
196
- * que para una etiqueta de lectura rápida en una lista densa.
197
- *
198
- * <kf-badge tone="ok" dot>Al día</kf-badge>
199
- * <kf-badge tone="error">3</kf-badge>
200
- *
201
- * Mapeo de `tone` a los seis conceptos de la página 10 y sus tokens
202
- * semánticos en projects/ui/src/tokens/base.css:
203
- * neutral -> Neutral (border/text-muted), info -> Información (info-soft/-strong),
204
- * ok -> Activo (success-soft/-strong), warn -> Pendiente (warning-soft/-strong),
205
- * error -> Vencido (error-soft/-strong), service -> Servicio (secondary-soft,
206
- * no existe un `-strong` para secondary todavía — usa el color base).
380
+ * Pill chica de fondo suave para un conteo, tag o estado liviano puntual —
381
+ * no un proceso en vivo (para eso, `kf-status-pill`, con borde en vez de
382
+ * relleno). Fondo soft y texto del tono fuerte según `tone`; `service` usa
383
+ * el color base porque `secondary` todavía no tiene variante `-strong`.
384
+ * `variant` es un eje ortogonal a `tone` (mismo patrón que `kf-button`):
385
+ * `soft` (default) es el fondo de color sólido de siempre; `glass` es
386
+ * translúcido con desenfoque, pensado para un badge sobre una superficie
387
+ * de marca (ej. un header navy), no para un banner suelto. En `glass` el
388
+ * punto (`dot`) usa el color BASE de cada tono, no `-strong` — ese último
389
+ * está pensado para texto legible sobre el fondo pastel de `soft` y se ve
390
+ * apagado sobre una superficie translúcida/oscura.
207
391
  */
208
392
  declare class Badge {
209
- readonly tone: _angular_core.InputSignal<"info" | "ok" | "warn" | "error" | "neutral" | "service">;
393
+ readonly tone: _angular_core.InputSignal<"neutral" | "ok" | "warn" | "error" | "info" | "service">;
394
+ readonly variant: _angular_core.InputSignal<"soft" | "glass">;
210
395
  readonly dot: _angular_core.InputSignalWithTransform<boolean, unknown>;
211
396
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Badge, never>;
212
- 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>;
397
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Badge, "kf-badge", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
213
398
  }
214
399
 
215
400
  interface BreadcrumbItem {
@@ -235,10 +420,9 @@ declare class Breadcrumb {
235
420
  readonly items: _angular_core.InputSignal<BreadcrumbItem[]>;
236
421
  /**
237
422
  * Click en un nivel con `href`. Incluye el `MouseEvent` para que un
238
- * consumidor con router pueda cortar la navegación nativa.
239
- *
240
- * ponytail: no dependemos de `@angular/router` acá — sería un peer dep
241
- * nuevo en la librería para lo que el consumidor resuelve en una línea.
423
+ * consumidor con router pueda cortar la navegación nativa. No se depende
424
+ * de `@angular/router` acá — sería un peer dep nuevo en la librería para
425
+ * lo que el consumidor resuelve en una línea.
242
426
  */
243
427
  readonly itemClick: _angular_core.OutputEmitterRef<{
244
428
  item: BreadcrumbItem;
@@ -250,6 +434,64 @@ declare class Breadcrumb {
250
434
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Breadcrumb, "kf-breadcrumb", never, { "items": { "alias": "items"; "required": true; "isSignal": true; }; }, { "itemClick": "itemClick"; }, never, never, true, never>;
251
435
  }
252
436
 
437
+ /**
438
+ * Una categoría de la barra — el componente no sabe qué significa "pagada"
439
+ * o "vencida", solo dibuja proporciones: `count` decide el ancho relativo
440
+ * del segmento (`flex-grow`) y de la fila de leyenda que lo acompaña.
441
+ * `color` es cualquier valor CSS válido (pensado para tokens `var(--kf-*)`,
442
+ * nunca hex suelto desde el consumidor).
443
+ */
444
+ interface BucketProgressItem {
445
+ key: string;
446
+ label: string;
447
+ count: number;
448
+ color: string;
449
+ }
450
+ /**
451
+ * Barra de progreso segmentada en categorías proporcionales — generaliza
452
+ * el resumen de "Progreso del plan de pagos" de Pagos y Facturas de OTD
453
+ * (cuotas pagadas/vencidas/próximas/pendientes) sin acoplarse a esas 4
454
+ * categorías puntuales: el consumidor pasa sus propios `items` con
455
+ * `{key, label, count, color}`.
456
+ *
457
+ * Los segmentos son proporcionales por CANTIDAD (`flex-grow: count`), no
458
+ * uno por unidad — con 36 cuotas y 6 pagadas, la barra dibuja 2 anchos
459
+ * distintos (pagado ~17% / resto ~83%), nunca 36 rectángulos idénticos
460
+ * (hallazgo real de UX: con volumen alto, un segmento por unidad se vuelve
461
+ * ilegible). Los `items` con `count: 0` no se dibujan (ni segmento ni
462
+ * fila de leyenda) — no tiene sentido mostrar una categoría vacía.
463
+ *
464
+ * La leyenda debajo reproduce el mismo orden y color que la barra, para
465
+ * que una categoría se pueda mapear a su cantidad de un vistazo (pedido
466
+ * explícito de UX: "posicionar correctamente los labels").
467
+ *
468
+ * <kf-bucket-progress
469
+ * title="Progreso del plan de pagos"
470
+ * [summary]="'6 de 30 cuotas pagadas'"
471
+ * [items]="[
472
+ * { key: 'paid', label: 'pagadas', count: 6, color: 'var(--kf-color-success)' },
473
+ * { key: 'overdue', label: 'vencidas', count: 2, color: 'var(--kf-color-error)' },
474
+ * { key: 'upcoming', label: 'próxima', count: 1, color: 'var(--kf-color-warning)' },
475
+ * { key: 'pending', label: 'pendientes', count: 21, color: 'var(--kf-color-gray-200)' },
476
+ * ]"
477
+ * />
478
+ */
479
+ declare class BucketProgress {
480
+ readonly title: _angular_core.InputSignal<string>;
481
+ /** Texto libre a la derecha del título (ej. "6 de 30 cuotas pagadas") —
482
+ * el componente no sabe calcular ese resumen, lo recibe hecho. */
483
+ readonly summary: _angular_core.InputSignal<string>;
484
+ readonly items: _angular_core.InputSignal<BucketProgressItem[]>;
485
+ protected readonly visibleItems: _angular_core.Signal<BucketProgressItem[]>;
486
+ protected readonly total: _angular_core.Signal<number>;
487
+ /** Descripción accesible de la barra completa — un solo `role="img"`
488
+ * con el desglose en texto, en vez de que cada segmento individual
489
+ * compita por foco/lectura de screen reader. */
490
+ protected readonly ariaLabel: _angular_core.Signal<string>;
491
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<BucketProgress, never>;
492
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BucketProgress, "kf-bucket-progress", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "summary": { "alias": "summary"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
493
+ }
494
+
253
495
  /**
254
496
  * Contenedor base con esquinas redondeadas y sombra sutil — la base visual
255
497
  * de paneles, tarjetas de métrica, tablas y bloques de contenido de todo el
@@ -263,7 +505,16 @@ declare class Breadcrumb {
263
505
  * cualquier composición (una métrica, una lista, un formulario) pueda vivir
264
506
  * dentro sin pelear con estilos propios del `Card`.
265
507
  *
266
- * <kf-card>Contenido simple</kf-card>
508
+ * `gap` controla el espacio entre los hijos directos del cuerpo — `md`
509
+ * (default) separa bloques con peso propio (un `kf-section-header`
510
+ * seguido de datos, por ejemplo); `xs`/`sm` para texto corrido o bloques
511
+ * más compactos, `lg`/`xl` para composiciones con más aire entre secciones.
512
+ *
513
+ * <kf-card>
514
+ * <kf-section-header title="Cliente" />
515
+ * <dl>…</dl>
516
+ * </kf-card>
517
+ * <kf-card gap="xs">Texto corrido, sin bloques que separar</kf-card>
267
518
  * <kf-card flush>Sin sombra — para superficies ya delimitadas por otra card</kf-card>
268
519
  * <kf-card>
269
520
  * <img kf-card-media src="..." alt="" />
@@ -278,8 +529,10 @@ declare class Breadcrumb {
278
529
  declare class Card {
279
530
  /** Quita la sombra — para cards anidadas dentro de otra superficie ya elevada. */
280
531
  readonly flush: _angular_core.InputSignalWithTransform<boolean, unknown>;
532
+ /** Espacio entre los hijos directos del cuerpo. `md` es el default. */
533
+ readonly gap: _angular_core.InputSignal<"sm" | "md" | "lg" | "xs" | "xl">;
281
534
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Card, never>;
282
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Card, "kf-card", never, { "flush": { "alias": "flush"; "required": false; "isSignal": true; }; }, {}, never, ["[kf-card-media]", "*", "[kf-card-actions]"], true, never>;
535
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Card, "kf-card", never, { "flush": { "alias": "flush"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; }, {}, never, ["[kf-card-media]", "*", "[kf-card-actions]"], true, never>;
283
536
  }
284
537
 
285
538
  /**
@@ -294,6 +547,7 @@ declare class CarouselSlide {
294
547
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<CarouselSlide, never>;
295
548
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<CarouselSlide, "kf-carousel-slide", never, {}, {}, never, ["*"], true, never>;
296
549
  }
550
+
297
551
  /**
298
552
  * Carrusel de slides con flechas, dots y autoplay opcional.
299
553
  *
@@ -384,6 +638,13 @@ declare class Checkbox implements ControlValueAccessor {
384
638
  *
385
639
  * <kf-chip tone="warn">Pendiente</kf-chip>
386
640
  * <kf-chip tone="ok">Aprobado</kf-chip>
641
+ * <kf-chip [dot]="false">4 Unidades</kf-chip>
642
+ *
643
+ * `dot` (default `true`) oculta el punto cuando el chip muestra un DATO
644
+ * (ej. una cantidad — "4 Unidades") y no un ESTADO — antes forzaba a cada
645
+ * portal a reimplementar el mismo patrón "pill con ícono + texto sin punto
646
+ * de estado" a mano (visto 2-3 veces idéntico en `web-otd-frontend`:
647
+ * `quotes-grid.html`/`quote-detail.html` para el conteo de unidades).
387
648
  *
388
649
  * Nota: la spec original define la unión de tonos como "ok" | "warn" |
389
650
  * "error" | "neutral". Internamente se mapean a los tokens semánticos de
@@ -393,9 +654,41 @@ declare class Checkbox implements ControlValueAccessor {
393
654
  * de superficie/borde/texto-muted más cercanos).
394
655
  */
395
656
  declare class Chip {
396
- readonly tone: _angular_core.InputSignal<"ok" | "warn" | "error" | "neutral">;
657
+ readonly tone: _angular_core.InputSignal<"neutral" | "ok" | "warn" | "error">;
658
+ readonly dot: _angular_core.InputSignalWithTransform<boolean, unknown>;
397
659
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Chip, never>;
398
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Chip, "kf-chip", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
660
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Chip, "kf-chip", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
661
+ }
662
+
663
+ /**
664
+ * Bloque colapsable genérico: un header clickeable (título + chevron) que
665
+ * expande/colapsa contenido proyectado libre, sin saber nada de lo que hay
666
+ * adentro. Building block para layouts responsive donde un bloque se
667
+ * muestra inline en desktop pero se colapsa en mobile/tablet para ahorrar
668
+ * espacio vertical — el primer caso de uso real es un panel de filtros de
669
+ * tabla que en desktop queda siempre abierto y en mobile arranca cerrado.
670
+ *
671
+ * `expanded` es two-way, mismo patrón que `Drawer.open`: el consumidor
672
+ * decide el estado inicial y puede reaccionar a los cambios, pero el propio
673
+ * click en el header ya alterna el estado sin que el consumidor tenga que
674
+ * cablear un `(click)` manual.
675
+ *
676
+ * <kf-collapsible title="Filtros" [(expanded)]="filtersOpen">
677
+ * ...
678
+ * </kf-collapsible>
679
+ *
680
+ * El ícono chevron es un SVG inline fijo (no una prop `icon` configurable)
681
+ * porque esta librería todavía no tiene un componente `Icon`/sprite
682
+ * compartido — un solo ícono fijo no amerita esperar esa abstracción.
683
+ */
684
+ declare class Collapsible {
685
+ readonly title: _angular_core.InputSignal<string>;
686
+ readonly expanded: _angular_core.ModelSignal<boolean>;
687
+ private readonly instanceId;
688
+ readonly triggerId: string;
689
+ readonly panelId: string;
690
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Collapsible, never>;
691
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Collapsible, "kf-collapsible", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; }, never, ["*"], true, never>;
399
692
  }
400
693
 
401
694
  /** Metadato clave mostrado junto al selector (RUC, identificación, sucursal). */
@@ -491,6 +784,53 @@ declare class ContentState {
491
784
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentState, "kf-content-state", never, { "state": { "alias": "state"; "required": true; "isSignal": true; }; "load": { "alias": "load"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "loadingLabel": { "alias": "loadingLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*", "[actions]"], true, never>;
492
785
  }
493
786
 
787
+ type ContentViewerType = 'image' | 'video';
788
+ /**
789
+ * Visor a pantalla completa para ampliar una pieza de contenido — hoy
790
+ * imagen, con `video` ya soportado para cuando haya contenido de ese tipo.
791
+ * Mismo mecanismo que `kf-modal` (el `<dialog>` nativo resuelve backdrop,
792
+ * atrapado de foco, top-layer y `Escape`), pero sin panel/título/footer:
793
+ * el contenido ocupa toda la pantalla sobre un backdrop oscuro.
794
+ *
795
+ * `src` acepta lo que venga del backend sin que el consumidor tenga que
796
+ * normalizarlo primero: una URL (`http(s)://`), un data URI ya armado
797
+ * (`data:...`), o base64 crudo (se le antepone el prefijo `data:` según
798
+ * `type`, asumiendo JPEG para imagen y MP4 para video).
799
+ *
800
+ * <kf-content-viewer [(open)]="viewerOpen" [src]="viewerSrc()" alt="Foto del vehículo" />
801
+ * <kf-content-viewer [(open)]="viewerOpen" type="video" [src]="viewerSrc()" title="Inspección" />
802
+ */
803
+ declare class ContentViewer {
804
+ readonly type: _angular_core.InputSignal<ContentViewerType>;
805
+ /** URL, data URI, o base64 crudo — ver JSDoc de la clase. */
806
+ readonly src: _angular_core.InputSignal<string | null>;
807
+ /** Texto alternativo cuando `type="image"`. */
808
+ readonly alt: _angular_core.InputSignal<string>;
809
+ readonly title: _angular_core.InputSignal<string>;
810
+ /** Visibilidad, two-way: `[(open)]="viewerOpen"`. */
811
+ readonly open: _angular_core.ModelSignal<boolean>;
812
+ /** Permite cerrar con el botón, `Escape` o clic en el backdrop. */
813
+ readonly dismissible: _angular_core.InputSignalWithTransform<boolean, unknown>;
814
+ protected readonly titleId: string;
815
+ /** Detecta si `src` ya es una URL o un data URI armado; si no, asume
816
+ * base64 crudo y le antepone el prefijo `data:` correspondiente a `type`
817
+ * — el backend a veces manda una cosa, a veces la otra. */
818
+ protected readonly resolvedSrc: _angular_core.Signal<string | null>;
819
+ private readonly dialog;
820
+ private readonly document;
821
+ constructor();
822
+ /** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
823
+ protected onCancel(event: Event): void;
824
+ /**
825
+ * Clic en el backdrop. Un clic en la zona del backdrop tiene como `target` al
826
+ * propio `<dialog>` (el contenido vive en el panel de adentro), así que basta
827
+ * comparar contra él — no hace falta cortar la propagación en el panel.
828
+ */
829
+ protected onHostClick(event: MouseEvent): void;
830
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContentViewer, never>;
831
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ContentViewer, "kf-content-viewer", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "src": { "alias": "src"; "required": false; "isSignal": true; }; "alt": { "alias": "alt"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, never, true, never>;
832
+ }
833
+
494
834
  /**
495
835
  * Zona que ocupa la columna en la vista de cards. La vista de tabla ignora
496
836
  * esto (salvo `media`, que igual se rinde como thumbnail en vez de imprimir
@@ -509,9 +849,40 @@ interface DataTableColumn {
509
849
  right?: boolean;
510
850
  className?: string;
511
851
  cardRole?: DataTableCardRole;
852
+ /** Habilita ordenar por esta columna clickeando su header (solo vista
853
+ * tabla — en cards no hay header clickeable). Ciclo asc → desc → sin
854
+ * orden, un click a la vez. */
855
+ sortable?: boolean;
856
+ /** Fija la columna a un borde del scroll horizontal — típicamente la
857
+ * primera (identificador) y/o la última (acciones). Columnas `sticky`
858
+ * del mismo lado deben ser consecutivas y estar en ese extremo del
859
+ * array `columns`; el offset de cada una se calcula sumando el ancho
860
+ * real de las que ya quedaron fijas antes, medido en runtime (no hay
861
+ * forma de conocer el ancho de antemano, las columnas son de ancho
862
+ * variable según su contenido). */
863
+ sticky?: 'left' | 'right';
864
+ /** Ancho mínimo forzado — evita que una columna angosta (ej. un chip
865
+ * corto) se comprima más de la cuenta cuando hay poco espacio y otras
866
+ * columnas compiten por él. */
867
+ minWidth?: string;
512
868
  }
513
869
  /** Vistas disponibles cuando `cardable` está activo. */
514
870
  type DataTableView = 'table' | 'cards';
871
+ /** Opciones de "filas por página" estándar del portal — 10 como default
872
+ * cubre bien tanto un dataset chico (ej. Beneficios) como uno con volumen
873
+ * real (ej. 30+ cuotas en Billing), sin sentirse vacío ni insuficiente; 25
874
+ * y 50 escalan ~2.5x cada paso, evitando un salto brusco donde la opción
875
+ * del medio no aporta nada. Cualquier consumidor puede pisar este default
876
+ * con su propio `pageSizeOptions` si su caso lo justifica — pasar `[]`
877
+ * explícitamente desactiva el selector aunque `pageSize` esté activo. */
878
+ declare const DEFAULT_PAGE_SIZE_OPTIONS: number[];
879
+ /** Densidad de fila: `comfortable` (default, el padding actual) o
880
+ * `compact` (menos padding vertical, para tablas con muchas filas donde
881
+ * priorizar cantidad visible sobre aire entre filas). */
882
+ type DataTableDensity = 'comfortable' | 'compact';
883
+ /** `key:direction` — mismo formato que `sort` de `kf-quote-picker`, para
884
+ * que un consumidor que ya conoce ese patrón no aprenda uno nuevo acá. */
885
+ type DataTableSort = `${string}:${'asc' | 'desc'}`;
515
886
  /**
516
887
  * Tabla estandarizada con soporte opcional de toggle tabla/cards.
517
888
  *
@@ -533,10 +904,7 @@ type DataTableView = 'table' | 'cards';
533
904
  * pie separado por una línea para las acciones. Sin `cardRole`, todas las
534
905
  * columnas caen en el cuerpo, igual que antes.
535
906
  *
536
- * El modo `cards` no depende de un componente ViewToggle separado: expone
537
- * un control de segmentos mínimo inline (mismo patrón visual que
538
- * kf-segmented) para no acoplarse a un componente que otro agente puede
539
- * estar construyendo en paralelo — ver nota en el resumen de la tarea.
907
+ * El toggle tabla/cards es `kf-view-toggle`, componente standalone propio.
540
908
  *
541
909
  * <kf-data-table [columns]="columns" [rows]="rows" (rowClick)="onRowClick($event)">
542
910
  * <kf-empty-state empty>No hay resultados para mostrar.</kf-empty-state>
@@ -544,6 +912,18 @@ type DataTableView = 'table' | 'cards';
544
912
  *
545
913
  * <kf-data-table [columns]="columns" [rows]="rows" [cardable]="true" defaultView="cards" />
546
914
  *
915
+ * Loading nativo: con `loading` en `true`, la tabla se reemplaza por un
916
+ * placeholder (`kf-skeleton` por celda/card) — mismo número de filas que
917
+ * `pageSize`, o 5 si no hay paginación activa. Ignora `rows()`/el slot
918
+ * `[empty]` mientras dura (no sabemos aún si el resultado real estará
919
+ * vacío), y respeta la vista activa (tabla o cards) para no cambiar el
920
+ * layout de golpe cuando los datos reales lleguen. El toggle tabla/cards y
921
+ * el header de columnas siguen visibles — solo el cuerpo se reemplaza:
922
+ *
923
+ * <kf-data-table [columns]="columns" [rows]="rows()" [loading]="isLoading()">
924
+ * <kf-empty-state empty>No hay resultados para mostrar.</kf-empty-state>
925
+ * </kf-data-table>
926
+ *
547
927
  * El slot `[footer]` (página 15: "slot de pie para paginación") va siempre
548
928
  * al final, en ambas vistas, separado por una línea — pensado para proyectar
549
929
  * `<kf-pagination>`, que sigue siendo un componente independiente.
@@ -551,10 +931,52 @@ type DataTableView = 'table' | 'cards';
551
931
  * <kf-data-table [columns]="columns" [rows]="rows">
552
932
  * <kf-pagination footer [page]="page()" [total]="total()" (pageChange)="page.set($event)" />
553
933
  * </kf-data-table>
934
+ *
935
+ * Paginación interna opcional: con `pageSize`, la tabla pagina sola y
936
+ * agrega su propio `kf-pagination` en el footer nativo (el slot
937
+ * `[footer]` proyectado sigue disponible en paralelo). El selector de "N
938
+ * por página" viene activo por defecto con `DEFAULT_PAGE_SIZE_OPTIONS`
939
+ * ([10, 25, 50], estándar del portal) — pasar `pageSizeOptions` propio lo
940
+ * pisa, y pasar `[]` lo desactiva (paginación sin selector). `itemLabel`
941
+ * completa el texto informativo ("1–10 de 42 beneficios").
942
+ *
943
+ * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" itemLabel="beneficios" />
944
+ * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" [pageSizeOptions]="[]" />
945
+ *
946
+ * Ordenamiento por columna: cada `DataTableColumn` con `sortable: true`
947
+ * vuelve su header clickeable, con el mismo formato `key:direction` que
948
+ * usa `sort` de `kf-quote-picker` (two-way manual, no `model()` — el
949
+ * consumidor decide si persiste el valor o lo descarta al desmontar):
950
+ *
951
+ * <kf-data-table [columns]="columns" [rows]="rows" [sort]="sort()" (sortChange)="sort.set($event)" />
952
+ *
953
+ * Columnas fijas: `sticky: 'left' | 'right'` en la columna ancla esa
954
+ * columna a un borde del scroll horizontal — típicamente el identificador
955
+ * (izquierda) y/o las acciones (derecha) en una tabla con muchas columnas.
956
+ *
957
+ * Densidad: `density="compact"` reduce el padding vertical de cada fila,
958
+ * para priorizar cantidad de filas visibles sobre aire entre ellas.
554
959
  */
555
960
  declare class DataTable<T = unknown> {
556
961
  readonly columns: _angular_core.InputSignal<DataTableColumn[]>;
557
962
  readonly rows: _angular_core.InputSignal<T[]>;
963
+ /** Placeholder nativo (`kf-skeleton`) mientras los datos reales cargan —
964
+ * ver el doc-comment de la clase para el detalle. */
965
+ readonly loading: _angular_core.InputSignal<boolean>;
966
+ private readonly scrollEl;
967
+ private readonly destroyRef;
968
+ /**
969
+ * `.kf-data-table__scroll` tiene `overflow-x: auto` sin ningún indicador
970
+ * visual — con varias columnas y poco ancho disponible, la tabla se corta
971
+ * en el borde y nada le avisa al usuario que hay más contenido
972
+ * scrolleando. `canScrollLeft`/`canScrollRight` alimentan un fade en cada
973
+ * borde — visible solo cuando de verdad hay algo que revelar de ese lado,
974
+ * así una tabla que entra completa no muestra fades falsos.
975
+ */
976
+ protected readonly canScrollLeft: _angular_core.WritableSignal<boolean>;
977
+ protected readonly canScrollRight: _angular_core.WritableSignal<boolean>;
978
+ constructor();
979
+ private updateScrollState;
558
980
  /** Default: índice de la fila dentro de `rows`. */
559
981
  readonly rowKey: _angular_core.InputSignal<((row: T, index: number) => string | number) | undefined>;
560
982
  /** Sin borde/sombra de card — para tablas anidadas dentro de otra card. */
@@ -572,10 +994,76 @@ declare class DataTable<T = unknown> {
572
994
  $implicit: T;
573
995
  index: number;
574
996
  }>> | undefined>;
997
+ /** Densidad de fila — ver `DataTableDensity`. */
998
+ readonly density: _angular_core.InputSignal<DataTableDensity>;
999
+ /** Fuerza si la fila entera dispara `rowClick` en la vista de tabla —
1000
+ * ver `rowInteractive` para el cálculo automático cuando no se define. */
1001
+ readonly rowClickable: _angular_core.InputSignal<boolean | null>;
575
1002
  readonly rowClick: _angular_core.OutputEmitterRef<{
576
1003
  row: T;
577
1004
  index: number;
578
1005
  }>;
1006
+ /**
1007
+ * Paginación interna opcional — sin `pageSize`, cero cambio de
1008
+ * comportamiento (el consumidor sigue paginando afuera si quiere, vía el
1009
+ * slot `[footer]` con su propio `kf-pagination`, como ya hace Billing).
1010
+ * Con `pageSize`, la tabla pagina `rows()` sola y agrega su propio
1011
+ * `kf-pagination` en el footer nativo — el slot `[footer]` proyectado
1012
+ * sigue disponible para cualquier otra cosa que el consumidor quiera
1013
+ * agregar ahí (no son mutuamente excluyentes).
1014
+ */
1015
+ readonly pageSize: _angular_core.InputSignal<number | null>;
1016
+ /** Default `DEFAULT_PAGE_SIZE_OPTIONS` ([10, 25, 50], estándar del
1017
+ * portal) — pasar `[]` explícitamente desactiva el selector de tamaño
1018
+ * aunque `pageSize` esté activo (paginación sin selector). */
1019
+ readonly pageSizeOptions: _angular_core.InputSignal<number[]>;
1020
+ readonly pageChange: _angular_core.OutputEmitterRef<number>;
1021
+ readonly pageSizeChange: _angular_core.OutputEmitterRef<number>;
1022
+ /** Nombre de la entidad listada (ej. "beneficios", "facturas"), en
1023
+ * plural — se agrega al final del texto informativo de la paginación
1024
+ * interna ("1–5 de 7 beneficios"). Sin esto, el texto queda solo con
1025
+ * números ("1–5 de 7"): la librería no impone un idioma ni un nombre
1026
+ * fijo, cada consumidor decide el suyo. */
1027
+ readonly itemLabel: _angular_core.InputSignal<string>;
1028
+ protected readonly page: _angular_core.WritableSignal<number>;
1029
+ private readonly internalPageSize;
1030
+ /** Tamaño de página efectivo: el que el usuario elija en el selector
1031
+ * (si `pageSizeOptions` está activo), o el `pageSize` del consumidor. */
1032
+ protected readonly effectivePageSize: _angular_core.Signal<number | null>;
1033
+ /** Cantidad de filas/cards placeholder mientras `loading` es true — el
1034
+ * `pageSize` efectivo si hay paginación activa (así el placeholder ya
1035
+ * "adivina" cuántas filas reales van a aparecer, sin salto de layout),
1036
+ * o 5 si no hay paginación. */
1037
+ protected readonly skeletonRowIndexes: _angular_core.Signal<number[]>;
1038
+ protected readonly totalPages: _angular_core.Signal<number>;
1039
+ /** Texto informativo del footer de paginación interna ("1–5 de 7
1040
+ * beneficios") — se recalcula con cada cambio de página/tamaño/dataset,
1041
+ * nunca queda desactualizado tras filtrar o pasar de página. */
1042
+ protected readonly paginationSummary: _angular_core.Signal<string>;
1043
+ /**
1044
+ * Ordenamiento por columna — mismo formato `key:direction` que `sort` de
1045
+ * `kf-quote-picker`, para que un consumidor que ya conoce ese patrón no
1046
+ * aprenda uno nuevo. Un click en un header `sortable` avanza el ciclo
1047
+ * asc → desc → sin orden; clickear otra columna reinicia el ciclo en esa
1048
+ * columna nueva.
1049
+ */
1050
+ readonly sort: _angular_core.InputSignal<`${string}:desc` | `${string}:asc` | null>;
1051
+ readonly sortChange: _angular_core.OutputEmitterRef<`${string}:desc` | `${string}:asc` | null>;
1052
+ protected readonly sortedRows: _angular_core.Signal<T[]>;
1053
+ /** Filas realmente renderizadas: ordenadas y, si `pageSize` está activo,
1054
+ * recortadas a la página actual. */
1055
+ protected readonly displayedRows: _angular_core.Signal<T[]>;
1056
+ protected sortDirectionFor(column: DataTableColumn): 'asc' | 'desc' | null;
1057
+ /** Ciclo asc → desc → sin orden, disparado al clickear un header `sortable`. */
1058
+ protected toggleSort(column: DataTableColumn): void;
1059
+ protected goToPage(target: number): void;
1060
+ protected setPageSize(size: number | null): void;
1061
+ /** Columnas fijas del lado izquierdo/derecho, con el offset acumulado
1062
+ * (medido en runtime, ver `measureStickyOffsets`) para `position: sticky`. */
1063
+ protected readonly stickyLeftColumns: _angular_core.Signal<DataTableColumn[]>;
1064
+ protected readonly stickyRightColumns: _angular_core.Signal<DataTableColumn[]>;
1065
+ protected readonly stickyOffsets: _angular_core.WritableSignal<Map<string, number>>;
1066
+ protected stickyStyle(column: DataTableColumn): Record<string, string> | null;
579
1067
  private readonly internalView;
580
1068
  /** Vista activa: la elegida por el usuario, o `defaultView` si no tocó el toggle. */
581
1069
  readonly view: _angular_core.Signal<DataTableView>;
@@ -591,14 +1079,45 @@ declare class DataTable<T = unknown> {
591
1079
  * Con columnas `action`, la interacción la manejan esos botones.
592
1080
  */
593
1081
  readonly cardInteractive: _angular_core.Signal<boolean>;
1082
+ /**
1083
+ * Mismo criterio que `cardInteractive`, para la vista de tabla: la fila
1084
+ * entera dispara `rowClick` solo si no hay una columna `action` que ya
1085
+ * resuelva la interacción con su propio botón — sin esto, un click en
1086
+ * "Ver detalle" dispararía TAMBIÉN el click de la fila por burbujeo.
1087
+ * `rowClickable` fuerza el comportamiento en cualquier sentido si el
1088
+ * consumidor lo necesita.
1089
+ */
1090
+ readonly rowInteractive: _angular_core.Signal<boolean>;
594
1091
  setView(next: DataTableView): void;
595
1092
  trackRow(row: T, index: number): string | number;
596
1093
  cellValue(row: T, column: DataTableColumn): string;
597
1094
  onRowClick(row: T, index: number): void;
1095
+ /**
1096
+ * Ancho real de cada columna `sticky` — se mide después de cada render
1097
+ * porque el ancho de una columna depende de su contenido, no hay forma
1098
+ * de conocerlo de antemano. Cada columna fija toma como offset la suma
1099
+ * de anchos de las columnas fijas del MISMO lado que ya quedaron antes
1100
+ * que ella (así "left" apila de izquierda a derecha, "right" de derecha
1101
+ * a izquierda).
1102
+ */
1103
+ protected measureStickyOffsets(): void;
598
1104
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataTable<any>, never>;
599
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTable<any>, "kf-data-table", never, { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "rows": { "alias": "rows"; "required": true; "isSignal": true; }; "rowKey": { "alias": "rowKey"; "required": false; "isSignal": true; }; "flush": { "alias": "flush"; "required": false; "isSignal": true; }; "cardable": { "alias": "cardable"; "required": false; "isSignal": true; }; "defaultView": { "alias": "defaultView"; "required": false; "isSignal": true; }; "render": { "alias": "render"; "required": false; "isSignal": true; }; "cellTemplates": { "alias": "cellTemplates"; "required": false; "isSignal": true; }; }, { "rowClick": "rowClick"; }, never, ["[empty]", "[footer]"], true, never>;
1105
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTable<any>, "kf-data-table", never, { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "rows": { "alias": "rows"; "required": true; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "rowKey": { "alias": "rowKey"; "required": false; "isSignal": true; }; "flush": { "alias": "flush"; "required": false; "isSignal": true; }; "cardable": { "alias": "cardable"; "required": false; "isSignal": true; }; "defaultView": { "alias": "defaultView"; "required": false; "isSignal": true; }; "render": { "alias": "render"; "required": false; "isSignal": true; }; "cellTemplates": { "alias": "cellTemplates"; "required": false; "isSignal": true; }; "density": { "alias": "density"; "required": false; "isSignal": true; }; "rowClickable": { "alias": "rowClickable"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "itemLabel": { "alias": "itemLabel"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; }, { "rowClick": "rowClick"; "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; "sortChange": "sortChange"; }, never, ["[empty]", "[footer]"], true, never>;
600
1106
  }
601
1107
 
1108
+ /**
1109
+ * Signal con la vista de `kf-data-table` ('table' | 'cards') derivada de un
1110
+ * breakpoint — mismo criterio que cualquier tabla con muchas columnas: bajo
1111
+ * el ancho dado, la vista de tabla no entra sin scroll horizontal, así que
1112
+ * se fuerza `cards`. Centraliza el patrón `matchMediaSignal` +
1113
+ * `computed<DataTableView>` que cada tabla del portal repetía igual.
1114
+ *
1115
+ * protected readonly tableView = dataTableViewSignal();
1116
+ * // con un breakpoint distinto al default de 900px:
1117
+ * protected readonly tableView = dataTableViewSignal('(max-width: 768px)');
1118
+ */
1119
+ declare function dataTableViewSignal(query?: string): Signal<DataTableView>;
1120
+
602
1121
  type DividerOrientation = 'horizontal' | 'vertical';
603
1122
  /**
604
1123
  * Separador visual simple entre bloques de contenido. `orientation`
@@ -621,84 +1140,191 @@ declare class Divider {
621
1140
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Divider, "kf-divider", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
622
1141
  }
623
1142
 
624
- type DropdownItemTone = 'neutral' | 'error';
1143
+ /** Color del chip de ícono. Identifica la CLASE de documento de un vistazo
1144
+ * en una lista de nombres de archivo parecidos entre sí. */
1145
+ type DocumentRowTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
625
1146
  /**
626
- * Í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.
1147
+ * Fila de un documento descargable: chip de ícono coloreado por tipo,
1148
+ * título, metadata y la acción de descarga al final. Sin `href` rinde
1149
+ * `pendingLabel` en vez de un link roto. El contenido proyectado cae entre
1150
+ * la metadata y la acción, como slot para un `kf-badge` de estado.
1151
+ */
1152
+ declare class DocumentRow {
1153
+ readonly icon: _angular_core.InputSignal<IconName>;
1154
+ readonly tone: _angular_core.InputSignal<DocumentRowTone>;
1155
+ /** Qué documento es. El nombre humano del tipo, no el del archivo. */
1156
+ readonly title: _angular_core.InputSignal<string>;
1157
+ /** Línea secundaria — archivo, fecha, tamaño. El consumidor la arma ya
1158
+ * formateada: la librería no decide en qué unidad se lee un tamaño ni en
1159
+ * qué formato una fecha. */
1160
+ readonly meta: _angular_core.InputSignal<string | undefined>;
1161
+ /** Ausente rinde `pendingLabel` en vez del link de descarga. */
1162
+ readonly href: _angular_core.InputSignal<string | undefined>;
1163
+ readonly downloadLabel: _angular_core.InputSignal<string>;
1164
+ /** Qué se dice cuando no hay `href`. */
1165
+ readonly pendingLabel: _angular_core.InputSignal<string>;
1166
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DocumentRow, never>;
1167
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DocumentRow, "kf-document-row", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "meta": { "alias": "meta"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "downloadLabel": { "alias": "downloadLabel"; "required": false; "isSignal": true; }; "pendingLabel": { "alias": "pendingLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1168
+ }
1169
+
1170
+ type DrawerSide = 'left' | 'right';
1171
+ /**
1172
+ * Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
1173
+ * ni qué es sesión, solo da tres lugares para proyectar contenido
1174
+ * (`[kf-drawer-header]`, default, `[kf-drawer-footer]`) y resuelve lo que
1175
+ * cualquier drawer necesita resolver igual sin importar qué haya adentro:
1176
+ * overlay con blur que cierra al click, `Escape`, atrapado de foco real y
1177
+ * devolución de foco al cerrar.
1178
+ *
1179
+ * `open` es two-way, mismo patrón que `Dropdown`/`Modal`: el consumidor
1180
+ * decide qué dispara la apertura (un botón de hamburguesa, por ejemplo) y
1181
+ * qué hace con el cierre.
1182
+ *
1183
+ * Foco: al abrir, se crea un `FocusTrap` del CDK sobre el panel (la misma
1184
+ * pieza que usa Angular Material para `mat-sidenav`/`mat-dialog`) — Tab y
1185
+ * Shift+Tab ciclan solo entre los elementos focuseables del drawer, sin
1186
+ * escapar al contenido de fondo. Al cerrar, el foco vuelve al elemento que
1187
+ * tenía el foco antes de abrir (guardado en `document.activeElement`), no
1188
+ * se pierde en el `<body>`.
1189
+ *
1190
+ * `[kf-drawer-header]` tiene fondo oscuro propio (`--kf-color-tertiary-deep`),
1191
+ * distinto del body (`--kf-color-white`) — pensado para contener
1192
+ * `kf-drawer-session`, que asume ese fondo y no lo trae él mismo (ver
1193
+ * `DrawerSession.ts`). Si el slot queda vacío, el contenedor colapsa
1194
+ * (`display: none`) y no deja una franja oscura sin contenido.
1195
+ *
1196
+ * <kf-drawer [(open)]="menuOpen" ariaLabel="Menú de navegación">
1197
+ * <div kf-drawer-header>
1198
+ * <kf-drawer-session name="Tomás Soto" email="tomas@kaufmann.com" (logoutClick)="onLogout()" />
1199
+ * </div>
1200
+ * <a href="/">Home</a>
1201
+ * <a href="/vehiculos">Vehículos</a>
1202
+ * </kf-drawer>
1203
+ */
1204
+ declare class Drawer {
1205
+ /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
1206
+ readonly open: _angular_core.ModelSignal<boolean>;
1207
+ /** Borde de anclaje del panel. `left` (default) es la convención dominante
1208
+ * (Material/iOS/Android): el botón que abre el drawer y el propio panel
1209
+ * quedan del mismo lado. */
1210
+ readonly side: _angular_core.InputSignal<DrawerSide>;
1211
+ /** `aria-label` del `<nav>` interno — describe el propósito del panel para
1212
+ * lectores de pantalla (ej. "Menú de navegación"). */
1213
+ readonly ariaLabel: _angular_core.InputSignal<string>;
1214
+ private readonly panelRef;
1215
+ private readonly focusTrapFactory;
1216
+ private readonly document;
1217
+ private focusTrap;
1218
+ private lastFocusedElement;
1219
+ constructor();
1220
+ /** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
1221
+ protected onEscape(): void;
1222
+ private getOrCreateFocusTrap;
1223
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Drawer, never>;
1224
+ 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>;
1225
+ }
1226
+
1227
+ /**
1228
+ * Botón de cerrar sesión, pensado para proyectarse dentro de
1229
+ * `[kf-drawer-footer]` de `kf-drawer`, o junto a `kf-drawer-session` dentro
1230
+ * de `[kf-sidebar-footer]` de `kf-sidebar-shell` — separado de
1231
+ * `kf-drawer-session` (que solo muestra identidad: brand, avatar, nombre,
1232
+ * email), no anidado dentro de ella.
1233
+ *
1234
+ * Ícono `log-out` + label, color `--kf-color-error` con hover en
1235
+ * `--kf-color-error-soft` y foco visible con `--kf-shadow-focus`. Separado
1236
+ * de `kf-drawer-session` (solo identidad) por responsabilidad única.
630
1237
  *
631
- * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
1238
+ * No sabe nada de autenticación real — solo emite `logoutClick` cuando el
1239
+ * consumidor debe cerrar la sesión real.
1240
+ *
1241
+ * `collapsed` oculta el label y centra el ícono, exponiéndolo vía `title`
1242
+ * — mismo criterio que `SidebarNavItem.collapsed` y
1243
+ * `DrawerSession.collapsed`, para cuando este botón vive dentro de un
1244
+ * `kf-sidebar-shell` colapsado.
1245
+ *
1246
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
632
1247
  */
633
- declare class DropdownItem {
634
- readonly tone: _angular_core.InputSignal<DropdownItemTone>;
635
- 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>;
1248
+ declare class DrawerLogout {
1249
+ /** Label del botón, en español neutro por defecto. Input opcional por si
1250
+ * algún consumidor futuro necesita otro texto. */
1251
+ readonly label: _angular_core.InputSignal<string>;
1252
+ /** Oculta el label y centra el ícono — ver comentario de la clase. */
1253
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
1254
+ readonly logoutClick: _angular_core.OutputEmitterRef<void>;
1255
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerLogout, never>;
1256
+ 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>;
637
1257
  }
1258
+
638
1259
  /**
639
- * 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.
1260
+ * Bloque de identidad para `[kf-drawer-header]` de `kf-drawer` o
1261
+ * `[kf-sidebar-footer]` de `kf-sidebar-shell`: avatar + nombre + email,
1262
+ * brand opcional arriba. Solo identidad — el logout vive aparte, en
1263
+ * `kf-drawer-logout`.
1264
+ *
1265
+ * `collapsed` oculta brand/nombre/email y deja solo el avatar (para un
1266
+ * `kf-sidebar-shell` colapsado). `interactive` convierte la tarjeta en un
1267
+ * botón real que emite `cardClick`, para cuando la identidad es el
1268
+ * disparador de un menú de acciones (ej. envuelta en `kf-dropdown`).
644
1269
  *
645
1270
  * <kf-dropdown [(open)]="menuOpen">
646
- * <kf-dropdown-panel>
647
- * <kf-dropdown-item (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
1271
+ * <kf-drawer-session interactive name="Juan Pérez" email="juan@empresa.com" (cardClick)="menuOpen.set(!menuOpen())" />
1272
+ * <kf-dropdown-panel variant="dark">
1273
+ * <kf-dropdown-item icon="log-out" tone="error" (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
648
1274
  * </kf-dropdown-panel>
649
1275
  * </kf-dropdown>
650
1276
  */
651
- declare class DropdownPanel {
652
- readonly templateRef: _angular_core.Signal<TemplateRef<any>>;
653
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownPanel, never>;
654
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, {}, {}, never, ["*"], true, never>;
1277
+ declare class DrawerSession {
1278
+ readonly name: _angular_core.InputSignal<string>;
1279
+ readonly email: _angular_core.InputSignal<string>;
1280
+ /**
1281
+ * Etiqueta de rol o función del usuario dentro de la sesión (ej.
1282
+ * "Coordinador", "Supervisor"). Se muestra como badge dentro de la
1283
+ * identidad, debajo del email — distinto de `brand`, que es la marca o
1284
+ * cliente mostrada como encabezado de todo el bloque, no un atributo de
1285
+ * la persona.
1286
+ */
1287
+ readonly role: _angular_core.InputSignal<string>;
1288
+ readonly initials: _angular_core.InputSignal<string>;
1289
+ readonly avatarUrl: _angular_core.InputSignal<string>;
1290
+ /** Label superior opcional, ej. "Portal Kaufmann LATAM". */
1291
+ readonly brand: _angular_core.InputSignal<string>;
1292
+ /** Oculta brand/nombre/email y centra el avatar — ver comentario de la clase. */
1293
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
1294
+ /** Convierte la tarjeta en un botón real con ícono de kebab — ver comentario de la clase. */
1295
+ readonly interactive: _angular_core.InputSignalWithTransform<boolean, unknown>;
1296
+ /** Se emite al clickear la tarjeta, solo cuando `interactive` es `true`. */
1297
+ readonly cardClick: _angular_core.OutputEmitterRef<void>;
1298
+ /** Si no se pasa `initials`, se derivan de `name`: primera letra de cada
1299
+ * una de las primeras 2 palabras, en mayúscula. */
1300
+ protected readonly displayInitials: _angular_core.Signal<string>;
1301
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerSession, never>;
1302
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DrawerSession, "kf-drawer-session", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "email": { "alias": "email"; "required": false; "isSignal": true; }; "role": { "alias": "role"; "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>;
655
1303
  }
1304
+
656
1305
  /**
657
1306
  * Menú de acciones flotante, conectado a un elemento disparador vía
658
1307
  * `@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.
1308
+ * `mat-menu`/`mat-select`. El disparador real puede vivir en OTRO
1309
+ * componente por completo (ej. el avatar dentro de `kf-portal-header`), y
1310
+ * el panel aparece pegado a ESE elemento sin importar dónde el consumidor
1311
+ * ubique al `Dropdown` en su HTML.
678
1312
  *
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>
1313
+ * `open` es two-way: el consumidor decide qué dispara la apertura —
1314
+ * `Dropdown` en sí no renderiza ningún trigger, su propio host es el punto
1315
+ * de anclaje visual del panel.
1316
+ *
1317
+ * <kf-dropdown [(open)]="menuOpen"><kf-dropdown-panel>...</kf-dropdown-panel></kf-dropdown>
685
1318
  */
686
1319
  declare class Dropdown {
687
1320
  /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
688
1321
  readonly open: _angular_core.ModelSignal<boolean>;
689
1322
  /**
690
1323
  * 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()">
1324
+ * de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es el
1325
+ * disparador. Útil cuando el disparador real vive dentro de OTRO
1326
+ * componente que no se puede envolver sin ensanchar el área de click
1327
+ * (ej. el avatar de sesión dentro de `PortalHeader`).
702
1328
  */
703
1329
  readonly anchor: _angular_core.InputSignal<ElementRef<HTMLElement> | undefined>;
704
1330
  private readonly panel;
@@ -707,24 +1333,81 @@ declare class Dropdown {
707
1333
  private readonly viewContainerRef;
708
1334
  private readonly destroyRef;
709
1335
  private overlayRef;
1336
+ /**
1337
+ * Suscripciones a `outsidePointerEvents()`/`keydownEvents()` del overlay
1338
+ * ACTUALMENTE adjunto. `getOrCreateOverlayRef()` reutiliza el mismo
1339
+ * `overlayRef` entre aperturas (no lo recrea cada vez), así que se
1340
+ * cancelan explícitamente en `detach()` antes de la siguiente `attach()`
1341
+ * — de lo contrario se acumularían N suscripciones activas sobre el
1342
+ * mismo overlay persistente.
1343
+ */
1344
+ private overlaySubscription;
710
1345
  constructor();
711
1346
  /**
712
1347
  * 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.
1348
+ * ve teclas con el foco DENTRO del panel. Si el foco queda en el botón
1349
+ * disparador (caso común), `keydownEvents()` nunca dispara — este
1350
+ * listener a nivel de documento cubre ese caso.
719
1351
  */
720
1352
  onDocumentEscape(): void;
721
1353
  private attach;
1354
+ /**
1355
+ * El trigger real (el `anchor`, o el propio host si no se pasó ninguno —
1356
+ * ver comentario de la clase) vive FUERA del overlay a propósito: el
1357
+ * overlay recién se crea/adjunta cuando `open` pasa a `true`, así que el
1358
+ * CDK no tiene forma de saber que ese elemento externo "es parte" de su
1359
+ * disparador. Sin este filtro, el mismo `pointerdown` que abre el menú
1360
+ * también llega a `outsidePointerEvents()`, cerrando el panel en el
1361
+ * mismo gesto que lo abrió.
1362
+ */
1363
+ private isEventOnTrigger;
722
1364
  private detach;
723
1365
  private getOrCreateOverlayRef;
724
1366
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Dropdown, never>;
725
1367
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Dropdown, "kf-dropdown", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "anchor": { "alias": "anchor"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, ["panel"], ["*"], true, never>;
726
1368
  }
727
1369
 
1370
+ type DropdownItemTone = 'neutral' | 'error';
1371
+ /**
1372
+ * Ítem de acción dentro de un `kf-dropdown-panel`. Es un `<button>` nativo
1373
+ * (no un `<a>`): dispara comportamiento en la pantalla actual, no navega.
1374
+ *
1375
+ * `icon` es opcional — sin él, el ítem es solo texto.
1376
+ *
1377
+ * <kf-dropdown-item icon="log-out" (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
1378
+ */
1379
+ declare class DropdownItem {
1380
+ readonly tone: _angular_core.InputSignal<DropdownItemTone>;
1381
+ /** Ícono opcional al inicio de la fila. */
1382
+ readonly icon: _angular_core.InputSignal<IconName | undefined>;
1383
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownItem, never>;
1384
+ 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>;
1385
+ }
1386
+
1387
+ type DropdownPanelVariant = 'solid' | 'dark';
1388
+ /**
1389
+ * Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
1390
+ * (`<ng-template>`, capturada vía `TemplateRef`) — `Dropdown` la conecta al
1391
+ * overlay real cuando `open` pasa a `true`, no la renderiza en su propio
1392
+ * lugar del DOM. Esto permite que el panel aparezca pegado al host de
1393
+ * `Dropdown` sin importar dónde esté ese host en la página.
1394
+ *
1395
+ * `variant`: `'solid'` (default, fondo blanco) o `'dark'` (fondo sólido
1396
+ * oscuro, mismo tono que `[kf-drawer-header]`) para cuando el trigger vive
1397
+ * sobre una superficie oscura de marca.
1398
+ *
1399
+ * `dark` es sólida a propósito, no semitransparente: el CDK Overlay
1400
+ * reposiciona el panel según dónde entra en el viewport y puede terminar
1401
+ * cayendo sobre fondo blanco en vez de sobre el rail oscuro — un color
1402
+ * sólido se ve intencional sin importar dónde lo posicione el CDK.
1403
+ */
1404
+ declare class DropdownPanel {
1405
+ readonly variant: _angular_core.InputSignal<DropdownPanelVariant>;
1406
+ readonly templateRef: _angular_core.Signal<TemplateRef<any>>;
1407
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownPanel, never>;
1408
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1409
+ }
1410
+
728
1411
  /**
729
1412
  * Placeholder para una sección sin contenido. Dos tonos, con anatomía
730
1413
  * distinta (páginas 27-28 del design system, que los trata como piezas
@@ -748,11 +1431,38 @@ declare class Dropdown {
748
1431
  declare class EmptyState {
749
1432
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
750
1433
  /** `neutral` (default): cero resultados. `error`: fallo real de carga. */
751
- readonly tone: _angular_core.InputSignal<"error" | "neutral">;
1434
+ readonly tone: _angular_core.InputSignal<"neutral" | "error">;
752
1435
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EmptyState, never>;
753
1436
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<EmptyState, "kf-empty-state", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
754
1437
  }
755
1438
 
1439
+ /** Color del marcador. Distingue la CLASE de evento de un vistazo en un log
1440
+ * largo — no es un estado de progreso. */
1441
+ type EventTimelineTone = 'neutral' | 'primary' | 'ok' | 'warn' | 'error' | 'info';
1442
+ /** Un evento ya ocurrido. Los tres campos de texto son los tres niveles de
1443
+ * lectura: qué pasó, por qué, y cuándo/quién. */
1444
+ interface EventTimelineItem {
1445
+ /** Clave de `track`. */
1446
+ readonly id: string;
1447
+ readonly title: string;
1448
+ readonly detail?: string | null;
1449
+ /** Hora y autor, ya formateados por el consumidor. */
1450
+ readonly meta?: string | null;
1451
+ readonly icon?: IconName;
1452
+ readonly tone?: EventTimelineTone;
1453
+ }
1454
+ /**
1455
+ * Línea de tiempo de AUDITORÍA: eventos ya ocurridos, en orden, cada uno con
1456
+ * marcador coloreado por CLASE y tres niveles de texto (titular, detalle,
1457
+ * hora + autor). Distinto de `kf-journey-stepper` (de PROGRESO, con pasos
1458
+ * futuros conocidos de antemano): acá todo ya pasó.
1459
+ */
1460
+ declare class EventTimeline {
1461
+ readonly items: _angular_core.InputSignal<readonly EventTimelineItem[]>;
1462
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<EventTimeline, never>;
1463
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EventTimeline, "kf-event-timeline", never, { "items": { "alias": "items"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
1464
+ }
1465
+
756
1466
  /**
757
1467
  * Bloque promocional sobre fondo oscuro de marca (página 37) — campañas,
758
1468
  * accesos directos o el portal de autoatención. Usa `--kf-color-tertiary-deep`
@@ -838,6 +1548,159 @@ declare class FieldLabel {
838
1548
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<FieldLabel, "kf-field-label", never, { "for": { "alias": "for"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
839
1549
  }
840
1550
 
1551
+ interface MultiSelectOption {
1552
+ value: string;
1553
+ label: string;
1554
+ }
1555
+ /**
1556
+ * Dropdown de selección múltiple con checkboxes internos. El panel flotante
1557
+ * se conecta al trigger vía `@angular/cdk/overlay` — el mismo mecanismo que
1558
+ * `Select` (ver `../select/Select.ts`) y `Dropdown` (`../dropdown/Dropdown.ts`).
1559
+ *
1560
+ * A diferencia de `Select`, el disparador no muestra un único valor sino los
1561
+ * nombres seleccionados junto a un badge circular con el conteo, y cada
1562
+ * opción del panel se marca/desmarca con un checkbox sin cerrar el panel.
1563
+ * Incluye una opción "Todas" al inicio de la lista, con estado
1564
+ * `indeterminate` cuando hay selección parcial — el mismo patrón de
1565
+ * "seleccionar todos" de `Checkbox`. "Todas" nunca se filtra por el buscador:
1566
+ * queda siempre visible y sigue actuando sobre el universo COMPLETO de
1567
+ * `options()`, no sobre el subconjunto filtrado (ver JSDoc de `toggleAll()`).
1568
+ *
1569
+ * <kf-multi-select
1570
+ * label="Sucursales"
1571
+ * [options]="branches"
1572
+ * [values]="selectedBranches()"
1573
+ * (valuesChange)="selectedBranches.set($event)"
1574
+ * />
1575
+ *
1576
+ * `width="full"` ocupa el 100% del contenedor — para multi-selects dentro
1577
+ * de layouts flex/grid donde el ancho fijo de `md` no encaja.
1578
+ *
1579
+ * <kf-multi-select width="full" [options]="branches" [values]="selectedBranches()" (valuesChange)="..." />
1580
+ */
1581
+ declare class MultiSelect {
1582
+ readonly label: _angular_core.InputSignal<string | undefined>;
1583
+ readonly options: _angular_core.InputSignal<MultiSelectOption[]>;
1584
+ readonly values: _angular_core.InputSignal<string[]>;
1585
+ readonly placeholder: _angular_core.InputSignal<string>;
1586
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1587
+ /**
1588
+ * Ancho del control. 'md' (240px) es el default histórico y preserva el
1589
+ * comportamiento actual sin cambios para consumidores existentes.
1590
+ * 'full' ocupa el 100% del contenedor — para multi-selects dentro de
1591
+ * layouts flex/grid donde el ancho fijo no encaja (ej. un footer de
1592
+ * sidebar).
1593
+ */
1594
+ readonly width: _angular_core.InputSignal<"sm" | "md" | "lg" | "full">;
1595
+ /**
1596
+ * Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
1597
+ * se activa solo cuando `options().length` supera `searchThreshold()`. Si
1598
+ * el consumidor define `true`/`false` explícitamente, ese valor manda por
1599
+ * sobre el umbral automático. Mismo criterio que `Select`.
1600
+ */
1601
+ readonly searchable: _angular_core.InputSignal<boolean | undefined>;
1602
+ readonly searchThreshold: _angular_core.InputSignal<number>;
1603
+ readonly valuesChange: _angular_core.OutputEmitterRef<string[]>;
1604
+ private readonly instanceId;
1605
+ readonly triggerId: string;
1606
+ readonly listboxId: string;
1607
+ readonly isOpen: _angular_core.WritableSignal<boolean>;
1608
+ readonly searchQuery: _angular_core.WritableSignal<string>;
1609
+ readonly activeIndex: _angular_core.WritableSignal<number>;
1610
+ readonly selectedCount: _angular_core.Signal<number>;
1611
+ readonly allSelected: _angular_core.Signal<boolean>;
1612
+ readonly isPartiallySelected: _angular_core.Signal<boolean>;
1613
+ readonly showSearch: _angular_core.Signal<boolean>;
1614
+ /** Opciones visibles tras aplicar el filtro de búsqueda ("Todas" nunca se filtra). */
1615
+ readonly filteredOptions: _angular_core.Signal<MultiSelectOption[]>;
1616
+ /**
1617
+ * Texto del disparador: placeholder si no hay selección, o los nombres
1618
+ * seleccionados unidos por coma (el conteo va aparte, en el badge). Con
1619
+ * muchas opciones el `text-overflow: ellipsis` del `.kf-multi-select__value`
1620
+ * corta el texto — el badge de conteo siempre queda visible al lado.
1621
+ */
1622
+ readonly summaryText: _angular_core.Signal<string>;
1623
+ private readonly triggerRef;
1624
+ private readonly panelTemplate;
1625
+ private readonly searchInputRef;
1626
+ private readonly overlay;
1627
+ constructor();
1628
+ isSelected(value: string): boolean;
1629
+ toggle(): void;
1630
+ toggleOption(value: string): void;
1631
+ /**
1632
+ * Alterna entre "todas seleccionadas" y "ninguna seleccionada". Actúa
1633
+ * siempre sobre el universo COMPLETO de `options()`, no sobre
1634
+ * `filteredOptions()` — con un buscador activo, "Todas" no se filtra ni
1635
+ * cambia de significado: seguir marcando/desmarcando únicamente lo visible
1636
+ * sería sorprendente para quien ya tenía otras opciones (no visibles en el
1637
+ * filtro actual) seleccionadas de antes.
1638
+ */
1639
+ toggleAll(): void;
1640
+ onSearchInput(event: Event): void;
1641
+ /** Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp simple, sin wraparound. */
1642
+ moveActive(delta: number): void;
1643
+ toggleActive(): void;
1644
+ onEscape(): void;
1645
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MultiSelect, never>;
1646
+ 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; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; }, { "valuesChange": "valuesChange"; }, never, never, true, never>;
1647
+ }
1648
+
1649
+ type FilterFieldType = 'select' | 'multi-select' | 'search';
1650
+ interface FilterFieldConfig {
1651
+ key: string;
1652
+ type: FilterFieldType;
1653
+ label?: string;
1654
+ placeholder?: string;
1655
+ width?: string;
1656
+ icon?: IconName;
1657
+ showDivider?: boolean;
1658
+ }
1659
+ type FilterValue = string | string[];
1660
+ interface FilterFieldChange {
1661
+ key: string;
1662
+ value: FilterValue;
1663
+ }
1664
+ /**
1665
+ * Fila de filtros config-driven (select / multi-select / búsqueda) para el
1666
+ * header de una tabla, compuesta con piezas YA existentes de la librería
1667
+ * (`kf-select`, `kf-multi-select`, `kf-input`, `kf-divider`, `kf-collapsible`).
1668
+ *
1669
+ * No tiene lógica de filtrado propia: solo renderiza los controles descritos
1670
+ * por `fields()` y emite `fieldChange` cuando alguno cambia — filtrar filas
1671
+ * sigue siendo responsabilidad de quien lo consume.
1672
+ *
1673
+ * En escritorio los campos se muestran en una fila; en mobile/tablet
1674
+ * (≤768px, mismo breakpoint que `kf-portal-header`) se colapsan dentro de un
1675
+ * `kf-collapsible`.
1676
+ *
1677
+ * <kf-filter
1678
+ * [fields]="[
1679
+ * { key: 'model', type: 'multi-select', label: 'Modelo', placeholder: 'Todos los modelos', showDivider: true },
1680
+ * { key: 'search', type: 'search', label: 'Buscar vehículo', placeholder: 'Ingresa N° de orden o placa', width: '280px' },
1681
+ * ]"
1682
+ * [values]="{ model: selectedModels(), search: searchTerm() }"
1683
+ * [options]="{ model: modelOptions() }"
1684
+ * (fieldChange)="onFieldChange($event)"
1685
+ * />
1686
+ */
1687
+ declare class Filter {
1688
+ readonly fields: _angular_core.InputSignal<FilterFieldConfig[]>;
1689
+ readonly values: _angular_core.InputSignal<Record<string, FilterValue>>;
1690
+ readonly options: _angular_core.InputSignal<Record<string, string[]>>;
1691
+ readonly mobileTitle: _angular_core.InputSignal<string>;
1692
+ readonly fieldChange: _angular_core.OutputEmitterRef<FilterFieldChange>;
1693
+ protected readonly multiOptionsByKey: _angular_core.Signal<Record<string, MultiSelectOption[]>>;
1694
+ protected singleValueFor(key: string): string;
1695
+ protected multiValueFor(key: string): string[];
1696
+ protected fieldWidth(field: FilterFieldConfig): string;
1697
+ protected optionsFor(key: string): string[];
1698
+ protected multiOptionsFor(key: string): MultiSelectOption[];
1699
+ protected onFieldChange(key: string, value: FilterValue): void;
1700
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Filter, never>;
1701
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Filter, "kf-filter", never, { "fields": { "alias": "fields"; "required": true; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "mobileTitle": { "alias": "mobileTitle"; "required": false; "isSignal": true; }; }, { "fieldChange": "fieldChange"; }, never, never, true, never>;
1702
+ }
1703
+
841
1704
  /** Forma normalizada interna de cada opción, ya resuelta a value/label. */
842
1705
  interface FilterChipOption {
843
1706
  value: string;
@@ -850,9 +1713,10 @@ interface FilterChipOption {
850
1713
  * a la vez) o como objeto { value, label } cuando el texto mostrado difiere
851
1714
  * del valor interno.
852
1715
  *
853
- * El chip activo se resalta con el color "ink" oscuro (--kf-color-ink), no
854
- * con el color primario de marca: así lo define el sistema de diseño de
855
- * referencia para este componente en particular.
1716
+ * El chip activo se resalta con `--kf-color-primary` — mismo color de marca
1717
+ * que `kf-tabs`/`kf-select` para su propio estado activo, así el filtro
1718
+ * seleccionado se lee con el color del tema activo, no un negro fijo que
1719
+ * no cambia entre marcas.
856
1720
  *
857
1721
  * <kf-filter-chips [options]="['Todos', 'Activos', 'Archivados']"
858
1722
  * [value]="filtro"
@@ -876,92 +1740,132 @@ declare class FilterChips {
876
1740
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<FilterChips, "kf-filter-chips", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
877
1741
  }
878
1742
 
879
- interface FolderTabOption {
880
- value: string;
1743
+ /** Un link del pie — legales, contacto, soporte. */
1744
+ interface FooterLink {
881
1745
  label: string;
882
- icon?: IconName;
1746
+ href?: string;
883
1747
  }
884
1748
  /**
885
- * Marca el contenido que `kf-folder-tabs` debe proyectar dentro de su
886
- * panel. Sin lógica propia — existe solo como token de tipo para que
887
- * `contentChild(FolderTabsPanel)` pueda detectar si el consumidor proyectó
888
- * algo (mismo mecanismo que `kf-dropdown` usa con `contentChild(DropdownPanel)`;
889
- * acá no hace falta un componente completo, alcanza con una directiva de
890
- * atributo porque el panel no necesita comportamiento propio, solo marcar
891
- * su presencia).
892
- *
893
- * <div kf-folder-tabs-panel>...</div>
1749
+ * Pie de página general del portal (página 38): logo + copyright a la
1750
+ * izquierda, links legales/soporte empujados a la derecha. Logo y copyright
1751
+ * son inputs (cada portal del ecosistema — LATAM, OTD, Star — trae su
1752
+ * propia marca y texto legal); `links` es la lista de accesos, típicamente
1753
+ * placeholders hasta que existan destinos reales.
1754
+ *
1755
+ * <kf-footer
1756
+ * logoSrc="images/logo-kaufmann-dark.png"
1757
+ * logoAlt="Grupo Kaufmann"
1758
+ * copyright="© 2026 Kaufmann LATAM · Todos los derechos reservados"
1759
+ * [links]="[
1760
+ * { label: 'Políticas de privacidad' },
1761
+ * { label: 'Contacto' },
1762
+ * { label: 'Sugerencias y reclamos' },
1763
+ * ]"
1764
+ * />
894
1765
  */
895
- declare class FolderTabsPanel {
896
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabsPanel, never>;
897
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FolderTabsPanel, "[kf-folder-tabs-panel]", never, {}, {}, never, never, true, never>;
898
- }
899
- /**
900
- * Tabs con forma de "carpeta": el borde superior queda redondeado y el borde
901
- * inferior de la pestaña activa se funde visualmente con el panel de
902
- * contenido que va debajo (misma superficie, sin línea divisoria).
903
- *
904
- * Al cambiar de pestaña, la que se activa se "abre": gira sobre su borde
905
- * inferior (hinge 3D) y sube con un pequeño rebote. Es CSS solo: la clase
906
- * `--active` se mueve de un botón a otro, y eso vuelve a disparar el keyframe
907
- * sin tocar JS.
908
- *
909
- * El panel de contenido es un `<ng-content>` OPCIONAL — si el consumidor no
910
- * proyecta nada, `kf-folder-tabs` renderiza solo la fila de pestañas (mismo
911
- * comportamiento que antes de que existiera este slot, detectado con
912
- * `contentChild()` sin `.required`, igual patrón que usa `kf-dropdown` para
913
- * saber si hay un `kf-dropdown-panel` proyectado). Si SÍ proyecta contenido,
914
- * se envuelve en un panel con la esquina superior-izquierda recta (nace ahí
915
- * la primera pestaña, siempre pegada al borde izquierdo de la fila — un
916
- * radio ahí competiría visualmente con el propio borde redondeado de esa
917
- * pestaña) y las otras tres redondeadas. El `border-bottom` de 2px de la
918
- * pestaña activa (ver `.kf-folder-tabs__tab--active` más abajo) es la señal
919
- * visual de conexión con el panel para CUALQUIER pestaña activa, sin
920
- * necesitar mover ni medir nada.
921
- *
922
- * El borde izquierdo de toda la fila (y por lo tanto de la esquina recta
923
- * del panel) depende de que ninguna pestaña se corra al cambiar de estado
924
- * — ver `transform-origin: bottom left` en `.kf-folder-tabs__tab`: con el
925
- * origen centrado (como estaba antes), el `scale()` de la pestaña inactiva
926
- * encogía el borde izquierdo unos px, y ese corrimiento sub-pixel es lo que
927
- * hacía que este mismo diseño (esquina fija) se viera "roto" cuando la
928
- * pestaña activa no era la primera. Con el origen anclado a la izquierda,
929
- * el corrimiento desaparece y la esquina fija vuelve a ser una solución
930
- * válida y simple — sin necesitar medir la pestaña activa por JS.
931
- *
932
- * Antes de este slot, el patrón era que el consumidor pusiera su propia
933
- * card como HERMANO de `kf-folder-tabs` y anulara a mano el radio superior
934
- * completo (`border-top-left-radius: 0`, `border-top-right-radius: 0` — ver
935
- * el caso original de `web-portal-latam-frontend`). Encapsular esto acá
936
- * evita que cada consumidor repita esas reglas a mano.
937
- *
938
- * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
939
- * <div kf-folder-tabs-panel>contenido de la pestaña activa</div>
940
- * </kf-folder-tabs>
1766
+ declare class Footer {
1767
+ readonly logoSrc: _angular_core.InputSignal<string | undefined>;
1768
+ readonly logoAlt: _angular_core.InputSignal<string>;
1769
+ readonly copyright: _angular_core.InputSignal<string>;
1770
+ readonly links: _angular_core.InputSignal<FooterLink[]>;
1771
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Footer, never>;
1772
+ 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>;
1773
+ }
1774
+
1775
+ /** Opciones de `formatNumber` — todas opcionales. */
1776
+ interface FormatNumberOptions {
1777
+ /** Cantidad de decimales a mostrar. Default `0`. */
1778
+ decimals?: number;
1779
+ /** Locale de `Intl`/`toLocaleString` — decide separador de miles y de
1780
+ * decimales. Default `'es'` (punto de miles, coma decimal). */
1781
+ locale?: string;
1782
+ }
1783
+ /**
1784
+ * Separador de miles genérico — sin signo, sin asumir que el valor es
1785
+ * dinero. Cubre casos como kilometraje/horómetro (`formatNumber(45231,
1786
+ * { locale: 'en-US' })` → `'45,231'`), no solo moneda.
941
1787
  *
942
- * <!-- o, sin contenido, como antes: -->
943
- * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event" />
1788
+ * formatNumber(16800); // '16.800'
1789
+ * formatNumber(16800.5, { decimals: 2 }); // '16.800,50'
1790
+ * formatNumber(45231, { locale: 'en-US' }); // '45,231'
944
1791
  */
945
- declare class FolderTabs {
946
- readonly options: _angular_core.InputSignal<FolderTabOption[]>;
947
- readonly value: _angular_core.InputSignal<string>;
948
- readonly valueChange: _angular_core.OutputEmitterRef<string>;
949
- /**
950
- * Detecta si el consumidor proyectó contenido marcado con
951
- * `kf-folder-tabs-panel` — sin `.required`, así que es `undefined`
952
- * cuando no hay nada proyectado (mismo patrón que usa `kf-dropdown` con
953
- * `contentChild(DropdownPanel)` para saber si hay un panel que
954
- * attachear). `hasPanel()` decide si el wrapper del panel participa del
955
- * layout (`display: contents` si no).
956
- */
957
- private readonly projectedPanel;
958
- protected readonly hasPanel: _angular_core.Signal<boolean>;
959
- select(value: string): void;
960
- /** Navegación estándar de ARIA tabs: flechas mueven el foco y activan la pestaña. */
961
- onKeydown(event: KeyboardEvent, currentValue: string): void;
962
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabs, never>;
963
- 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"], ["[kf-folder-tabs-panel]"], true, never>;
1792
+ declare function formatNumber(value: number, options?: FormatNumberOptions): string;
1793
+ /** Opciones de `formatCurrency` — extiende `FormatNumberOptions` con el
1794
+ * signo/prefijo de moneda. */
1795
+ interface FormatCurrencyOptions extends FormatNumberOptions {
1796
+ /** Símbolo o prefijo antepuesto al monto. Default `'$'`. */
1797
+ sign?: string;
964
1798
  }
1799
+ /**
1800
+ * Formatea un monto de dinero — compone sobre `formatNumber` agregando el
1801
+ * signo. Mismos defaults que la convención de OTD (punto de miles, sin
1802
+ * decimales, prefijo `$`), overrideables para otra moneda/país/formato:
1803
+ *
1804
+ * formatCurrency(16800); // '$16.800'
1805
+ * formatCurrency(16800.5, { decimals: 2 }); // '$16.800,50'
1806
+ * formatCurrency(16800, { sign: '' }); // '16.800'
1807
+ * formatCurrency(16800, { sign: 'US$', locale: 'en-US' }); // 'US$16,800'
1808
+ */
1809
+ declare function formatCurrency(amount: number, options?: FormatCurrencyOptions): string;
1810
+ /**
1811
+ * Inverso de `formatNumber` — recupera el número crudo de un valor ya
1812
+ * formateado con el mismo `locale`. Solo soporta `locale` cuyo separador de
1813
+ * miles y decimal sean `'.'`/`','` (`es`) o `','`/`'.'` (`en-US`) — son los
1814
+ * 2 casos reales del ecosistema hoy; si aparece un tercer locale con otros
1815
+ * separadores, extender acá antes de usarlo.
1816
+ */
1817
+ declare function parseNumber(formatted: string, locale?: string): number;
1818
+ /**
1819
+ * Inverso de `formatCurrency` — recupera el número crudo de un monto ya
1820
+ * formateado, descartando cualquier signo/prefijo de moneda.
1821
+ *
1822
+ * parseCurrency('$16.800'); // 16800
1823
+ */
1824
+ declare function parseCurrency(formatted: string, locale?: string): number;
1825
+ /** "12:40" — hora en formato de 24 horas. */
1826
+ declare function formatClockTime(date: Date): string;
1827
+ /**
1828
+ * Quita espacios (incluido cualquier espacio en blanco intermedio, no solo
1829
+ * al principio/final) y puntos de un identificador de documento — un
1830
+ * espacio invisible (ej. pegado por accidente desde otra fuente) es un
1831
+ * motivo de rechazo silencioso real que un usuario no sabe explicarse.
1832
+ * Genérico: sirve tanto para RUT (`formatChileanRut` ya lo usa por
1833
+ * dentro) como para cualquier otro identificador con el mismo problema.
1834
+ *
1835
+ * sanitizeDocumentId(' 51 00004-8 '); // '5100004-8'
1836
+ * sanitizeDocumentId('12.345.678-9'); // '12345678-9'
1837
+ */
1838
+ declare function sanitizeDocumentId(raw: string): string;
1839
+ /**
1840
+ * Formatea un RUT chileno insertando el guion automáticamente a medida
1841
+ * que se escribe — igual que un input de tarjeta de crédito, el usuario
1842
+ * solo tipea dígitos (y opcionalmente `k`/`K` como dígito verificador) y
1843
+ * el guion aparece solo antes del último carácter. Solo da forma al
1844
+ * string — no valida si el dígito verificador es matemáticamente
1845
+ * correcto (ver `isValidChileanRut` para eso) ni si la cuenta existe
1846
+ * (eso lo confirma GAM del lado servidor al autenticar).
1847
+ *
1848
+ * formatChileanRut('123456789'); // '12345678-9'
1849
+ * formatChileanRut('5100004-8'); // '5100004-8' (ya formateado, idempotente)
1850
+ * formatChileanRut('5100004k'); // '5100004-k'
1851
+ * formatChileanRut('510000 4-8 '); // '5100004-8' (espacios ignorados)
1852
+ */
1853
+ declare function formatChileanRut(raw: string): string;
1854
+ /**
1855
+ * Valida un RUT chileno YA formateado (`cuerpo-DV`, ej. `'12345678-9'`)
1856
+ * contra el algoritmo módulo 11 real — a diferencia del `pattern` de
1857
+ * `document-id.config.ts` en cada portal consumidor (que solo exige la
1858
+ * FORMA: dígitos + guion + dígito verificador), esta función confirma
1859
+ * que el dígito verificador sea matemáticamente correcto para ese
1860
+ * cuerpo. Sigue sin confirmar que la cuenta exista — eso solo lo sabe
1861
+ * GAM al autenticar — pero atrapa el error tipográfico más común
1862
+ * (invertir/cambiar un dígito) antes de intentar el login.
1863
+ *
1864
+ * isValidChileanRut('20236276-1'); // true (DV real de ese cuerpo)
1865
+ * isValidChileanRut('20236276-8'); // false (DV real es '1', no '8')
1866
+ * isValidChileanRut('no-es-rut'); // false (ni siquiera tiene la forma)
1867
+ */
1868
+ declare function isValidChileanRut(rut: string): boolean;
965
1869
 
966
1870
  /**
967
1871
  * Input de formulario apilado: label arriba (en mayúsculas vía CSS), campo con
@@ -994,12 +1898,26 @@ declare class FolderTabs {
994
1898
  * de una sola acción.
995
1899
  *
996
1900
  * <kf-input type="password" label="Contraseña" toggleable formControlName="password" />
1901
+ *
1902
+ * `readonly` es distinto de `disabled`: un campo `disabled` no recibe foco
1903
+ * por teclado ni se puede seleccionar/copiar su texto (y se lee como
1904
+ * "deshabilitado" en un lector de pantalla); un campo `readonly` sí — el
1905
+ * valor se puede ver, enfocar y copiar, solo no se puede editar. Usar
1906
+ * `readonly` para mostrar un dato existente que el usuario puede querer
1907
+ * copiar (ej. un documento de identidad en un formulario de perfil), y
1908
+ * `disabled` para un campo genuinamente inactivo (ej. mientras un guardado
1909
+ * está en vuelo).
1910
+ *
1911
+ * <kf-input label="DNI" readonly formControlName="documentId" />
997
1912
  */
998
1913
  declare class Input implements ControlValueAccessor, AfterViewInit {
999
1914
  private static nextId;
1000
1915
  private readonly nativeInput;
1001
1916
  readonly label: _angular_core.InputSignal<string | undefined>;
1002
- /** Marca el label con un asterisco visual — no aplica `required` al `<input>` nativo por sí solo. */
1917
+ /** Marca el label con un asterisco visual y aplica `required` al `<input>`
1918
+ * nativo — el atributo HTML ya expone el estado obligatorio a lectores de
1919
+ * pantalla (mapeo implícito a `aria-required`), sin necesidad de un
1920
+ * `aria-required` explícito redundante. */
1003
1921
  readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
1004
1922
  /** Ícono líder, renderizado vía `kf-icon`. */
1005
1923
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
@@ -1010,6 +1928,11 @@ declare class Input implements ControlValueAccessor, AfterViewInit {
1010
1928
  /** Mensaje de error con `role="alert"` — reemplaza a `hint` mientras esté presente. */
1011
1929
  readonly errorMessage: _angular_core.InputSignal<string | undefined>;
1012
1930
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1931
+ /** Ver JSDoc de la clase para la diferencia con `disabled`. No pasa por
1932
+ * Reactive Forms (a diferencia de `disabled`, que también puede llegar
1933
+ * vía `FormControl.disable()`) — es siempre una decisión explícita del
1934
+ * consumidor del componente. */
1935
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, unknown>;
1013
1936
  readonly type: _angular_core.InputSignal<string>;
1014
1937
  readonly placeholder: _angular_core.InputSignal<string>;
1015
1938
  /** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
@@ -1050,39 +1973,787 @@ declare class Input implements ControlValueAccessor, AfterViewInit {
1050
1973
  */
1051
1974
  protected onTogglePasswordVisibility(): void;
1052
1975
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Input, never>;
1053
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Input, "kf-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "suffix": { "alias": "suffix"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "toggleable": { "alias": "toggleable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1976
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Input, "kf-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "suffix": { "alias": "suffix"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "toggleable": { "alias": "toggleable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1977
+ }
1978
+
1979
+ type JourneyStepStatus = 'done' | 'current' | 'delayed' | 'pending';
1980
+ type JourneyStepperSurface = 'dark' | 'card';
1981
+ /**
1982
+ * Paleta de color de los estados, independiente de `orientation`:
1983
+ *
1984
+ * - `'brand'` (default): azul de marca para completado/actual, sin
1985
+ * semántica de semáforo. Para cuando horizontal y vertical representan
1986
+ * el mismo journey en distintos tamaños de pantalla.
1987
+ * - `'semantic'`: verde/amarillo/rojo real por paso (solo con
1988
+ * `orientation="vertical"`, la única con 4 estados incluido `delayed`).
1989
+ * Para cuando cada paso comunica su propio estado puntual.
1990
+ */
1991
+ type JourneyStepperColorScheme = 'brand' | 'semantic';
1992
+ interface JourneyStep {
1993
+ key: string;
1994
+ label: string;
1995
+ icon: IconName;
1996
+ /** Solo con `orientation="vertical"` — en horizontal el status se calcula desde `currentStep`. */
1997
+ status?: JourneyStepStatus;
1998
+ /** Texto bajo el label — solo con `orientation="vertical"`. */
1999
+ date?: string;
2000
+ }
2001
+ /**
2002
+ * Progreso de un flujo de N pasos.
2003
+ *
2004
+ * - `orientation` (`'horizontal'` default | `'vertical'`): horizontal
2005
+ * deriva done/current/pending de `currentStep`; vertical usa
2006
+ * `steps[].status` explícito, con `delayed` como cuarto estado.
2007
+ * - `surface` (`'dark'` default | `'card'`): fondo sólido oscuro de marca,
2008
+ * o superficie clara con borde+sombra.
2009
+ * - `colorScheme` (`'brand'` default | `'semantic'`): ver `JourneyStepperColorScheme`.
2010
+ *
2011
+ * <kf-journey-stepper surface="card" [steps]="steps" [currentStep]="4" (stepClick)="onNavigate($event)" />
2012
+ * <kf-journey-stepper orientation="vertical" [steps]="unitSteps" />
2013
+ * <kf-journey-stepper orientation="vertical" colorScheme="semantic" [steps]="unitStepsWithDelays" />
2014
+ */
2015
+ declare class JourneyStepper {
2016
+ readonly steps: _angular_core.InputSignal<JourneyStep[]>;
2017
+ readonly orientation: _angular_core.InputSignal<"horizontal" | "vertical">;
2018
+ readonly surface: _angular_core.InputSignal<JourneyStepperSurface>;
2019
+ readonly colorScheme: _angular_core.InputSignal<JourneyStepperColorScheme>;
2020
+ /** Índice del paso actual — solo con `orientation="horizontal"`. */
2021
+ readonly currentStep: _angular_core.InputSignal<number>;
2022
+ /** Se emite con `step.key` al clickear un paso — solo activo con `orientation="horizontal"`. */
2023
+ readonly stepClick: _angular_core.OutputEmitterRef<string>;
2024
+ private readonly track;
2025
+ private readonly destroyRef;
2026
+ /** `true` cuando el contenido desborda el ancho visible — controla el fade de los bordes. */
2027
+ protected readonly hasOverflow: _angular_core.WritableSignal<boolean>;
2028
+ protected readonly clickable: _angular_core.Signal<boolean>;
2029
+ constructor();
2030
+ private measureOverflow;
2031
+ protected readonly resolvedSteps: _angular_core.Signal<(JourneyStep & {
2032
+ status: JourneyStepStatus;
2033
+ })[]>;
2034
+ protected dotIcon(status: JourneyStepStatus, stepIcon: IconName): IconName;
2035
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<JourneyStepper, never>;
2036
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<JourneyStepper, "kf-journey-stepper", never, { "steps": { "alias": "steps"; "required": true; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "surface": { "alias": "surface"; "required": false; "isSignal": true; }; "colorScheme": { "alias": "colorScheme"; "required": false; "isSignal": true; }; "currentStep": { "alias": "currentStep"; "required": false; "isSignal": true; }; }, { "stepClick": "stepClick"; }, never, never, true, never>;
1054
2037
  }
1055
2038
 
1056
2039
  type LinkVariant = 'default' | 'muted';
1057
2040
  /**
1058
2041
  * Acción de bajo énfasis con apariencia de texto — "¿Olvidaste tu
1059
- * contraseña?", "Volver a iniciar sesión". Antes de este componente,
1060
- * `web-portal-latam-frontend` reimplementaba esto dos veces (`.login__forgot`
1061
- * como `<a href="#">` con `preventDefault()`, `.login__back` como
1062
- * `<button>`), cada una con su propio `:focus-visible` copiado literal.
1063
- *
1064
- * Es siempre un `<button type="button">` por dentro, nunca un `<a>`: ninguno
1065
- * de los dos casos reales navega a otra URL, ambos disparan una acción en la
1066
- * misma pantalla (cambiar de vista, someter un flujo). Si en el futuro un
1067
- * consumidor necesita navegación real con href, esa es una discusión de API
1068
- * distinta (`kf-link` con `[routerLink]`/`href`), no se fuerza acá sin caso
1069
- * de uso confirmado.
2042
+ * contraseña?", "Volver a iniciar sesión", o un link real de navegación
2043
+ * como "Ir a pagos →".
2044
+ *
2045
+ * Sin `href` (default) es un `<button type="button">`: dispara una acción
2046
+ * en la misma pantalla, vía `(click)`. Con `href`, es un `<a>` real: navega
2047
+ * a otra URL. `showArrow` agrega un ícono `chevron-right` al final — para
2048
+ * el patrón "texto + flecha" de un link que lleva a otro lado.
1070
2049
  *
1071
2050
  * `variant="default"` (color primario, subrayado en hover) es para la
1072
- * acción de mayor jerarquía relativa de la pantalla (ej. "¿Olvidaste tu
1073
- * contraseña?"); `variant="muted"` (gris que pasa a primario en hover) es
1074
- * para acciones de retroceso/salida (ej. "← Volver a iniciar sesión").
2051
+ * acción de mayor jerarquía relativa de la pantalla; `variant="muted"`
2052
+ * (gris que pasa a primario en hover) es para acciones de retroceso/salida.
1075
2053
  *
1076
2054
  * <kf-link (click)="onForgotPassword()">¿Olvidaste tu contraseña?</kf-link>
1077
2055
  * <kf-link variant="muted" (click)="onBack()">← Volver a iniciar sesión</kf-link>
2056
+ * <kf-link href="/pagos" showArrow>Ir a pagos</kf-link>
1078
2057
  */
1079
2058
  declare class Link {
1080
2059
  readonly variant: _angular_core.InputSignal<LinkVariant>;
1081
2060
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
2061
+ /** Si se pasa, renderiza un `<a>` real en vez de `<button>` — navega en vez de disparar `(click)`. */
2062
+ readonly href: _angular_core.InputSignal<string | undefined>;
2063
+ /** Ícono `chevron-right` al final — para el patrón "texto + flecha" de un link que lleva a otro lado. */
2064
+ readonly showArrow: _angular_core.InputSignalWithTransform<boolean, unknown>;
1082
2065
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Link, never>;
1083
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Link, "kf-link", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2066
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Link, "kf-link", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "showArrow": { "alias": "showArrow"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2067
+ }
2068
+
2069
+ /**
2070
+ * Motor de mapas configurado a nivel app, no por uso — mismo criterio que
2071
+ * `API_CONFIG` en `packages/api-client` de `route-assistance-monorepo`: el
2072
+ * access token de Mapbox es un dato de despliegue (cada app/entorno tiene
2073
+ * el propio), no algo que cada `<kf-map>` deba repetir.
2074
+ */
2075
+ interface MapConfig {
2076
+ /** Access token público de Mapbox (`pk.*`) de la cuenta del proyecto que consume la librería. */
2077
+ accessToken: string;
2078
+ /**
2079
+ * Estilo de Mapbox Studio propio del cliente, si lo tiene. Cuando se
2080
+ * define, gana siempre sobre `theme()` del componente — un estilo de
2081
+ * marca custom no tiene una variante "light"/"dark" que elegir, ya viene
2082
+ * resuelto. Sin esto, `kf-map` usa el estilo `light-v11`/`dark-v11` de
2083
+ * Mapbox según `theme()`.
2084
+ */
2085
+ styleUrl?: string;
2086
+ }
2087
+ declare const MAP_CONFIG: InjectionToken<MapConfig>;
2088
+ /**
2089
+ * `provide*` idiomático (mismo patrón que `provideHttpClient`/`provideRouter`)
2090
+ * para inyectar una sola vez en `app.config.ts` de cada app consumidora:
2091
+ *
2092
+ * providers: [provideMapConfig({ accessToken: 'pk.xxx' })]
2093
+ */
2094
+ declare function provideMapConfig(config: MapConfig): Provider;
2095
+
2096
+ /**
2097
+ * Vocabulario de temas de `kf-map`, separado de `MapView.ts` para que
2098
+ * `MapThemePicker` (el panel flotante del control `mapStyle`) pueda
2099
+ * importarlo sin crear un ciclo `MapView → MapControls → MapThemePicker →
2100
+ * MapView`.
2101
+ *
2102
+ * `streets`/`satellite`/`outdoors` se suman a los `light`/`dark`
2103
+ * originales — los 3 IDs de estilo (`streets-v12`, `satellite-streets-v12`,
2104
+ * `outdoors-v12`) están confirmados contra la cuenta/token real del
2105
+ * ecosistema: la API de estilos de Mapbox es por cuenta, no por versión de
2106
+ * `mapbox-gl`, así que cualquier duda se resuelve probando contra el token
2107
+ * real, no leyendo el número de versión instalado.
2108
+ */
2109
+ type MapTheme = 'light' | 'dark' | 'streets' | 'satellite' | 'outdoors';
2110
+ declare const STYLE_BY_THEME: Record<MapTheme, string>;
2111
+ /** Orden fijo en que `MapThemePicker` lista las 5 opciones — predecible
2112
+ * (siempre el mismo orden en toda pantalla que use el picker), no importa
2113
+ * cuál esté activa. */
2114
+ declare const THEME_ORDER: MapTheme[];
2115
+ /** Nombre en español de cada tema, para el `aria-label`/label del control
2116
+ * `mapStyle` y las opciones del picker — anuncia qué tema rige HOY o qué
2117
+ * tema se está por elegir, no solo "otro tema distinto". */
2118
+ declare const THEME_LABEL: Record<MapTheme, string>;
2119
+
2120
+ /**
2121
+ * Cada pieza es independiente entre sí — agregar/sacar un `MapControlKind`
2122
+ * de `controls` en `kf-map` no reacomoda ni afecta a los demás. Array vacío
2123
+ * (default de `kf-map`) es "ningún control": opt-in explícito del
2124
+ * consumidor, nunca activado por default (ver JSDoc de `controls` en
2125
+ * `MapView.ts`).
2126
+ *
2127
+ * zoom — botones +/- reales (`map.zoomIn()`/`zoomOut()`).
2128
+ * fullscreen — expandir/contraer el mapa (Fullscreen API nativa).
2129
+ * mapStyle — ciclar entre los temas de `MapTheme`.
2130
+ * recenter — re-ejecuta el encuadre a markers/route bajo demanda.
2131
+ * cluster — togglear agrupamiento nativo de markers cercanos.
2132
+ * follow — la cámara sigue al marker `live` mientras se mueve.
2133
+ */
2134
+ type MapControlKind = 'zoom' | 'fullscreen' | 'mapStyle' | 'recenter' | 'cluster' | 'follow';
2135
+ /**
2136
+ * Barra de controles flotantes de `kf-map` — pieza puramente presentacional,
2137
+ * sin estado propio: todo lo que sabe se lo pasa `MapView` por input, todo
2138
+ * lo que hace lo reporta por output para que `MapView` (única dueña del
2139
+ * `Map` de Mapbox) decida qué hacer. Vive en la misma carpeta que `MapView`
2140
+ * a propósito (mismo criterio que `DropdownItem`/`DropdownPanel` con
2141
+ * `Dropdown`): el acoplamiento es real, no de nombre — no tiene sentido
2142
+ * fuera de un `kf-map` que la controle.
2143
+ *
2144
+ * Un solo stack vertical, siempre en la esquina superior derecha del mapa
2145
+ * (fijado por `MapView.css`, no acá): es donde ya vivía el `NavigationControl`
2146
+ * de Mapbox que este componente reemplaza, y la atribución obligatoria de
2147
+ * Mapbox (`AttributionControl`, nunca oculta) vive abajo-derecha — un solo
2148
+ * stack evita que dos grupos floten en esquinas distintas y compitan por
2149
+ * espacio en un mapa `size="sm"` de 200px de alto.
2150
+ *
2151
+ * El stack no es una lista continua de botones idénticos: cada grupo lógico
2152
+ * es su propia "card" flotante (`kf-radius-md`+`kf-shadow-md`+fondo blanco,
2153
+ * ver `.kf-map-controls__group` en `MapControls.css`), separadas entre sí
2154
+ * por el `gap` del `:host` — mismo lenguaje ya verificado visualmente en
2155
+ * `route-assistance-monorepo/design/mockups/operations-final.html`
2156
+ * (`.map__ctrl-group`). Orden fijo de arriba a abajo, independiente del
2157
+ * orden en el array `controls` del consumidor (para que dos pantallas que
2158
+ * piden los mismos controles en distinto orden se vean IGUAL):
2159
+ *
2160
+ * 1. zoom — magnificación (par +/-, unido por un divisor, sin gap entre sí).
2161
+ * 2. cámara — recenter + follow (posición de la cámara).
2162
+ * 3. datos — cluster (densidad de los markers, eje distinto de cámara).
2163
+ * 4. chrome — mapStyle + fullscreen (nada del mapa en sí).
2164
+ *
2165
+ * Cada grupo usa `@if` sobre AL MENOS UNO de sus controles (ej. el grupo de
2166
+ * cámara aparece con solo `recenter`, solo `follow`, o ambos) — agregar o
2167
+ * sacar un control nunca reacomoda ni depende de los demás grupos.
2168
+ *
2169
+ * Todos los botones siguen siendo `kf-button[variant=surface][shape=circle]`
2170
+ * — sin tocar `Button.ts`: el fondo/sombra propios de `surface` se anulan
2171
+ * SOLO dentro de un `.kf-map-controls__group` vía una custom property CSS
2172
+ * (ver el comentario junto a esa clase en `MapControls.css` sobre por qué
2173
+ * eso sí atraviesa la encapsulación de Angular y un selector normal no).
2174
+ */
2175
+ declare class MapControls {
2176
+ readonly controls: _angular_core.InputSignal<MapControlKind[]>;
2177
+ readonly clusterActive: _angular_core.InputSignal<boolean>;
2178
+ readonly followActive: _angular_core.InputSignal<boolean>;
2179
+ /** Sin ningún marker `live`, togglear "seguir" no tendría ningún efecto
2180
+ * visible — se deshabilita en vez de dejar un botón que parece no hacer nada. */
2181
+ readonly canFollow: _angular_core.InputSignal<boolean>;
2182
+ readonly isFullscreen: _angular_core.InputSignal<boolean>;
2183
+ /** Nombre en español del tema actualmente resuelto (ej. "Calles"), para
2184
+ * anunciar en el `aria-label` del botón de estilo qué se está por perder,
2185
+ * no solo que "algo" va a cambiar. */
2186
+ readonly styleLabel: _angular_core.InputSignal<string>;
2187
+ /** Tema resuelto HOY (`light|dark|...`) — a diferencia de `styleLabel`
2188
+ * (ya traducido a texto), esto es lo que `MapThemePicker` necesita para
2189
+ * marcar la opción activa. */
2190
+ readonly activeTheme: _angular_core.InputSignal<MapTheme>;
2191
+ readonly zoomIn: _angular_core.OutputEmitterRef<void>;
2192
+ readonly zoomOut: _angular_core.OutputEmitterRef<void>;
2193
+ readonly recenter: _angular_core.OutputEmitterRef<void>;
2194
+ readonly followToggle: _angular_core.OutputEmitterRef<void>;
2195
+ readonly clusterToggle: _angular_core.OutputEmitterRef<void>;
2196
+ /** Reemplaza al viejo `styleCycle` (ciclado de un click): ahora el botón
2197
+ * `mapStyle` abre `MapThemePicker` y este output reporta la elección real
2198
+ * hecha ahí. */
2199
+ readonly themeSelect: _angular_core.OutputEmitterRef<MapTheme>;
2200
+ readonly fullscreenToggle: _angular_core.OutputEmitterRef<void>;
2201
+ /** Visibilidad del panel de temas — estado puramente de UI (abierto o no),
2202
+ * no de negocio: `MapView` no necesita saberlo, así que vive acá y no
2203
+ * rompe el criterio de "sin estado propio" del resto de la clase (ver
2204
+ * JSDoc). Se cierra solo (click afuera/Escape) vía `kf-dropdown`, y acá
2205
+ * además al elegir un tema. */
2206
+ protected readonly mapStyleOpen: _angular_core.WritableSignal<boolean>;
2207
+ protected onThemeSelect(theme: MapTheme): void;
2208
+ protected readonly hasZoom: _angular_core.Signal<boolean>;
2209
+ protected readonly hasRecenter: _angular_core.Signal<boolean>;
2210
+ protected readonly hasFollow: _angular_core.Signal<boolean>;
2211
+ protected readonly hasCluster: _angular_core.Signal<boolean>;
2212
+ protected readonly hasMapStyle: _angular_core.Signal<boolean>;
2213
+ protected readonly hasFullscreen: _angular_core.Signal<boolean>;
2214
+ /** El grupo "cámara" (card) aparece con solo `recenter`, solo `follow`, o
2215
+ * ambos — ver JSDoc de la clase sobre por qué cada `@if` de grupo cubre
2216
+ * "al menos uno de sus controles" en vez de un control puntual. */
2217
+ protected readonly hasCamera: _angular_core.Signal<boolean>;
2218
+ protected readonly hasChrome: _angular_core.Signal<boolean>;
2219
+ /**
2220
+ * Texto corto que revela cada botón en hover/foco vía `[kfTooltip]` (ver
2221
+ * `MapControls.html` — la librería ya resuelve este patrón con
2222
+ * `TooltipDirective`, no con CSS a mano) — la misma acción que ya describe
2223
+ * `ariaLabel`, condensada para caber en una píldora en vez de leerse como
2224
+ * oración completa. No reemplaza `ariaLabel` (que sigue siendo la fuente
2225
+ * real para lectores de pantalla): esto es SOLO visual, para quien puede
2226
+ * ver el mapa pero no sabe qué hace un ícono suelto.
2227
+ */
2228
+ protected readonly followLabel: _angular_core.Signal<"Dejar de seguir" | "Seguir">;
2229
+ protected readonly clusterLabel: _angular_core.Signal<"Desagrupar" | "Agrupar">;
2230
+ protected readonly mapStyleLabel: _angular_core.Signal<string>;
2231
+ protected readonly fullscreenLabel: _angular_core.Signal<"Salir de pantalla completa" | "Pantalla completa">;
2232
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MapControls, never>;
2233
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MapControls, "kf-map-controls", never, { "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "clusterActive": { "alias": "clusterActive"; "required": false; "isSignal": true; }; "followActive": { "alias": "followActive"; "required": false; "isSignal": true; }; "canFollow": { "alias": "canFollow"; "required": false; "isSignal": true; }; "isFullscreen": { "alias": "isFullscreen"; "required": false; "isSignal": true; }; "styleLabel": { "alias": "styleLabel"; "required": false; "isSignal": true; }; "activeTheme": { "alias": "activeTheme"; "required": false; "isSignal": true; }; }, { "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "recenter": "recenter"; "followToggle": "followToggle"; "clusterToggle": "clusterToggle"; "themeSelect": "themeSelect"; "fullscreenToggle": "fullscreenToggle"; }, never, never, true, never>;
2234
+ }
2235
+
2236
+ type MapSize = 'sm' | 'md' | 'lg' | 'full';
2237
+ type MapVariant = 'interactive' | 'preview';
2238
+ /** Vocabulario semántico único de tono, compartido con `TextTone`/`StatTileTone`. */
2239
+ type MapMarkerTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
2240
+ interface MapLatLng {
2241
+ lat: number;
2242
+ lng: number;
2243
+ }
2244
+ /**
2245
+ * Botón de acción real dentro de `MapMarker.popup` — cada click emite
2246
+ * `markerAction` con su `key` (nunca el `label`, ver JSDoc de `popup` más
2247
+ * abajo). `icon` es OPCIONAL a propósito: no romper a los consumidores que
2248
+ * ya usaban `actions` sin él (era el único campo hasta esta ronda).
2249
+ *
2250
+ * La jerarquía visual entre acciones NO es un campo aparte (`variant`) sino
2251
+ * la POSICIÓN en el array: la primera acción se renderiza con estilo
2252
+ * prominente (fill, mismo peso que `kf-button[variant=primary]`, el
2253
+ * default del botón); el resto con estilo discreto (mismo peso que
2254
+ * `kf-button[variant=ghost]` — ver el ejemplo de `Modal` con
2255
+ * `variant="ghost"` para "Cancelar" junto a un primary implícito). Un
2256
+ * consumidor con una sola acción no necesita pensar en esto: esa acción ya
2257
+ * es "la primera".
2258
+ */
2259
+ interface MapPopupAction {
2260
+ key: string;
2261
+ label: string;
2262
+ /** Ícono líder del catálogo de `kf-icon` — sin esto, la acción se ve solo
2263
+ * con texto (compatibilidad con `actions` ya declarado sin `icon`). */
2264
+ icon?: IconName;
2265
+ }
2266
+ interface MapMarker {
2267
+ position: MapLatLng;
2268
+ /** Sin `icon`, cae en `map-pin` — el pin genérico del catálogo. Se
2269
+ * ignora cuando el control `cluster` está activo (ver JSDoc de
2270
+ * `controls` — el clustering nativo resuelve por capas WebGL, no por
2271
+ * DOM, y ahí no hay dónde montar un `kf-icon`). */
2272
+ icon?: IconName;
2273
+ tone?: MapMarkerTone;
2274
+ /** Nombre accesible del marker (`aria-label`/`title`) — no abre un popup. */
2275
+ label?: string;
2276
+ /**
2277
+ * Identificador propio del consumidor (ej. el id del ticket/vehículo que
2278
+ * representa) — sin esto el marker no es clickeable: `kf-map` no tiene
2279
+ * forma de reportar "se hizo click en ESTE marker" sin algo que
2280
+ * identificarlo de vuelta hacia `markerSelect`. Opcional a propósito: un
2281
+ * mapa puramente decorativo (ej. la vista previa de `variant="preview"`)
2282
+ * no necesita que sus markers respondan a nada.
2283
+ */
2284
+ id?: string;
2285
+ /**
2286
+ * Marca este marker como el punto que sigue la cámara cuando el control
2287
+ * `follow` está activo — ej. la posición en tiempo real de un vehículo o
2288
+ * técnico. Genérico a propósito (no `isVehicle` ni ningún nombre atado al
2289
+ * dominio de un portal): `@kaufmann-lab/ui` no sabe qué es un "vehículo",
2290
+ * solo que ALGÚN marker es el que se sigue. Si más de un marker lo marca,
2291
+ * se sigue el primero en `markers()`. Distinto de la geolocalización del
2292
+ * propio dispositivo (`navigator.geolocation`, fuera de alcance): esto
2293
+ * sigue un dato que el consumidor ya tiene, no ubica al usuario.
2294
+ */
2295
+ live?: boolean;
2296
+ /**
2297
+ * Mini-card que aparece al hover/foco de este marker — solo si el marker
2298
+ * tiene `id` (ver JSDoc de arriba): sin `id` el marker no es clickeable ni
2299
+ * enfocable, así que no habría forma de revelarla por teclado.
2300
+ *
2301
+ * `actions` son botones REALES (no el viejo `ctaLabel` de texto plano sin
2302
+ * acción): cada click emite `markerAction` con su `key` — nunca el
2303
+ * `label`, que es solo texto de UI y puede repetirse con significados
2304
+ * distintos entre consumidores — y detiene la propagación del click para
2305
+ * no disparar además `markerSelect` sobre el marker completo (ver
2306
+ * `buildMarkerPopup()`).
2307
+ *
2308
+ * Vocabulario genérico a propósito (`title`/`description`/`details`/
2309
+ * `actions`, no `folio`/`cliente`/`verTicket`): esta librería no sabe qué
2310
+ * es un "ticket" — el caso de uso real (folio+cliente+estado+SLA+acciones)
2311
+ * es responsabilidad de quien arma cada `MapMarker`.
2312
+ */
2313
+ popup?: {
2314
+ title: string;
2315
+ description?: string;
2316
+ /** Pares clave/valor extra (ej. estado + SLA) — se listan debajo de `description`. */
2317
+ details?: {
2318
+ label: string;
2319
+ value: string;
2320
+ }[];
2321
+ /** Botones de acción reales — ver JSDoc de `MapPopupAction`. */
2322
+ actions?: MapPopupAction[];
2323
+ };
2324
+ }
2325
+ /**
2326
+ * Mapa con motor real de Mapbox GL JS — decisión de producto ya cerrada
2327
+ * (comparación de 6 proveedores). El access token vive a nivel app, no por
2328
+ * uso: ver `provideMapConfig` en `map-config.ts` (mismo criterio que
2329
+ * `API_CONFIG` de `packages/api-client`), inyectado una vez en
2330
+ * `app.config.ts` de cada app consumidora.
2331
+ *
2332
+ * `size` (`sm|md|lg` alturas fijas, `full` llena el contenedor padre) NO
2333
+ * hace step-down por viewport como `kf-page-header`: ese mecanismo existe
2334
+ * ahí porque un tamaño de FUENTE fijo se corta en pantallas chicas; una
2335
+ * caja de mapa no tiene ese problema — el canvas de Mapbox ya es
2336
+ * responsive dentro de cualquier tamaño de contenedor. Lo que sí hace
2337
+ * falta (y que el header no necesita) es un `ResizeObserver` real: Mapbox
2338
+ * no se entera solo de que su contenedor cambió de tamaño (un layout que
2339
+ * colapsa un sidebar, un cambio de orientación) — hay que llamar
2340
+ * `map.resize()` a mano cuando el contenedor cambia (mismo motivo por el
2341
+ * que `fullscreenchange` también llama `resize()`, ver `onFullscreenChange`).
2342
+ *
2343
+ * `variant` es un eje ortogonal a `size` (mismo criterio que `variant` +
2344
+ * `tone` en `kf-badge`): `interactive` trae la posibilidad de controles
2345
+ * flotantes reales (ver `controls`); `preview` (`interactive: false` de
2346
+ * Mapbox GL) es para una card de lista, sin chrome y sin foco por teclado —
2347
+ * no hace nada al recibir foco, así que no debe formar parte del tab order,
2348
+ * y por eso NUNCA renderiza `controls` aunque el consumidor los pida:
2349
+ * togglear "seguir" o abrir pantalla completa no tiene sentido en un mapa
2350
+ * que no acepta interacción. Ninguna de las dos variantes oculta la
2351
+ * atribución: es requisito de licencia de Mapbox, no cosmético.
2352
+ *
2353
+ * `theme` (`light|dark|streets|satellite|outdoors`) resuelve el estilo real
2354
+ * de Mapbox — eje totalmente distinto del `[data-theme]` de
2355
+ * `tokens/themes/*` (paleta de marca de la librería): ninguno de los 5
2356
+ * temas de `tokens/` es oscuro todavía, así que no hay ninguna señal real
2357
+ * de la que derivar un default "según el tema de marca activo" — construir
2358
+ * esa detección ahora sería simular una señal que no existe.
2359
+ *
2360
+ * `controls` (`MapControlKind[]`, default `[]`) es siempre opt-in: un mapa
2361
+ * sin `controls` no dibuja ningún botón flotante, ni siquiera en
2362
+ * `interactive` (antes de esta versión, `interactive` agregaba el
2363
+ * `NavigationControl` default de Mapbox gratis — se sacó a propósito: era
2364
+ * el look genérico de Mapbox, no el de esta librería, exactamente la queja
2365
+ * que motivó este rediseño. Un consumidor que quería esos botones de zoom
2366
+ * ahora los pide con `[controls]="['zoom']"`). Cada kind es una pieza
2367
+ * independiente entre sí (ver `MapControls.ts` para el detalle de cada
2368
+ * una) — agregar una no reacomoda ni depende de las demás.
2369
+ *
2370
+ * <kf-map label="Ubicación del vehículo y del destino" [markers]="markers()" />
2371
+ * <kf-map size="sm" variant="preview" label="Vista previa de la ruta" [route]="route()" />
2372
+ * <kf-map label="Flota completa" [markers]="fleet()" [controls]="['zoom', 'recenter', 'cluster', 'follow']" />
2373
+ */
2374
+ declare class MapView {
2375
+ readonly size: _angular_core.InputSignal<MapSize>;
2376
+ readonly variant: _angular_core.InputSignal<MapVariant>;
2377
+ readonly markers: _angular_core.InputSignal<MapMarker[]>;
2378
+ readonly route: _angular_core.InputSignal<MapLatLng[] | undefined>;
2379
+ readonly fitToMarkers: _angular_core.InputSignalWithTransform<boolean, unknown>;
2380
+ readonly center: _angular_core.InputSignal<MapLatLng | undefined>;
2381
+ readonly zoom: _angular_core.InputSignal<number | undefined>;
2382
+ /**
2383
+ * Default `'streets'`, no `'light'` ni `'dark'`. `'dark'` se probó
2384
+ * primero por coherencia con la paleta navy de marca, pero una
2385
+ * verificación visual real sobre una ubicación de servicio típica (una
2386
+ * faena/zona rural, no un centro urbano denso) mostró el problema real:
2387
+ * `dark-v11` casi no tiene datos que pintar fuera de ciudades grandes —
2388
+ * el mapa se ve prácticamente todo negro, ilegible, exactamente donde
2389
+ * más importa que se lea bien (ubicaciones de terreno, no de oficina).
2390
+ * `'streets'` SIEMPRE tiene cartografía base visible (calles, nombres,
2391
+ * uso de suelo) sin importar la densidad del lugar — es el único de los
2392
+ * 5 estilos sin ese riesgo. Sigue siendo overrideable por el consumidor. */
2393
+ readonly theme: _angular_core.InputSignal<MapTheme>;
2394
+ /** Controles flotantes a mostrar — ver JSDoc de la clase y `MapControls.ts`. */
2395
+ readonly controls: _angular_core.InputSignal<MapControlKind[]>;
2396
+ /**
2397
+ * Descripción accesible real del mapa — el `<div>` donde Mapbox renderiza
2398
+ * su canvas queda vacío de semántica para un lector de pantalla. Requerido
2399
+ * a propósito: un default genérico ("Mapa") no dice qué está mostrando
2400
+ * ESTE mapa; cada consumidor conoce su propio caso real (ej. "Ubicación
2401
+ * del vehículo y del destino").
2402
+ */
2403
+ readonly label: _angular_core.InputSignal<string>;
2404
+ /** Se emite al hacer click sobre un marker que declaró `id` (ver JSDoc de
2405
+ * `MapMarker.id`) — un marker SIN `id` no es clickeable, no emite nada.
2406
+ * No dispara en modo `cluster` (los markers pasan a ser capas WebGL sin
2407
+ * DOM propio donde colgar un listener) ni en `variant="preview"` (el
2408
+ * mapa entero no es interactivo ahí). */
2409
+ readonly markerSelect: _angular_core.OutputEmitterRef<string>;
2410
+ /** Se emite al clickear un botón de `MapMarker.popup.actions` — ver el
2411
+ * JSDoc de `popup` para por qué reporta `actionKey` (nunca el `label`) y
2412
+ * por qué el click no dispara además `markerSelect`. */
2413
+ readonly markerAction: _angular_core.OutputEmitterRef<{
2414
+ markerId: string;
2415
+ actionKey: string;
2416
+ }>;
2417
+ private readonly mapConfig;
2418
+ private readonly environmentInjector;
2419
+ private readonly document;
2420
+ private readonly destroyRef;
2421
+ private readonly hostElementRef;
2422
+ private readonly containerRef;
2423
+ /** `preview` no es foco-navegable ni se anuncia como región interactiva. */
2424
+ protected readonly isPreview: _angular_core.Signal<boolean>;
2425
+ protected readonly containerRole: _angular_core.Signal<"img" | "region">;
2426
+ /** Ver estado de carga/error real, más abajo (`status`). */
2427
+ protected readonly status: _angular_core.WritableSignal<"error" | "ready" | "loading">;
2428
+ /** Elección manual del consumidor final vía el control `mapStyle` — pisa
2429
+ * `theme()` de forma sostenida (no vuelve a `theme()` sola) hasta que se
2430
+ * destruya este `kf-map`; ver `resolvedTheme()`. */
2431
+ private readonly userTheme;
2432
+ protected readonly styleLabel: _angular_core.Signal<string>;
2433
+ /** Igual que `resolvedTheme()` pero público para el template — lo necesita
2434
+ * `MapThemePicker` (vía `MapControls`) para marcar la opción activa. */
2435
+ protected readonly activeTheme: _angular_core.Signal<MapTheme>;
2436
+ protected readonly clusterActive: _angular_core.WritableSignal<boolean>;
2437
+ protected readonly followActive: _angular_core.WritableSignal<boolean>;
2438
+ protected readonly hasLiveMarker: _angular_core.Signal<boolean>;
2439
+ protected readonly isFullscreen: _angular_core.WritableSignal<boolean>;
2440
+ /** Sin estilo de marca propio (Mapbox Studio) no hay nada que ciclar con
2441
+ * `mapStyle` — se filtra en vez de dejar un botón que no cambiaría nada. */
2442
+ protected readonly effectiveControls: _angular_core.Signal<MapControlKind[]>;
2443
+ protected readonly showControls: _angular_core.Signal<boolean>;
2444
+ private map;
2445
+ /** `extraIconRefs`: íconos de las acciones del popup (ver `buildMarkerPopup()`)
2446
+ * — se crean con `createComponent()` igual que `iconRef` (el pin), así que
2447
+ * necesitan el mismo `.destroy()` en `clearMarkers()`. */
2448
+ private markerHandles;
2449
+ /** Markers DOM reales sobre cada cluster real (punto 1, a11y) — separados
2450
+ * de `markerHandles` porque se reconstruyen en su propio ciclo (ver
2451
+ * `syncClusterMarkers()`) y no tienen `iconRef` propio (sin `kf-icon`
2452
+ * adentro, ver JSDoc de `addClusterMarkerToMap()`). */
2453
+ private clusterMarkerHandles;
2454
+ private resizeObserver;
2455
+ private currentStyle;
2456
+ private mapReady;
2457
+ /** Popup resumen de un cluster real (ver `renderClusterPopup()`) — a
2458
+ * diferencia del popup de marker individual (siempre en el DOM, mostrado
2459
+ * por CSS `:hover`/`:focus-within`), este se crea/destruye a demanda: no
2460
+ * cuelga de ningún elemento con estado hover propio, lo abre un click
2461
+ * sobre una capa WebGL sin DOM. */
2462
+ private clusterPopupElement;
2463
+ private clusterPopupIconRefs;
2464
+ /** Coordenadas del cluster abierto — necesarias para volver a proyectar
2465
+ * contra pantalla en cada `'move'` (ver `repositionClusterPopup()`); el
2466
+ * cluster en sí no se mueve, pero su posición EN PANTALLA sí cuando se
2467
+ * hace pan. */
2468
+ private clusterPopupCoordinates;
2469
+ private readonly onFullscreenChange;
2470
+ constructor();
2471
+ /**
2472
+ * El bundle default de `mapbox-gl` arma su Web Worker serializando el
2473
+ * código del worker como string y cargándolo por Blob URL — un bundler
2474
+ * ESM-nativo (esbuild, el builder `application` de Angular) minifica ESE
2475
+ * string también, sin saber que corre verbatim en otro contexto, y el
2476
+ * worker termina con un identificador roto en runtime
2477
+ * (`ReferenceError: <letra> is not defined`, ver mapbox/mapbox-gl-js#13700
2478
+ * y angular/angular-cli#27378). `workerClass` reemplaza ese mecanismo por
2479
+ * un `Worker` real armado con el patrón literal
2480
+ * `new Worker(new URL(..., import.meta.url), { type: 'module' })` — el
2481
+ * único que esbuild reconoce de forma ESTÁTICA y separa en su propio
2482
+ * chunk (esbuild lo detecta por análisis sintáctico puro, dondequiera que
2483
+ * el literal aparezca en el archivo — no hace falta que esté a nivel de
2484
+ * módulo). `mapbox-gl-csp.worker.js` es un asset copiado tal cual, nunca
2485
+ * TypeScript compilado, así que `import.meta.url` sigue apuntando a un
2486
+ * archivo real en cualquiera de los dos contextos donde esbuild procesa
2487
+ * este literal: en `ng test` (bundlea el `.ts` fuente tal cual, el asset
2488
+ * está al lado en `src/lib/map/`) y en el build real de un consumidor
2489
+ * (bundlea el FESM publicado — `ng-package.json` copia este mismo asset a
2490
+ * `dist/ui/fesm2022/`, junto al bundle aplanado, con esa carpeta EXACTA a
2491
+ * propósito). Ambos casos comparten la misma ruta relativa
2492
+ * (`./mapbox-gl-csp.worker.js`) porque el asset se coloca deliberadamente
2493
+ * al lado del consumidor en los dos. Si un cambio de Angular Package
2494
+ * Format renombra `fesm2022`, hay que actualizar el `output` en
2495
+ * `ng-package.json` a la par.
2496
+ *
2497
+ * A propósito NO es un side-effect de módulo (no corre con solo
2498
+ * `import`ar este archivo): un `<kf-map>` real en jsdom construye un
2499
+ * `Worker` real vía esta misma expresión, y jsdom no lo soporta — revienta
2500
+ * el proceso de Vitest entero, no solo el test, y se lleva puesto
2501
+ * CUALQUIER spec que solo importe algo del barrel de la librería (nada
2502
+ * que ver con mapas). Por eso vive acá, dentro de `initializeMap()`, que
2503
+ * únicamente corre cuando alguien de verdad instancia y renderiza este
2504
+ * componente — `MapView.spec.ts` mockea `mapbox-gl` entero, así que ahí
2505
+ * la asignación cae sobre el mock, no sobre el `mapboxgl` real.
2506
+ */
2507
+ private assignWorkerClass;
2508
+ private initializeMap;
2509
+ private resolvedTheme;
2510
+ private resolvedStyle;
2511
+ private toLngLat;
2512
+ /**
2513
+ * `matchMedia` (no una media query CSS): esta preferencia decide la
2514
+ * `duration` que se le pasa a la API de cámara de Mapbox, que corre en
2515
+ * JS/WebGL — CSS no tiene nada que animar acá. `document.defaultView` (no
2516
+ * el `window` global) para que quede mockeable en test igual que el resto
2517
+ * de la clase usa `DOCUMENT` inyectado.
2518
+ */
2519
+ private prefersReducedMotion;
2520
+ /** Colapsa cualquier duración a 0 (instantáneo, sin animar) si el usuario
2521
+ * pidió `prefers-reduced-motion: reduce` — un único punto de verdad para
2522
+ * que ningún movimiento de cámara se salga de esa preferencia. */
2523
+ private cameraDuration;
2524
+ private syncCamera;
2525
+ /**
2526
+ * Encuadra `bounds` con `fitBounds()`, salvo que sea una caja degenerada
2527
+ * (ver `DEGENERATE_BOUNDS_THRESHOLD_DEG`) — un solo punto total (un único
2528
+ * marker sin ruta, o vehículo+destino prácticamente en el mismo sitio) le
2529
+ * da a `fitBounds()` un ancho y alto ~cero, que Mapbox resuelve con un
2530
+ * `padding`/zoom que en la práctica deja el mapa mayormente en blanco
2531
+ * ("se ve todo blanco", el bug real que esto corrige). Un `easeTo` con
2532
+ * zoom fijo sensato muestra contexto real de calle en ese caso, animado
2533
+ * igual que el resto (antes era un `jumpTo` seco).
2534
+ */
2535
+ private frameBounds;
2536
+ private isDegenerateBounds;
2537
+ /**
2538
+ * Extiende sobre markers Y route: el nombre del input sigue el caso de
2539
+ * uso principal (encuadrar los markers), pero un mapa con solo `route` y
2540
+ * sin `markers` tampoco debería quedar en el encuadre default inútil.
2541
+ */
2542
+ private computeBounds;
2543
+ private syncMarkers;
2544
+ /**
2545
+ * Crea el marker de Mapbox (DOM + `kf-icon` + wiring de click/teclado/
2546
+ * popup) para UN `MapMarker` y lo agrega a `markerHandles` — compartido
2547
+ * entre el modo normal (`syncMarkers()`, todos los markers) y el modo
2548
+ * cluster (`syncClusterMarkers()`, solo los que quedan sueltos): un punto
2549
+ * suelto en modo cluster tiene que verse y comportarse EXACTAMENTE igual
2550
+ * que en modo normal, así que no puede haber dos implementaciones.
2551
+ */
2552
+ private addMarkerToMap;
2553
+ /**
2554
+ * Mini-card hija del `<div>` del marker — mismo patrón que
2555
+ * `.kf-map-controls__label` (ver `MapControls.css`): un elemento SIEMPRE
2556
+ * presente en el DOM, oculto por defecto vía `opacity`/`pointer-events` en
2557
+ * `MapView.css` y revelado por CSS puro en `:hover`/`:focus-within` del
2558
+ * marker padre — sin JS de show/hide, y sin `mapboxgl.Popup` (evita una
2559
+ * segunda gestión de overlay/posicionamiento cuando el CSS ya alcanza acá,
2560
+ * igual que ya decidió `.kf-map-controls__label`).
2561
+ *
2562
+ * Todo el subárbol (título, descripción, `details`, `actions`) se arma con
2563
+ * `document.createElement` puro, nunca `kf-text`/`kf-button`: este marker
2564
+ * entero vive fuera del template de Angular (ver el comentario grande de
2565
+ * `ViewEncapsulation.None` más arriba) — meter un componente real acá
2566
+ * significaría un `createComponent()` propio por cada línea de texto, el
2567
+ * mismo costo que ya paga `kf-icon` en `syncMarkers()` pero sin ninguna
2568
+ * necesidad real (no hay nada dinámico que un componente resuelva mejor
2569
+ * que texto plano estilado por clase). Es la MISMA decisión que ya regía
2570
+ * para `title`/`description` antes de esta ronda — se extiende acá para no
2571
+ * mezclar dos convenciones distintas dentro del mismo fragmento de DOM.
2572
+ *
2573
+ * `actions` sí necesita wiring propio (a diferencia del resto de la card,
2574
+ * que burbujea su click al listener ya cableado sobre `element` en
2575
+ * `syncMarkers()`): cada botón corta esa burbuja con `stopPropagation()`
2576
+ * para reportar `markerAction` en vez de (además) `markerSelect`.
2577
+ *
2578
+ * Jerarquía visual de `actions` (ver JSDoc de `MapPopupAction`): la
2579
+ * PRIMERA acción del array es la principal (`--primary`, fill); el resto
2580
+ * es secundaria (`--ghost`, transparente) — mismo peso que ya usan
2581
+ * `kf-button[variant=primary]`/`[variant=ghost]` en el resto de la
2582
+ * librería, sin inventar un esquema de color nuevo acá.
2583
+ *
2584
+ * El punto de color junto al título (`kf-map__marker-popup-tone`) reusa
2585
+ * el `tone` del propio marker — no el de cada `details` (que son pares
2586
+ * label/value genéricos, sin un campo "cuál de estos es el estado"): es
2587
+ * la misma señal que YA pinta el círculo del marker en el mapa, mostrada
2588
+ * de nuevo acá para que el popup se lea sin tener que mirar el marker.
2589
+ */
2590
+ private buildMarkerPopup;
2591
+ private clearMarkers;
2592
+ /**
2593
+ * Recorte de borde para el popup de marker individual (punto 2) —
2594
+ * `getBoundingClientRect()` del marker y del propio contenedor del mapa,
2595
+ * recalculado en el evento que dispara la apertura (`mouseenter`/
2596
+ * `focusin`, ver `addMarkerToMap()`). El mecanismo de mostrar/ocultar
2597
+ * sigue siendo 100% CSS; esto solo decide 3 clases modificadoras que
2598
+ * `MapView.css` traduce en alineación horizontal/vertical:
2599
+ *
2600
+ * `--align-start` — el popup se desborda por la izquierda del
2601
+ * contenedor si se centra sobre el marker: se alinea su borde
2602
+ * izquierdo con el del marker en vez de centrarlo.
2603
+ * `--align-end` — mismo caso, contra el borde derecho.
2604
+ * `--below` — no entra arriba del marker (cerca del borde superior del
2605
+ * contenedor): se muestra debajo en vez de arriba.
2606
+ *
2607
+ * El popup YA está en el DOM en este punto (oculto por `opacity`, no por
2608
+ * `display:none` — ver `MapView.css`), así que `getBoundingClientRect()`
2609
+ * devuelve su tamaño real aunque todavía no sea visible.
2610
+ */
2611
+ private positionMarkerPopup;
2612
+ /**
2613
+ * Clustering nativo de Mapbox (`cluster: true` en la fuente GeoJSON, no un
2614
+ * algoritmo armado a mano) — 2 capas, solo para clusters REALES (2+
2615
+ * puntos agrupados, `has: point_count`): círculo agregado y su label de
2616
+ * cantidad. Un punto que Mapbox deja suelto (sin `point_count`) YA NO
2617
+ * tiene capa WebGL propia — ver `syncClusterMarkers()`, que lo renderiza
2618
+ * como el mismo marker DOM de siempre (con ícono/tono/popup reales, cosa
2619
+ * que una capa `circle` no puede mostrar).
2620
+ */
2621
+ private syncClusterSource;
2622
+ /**
2623
+ * Contraparte en DOM de la capa WebGL: mientras esa capa solo dibuja
2624
+ * clusters reales, ESTE método decide qué puntos quedan sueltos al zoom
2625
+ * ACTUAL (`querySourceFeatures` con el mismo filtro `!has(point_count)`
2626
+ * que ya usa Mapbox para separar cluster de leaf) y les arma el mismo
2627
+ * marker DOM que tendrían sin clustering — vía `addMarkerToMap()`, la
2628
+ * MISMA función que usa el modo normal, no una copia.
2629
+ *
2630
+ * `querySourceFeatures` puede devolver una feature repetida si cae cerca
2631
+ * de un borde interno de tile — de ahí el `Set` sobre `index` (la
2632
+ * property que identifica al `MapMarker` de origen, ver
2633
+ * `markersToFeatureCollection()`), no una lista con posibles duplicados.
2634
+ * Mismo motivo (y mismo `Set`, esta vez sobre `cluster_id`) para los
2635
+ * clusters reales que se resuelven acá abajo.
2636
+ *
2637
+ * Se llama solo cuando `clusterActive()` — ver los listeners `'data'`/
2638
+ * `'moveend'` en `initializeMap()` sobre CUÁNDO se llama esto.
2639
+ */
2640
+ private syncClusterMarkers;
2641
+ /**
2642
+ * Marker DOM real sobre un cluster (punto 1, a11y) — MISMO patrón de
2643
+ * wiring que `addMarkerToMap()` (`role="button"`, `tabindex="0"`,
2644
+ * `click`+`keydown` de Enter/Space), aplicado acá porque el círculo que
2645
+ * pinta `CLUSTER_CIRCLE_LAYER_ID` es una capa WebGL sin nodo DOM propio
2646
+ * —inalcanzable por teclado (WCAG SC 2.1.1 Keyboard). `mapboxgl.Marker`
2647
+ * posiciona y reproyecta este `<div>` solo en cada pan/zoom, igual que ya
2648
+ * hace con cualquier marker individual — no hace falta reposicionarlo a
2649
+ * mano como sí necesita el popup resumen (ver `repositionClusterPopup()`).
2650
+ *
2651
+ * Sin `kf-icon`/texto propio adentro a propósito: el círculo+conteo que ya
2652
+ * pinta la capa WebGL sigue siendo la representación visual (mismo
2653
+ * `'circle-radius'`, ver `clusterMarkerRadiusPx()`); este `<div>` es
2654
+ * transparente y se superpone exacto encima, aportando SOLO la
2655
+ * interactividad real que la capa WebGL no puede tener.
2656
+ */
2657
+ private addClusterMarkerToMap;
2658
+ private clearClusterMarkers;
2659
+ /**
2660
+ * Activación de un cluster real (click O Enter/Space sobre su marker DOM,
2661
+ * ver `addClusterMarkerToMap()`): ANTES hacía zoom directo
2662
+ * (`getClusterExpansionZoom()` + `easeTo`) — ahora abre el popup resumen
2663
+ * (`renderClusterPopup()`) con la lista de tickets agrupados, y ESE zoom
2664
+ * pasa a ser la acción explícita "Acercar para separar" dentro del popup
2665
+ * (ver `expandCluster()`).
2666
+ */
2667
+ private activateCluster;
2668
+ /** Dispara el zoom que antes hacía el click directo sobre el círculo —
2669
+ * ahora vive detrás del botón "Acercar para separar" del popup resumen. */
2670
+ private expandCluster;
2671
+ /**
2672
+ * `index` identifica de vuelta al `MapMarker` de origen (ver
2673
+ * `syncClusterMarkers()`); `title`/`description`/`markerId` son las
2674
+ * mismas properties que ya arma `buildMarkerPopup()` (folio/cliente/id
2675
+ * del caso real) — se serializan acá TAMBIÉN para que
2676
+ * `getClusterLeaves()` pueda listarlas en el popup resumen sin tener que
2677
+ * ir a buscar cada `MapMarker` original por separado. Los campos son
2678
+ * `string` (no `undefined`): las properties de una `Feature` de GeoJSON
2679
+ * no viajan bien como `undefined` — `''` significa "sin dato" en los 3.
2680
+ */
2681
+ private markersToFeatureCollection;
2682
+ private clearClusterLayers;
2683
+ /**
2684
+ * Popup resumen de un cluster real (punto 4) — a diferencia del popup de
2685
+ * marker individual (siempre en el DOM, `:hover`/`:focus-within` puro),
2686
+ * este no cuelga de ningún elemento con estado hover propio: lo abre un
2687
+ * click sobre una capa WebGL sin DOM, así que se crea/destruye a demanda
2688
+ * y su visibilidad la maneja JS, no CSS. Se agrega como hijo del HOST
2689
+ * (`kf-map`, no `.kf-map__container`): ambos comparten exactamente la
2690
+ * misma caja (`.kf-map__container` es `width/height:100%` sin padding/
2691
+ * borde dentro de un host `position:relative`), así que las coordenadas
2692
+ * de `map.project()` (relativas al contenedor) valen también acá — mismo
2693
+ * ancestro posicionado que ya usa `.kf-map__overlay`.
2694
+ *
2695
+ * `title`/`description` de cada leaf son `popup.title`/`popup.description`
2696
+ * del `MapMarker` original (folio/cliente en el caso real) — ver
2697
+ * `markersToFeatureCollection()`. El punto de color reusa `tone`, MISMO
2698
+ * criterio que el punto de color del popup individual (ver JSDoc de
2699
+ * `buildMarkerPopup()`): no se inventa un texto de "estado" que la
2700
+ * librería no tiene, se reusa la señal de color que sí existe.
2701
+ */
2702
+ private renderClusterPopup;
2703
+ /** Reproyecta el popup de cluster contra pantalla — se llama en cada
2704
+ * `'move'` (ver `initializeMap()`): el punto geográfico no se mueve, pero
2705
+ * su posición EN PANTALLA sí cuando se hace pan. Mismo criterio de
2706
+ * recorte de borde que `positionMarkerPopup()` (punto 2), pero en
2707
+ * píxeles calculados a mano en vez de clases CSS: este popup no cuelga
2708
+ * de un ancestro posicionado por CSS relativo al marker, su posición
2709
+ * ENTERA viene de `map.project()`. */
2710
+ private repositionClusterPopup;
2711
+ private closeClusterPopup;
2712
+ private syncRoute;
2713
+ private resolveColorToken;
2714
+ protected handleZoomIn(): void;
2715
+ protected handleZoomOut(): void;
2716
+ /** Reencuadra a demanda, ignorando `fitToMarkers()` (tiene sentido pedirlo
2717
+ * justo cuando `fitToMarkers` está en `false`) y sin apagar `follow`: si
2718
+ * está activo, la próxima posición nueva vuelve a pegar la cámara al
2719
+ * marker `live` — este reencuadre es una corrección puntual, no un modo. */
2720
+ protected handleRecenter(): void;
2721
+ protected handleFollowToggle(): void;
2722
+ protected handleClusterToggle(): void;
2723
+ /** Reemplaza al viejo ciclado de un click (`handleStyleCycle`): la
2724
+ * elección real llega desde `MapThemePicker`, vía `MapControls`. */
2725
+ protected handleThemeSelect(theme: MapTheme): void;
2726
+ protected handleFullscreenToggle(): void;
2727
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MapView, never>;
2728
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MapView, "kf-map", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "markers": { "alias": "markers"; "required": false; "isSignal": true; }; "route": { "alias": "route"; "required": false; "isSignal": true; }; "fitToMarkers": { "alias": "fitToMarkers"; "required": false; "isSignal": true; }; "center": { "alias": "center"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; }, { "markerSelect": "markerSelect"; "markerAction": "markerAction"; }, never, never, true, never>;
1084
2729
  }
1085
2730
 
2731
+ /**
2732
+ * Signal reactivo con el estado actual de un media query — para cuando un
2733
+ * componente necesita SABER en qué lado de un breakpoint está (no solo
2734
+ * mostrar/ocultar con CSS). Un `@media` en `styles` no alcanza cuando la
2735
+ * decisión no es puramente visual: por ejemplo, un componente hijo que
2736
+ * recibe un input booleano (`[collapsed]`) no puede "leer" un breakpoint
2737
+ * CSS del padre — necesita que alguien en TypeScript se lo diga.
2738
+ *
2739
+ * Encontrado al construir `SidebarShell`: el estado `collapsed` (colapso
2740
+ * del rail en desktop) se pasaba tal cual a cada `kf-sidebar-nav-item`, sin
2741
+ * importar el viewport. En mobile, donde el rail deslizado siempre muestra
2742
+ * etiquetas completas (ver estilos de `SidebarShell`), los ítems seguían
2743
+ * ocultando su label si el usuario había dejado el rail colapsado en
2744
+ * desktop antes de achicar la ventana — un CSS que dice "en mobile se ve
2745
+ * ancho" no cambia el valor del input que ya llegó al hijo.
2746
+ *
2747
+ * Debe llamarse dentro de un contexto de inyección (el `constructor` de un
2748
+ * componente, o un campo de clase inicializado ahí — mismo requisito que
2749
+ * cualquier función que use `inject()`).
2750
+ *
2751
+ * protected readonly isMobile = matchMediaSignal('(max-width: 900px)');
2752
+ * // ...
2753
+ * [collapsed]="collapsed() && !isMobile()"
2754
+ */
2755
+ declare function matchMediaSignal(query: string): Signal<boolean>;
2756
+
1086
2757
  /**
1087
2758
  * Card de estadística/KPI: label, valor grande, ícono opcional en la esquina
1088
2759
  * superior derecha, badge de esquina opcional (pegado al valor) y una nota
@@ -1093,8 +2764,17 @@ declare class Link {
1093
2764
  * en toda la librería, tal como pide el design system (página 14: "Card...
1094
2765
  * es la base de paneles, tarjetas de métrica, tablas...").
1095
2766
  *
2767
+ * Slot `[actions]` opcional (proyectado al pie, después de `foot`) para un
2768
+ * CTA dentro de la card — ej. "Saldo Pendiente" con botón "Pagar Ahora" en
2769
+ * Fase 6 de OTD. Mismo mecanismo de proyección por selector que `[footer]`
2770
+ * en `kf-modal`, no un `<ng-content>` default: así una card sin acciones no
2771
+ * paga ningún espacio extra.
2772
+ *
1096
2773
  * <kf-metric-card label="Tickets abiertos" value="11" icon="ticket" foot="+2 hoy" />
1097
2774
  * <kf-metric-card label="Tasa de resolución" value="94%" badge="+2%" value-color="var(--kf-color-success)" />
2775
+ * <kf-metric-card label="Saldo Pendiente" value="$45,000">
2776
+ * <kf-button actions variant="primary" size="sm">Pagar Ahora</kf-button>
2777
+ * </kf-metric-card>
1098
2778
  */
1099
2779
  declare class MetricCard {
1100
2780
  readonly label: _angular_core.InputSignal<string>;
@@ -1108,7 +2788,57 @@ declare class MetricCard {
1108
2788
  /** Badge de esquina opcional, pegado al valor (ej. "+2%"). */
1109
2789
  readonly badge: _angular_core.InputSignal<string | undefined>;
1110
2790
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MetricCard, never>;
1111
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MetricCard, "kf-metric-card", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "unit": { "alias": "unit"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "foot": { "alias": "foot"; "required": false; "isSignal": true; }; "valueColor": { "alias": "valueColor"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2791
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MetricCard, "kf-metric-card", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "unit": { "alias": "unit"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "foot": { "alias": "foot"; "required": false; "isSignal": true; }; "valueColor": { "alias": "valueColor"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
2792
+ }
2793
+
2794
+ /**
2795
+ * Franja fija en la base de una pantalla completa para apps mobile del
2796
+ * ecosistema: encapsula el padding con `safe-area-inset-bottom` (para no
2797
+ * invadir la zona del home indicator), el borde superior y el fondo —
2798
+ * cualquier acción, chip de estado o hint que necesite vivir pegado al
2799
+ * borde inferior del dispositivo se proyecta acá vía `<ng-content>`.
2800
+ *
2801
+ * <kf-mobile-footer>
2802
+ * <kf-button>Cerrar orden</kf-button>
2803
+ * </kf-mobile-footer>
2804
+ */
2805
+ declare class MobileFooter {
2806
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileFooter, never>;
2807
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobileFooter, "kf-mobile-footer", never, {}, {}, never, ["*"], true, never>;
2808
+ }
2809
+
2810
+ /**
2811
+ * Encabezado de pantalla completa (fondo navy) para apps mobile del
2812
+ * ecosistema — cubre tanto la raíz de un tab (sin volver) como el detalle
2813
+ * de un flujo (con volver).
2814
+ *
2815
+ * <kf-mobile-header title="Mis asignaciones" />
2816
+ *
2817
+ * <kf-mobile-header title="STAR-20841" subtitle="Cerrado hoy · 15:18" [showBack]="true" (back)="onBack()" />
2818
+ *
2819
+ * Sin `kf-text` para `eyebrow`/`title`/`subtitle`: su `tone` es un set
2820
+ * cerrado sin blanco/inverso, y sus variantes fijan `color` explícito sobre
2821
+ * el elemento interno — un `color: white` heredado desde afuera nunca le
2822
+ * gana a esa declaración directa. Markup propio con los tokens reales.
2823
+ *
2824
+ * El botón "volver" tampoco es `kf-button`: su `variant="ghost"` fija el
2825
+ * mismo token de color que este fondo navy (el ícono quedaría invisible).
2826
+ */
2827
+ declare class MobileHeader {
2828
+ readonly title: _angular_core.InputSignal<string>;
2829
+ /** Línea chica y translúcida ARRIBA del título — ej. un folio o un saludo. */
2830
+ readonly eyebrow: _angular_core.InputSignal<string | undefined>;
2831
+ /** `true` cuando `eyebrow` es un código (folio) — usa tipografía monoespaciada. */
2832
+ readonly eyebrowMono: _angular_core.InputSignal<boolean>;
2833
+ /** Línea chica y translúcida DEBAJO del título — ej. "Cerrado hoy · 15:18". */
2834
+ readonly subtitle: _angular_core.InputSignal<string | undefined>;
2835
+ readonly showBack: _angular_core.InputSignal<boolean>;
2836
+ /** El componente no navega por sí solo: cada pantalla decide qué significa
2837
+ * "volver" (`Location.back()`, retroceder un paso del flujo, etc.). */
2838
+ readonly back: _angular_core.OutputEmitterRef<void>;
2839
+ protected onBackClick(): void;
2840
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MobileHeader, never>;
2841
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MobileHeader, "kf-mobile-header", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "eyebrow": { "alias": "eyebrow"; "required": false; "isSignal": true; }; "eyebrowMono": { "alias": "eyebrowMono"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "showBack": { "alias": "showBack"; "required": false; "isSignal": true; }; }, { "back": "back"; }, never, ["[kf-mobile-header-trailing]"], true, never>;
1112
2842
  }
1113
2843
 
1114
2844
  /**
@@ -1124,15 +2854,30 @@ declare class MetricCard {
1124
2854
  * destructiva está en vuelo) se cancelan `Escape` y el clic en el backdrop:
1125
2855
  * solo cierra el consumidor poniendo `open` en `false`.
1126
2856
  *
1127
- * Las acciones van en el slot `footer`.
2857
+ * Las acciones van en el slot `footer` — el atributo `footer` se pone
2858
+ * DIRECTO en cada `kf-button`, sin envolverlos en un `<div footer>`:
2859
+ * `.kf-modal__footer` ya es `display:flex` con `gap` propio entre sus
2860
+ * hijos DIRECTOS. Envolver los botones en un `<div>` intermedio dejaría
2861
+ * ese `<div>` como único hijo del footer — el gap nunca llegaría a
2862
+ * separar los botones entre sí (bug real encontrado en `web-otd-frontend`:
2863
+ * los 2 botones quedaban pegados, con 0px de separación medida, pese a que
2864
+ * el CSS del footer declaraba `gap: var(--kf-space-md)`).
1128
2865
  *
1129
2866
  * <kf-modal [(open)]="confirmOpen" title="Eliminar unidad">
1130
2867
  * <p>Esta acción no se puede deshacer.</p>
1131
- * <div footer>
1132
- * <kf-button variant="ghost" (click)="confirmOpen.set(false)">Cancelar</kf-button>
1133
- * <kf-button (click)="remove()">Eliminar</kf-button>
1134
- * </div>
2868
+ * <kf-button footer variant="ghost" (click)="confirmOpen.set(false)">Cancelar</kf-button>
2869
+ * <kf-button footer (click)="remove()">Eliminar</kf-button>
1135
2870
  * </kf-modal>
2871
+ *
2872
+ * El footer agrupa todo a la derecha por defecto (`justify-content:
2873
+ * flex-end`) — para una acción secundaria/informativa que debe quedar
2874
+ * separada a la izquierda (ej. un link "Ver documento" junto a un botón
2875
+ * "Cerrar"), agregar la clase `kf-modal__footer-start` AL MISMO elemento
2876
+ * que ya lleva `footer`, no a un wrapper (mismo motivo que el `footer`
2877
+ * directo: un `<div>` intermedio rompe el `gap`):
2878
+ *
2879
+ * <kf-link footer class="kf-modal__footer-start" disabled>Ver documento</kf-link>
2880
+ * <kf-button footer (click)="open.set(false)">Cerrar</kf-button>
1136
2881
  */
1137
2882
  declare class Modal {
1138
2883
  readonly title: _angular_core.InputSignal<string>;
@@ -1143,6 +2888,7 @@ declare class Modal {
1143
2888
  protected readonly titleId: string;
1144
2889
  private readonly dialog;
1145
2890
  private readonly document;
2891
+ private readonly overlayContainer;
1146
2892
  constructor();
1147
2893
  /** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
1148
2894
  protected onCancel(event: Event): void;
@@ -1160,86 +2906,108 @@ declare class Modal {
1160
2906
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Modal, "kf-modal", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["*", "[footer]"], true, never>;
1161
2907
  }
1162
2908
 
1163
- interface MultiSelectOption {
1164
- value: string;
1165
- label: string;
2909
+ /** Color de la franja izquierda y del fondo. */
2910
+ type NoteBlockTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
2911
+ /**
2912
+ * Bloque de texto atribuido a una PERSONA: encabezado, prosa y firma al pie,
2913
+ * con fondo suave y franja de color — la anatomía de una cita, distinta de
2914
+ * `kf-alert` (que interrumpe el flujo con `role="alert"`). El texto va como
2915
+ * contenido proyectado y respeta los saltos de línea del autor.
2916
+ */
2917
+ declare class NoteBlock {
2918
+ readonly title: _angular_core.InputSignal<string | undefined>;
2919
+ /** Segunda pieza del encabezado, en la misma línea del `title`. */
2920
+ readonly subtitle: _angular_core.InputSignal<string | undefined>;
2921
+ /** Pie de atribución — quién escribió y cuándo, ya formateado. */
2922
+ readonly signature: _angular_core.InputSignal<string | undefined>;
2923
+ readonly tone: _angular_core.InputSignal<NoteBlockTone>;
2924
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NoteBlock, never>;
2925
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NoteBlock, "kf-note-block", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "signature": { "alias": "signature"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1166
2926
  }
2927
+
2928
+ type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
2929
+ type TextVariant = 'title-xxl' | 'title-xl' | 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption' | 'overline' | 'overline-strong';
2930
+ type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
1167
2931
  /**
1168
- * Dropdown de selección múltiple con checkboxes internos, siguiendo el mismo
1169
- * patrón de panel flotante que `SelectMenu` (panel con `position: absolute`
1170
- * sobre el host, sin Angular CDK Overlay, que no es dependencia del proyecto).
1171
- * Se abre y cierra por click en el disparador, y se cierra al hacer click
1172
- * afuera o al presionar Escape.
2932
+ * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
2933
+ * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
2934
+ * ve). Antes de este componente, cada pantalla que consume la librería
2935
+ * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
2936
+ * (título de página, subtítulo, texto secundario) — auditoría sobre
2937
+ * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
2938
+ * home sin ningún paso de escala en común. `variant` resuelve eso contra
2939
+ * la escala de `tokens/base.css` (`--kf-font-size-*`).
2940
+ *
2941
+ * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
2942
+ * escape hatch de color libre. Si un caso necesita un color que `tone` no
2943
+ * cubre, es una discusión de design system, no una prop nueva acá.
1173
2944
  *
1174
- * A diferencia de `SelectMenu`, el disparador no muestra un único valor sino
1175
- * los nombres seleccionados junto a un badge circular con el conteo (página
1176
- * 12: "Sucursal Norte, Lima Centro" + badge "2"), y cada opción del panel se
1177
- * marca/desmarca con un checkbox sin cerrar el panel. Incluye una opción
1178
- * "Todas" al inicio de la lista, con estado `indeterminate` cuando hay
1179
- * selección parcial — el mismo patrón de "seleccionar todos" de `Checkbox`.
2945
+ * `title-xl`/`title-xxl` son un tier aparte de `title-lg|md|sm`: no vienen
2946
+ * de la auditoría de contenido de página (esa cerró en 2.125rem), son para
2947
+ * títulos protagonistas de pantalla completa (ej. bienvenida del home vía
2948
+ * `kf-page-header[size=xl|xxl]`) — ver tokens/base.css.
1180
2949
  *
1181
- * <kf-multi-select
1182
- * label="Sucursales"
1183
- * [options]="branches"
1184
- * [values]="selectedBranches()"
1185
- * (valuesChange)="selectedBranches.set($event)"
1186
- * />
2950
+ * `overline`/`overline-strong` son para el patrón "texto chico en mayúsculas
2951
+ * junto a un dato" (ej. el label "RUC" de `kf-company-selector`, el badge de
2952
+ * `kf-admin-badge`) — antes de estas variantes, ese patrón se reimplementaba
2953
+ * suelto en al menos 7 componentes con tamaños/pesos ligeramente distintos
2954
+ * entre sí. `overline` (peso 600) es para labels sin chrome propio;
2955
+ * `overline-strong` (peso 700) es para el texto de un badge/pill que ya trae
2956
+ * su propio fondo. Ninguna de las dos fija color — como el resto de los
2957
+ * variants, el color se resuelve con `tone`.
1187
2958
  *
1188
- * Pendiente: el prop `icon` de otros componentes del design system requiere
1189
- * un componente Icon / sprite SVG que esta librería todavía no tiene — este
1190
- * componente no usa iconos, así que no aplica acá.
2959
+ * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
2960
+ * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
2961
+ * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
2962
+ * <kf-text as="span" variant="overline">RUC</kf-text>
2963
+ * <kf-text as="span" variant="overline-strong" tone="warn">Vista administrador</kf-text>
1191
2964
  */
1192
- declare class MultiSelect {
1193
- readonly label: _angular_core.InputSignal<string | undefined>;
1194
- readonly options: _angular_core.InputSignal<MultiSelectOption[]>;
1195
- readonly values: _angular_core.InputSignal<string[]>;
1196
- readonly placeholder: _angular_core.InputSignal<string>;
1197
- readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1198
- readonly valuesChange: _angular_core.OutputEmitterRef<string[]>;
1199
- private readonly instanceId;
1200
- readonly triggerId: string;
1201
- readonly listboxId: string;
1202
- readonly isOpen: _angular_core.WritableSignal<boolean>;
1203
- readonly selectedCount: _angular_core.Signal<number>;
1204
- readonly allSelected: _angular_core.Signal<boolean>;
1205
- readonly isPartiallySelected: _angular_core.Signal<boolean>;
2965
+ declare class Text {
2966
+ /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
2967
+ readonly as: _angular_core.InputSignal<TextAs>;
2968
+ readonly variant: _angular_core.InputSignal<TextVariant>;
2969
+ readonly tone: _angular_core.InputSignal<TextTone>;
1206
2970
  /**
1207
- * Texto del disparador: placeholder si no hay selección, o los nombres
1208
- * seleccionados unidos por coma (el conteo va aparte, en el badge). Con
1209
- * muchas opciones el `text-overflow: ellipsis` del `.kf-multi-select__value`
1210
- * corta el texto — el badge de conteo siempre queda visible al lado.
2971
+ * Copia de `as` para uso dentro del template: `as` es palabra reservada
2972
+ * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
2973
+ * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
2974
+ * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
1211
2975
  */
1212
- readonly summaryText: _angular_core.Signal<string>;
1213
- private readonly host;
1214
- isSelected(value: string): boolean;
1215
- toggle(): void;
1216
- toggleOption(value: string): void;
1217
- /** Alterna entre "todas seleccionadas" y "ninguna seleccionada". */
1218
- toggleAll(): void;
1219
- onDocumentClick(event: MouseEvent): void;
1220
- onEscape(): void;
1221
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<MultiSelect, never>;
1222
- 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>;
2976
+ protected readonly tag: _angular_core.Signal<TextAs>;
2977
+ protected hostClass(): string;
2978
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
2979
+ 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>;
1223
2980
  }
1224
2981
 
2982
+ /**
2983
+ * Tamaño del título. Cada valor mapea 1:1 a una variante `title-*` de
2984
+ * `kf-text`; `xl`/`xxl` son para títulos protagonistas (ej. bienvenida del
2985
+ * home). Bajo los 900px, el tamaño efectivo baja un escalón (ver
2986
+ * `MOBILE_STEP_DOWN`).
2987
+ */
2988
+ type PageHeaderTitleSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
1225
2989
  /**
1226
2990
  * Bloque de título al tope de una página: chip de ícono opcional, título (h1),
1227
2991
  * subtítulo opcional y un área de acciones opcional proyectada vía
1228
- * `<ng-content select="[actions]">`, para no acoplar el componente a un set
1229
- * fijo de botones.
2992
+ * `<ng-content select="[actions]">`.
1230
2993
  *
1231
2994
  * <kf-page-header title="Vehículos" subtitle="Gestión de la flota activa">
1232
2995
  * <button actions>Nuevo vehículo</button>
1233
2996
  * </kf-page-header>
1234
2997
  *
1235
2998
  * <kf-page-header icon="settings" title="Configuración" />
2999
+ *
3000
+ * <kf-page-header size="xl" title="Bienvenido de nuevo" />
1236
3001
  */
1237
3002
  declare class PageHeader {
1238
3003
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
1239
3004
  readonly title: _angular_core.InputSignal<string>;
1240
3005
  readonly subtitle: _angular_core.InputSignal<string | undefined>;
3006
+ readonly size: _angular_core.InputSignal<PageHeaderTitleSize>;
3007
+ private readonly isMobile;
3008
+ protected readonly resolvedTitleVariant: _angular_core.Signal<TextVariant>;
1241
3009
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PageHeader, never>;
1242
- 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>;
3010
+ 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>;
1243
3011
  }
1244
3012
 
1245
3013
  /** Un número de página clickeable o un separador "…" no interactivo. */
@@ -1264,16 +3032,47 @@ type PaginationItem = {
1264
3032
  * <kf-pagination [page]="page" [pages]="totalPages" (pageChange)="page = $event">
1265
3033
  * 21–40 de 273
1266
3034
  * </kf-pagination>
3035
+ *
3036
+ * Con `pageSizeOptions`, agrega un select de "N por página" entre el texto
3037
+ * informativo y los controles de navegación — el consumidor decide el
3038
+ * tamaño real vía `pageSize`/`pageSizeChange` (two-way opcional, ninguno de
3039
+ * los dos es requerido si no se usa el selector):
3040
+ *
3041
+ * <kf-pagination [page]="page" [pages]="totalPages" (pageChange)="page = $event"
3042
+ * [pageSizeOptions]="[10, 25, 50]" [(pageSize)]="pageSize">
3043
+ * 21–40 de 273
3044
+ * </kf-pagination>
1267
3045
  */
1268
3046
  declare class Pagination {
1269
3047
  readonly page: _angular_core.InputSignal<number>;
1270
3048
  readonly pages: _angular_core.InputSignal<number>;
1271
3049
  readonly pageChange: _angular_core.OutputEmitterRef<number>;
3050
+ /** Opciones del selector "N por página" — sin esto, el selector no se
3051
+ * renderiza (mismo criterio que `[footer]`/`[actions]` de otros
3052
+ * componentes: ausente por default, nunca forzado). `kf-select` en
3053
+ * `variant="compact"` no impone un máximo de opciones, a diferencia de
3054
+ * `kf-segmented` (pensado solo para 2-3). */
3055
+ readonly pageSizeOptions: _angular_core.InputSignal<number[]>;
3056
+ readonly pageSize: _angular_core.ModelSignal<number | null>;
1272
3057
  /** Lista de números de página y separadores a renderizar, ya resuelta por el algoritmo de elipsis. */
1273
3058
  readonly items: _angular_core.Signal<PaginationItem[]>;
3059
+ /** Con una sola página (o ninguna), el `<nav>` de prev/next+números no
3060
+ * aporta nada navegable — antes se dejaba en el DOM igual (oculto solo
3061
+ * por un hack `::ng-deep` del consumidor, ver `route-assistance-monorepo`)
3062
+ * y el footer quedaba con el selector "Filas" descoordinado, como si le
3063
+ * faltara una pieza. Sacar el `<nav>` del DOM (no `display:none`) resuelve
3064
+ * esto para cualquier consumidor de `kf-pagination`, no solo para quien ya
3065
+ * tenía el hack. El `divider` reusa el mismo criterio: sin `nav` no hay
3066
+ * nada del otro lado que separar, así que tampoco corresponde dejarlo. */
3067
+ protected readonly hasNav: _angular_core.Signal<boolean>;
3068
+ /** `kf-select` trabaja con `value: string` — cada tamaño numérico se
3069
+ * mapea a su representación en texto, ida y vuelta. */
3070
+ protected readonly pageSizeSelectOptions: _angular_core.Signal<string[]>;
3071
+ protected readonly pageSizeValue: _angular_core.Signal<string>;
3072
+ protected setPageSize(value: string): void;
1274
3073
  goTo(target: number): void;
1275
3074
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Pagination, never>;
1276
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Pagination, "kf-pagination", never, { "page": { "alias": "page"; "required": false; "isSignal": true; }; "pages": { "alias": "pages"; "required": false; "isSignal": true; }; }, { "pageChange": "pageChange"; }, never, ["*"], true, never>;
3075
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Pagination, "kf-pagination", never, { "page": { "alias": "page"; "required": false; "isSignal": true; }; "pages": { "alias": "pages"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; }, { "pageChange": "pageChange"; "pageSize": "pageSizeChange"; }, never, ["*"], true, never>;
1277
3076
  }
1278
3077
 
1279
3078
  /**
@@ -1285,6 +3084,13 @@ declare class Pagination {
1285
3084
  * <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos">
1286
3085
  * <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
1287
3086
  * </kf-panel>
3087
+ *
3088
+ * El host y el `kf-card` interno son flex column con `flex:1` de punta a
3089
+ * punta — si un grid/flex externo estira `kf-panel` a la altura de un
3090
+ * hermano más alto, el contenido proyectado hereda esa altura en vez de
3091
+ * dejar un hueco vacío bajo el `kf-card`. Sin esa presión externa (altura
3092
+ * "auto" del contenedor), `flex:1` no tiene espacio extra que repartir y el
3093
+ * panel se comporta igual que antes.
1288
3094
  */
1289
3095
  declare class Panel {
1290
3096
  readonly title: _angular_core.InputSignal<string>;
@@ -1294,11 +3100,14 @@ declare class Panel {
1294
3100
  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>;
1295
3101
  }
1296
3102
 
1297
- /** Un ítem de navegación de la barra de marca. */
3103
+ /** Un ítem de navegación de la barra de marca. `icon` es opcional y solo se
3104
+ * usa en el drawer mobile (columna, ver template) — el `<nav>` horizontal de
3105
+ * escritorio se mantiene solo texto, sin ícono ni chevron. */
1298
3106
  interface PortalHeaderNavItem {
1299
3107
  label: string;
1300
3108
  href: string;
1301
3109
  active?: boolean;
3110
+ icon?: IconName;
1302
3111
  }
1303
3112
  /** Una acción de la franja de administrador (ej. "Consultar deuda clientes"). */
1304
3113
  interface PortalHeaderAdminAction {
@@ -1321,34 +3130,68 @@ interface PortalHeaderAdminAction {
1321
3130
  *
1322
3131
  * Bajo los 768px, el `<nav>` deja de mostrarse en línea junto al logo y
1323
3132
  * colapsa detrás de un botón de menú (ícono `menu`/`x`) que despliega un
1324
- * drawer lateral (desde el borde derecho, `position: fixed` + overlay de
1325
- * fondo) con los mismos `navItems` en columna. Ese drawer expone un slot
1326
- * (`<ng-content select="[kf-portal-header-extra]">`) al final, pensado
1327
- * para que el consumidor proyecte contenido propio que en escritorio vive
1328
- * en otro lugar (ej. el menú de sesión) — `PortalHeader` no sabe qué es
1329
- * ese contenido ni cómo se abre, solo le da un lugar dentro del drawer ya
1330
- * desplegado. El overlay cierra el drawer al tocarlo, y `Escape` también.
3133
+ * `kf-drawer` (panel lateral genérico, ver `Drawer.ts`) anclado al borde
3134
+ * IZQUIERDO — mismo lado que el botón que lo abre, convención dominante
3135
+ * Material/iOS/Android — con los mismos `navItems` en columna dentro del
3136
+ * body (fondo claro) de ese drawer.
3137
+ *
3138
+ * `PortalHeader` expone dos slots propios pensados para que el consumidor
3139
+ * proyecte contenido que en escritorio vive en otro lugar:
3140
+ * - `<ng-content select="[kf-portal-header-extra]">`: identidad de sesión
3141
+ * (ej. `kf-drawer-session`). Cae en `[kf-drawer-header]` de `kf-drawer` —
3142
+ * el único contenedor con fondo oscuro (`--kf-color-tertiary-deep`, ver
3143
+ * `Drawer.ts`) que `kf-drawer-session` espera como fondo (no trae uno
3144
+ * propio, ver `DrawerSession.ts`).
3145
+ * - `<ng-content select="[kf-portal-header-footer]">`: acciones separadas del
3146
+ * resto (ej. `kf-drawer-logout`). Cae en `[kf-drawer-footer]` de `kf-drawer`.
3147
+ *
3148
+ * `PortalHeader` no sabe qué es ninguno de los dos contenidos, solo les da
3149
+ * el lugar correcto dentro del drawer ya desplegado (ver `Drawer.ts`).
1331
3150
  *
1332
3151
  * <kf-portal-header
1333
3152
  * [isAdmin]="true"
1334
3153
  * [adminActions]="[{ label: 'Consultar deuda clientes', href: '/deuda' }, { label: 'Cambiar DNI', href: '/dni' }]"
1335
- * [navItems]="[{ label: 'Home', href: '/', active: true }, { label: 'Vehículos', href: '/vehiculos' }]"
3154
+ * [navItems]="[{ label: 'Home', href: '/', active: true, icon: 'grid' }, { label: 'Vehículos', href: '/vehiculos', icon: 'truck' }]"
3155
+ * tagline="Portal privado"
1336
3156
  * userInitials="TS"
3157
+ * userName="Tomás Soto"
3158
+ * [showCart]="true"
3159
+ * (cartClick)="onCartClick()"
1337
3160
  * >
1338
- * <div kf-portal-header-extra>contenido propio, solo visible en el panel mobile</div>
3161
+ * <kf-drawer-session
3162
+ * kf-portal-header-extra
3163
+ * brand="Portal Kaufmann LATAM"
3164
+ * name="Tomás Soto"
3165
+ * email="tomas.soto@kaufmann.com"
3166
+ * />
3167
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
1339
3168
  * </kf-portal-header>
1340
3169
  */
1341
3170
  declare class PortalHeader {
1342
3171
  readonly brand: _angular_core.InputSignal<string>;
3172
+ /** URL del logo como imagen. Sin ella (default), se mantiene el texto de `brand`. */
3173
+ readonly logoSrc: _angular_core.InputSignal<string | undefined>;
3174
+ /** `alt` del logo cuando se usa `logoSrc`. Sin ella, cae en `brand()`. */
3175
+ readonly logoAlt: _angular_core.InputSignal<string | undefined>;
1343
3176
  readonly isAdmin: _angular_core.InputSignalWithTransform<boolean, unknown>;
1344
3177
  readonly adminActions: _angular_core.InputSignal<PortalHeaderAdminAction[]>;
1345
3178
  readonly navItems: _angular_core.InputSignal<PortalHeaderNavItem[]>;
1346
3179
  readonly userInitials: _angular_core.InputSignal<string>;
3180
+ /** Texto junto al logo (ej. "Portal privado"), con separador vertical. Sin
3181
+ * separador ni texto si queda vacío. */
3182
+ readonly tagline: _angular_core.InputSignal<string>;
3183
+ /** Nombre completo para el bloque "HOLA / {nombre}" dentro de la pastilla
3184
+ * de sesión. Vacío (default) mantiene la pastilla como hoy: solo avatar,
3185
+ * sin bloque de texto ni chevron — retrocompatible con cualquier
3186
+ * consumidor que no pase este input. */
3187
+ readonly userName: _angular_core.InputSignal<string>;
3188
+ readonly showCart: _angular_core.InputSignalWithTransform<boolean, unknown>;
1347
3189
  readonly sessionClick: _angular_core.OutputEmitterRef<void>;
1348
- /** Visibilidad del drawer de navegación mobile (bajo los 768px). */
3190
+ readonly cartClick: _angular_core.OutputEmitterRef<void>;
3191
+ /** Visibilidad del drawer de navegación mobile (bajo los 768px). Se pasa a
3192
+ * `kf-drawer` como `[(open)]` — `PortalHeader` ya no implementa overlay,
3193
+ * `Escape` ni atrapado de foco por su cuenta, eso vive en `Drawer`. */
1349
3194
  protected readonly mobileMenuOpen: _angular_core.WritableSignal<boolean>;
1350
- /** Cierra el drawer con Escape — mismo patrón que cualquier drawer/modal. */
1351
- protected onEscape(): void;
1352
3195
  /**
1353
3196
  * `ElementRef` real del botón de sesión (el avatar en la esquina
1354
3197
  * derecha), no de todo el header. Pensado para anclar un `kf-dropdown`
@@ -1360,8 +3203,276 @@ declare class PortalHeader {
1360
3203
  * <kf-dropdown [(open)]="menuOpen" [anchor]="header().sessionButtonRef()">
1361
3204
  */
1362
3205
  readonly sessionButtonRef: _angular_core.Signal<ElementRef<any>>;
3206
+ /**
3207
+ * Ancla real de cada `<a>` del `<nav>` horizontal (una por `navItems()`,
3208
+ * mismo orden — `@for` trackea por `item.label`, así que la lista no se
3209
+ * recrea en cada cambio de ruta, solo cambia qué índice trae
3210
+ * `active: true`). Sirve para medir dónde poner la cápsula deslizante
3211
+ * (`thumbStyle`, ver abajo) sin duplicar el layout del nav en JS.
3212
+ */
3213
+ private readonly navItemAnchors;
3214
+ /** Fuerza recalcular `thumbStyle` en un resize — un resize de ventana no
3215
+ * cambia ningún signal de los que `thumbStyle` ya lee (`navItems`,
3216
+ * `navItemAnchors`), pero sí puede correr el layout de los `<a>` bajo un
3217
+ * ancho de columna distinto (texto largo pasando a otra línea, etc.). */
3218
+ private readonly resizeTick;
3219
+ protected onWindowResize(): void;
3220
+ /**
3221
+ * Posición/ancho de la cápsula blanca del ítem activo del `<nav>`, como
3222
+ * un elemento propio (`.kf-portal-header__nav-thumb`) que se desliza con
3223
+ * `transition` en vez de que cada `<a>` traiga su propio fondo blanco
3224
+ * (eso hacía que el fondo "saltara" de un ítem a otro sin animación,
3225
+ * porque son elementos DOM distintos — no hay nada que Angular pueda
3226
+ * animar entre ellos).
3227
+ */
3228
+ protected readonly thumbStyle: _angular_core.Signal<Record<string, string>>;
1363
3229
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PortalHeader, never>;
1364
- 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]"], true, never>;
3230
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "logoSrc": { "alias": "logoSrc"; "required": false; "isSignal": true; }; "logoAlt": { "alias": "logoAlt"; "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; }; "tagline": { "alias": "tagline"; "required": false; "isSignal": true; }; "userName": { "alias": "userName"; "required": false; "isSignal": true; }; "showCart": { "alias": "showCart"; "required": false; "isSignal": true; }; }, { "sessionClick": "sessionClick"; "cartClick": "cartClick"; }, never, ["[kf-portal-header-extra]", "[kf-portal-header-footer]"], true, never>;
3231
+ }
3232
+
3233
+ /** Mismo vocabulario que `Badge.tone`/`TextTone` (menos `info`/`service`,
3234
+ * que no aplican a un valor de progreso): `neutral` es el color de marca
3235
+ * por defecto (sin implicar buen/mal estado), `ok`/`warn`/`error` sí
3236
+ * comunican severidad. La librería no conoce el dominio que produce ese
3237
+ * valor (SLA, storage, lo que sea) — el consumidor decide qué tone usar. */
3238
+ type ProgressTone = 'neutral' | 'ok' | 'warn' | 'error';
3239
+ /**
3240
+ * Barra de progreso de un solo valor (0-100%) con color por severidad —
3241
+ * para un porcentaje simple, a diferencia de `kf-bucket-progress`, que
3242
+ * dibuja varias categorías proporcionales en la misma barra.
3243
+ *
3244
+ * <kf-progress [value]="80" tone="ok" label="80%" />
3245
+ * <kf-progress [value]="ratio()" [tone]="severityTone()" />
3246
+ *
3247
+ * `value` se clampea a [0, 100] — un consumidor que calcule un ratio fuera
3248
+ * de rango (ej. tiempo transcurrido sobre un SLA ya vencido) no rompe el
3249
+ * ancho de la barra. `label` es texto libre opcional sobre la barra (ej.
3250
+ * "80%"); sin él, no se renderiza ningún texto, solo la barra.
3251
+ */
3252
+ declare class Progress {
3253
+ readonly value: _angular_core.InputSignal<number>;
3254
+ readonly tone: _angular_core.InputSignal<ProgressTone>;
3255
+ readonly label: _angular_core.InputSignal<string | undefined>;
3256
+ protected readonly clampedValue: _angular_core.Signal<number>;
3257
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Progress, never>;
3258
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Progress, "kf-progress", never, { "value": { "alias": "value"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3259
+ }
3260
+
3261
+ /**
3262
+ * Punto que emite ondas concéntricas en bucle, para marcar un dato que
3263
+ * necesita atención inmediata sin depender de que el operador lo vea por
3264
+ * casualidad — el mismo rol que un punto rojo estático ya cumple en varios
3265
+ * lugares del producto (ver `.now__d`/`.board-alerts__dot` en
3266
+ * `operations-final.html`), llevado a movimiento porque el color solo
3267
+ * compite por atención con todo lo demás en pantalla; el movimiento no.
3268
+ *
3269
+ * Es una primitiva de énfasis, no un indicador de estado — para eso está
3270
+ * `kf-status-pill` (punto estático + texto). Este componente no tiene
3271
+ * texto ni tono semántico de "ok/warn/error": una sola apariencia (rojo,
3272
+ * el color reservado para lo urgente en el design system), porque emitir
3273
+ * ondas por algo que no es urgente banalizaría la señal la próxima vez que
3274
+ * sí lo sea.
3275
+ *
3276
+ * <kf-pulse-dot />
3277
+ * <kf-pulse-dot size="lg" />
3278
+ *
3279
+ * Respeta `prefers-reduced-motion` (ver PulseDot.css): la onda deja de
3280
+ * animarse y el punto queda como un punto estático, sin perder la
3281
+ * información de "esto es urgente" que da el color.
3282
+ */
3283
+ declare class PulseDot {
3284
+ readonly size: _angular_core.InputSignal<"sm" | "md" | "lg">;
3285
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<PulseDot, never>;
3286
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PulseDot, "kf-pulse-dot", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3287
+ }
3288
+
3289
+ /** Chip de filtro genérico con contador — nunca hardcodeado a un dominio
3290
+ * concreto (ej. estado de una cotización); el consumidor decide qué
3291
+ * representa cada `value` y cómo mapea sus items contra él vía `predicate`.
3292
+ *
3293
+ * Bug real corregido: la primera versión leía `item.status`/`item.chip` por
3294
+ * nombre de campo mágico en vez de un `predicate` — colisionaba con
3295
+ * dominios donde `status` ya significa otra cosa (ej. `Quote.status` en
3296
+ * `web-otd-frontend` es 'ok'|'error'|'warn' de aprobación, no "tiene saldo
3297
+ * pendiente"). Un chip "Con saldo pendiente" sobre cotizaciones terminaba
3298
+ * filtrando por un campo que no tenía nada que ver, mostrando 0 resultados
3299
+ * siempre. `predicate` es explícito y no depende de convenciones de nombre. */
3300
+ interface QuotePickerChip<T = unknown> {
3301
+ value: string;
3302
+ label: string;
3303
+ count: number;
3304
+ predicate: (item: T) => boolean;
3305
+ }
3306
+ /** Una opción del dropdown "Ordenar por", visible solo en `variant="modal"`. */
3307
+ interface QuotePickerSortOption {
3308
+ value: string;
3309
+ label: string;
3310
+ }
3311
+ /**
3312
+ * Selector de un ítem "rico" de una lista buscable/filtrable — generalización
3313
+ * del patrón "elegir una cotización" de Pagos y Facturas de OTD, pero sin
3314
+ * ningún acoplamiento a `Quote`: todo el contenido de cada fila/card llega
3315
+ * por `columns` (mismo `DataTableColumn` de `kf-data-table`, reexportado acá
3316
+ * en vez de duplicar el concepto) + `cellTemplates` para contenido custom
3317
+ * (thumbnail, monto formateado, etc. — mismo mecanismo `TemplateRef` +
3318
+ * `ngTemplateOutlet` que ya usa `kf-data-table`).
3319
+ *
3320
+ * Un solo componente, dos contenedores (`variant: 'inline' | 'modal'`), UNA
3321
+ * sola lógica de búsqueda/filtro/orden interna. Nunca dos implementaciones
3322
+ * paralelas: el riesgo real de eso es que un fix de filtro se aplique en un
3323
+ * modo y se olvide en el otro. Los `computed()` de más abajo
3324
+ * (`filteredItems`, `sortedItems`) son la ÚNICA fuente de verdad para ambos
3325
+ * `variant`; lo que cambia entre inline y modal es solo qué contenedor
3326
+ * envuelve esos datos ya resueltos.
3327
+ *
3328
+ * ## Modo `inline` (default)
3329
+ * Trigger a ANCHO COMPLETO (nunca una caja angosta de ancho fijo) que
3330
+ * muestra el ítem activo colapsado — thumbnail, id + modelo, unidades +
3331
+ * fecha + monto, CTA "Cambiar cotización" con chevron-down. Al click, el
3332
+ * panel se expande INLINE (empuja el contenido de abajo, nunca flota
3333
+ * encima) con buscador propio + chips de filtro + un grid de 2 columnas (1
3334
+ * en mobile, breakpoint `max-width: 900px` vía `matchMediaSignal` — mismo
3335
+ * mecanismo que ya usa el resto de la librería para decisiones de layout en
3336
+ * TypeScript, no solo CSS). Por encima de `expandThreshold` resultados
3337
+ * visibles, el panel recorta a un preview de `PREVIEW_ROWS` filas con un
3338
+ * fade inferior y un botón dashed "Ver las N en pantalla completa" que
3339
+ * emite `expandToModal` — el picker no decide qué pasa después (típicamente
3340
+ * el consumidor pone `variant` en `'modal'` y `open` en `true`).
3341
+ *
3342
+ * ## Modo `modal`
3343
+ * Mismo buscador, mismos chips, misma lógica de filtrado — compone
3344
+ * `kf-modal` (proyectando el contenido) en vez de reimplementar un
3345
+ * `<dialog>` a mano: la API de `Modal` (`[(open)]`, cierre con
3346
+ * Escape/backdrop/botón vía `<dialog>` nativo) ya cubre exactamente lo que
3347
+ * este modo necesita. Compone también `kf-data-table` para la tabla
3348
+ * completa (columnas configurables, fade de overflow horizontal ya
3349
+ * resuelto ahí) y `kf-pagination` para paginar — ninguno de los tres se
3350
+ * reimplementa. Agrega un dropdown propio de "Ordenar por" (calibrado
3351
+ * visualmente contra el trigger de `kf-select`, pero compacto: `kf-select`
3352
+ * fuerza un ancho fijo de 240px pensado para formularios, no para una
3353
+ * barra de herramientas de tabla — ver JSDoc de `sortOpen` más abajo).
3354
+ *
3355
+ * En mobile, `kf-data-table[cardable]` con `defaultView` forzado a
3356
+ * `'cards'` decide la vista (mismo criterio que `matchMediaSignal` ya
3357
+ * resolvió en otros consumidores): dentro de un modal a pantalla completa
3358
+ * no tiene sentido ofrecer un toggle tabla/cards que en mobile daría una
3359
+ * tabla angosta con scroll horizontal.
3360
+ *
3361
+ * DEUDA TÉCNICA CONOCIDA (confirmada con el usuario, ver CHANGELOG.md): el
3362
+ * diseño validado con mockups pedía que en mobile el modal ocupe el 100%
3363
+ * de la pantalla (botón "atrás", sin overlay centrado) — `kf-modal` no
3364
+ * tiene ese modo, así que hoy en mobile se ve como un diálogo centrado
3365
+ * angosto (`width: min(480px, ...)`). Es funcional (cards legibles,
3366
+ * búsqueda, chips, paginación), solo visualmente distinto del mockup.
3367
+ *
3368
+ * <kf-quote-picker
3369
+ * [items]="quotes()"
3370
+ * [columns]="columns"
3371
+ * [selectedId]="selectedQuoteId()"
3372
+ * [idKey]="(q) => q.id"
3373
+ * [chips]="statusChips()"
3374
+ * [cellTemplates]="{ photo: photoCell(), amount: amountCell() }"
3375
+ * (selectedIdChange)="selectedQuoteId.set($event)"
3376
+ * (expandToModal)="variant.set('modal'); open.set(true)"
3377
+ * />
3378
+ */
3379
+ declare class QuotePicker<T = unknown> {
3380
+ readonly items: _angular_core.InputSignal<T[]>;
3381
+ readonly columns: _angular_core.InputSignal<DataTableColumn[]>;
3382
+ readonly selectedId: _angular_core.InputSignal<string | null>;
3383
+ readonly idKey: _angular_core.InputSignal<(item: T) => string>;
3384
+ /**
3385
+ * Mapa `columnKey -> TemplateRef`, mismo mecanismo que `kf-data-table`
3386
+ * (`ngTemplateOutlet` + contexto `{ $implicit: item, index }`) — se
3387
+ * reenvía tal cual a la tabla interna del modo `modal`, y se usa
3388
+ * directamente en el grid de cards del modo `inline`. Un solo mapa para
3389
+ * ambos modos, consistente con la regla de "una sola lógica interna".
3390
+ */
3391
+ readonly cellTemplates: _angular_core.InputSignal<Record<string, TemplateRef<{
3392
+ $implicit: T;
3393
+ index: number;
3394
+ }>> | undefined>;
3395
+ readonly chips: _angular_core.InputSignal<QuotePickerChip<T>[]>;
3396
+ readonly sortOptions: _angular_core.InputSignal<QuotePickerSortOption[]>;
3397
+ readonly sort: _angular_core.ModelSignal<string | null>;
3398
+ /**
3399
+ * Umbral de volumen alto — el consumidor decide el número exacto, nunca
3400
+ * hardcodeado a "cotizaciones" ni a ningún caso de uso puntual. Default
3401
+ * 15 según el diseño validado con mockups.
3402
+ */
3403
+ readonly expandThreshold: _angular_core.InputSignal<number>;
3404
+ readonly variant: _angular_core.InputSignal<"inline" | "modal">;
3405
+ /** Visibilidad del modo `modal`, two-way — mismo patrón que `[(open)]` de `kf-modal`. */
3406
+ readonly open: _angular_core.ModelSignal<boolean>;
3407
+ readonly title: _angular_core.InputSignal<string>;
3408
+ readonly searchPlaceholder: _angular_core.InputSignal<string>;
3409
+ /** Ícono de placeholder cuando ninguna columna `cardRole: 'media'` resuelve
3410
+ * un thumbnail real vía `cellTemplates` — el picker no asume qué dominio
3411
+ * consume (cotización, vehículo, cliente...), así que no hardcodea un
3412
+ * ícono fijo. Default `'file'` solo por no dejar el input sin valor. */
3413
+ readonly placeholderIcon: _angular_core.InputSignal<IconName>;
3414
+ readonly selectedIdChange: _angular_core.OutputEmitterRef<string>;
3415
+ /** Disparado al superar `expandThreshold` en modo inline; el consumidor decide pasar a `modal`. */
3416
+ readonly expandToModal: _angular_core.OutputEmitterRef<void>;
3417
+ protected readonly searchQuery: _angular_core.WritableSignal<string>;
3418
+ protected readonly activeChip: _angular_core.WritableSignal<string | null>;
3419
+ protected readonly expanded: _angular_core.WritableSignal<boolean>;
3420
+ protected readonly page: _angular_core.WritableSignal<number>;
3421
+ protected readonly sortOpen: _angular_core.WritableSignal<boolean>;
3422
+ /**
3423
+ * Mismo breakpoint (`max-width: 900px`) que el resto de la librería usa
3424
+ * para decisiones de layout en TypeScript (no solo CSS) — ver
3425
+ * `matchMediaSignal`. Decide 1 vs. 2 columnas en el grid inline, y tabla
3426
+ * vs. cards apiladas dentro del modal.
3427
+ */
3428
+ protected readonly isMobile: _angular_core.Signal<boolean>;
3429
+ private readonly destroyRef;
3430
+ private readonly previewScrollEl;
3431
+ /** Mismo mecanismo de fade que `kf-data-table` — ver comentario en QuotePicker.css. */
3432
+ protected readonly canFadeBottom: _angular_core.WritableSignal<boolean>;
3433
+ readonly selectedItem: _angular_core.Signal<T | undefined>;
3434
+ /** Filtro por texto (buscador propio) + filtro por chip activo, en ese orden. */
3435
+ protected readonly filteredItems: _angular_core.Signal<T[]>;
3436
+ /**
3437
+ * Orden solo tiene efecto visible en modo `modal` (el diseño validado no
3438
+ * pide dropdown de orden en inline) — se calcula igual en ambos casos
3439
+ * porque es la MISMA lógica interna; en inline `sort()` simplemente se
3440
+ * queda en `null` salvo que el consumidor lo setee explícitamente.
3441
+ */
3442
+ protected readonly sortedItems: _angular_core.Signal<T[]>;
3443
+ /** Por encima del umbral, el preview inline se recorta salvo que el usuario ya haya expandido. */
3444
+ protected readonly isHighVolume: _angular_core.Signal<boolean>;
3445
+ protected readonly previewItems: _angular_core.Signal<T[]>;
3446
+ protected readonly pages: _angular_core.Signal<number>;
3447
+ protected readonly pagedItems: _angular_core.Signal<T[]>;
3448
+ protected readonly sortLabel: _angular_core.Signal<string>;
3449
+ constructor();
3450
+ private updateFadeState;
3451
+ private cellText;
3452
+ toggleExpandedPanel(): void;
3453
+ onSearchInput(event: Event): void;
3454
+ selectChip(value: string): void;
3455
+ select(item: T): void;
3456
+ onItemKeydown(event: KeyboardEvent, item: T): void;
3457
+ onSeeAll(): void;
3458
+ onTableRowClick(event: {
3459
+ row: T;
3460
+ index: number;
3461
+ }): void;
3462
+ toggleSort(): void;
3463
+ setSort(value: string): void;
3464
+ trackItem(item: T, index: number): string | number;
3465
+ /** Arrow function como campo (no un método) para que `[rowKey]` reciba
3466
+ * una referencia ESTABLE entre change detections — `trackItem.bind(this)`
3467
+ * en el template recrearía una función nueva en cada ciclo, invalidando
3468
+ * cualquier optimización de `kf-data-table` que dependa de identidad. */
3469
+ protected readonly rowKeyFn: (item: T, index: number) => string | number;
3470
+ protected cellValue(item: T, column: DataTableColumn): string;
3471
+ protected readonly mediaColumn: _angular_core.Signal<DataTableColumn | undefined>;
3472
+ protected readonly titleColumn: _angular_core.Signal<DataTableColumn | undefined>;
3473
+ protected readonly fieldColumns: _angular_core.Signal<DataTableColumn[]>;
3474
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<QuotePicker<any>, never>;
3475
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<QuotePicker<any>, "kf-quote-picker", never, { "items": { "alias": "items"; "required": true; "isSignal": true; }; "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "selectedId": { "alias": "selectedId"; "required": false; "isSignal": true; }; "idKey": { "alias": "idKey"; "required": true; "isSignal": true; }; "cellTemplates": { "alias": "cellTemplates"; "required": false; "isSignal": true; }; "chips": { "alias": "chips"; "required": false; "isSignal": true; }; "sortOptions": { "alias": "sortOptions"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "expandThreshold": { "alias": "expandThreshold"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "placeholderIcon": { "alias": "placeholderIcon"; "required": false; "isSignal": true; }; }, { "sort": "sortChange"; "open": "openChange"; "selectedIdChange": "selectedIdChange"; "expandToModal": "expandToModal"; }, never, never, true, never>;
1365
3476
  }
1366
3477
 
1367
3478
  /**
@@ -1375,7 +3486,12 @@ declare class PortalHeader {
1375
3486
  * invitación + las acciones como un bloque compuesto propio del dominio
1376
3487
  * "taller", no un placeholder de una sola pieza.
1377
3488
  *
1378
- * <kf-reserve-block emptyLabel="No tienes reservas de taller"
3489
+ * `grow` estira el panel de invitación para llenar el espacio vertical
3490
+ * disponible — solo cuando el consumidor comparte fila con una tarjeta más
3491
+ * alta (ej. junto a "Total de vehículos"); en un lugar sin esa presión
3492
+ * (home, debajo del carousel) se deja en su alto natural.
3493
+ *
3494
+ * <kf-reserve-block emptyLabel="No tienes reservas de taller" grow
1379
3495
  * title="¿Necesitas mantención?" description="Tenemos la mejor red de talleres."
1380
3496
  * actionLabel="Agenda tu reserva aquí" actionHref="/taller/agendar">
1381
3497
  * <kf-button actions icon="calendar">Reservar hora</kf-button>
@@ -1388,8 +3504,10 @@ declare class ReserveBlock {
1388
3504
  readonly description: _angular_core.InputSignal<string | undefined>;
1389
3505
  readonly actionLabel: _angular_core.InputSignal<string | undefined>;
1390
3506
  readonly actionHref: _angular_core.InputSignal<string | undefined>;
3507
+ /** La invitación se estira para llenar la tarjeta (ej. junto a "Total de vehículos"). */
3508
+ readonly grow: _angular_core.InputSignalWithTransform<boolean, unknown>;
1391
3509
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ReserveBlock, never>;
1392
- 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>;
3510
+ 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>;
1393
3511
  }
1394
3512
 
1395
3513
  /**
@@ -1402,14 +3520,10 @@ declare class ReserveBlock {
1402
3520
  * `size` sigue la misma escala `'sm'|'md'|'lg'` que `Button`/`Icon`/`Spinner`
1403
3521
  * (`md` es el default): `sm` reduce tipografía y espaciado para usarse
1404
3522
  * dentro de cards ya compactas, `lg` los agranda para encabezar un bloque
1405
- * más prominente. Antes de la `0.2.0`, `size` solo aceptaba `'sm'` —
1406
- * placeholder incompleto de esta misma escala; los valores de `lg` son una
1407
- * extrapolación proporcional de la escala existente (sin un caso de uso
1408
- * real todavía que los valide, a diferencia de `sm`, que sí viene de un
1409
- * patrón real de uso dentro de cards). `solid` rellena el chip de ícono con
1410
- * el color primario sólido en vez del tratamiento "soft" (fondo tenue) —
1411
- * pensado para superficies que ya tienen un tinte de color propio, donde el
1412
- * chip "soft" perdería contraste.
3523
+ * más prominente. `solid` rellena el chip de ícono con el color primario
3524
+ * sólido en vez del tratamiento "soft" (fondo tenue) — pensado para
3525
+ * superficies que ya tienen un tinte de color propio, donde el chip "soft"
3526
+ * perdería contraste.
1413
3527
  *
1414
3528
  * <kf-section-header icon="settings" title="Preferencias"
1415
3529
  * subtitle="Configuración general de la cuenta">
@@ -1430,6 +3544,118 @@ declare class SectionHeader {
1430
3544
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SectionHeader, "kf-section-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; }; "solid": { "alias": "solid"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1431
3545
  }
1432
3546
 
3547
+ /**
3548
+ * Contenedor de navegación por secciones dentro de una misma pantalla —
3549
+ * agrupa `kf-section-nav-item` y resuelve su layout fusionado según
3550
+ * viewport, con un breakpoint PROPIO de 1024px (no el $mobile:900px
3551
+ * compartido del resto de la librería — ver el comentario en
3552
+ * `SectionNav.scss`):
3553
+ *
3554
+ * - Desktop (≥1024px): columna de botones a la izquierda + panel de
3555
+ * contenido separado a la derecha, mostrando el contenido del ítem (o
3556
+ * sub-ítem) activo.
3557
+ * - Mobile y tablet (<1024px): acordeón — el contenido del ítem activo se
3558
+ * despliega inline, justo debajo de su propio botón.
3559
+ *
3560
+ * El cambio entre ambos layouts es solo CSS (`SectionNav.scss` +
3561
+ * `SectionNavItem.scss`, ver comentarios ahí): cada `kf-section-nav-item`
3562
+ * proyecta su contenido una única vez y es la grilla la que lo reposiciona
3563
+ * visualmente, sin recrearlo ni duplicarlo en el DOM.
3564
+ *
3565
+ * A diferencia de `kf-tabs` (selector de vista plano, sin contenido propio)
3566
+ * y de `kf-sidebar-shell` (rail persistente ENTRE rutas, colapsable, fondo
3567
+ * oscuro): esto es navegación DENTRO de una sola pantalla, con paneles de
3568
+ * contenido rico por sección.
3569
+ *
3570
+ * La exclusividad entre ítems (y entre sub-ítems, dentro de cada uno) la
3571
+ * decide quien consume el componente, igual que cada `kf-section-nav-item`
3572
+ * por separado — `kf-section-nav` no guarda ningún estado de selección.
3573
+ *
3574
+ * <kf-section-nav>
3575
+ * <kf-section-nav-item label="Mis datos personales" icon="user"
3576
+ * [active]="openSection() === 'datos'" (toggled)="openSection.set('datos')">
3577
+ * <app-personal-data />
3578
+ * </kf-section-nav-item>
3579
+ * <kf-section-nav-item label="Mis empresas" icon="layers"
3580
+ * [active]="openSection() === 'empresas'" (toggled)="openSection.set('empresas')">
3581
+ * <app-my-companies />
3582
+ * </kf-section-nav-item>
3583
+ * </kf-section-nav>
3584
+ */
3585
+ declare class SectionNav {
3586
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SectionNav, never>;
3587
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SectionNav, "kf-section-nav", never, {}, {}, never, ["*"], true, never>;
3588
+ }
3589
+
3590
+ interface SectionNavSubsectionData {
3591
+ key: string;
3592
+ label: string;
3593
+ icon: IconName;
3594
+ disabled?: boolean;
3595
+ }
3596
+ /**
3597
+ * Ítem de nivel superior de `kf-section-nav`: header clickeable (ícono
3598
+ * identificador opcional + label + chevron) que expande/colapsa contenido
3599
+ * proyectado. La exclusividad entre ítems la decide quien consume el
3600
+ * componente — cada click solo emite `toggled` y el padre resuelve qué
3601
+ * ítem queda `active`, igual que `kf-folder-tabs` con `value`.
3602
+ *
3603
+ * `subsections` agrega un segundo nivel de navegación DENTRO del ítem
3604
+ * (solo visible cuando el ítem está `active`), cada uno con su propio
3605
+ * estado activo (`activeSubsection`) independiente del padre. El contenido
3606
+ * de cada sub-ítem se proyecta envolviéndolo en `<kf-section-nav-subsection
3607
+ * key="...">` dentro del mismo slot por defecto — ver esa clase.
3608
+ *
3609
+ * El contenido proyectado usa `[hidden]`, no `@if`: un ítem puede envolver
3610
+ * un formulario con estado que el usuario ya completó, y colapsar/expandir
3611
+ * (o cruzar el breakpoint propio de 1024px, ver `SectionNav.scss`) no debe
3612
+ * destruir ese subárbol.
3613
+ *
3614
+ * <kf-section-nav-item
3615
+ * label="Mis datos personales"
3616
+ * icon="user"
3617
+ * [active]="openSection() === 'datos'"
3618
+ * (toggled)="openSection.set('datos')"
3619
+ * >
3620
+ * ...
3621
+ * </kf-section-nav-item>
3622
+ */
3623
+ declare class SectionNavItem {
3624
+ readonly label: _angular_core.InputSignal<string>;
3625
+ readonly icon: _angular_core.InputSignal<IconName | undefined>;
3626
+ readonly active: _angular_core.InputSignalWithTransform<boolean, unknown>;
3627
+ readonly toggled: _angular_core.OutputEmitterRef<void>;
3628
+ /** Sub-ítems navegables de este ítem, visibles solo mientras `active()`. */
3629
+ readonly subsections: _angular_core.InputSignal<SectionNavSubsectionData[]>;
3630
+ /** `key` del sub-ítem actualmente activo — ver `SectionNavSubsectionData`. */
3631
+ readonly activeSubsection: _angular_core.InputSignal<string | undefined>;
3632
+ readonly subsectionSelected: _angular_core.OutputEmitterRef<string>;
3633
+ protected readonly showSubsections: _angular_core.Signal<boolean>;
3634
+ protected onSubsectionClick(key: string, disabled: boolean | undefined, event: MouseEvent): void;
3635
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SectionNavItem, never>;
3636
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SectionNavItem, "kf-section-nav-item", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "subsections": { "alias": "subsections"; "required": false; "isSignal": true; }; "activeSubsection": { "alias": "activeSubsection"; "required": false; "isSignal": true; }; }, { "toggled": "toggled"; "subsectionSelected": "subsectionSelected"; }, never, ["*"], true, never>;
3637
+ }
3638
+
3639
+ /**
3640
+ * Envoltorio de contenido para UN sub-ítem de `kf-section-nav-item`. Varios
3641
+ * pueden proyectarse juntos, uno por `key` de `subsections()` del padre —
3642
+ * cada uno queda montado siempre (mismo criterio `[hidden]` que el propio
3643
+ * `kf-section-nav-item`), y solo se muestra el que coincide con
3644
+ * `activeSubsection()` del padre.
3645
+ *
3646
+ * <kf-section-nav-item [subsections]="subs" [activeSubsection]="active()" ...>
3647
+ * <kf-section-nav-subsection key="general"><app-general /></kf-section-nav-subsection>
3648
+ * <kf-section-nav-subsection key="seguridad"><app-seguridad /></kf-section-nav-subsection>
3649
+ * </kf-section-nav-item>
3650
+ */
3651
+ declare class SectionNavSubsection {
3652
+ readonly key: _angular_core.InputSignal<string>;
3653
+ private readonly parent;
3654
+ protected readonly isActive: _angular_core.Signal<boolean>;
3655
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SectionNavSubsection, never>;
3656
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SectionNavSubsection, "kf-section-nav-subsection", never, { "key": { "alias": "key"; "required": true; "isSignal": true; }; }, {}, never, ["*"], true, never>;
3657
+ }
3658
+
1433
3659
  interface SegmentedOption {
1434
3660
  value: string;
1435
3661
  label: string;
@@ -1466,23 +3692,27 @@ interface SelectOption {
1466
3692
  * opciones al sistema operativo, y ningún CSS puede controlar cómo se ve
1467
3693
  * ese popup.
1468
3694
  *
1469
- * Hasta la `0.1.5` existía además `kf-select-menu`, un segundo componente
1470
- * prácticamente idéntico (mismo trigger, mismo panel, mismo CSS) que solo
1471
- * se diferenciaba en aceptar `options` únicamente como `{ value, label }[]`
1472
- * (sin el atajo `string[]` que `Select` ya tenía). Se fusionaron en este
1473
- * componente en la `0.2.0` — mantener dos componentes con la misma
1474
- * interacción y el mismo aspecto visual, cuya única diferencia real era la
1475
- * forma de un input, no se sostenía como dos piezas separadas del catálogo.
1476
- *
1477
3695
  * El panel flotante se conecta al trigger vía `@angular/cdk/overlay` — el
1478
3696
  * mismo mecanismo que `Dropdown` (ver `../dropdown/Dropdown.ts`).
1479
3697
  *
1480
3698
  * <kf-select label="Sede" [options]="['Lima', 'Arequipa', 'Trujillo']" placeholder="Selecciona…" />
1481
3699
  * <kf-select [options]="[{ value: 'pe', label: 'Perú' }]" (valueChange)="onCountry($event)" />
1482
3700
  *
1483
- * Pendiente: el prop `icon` de otros componentes del design system requiere
1484
- * un componente Icon / sprite SVG que esta librería todavía no tiene — este
1485
- * componente no usa iconos, así que no aplica acá.
3701
+ * `variant="compact"` cambia el ancho de `240px` fijo a intrínseco con un
3702
+ * techo de `160px` (se ajusta al contenido de la opción seleccionada hasta
3703
+ * ese máximo, truncando con ellipsis si no entra) y reduce padding/tamaño de
3704
+ * fuente — pensado para selectores cortos embebidos en una barra (ej. "Filas
3705
+ * por página" en el footer de `kf-data-table`), nunca para un campo de
3706
+ * formulario largo, que sigue usando el `variant="field"` por defecto. El
3707
+ * panel flotante siempre mide el ancho real del trigger en cada apertura
3708
+ * (`SelectOverlay.attach`), así que se angosta solo sin cambios adicionales.
3709
+ *
3710
+ * <kf-select variant="compact" [options]="['5', '10', '25']" [value]="pageSize()" (valueChange)="..." />
3711
+ *
3712
+ * `width="full"` ocupa el 100% del contenedor — para selects dentro de
3713
+ * layouts flex/grid donde el ancho fijo de `md` no encaja.
3714
+ *
3715
+ * <kf-select width="full" [options]="branches" [value]="branch()" (valueChange)="..." />
1486
3716
  */
1487
3717
  declare class Select {
1488
3718
  readonly label: _angular_core.InputSignal<string | undefined>;
@@ -1490,20 +3720,44 @@ declare class Select {
1490
3720
  readonly value: _angular_core.InputSignal<string | undefined>;
1491
3721
  readonly placeholder: _angular_core.InputSignal<string>;
1492
3722
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
3723
+ /** Borde en rojo y `aria-invalid` en el trigger. Ver `errorMessage` para el mensaje asociado — mismo contrato que `kf-input`. */
3724
+ readonly invalid: _angular_core.InputSignalWithTransform<boolean, unknown>;
3725
+ /** Mensaje de error con `role="alert"`, debajo del trigger. */
3726
+ readonly errorMessage: _angular_core.InputSignal<string | undefined>;
3727
+ readonly variant: _angular_core.InputSignal<"field" | "compact">;
3728
+ /**
3729
+ * Ancho del control. 'md' (240px) es el default histórico y preserva el
3730
+ * comportamiento actual sin cambios para consumidores existentes.
3731
+ * 'full' ocupa el 100% del contenedor — para selects dentro de layouts
3732
+ * flex/grid donde el ancho fijo no encaja (ej. un footer de sidebar).
3733
+ */
3734
+ readonly width: _angular_core.InputSignal<"sm" | "md" | "lg" | "full">;
3735
+ /**
3736
+ * Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
3737
+ * se activa solo cuando `options().length` supera `searchThreshold()`. Si
3738
+ * el consumidor define `true`/`false` explícitamente, ese valor manda por
3739
+ * sobre el umbral automático.
3740
+ */
3741
+ readonly searchable: _angular_core.InputSignal<boolean | undefined>;
3742
+ readonly searchThreshold: _angular_core.InputSignal<number>;
1493
3743
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
1494
3744
  private readonly instanceId;
1495
3745
  readonly triggerId: string;
1496
3746
  readonly listboxId: string;
3747
+ readonly errorId: string;
1497
3748
  readonly isOpen: _angular_core.WritableSignal<boolean>;
3749
+ readonly searchQuery: _angular_core.WritableSignal<string>;
3750
+ readonly activeIndex: _angular_core.WritableSignal<number>;
1498
3751
  /** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
1499
3752
  readonly normalizedOptions: _angular_core.Signal<SelectOption[]>;
1500
3753
  readonly selectedOption: _angular_core.Signal<SelectOption | undefined>;
3754
+ readonly showSearch: _angular_core.Signal<boolean>;
3755
+ /** Opciones visibles tras aplicar el filtro de búsqueda (case/acento-insensitive). */
3756
+ readonly filteredOptions: _angular_core.Signal<SelectOption[]>;
1501
3757
  private readonly triggerRef;
1502
3758
  private readonly panelTemplate;
1503
- private readonly overlayService;
1504
- private readonly viewContainerRef;
1505
- private readonly destroyRef;
1506
- private overlayRef;
3759
+ private readonly searchInputRef;
3760
+ private readonly overlay;
1507
3761
  constructor();
1508
3762
  /**
1509
3763
  * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo ve
@@ -1514,11 +3768,132 @@ declare class Select {
1514
3768
  onDocumentEscape(): void;
1515
3769
  toggle(): void;
1516
3770
  select(option: SelectOption): void;
1517
- private attach;
1518
- private detach;
1519
- private getOrCreateOverlayRef;
3771
+ onSearchInput(event: Event): void;
3772
+ /**
3773
+ * Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp
3774
+ * simple (sin wraparound): llegar al final o al principio de la lista se
3775
+ * queda ahí en vez de dar la vuelta — es el comportamiento más predecible
3776
+ * cuando se combina con un buscador que puede reducir la lista a 1 opción.
3777
+ */
3778
+ moveActive(delta: number): void;
3779
+ selectActive(): void;
1520
3780
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Select, never>;
1521
- 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>;
3781
+ 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; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
3782
+ }
3783
+
3784
+ interface SelectableCardOption {
3785
+ value: string;
3786
+ label: string;
3787
+ icon?: IconName;
3788
+ }
3789
+ /**
3790
+ * Radio group real (`role="radiogroup"` + `role="radio"` + `aria-checked`),
3791
+ * con apariencia de cards apiladas verticalmente — para una decisión ÚNICA Y
3792
+ * EXCLUYENTE dentro de un flujo (ej. método de pago, tipo de documento a
3793
+ * subir), no para filtrar una lista.
3794
+ *
3795
+ * Distinto de `kf-filter-chips` a propósito: `FilterChips` es semántica y
3796
+ * visualmente un FILTRO (chips horizontales, se resaltan con `--kf-color-ink`
3797
+ * — pensado para "Todos/Activos/Archivados" sobre una lista, donde elegir
3798
+ * una opción cambia qué se MUESTRA). Acá la elección es la respuesta misma
3799
+ * a una pregunta del flujo ("¿con qué método pagás?"), no un filtro — por
3800
+ * eso necesita affordance de radio (círculo con punto, más peso visual por
3801
+ * opción) en vez de un pill de filtro reciclado.
3802
+ *
3803
+ * Réplica fiel del patrón ya resuelto en el Design System de referencia
3804
+ * (`otd-pagos.jsx` / `otd.css`: `.otd-pay-method` + `.radio`) — apilado
3805
+ * vertical con gap 10px, radio de 18px con punto interno vía `::after`,
3806
+ * borde+fondo+ícono cambian de color al seleccionar. Se agrega manejo de
3807
+ * teclado (flechas arriba/abajo, Home/End) y `:focus-visible` real, ninguno
3808
+ * de los dos presentes en el mockup original — mínimos de accesibilidad que
3809
+ * no dependen de fidelidad visual al mockup.
3810
+ *
3811
+ * <kf-selectable-card-group
3812
+ * [options]="[{ value: 'card', label: 'Tarjeta', icon: 'credit-card' }]"
3813
+ * [value]="method()"
3814
+ * (valueChange)="method.set($event)"
3815
+ * />
3816
+ */
3817
+ declare class SelectableCardGroup {
3818
+ readonly options: _angular_core.InputSignal<SelectableCardOption[]>;
3819
+ readonly value: _angular_core.InputSignal<string>;
3820
+ /** Accesible: describe qué está eligiendo el usuario (ej. "Método de pago"). */
3821
+ readonly ariaLabel: _angular_core.InputSignal<string>;
3822
+ readonly valueChange: _angular_core.OutputEmitterRef<string>;
3823
+ select(value: string): void;
3824
+ protected onKeydown(event: KeyboardEvent, currentValue: string): void;
3825
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SelectableCardGroup, never>;
3826
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SelectableCardGroup, "kf-selectable-card-group", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
3827
+ }
3828
+
3829
+ /** Contrato de datos de `kf-sidebar-shell.navItems`. `icon` es requerido:
3830
+ * en el sidebar persistente el ícono es parte permanente del diseño. */
3831
+ interface SidebarNavItemData {
3832
+ icon: IconName;
3833
+ label: string;
3834
+ href: string;
3835
+ active?: boolean;
3836
+ }
3837
+
3838
+ /**
3839
+ * Shell de layout con navegación lateral persistente — el complemento
3840
+ * vertical de `kf-portal-header` (navbar horizontal). Pensado para
3841
+ * portales tipo "aplicación interna" (Portal OTD, Servicio Star).
3842
+ *
3843
+ * `navItems` se recibe como array de datos (no como contenido proyectado):
3844
+ * el componente no sabe nada de routing, emite `navItemClick` con el ítem
3845
+ * clickeado y deja que el consumidor decida cómo navegar.
3846
+ *
3847
+ * Slots: `[kf-sidebar-brand]` (logo del rail expandido, visible en desktop
3848
+ * y en el panel mobile — es el MISMO rail), `[kf-sidebar-brand-collapsed]`
3849
+ * (reemplazo opcional cuando el rail está colapsado en desktop),
3850
+ * `[kf-sidebar-brand-mobile]` (logo de la topbar angosta de mobile, fondo
3851
+ * claro) y `[kf-sidebar-footer]` (tarjeta de usuario / acciones al pie).
3852
+ *
3853
+ * Botón de colapso: vive como último ítem de la propia lista de
3854
+ * navegación, no flotando en el borde del rail — mismo criterio de
3855
+ * tap-target grande y predecible que el resto de los ítems.
3856
+ *
3857
+ * Responsive: bajo los 900px el sidebar NO se reemplaza por un panel
3858
+ * distinto — es el MISMO rail el que pasa a `position: fixed` fuera de la
3859
+ * pantalla (`translateX(-100%)`) y se desliza a la vista (`translateX(0)`)
3860
+ * sobre un backdrop cuando `mobileOpen` es `true`. Una topbar angosta con
3861
+ * botón de hamburguesa reemplaza al rail en el flujo del documento
3862
+ * mientras el panel está cerrado.
3863
+ *
3864
+ * A propósito NO reutiliza `kf-drawer` (el panel lateral genérico de la
3865
+ * librería): `kf-drawer` tiene su propio fondo claro fijo, incompatible
3866
+ * con el texto claro de `kf-sidebar-nav-item` (pensado para el gradiente
3867
+ * oscuro), y duplicaría el árbol de navegación en un segundo lugar del
3868
+ * DOM. Acá hay un solo rail, un solo árbol de nav, que cambia de posición
3869
+ * según el viewport.
3870
+ */
3871
+ declare class SidebarShell {
3872
+ /** Ítems del menú del rail. */
3873
+ readonly navItems: _angular_core.InputSignal<SidebarNavItemData[]>;
3874
+ /** Estado de colapso del rail en desktop, two-way. Sin efecto bajo los 900px (ver estilos). */
3875
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
3876
+ /** Visibilidad del rail deslizado como panel en mobile, two-way. */
3877
+ readonly mobileOpen: _angular_core.ModelSignal<boolean>;
3878
+ /** Se emite al clickear un ítem — el consumidor decide cómo navegar (ej. `router.navigateByUrl`). */
3879
+ readonly navItemClick: _angular_core.OutputEmitterRef<SidebarNavItemData>;
3880
+ /** `true` bajo los 900px. */
3881
+ private readonly isMobileViewport;
3882
+ /**
3883
+ * Estado de colapso EFECTIVO — el que ya considera el viewport, no el
3884
+ * `collapsed` crudo. Es lo que usa el propio rail para sus nav-items y
3885
+ * el botón de colapso; se expone público para que el consumidor le
3886
+ * pase el mismo valor a lo que proyecte en `[kf-sidebar-footer]` (ej.
3887
+ * `kf-drawer-session`) — así un footer proyectado no queda colapsado
3888
+ * dentro del panel deslizado de mobile mientras el resto del rail ya
3889
+ * muestra todo expandido.
3890
+ */
3891
+ readonly itemsCollapsed: _angular_core.Signal<boolean>;
3892
+ /** Cierra el panel mobile con Escape — mismo criterio que `Dropdown`/`Modal`/`Drawer`. */
3893
+ protected onEscape(): void;
3894
+ protected onNavItemClick(event: Event, item: SidebarNavItemData): void;
3895
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarShell, never>;
3896
+ 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>;
1522
3897
  }
1523
3898
 
1524
3899
  /** Forma del placeholder. */
@@ -1573,32 +3948,124 @@ declare class Spinner {
1573
3948
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Spinner, "kf-spinner", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "onDark": { "alias": "onDark"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1574
3949
  }
1575
3950
 
3951
+ type SsoProvider = 'microsoft';
3952
+ /** Copy y glyph por proveedor — agregar una entrada acá es todo lo que hace
3953
+ * falta para soportar un proveedor nuevo (ej. `google`), sin tocar el
3954
+ * template ni los estilos del componente. */
3955
+ interface SsoProviderConfig {
3956
+ readonly label: string;
3957
+ }
1576
3958
  /**
1577
- * Indicador numérico compacto (página 34): label + valor grande, con un
1578
- * ícono a la derecha del label — más chico y sin badge de esquina que
1579
- * `kf-metric-card`, pensado para grillas densas de KPIs simples ("Total
1580
- * vehículos: 4", "En taller: 1"), no para dashboards con tendencia.
1581
- *
1582
- * Con `cta` en vez de `value`, se convierte en la variante de llamada a la
1583
- * acción: un bloque de fondo suave con título, descripción y un link — sin
1584
- * número, para promover el siguiente paso (ej. "¿Necesitas mantención?").
3959
+ * Botón de inicio de sesión vía SSO corporativo — pensado como pieza suelta,
3960
+ * no acoplada a `kf-auth-layout`: cualquier consumidor puede usarlo solo,
3961
+ * dentro o fuera del slot `[sso]` de `AuthLayout`.
3962
+ *
3963
+ * `provider` resuelve copy y glyph — hoy solo `'microsoft'` (el método
3964
+ * principal de acceso del ecosistema, cuenta corporativa de Grupo Kaufmann),
3965
+ * con la puerta abierta a sumar otros a futuro sin romper la API existente.
3966
+ *
3967
+ * El glyph es el ícono GENÉRICO de 4 cuadrados de colores que Microsoft
3968
+ * publica en sus propias guías de marca para botones de terceros ("Sign in
3969
+ * with Microsoft") — nunca el logotipo/wordmark oficial completo. No vive en
3970
+ * el sprite de `kf-icon` (`icon-sprite.ts`) a propósito: ese catálogo es
3971
+ * monocromático (`currentColor`, ver `Icon.ts`), pensado para trazos de un
3972
+ * solo color — este glyph necesita sus 4 colores fijos tal como los define
3973
+ * la guía de marca, así que un SVG inline propio es la opción correcta, no
3974
+ * un glyph nuevo forzado al sprite compartido.
3975
+ *
3976
+ * El label por defecto viene de `provider` — pasar contenido propio via
3977
+ * `<ng-content>` lo reemplaza (ej. un texto más corto en un lugar angosto).
3978
+ *
3979
+ * <kf-sso-button provider="microsoft" (ssoClick)="loginWithMicrosoft()" />
3980
+ * <kf-sso-button provider="microsoft" [loading]="loggingIn()">
3981
+ * Ingresar con cuenta corporativa
3982
+ * </kf-sso-button>
3983
+ */
3984
+ declare class SsoButton {
3985
+ readonly provider: _angular_core.InputSignal<"microsoft">;
3986
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
3987
+ /** Reemplaza el glyph + label por `kf-spinner` y fuerza `disabled` — mismo
3988
+ * patrón que `kf-button[loading]`. */
3989
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
3990
+ /** Igual que `kf-button`: por defecto `submit` dentro de un `<form>`, pero
3991
+ * este botón normalmente vive fuera de cualquier form (dispara un
3992
+ * redirect a Microsoft Entra ID), así que casi siempre conviene `button`. */
3993
+ readonly type: _angular_core.InputSignal<"button" | "submit">;
3994
+ protected readonly config: _angular_core.Signal<SsoProviderConfig>;
3995
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SsoButton, never>;
3996
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SsoButton, "kf-sso-button", never, { "provider": { "alias": "provider"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
3997
+ }
3998
+
3999
+ type StatCardTone = 'primary' | 'success' | 'error' | 'purple';
4000
+ /**
4001
+ * Card de estadística horizontal: ícono circular grande a la izquierda,
4002
+ * valor + label + nota apilados al centro, chevron opcional al final si es
4003
+ * clickeable. Distinta de `kf-metric-card` (layout vertical, label arriba +
4004
+ * ícono a la derecha) — este es el patrón real del dashboard de OTD ("Bonos
4005
+ * otorgados", "Cortesías otorgadas"), no una variante de la misma card.
1585
4006
  *
1586
- * <kf-stat-tile label="Total vehículos" value="4" icon="car" />
1587
- * <kf-stat-tile cta title="¿Necesitas mantención?"
1588
- * description="Tenemos la mejor red de talleres."
1589
- * actionLabel="Agenda aquí" actionHref="/taller/agendar" />
4007
+ * <kf-stat-card icon="gift" tone="purple" value="4" label="Bonos otorgados" note="$15,000.00 en beneficios" (cardClick)="onNavigate('beneficios')" />
4008
+ */
4009
+ declare class StatCard {
4010
+ readonly icon: _angular_core.InputSignal<IconName>;
4011
+ readonly tone: _angular_core.InputSignal<StatCardTone>;
4012
+ readonly value: _angular_core.InputSignal<string | number>;
4013
+ readonly label: _angular_core.InputSignal<string>;
4014
+ readonly note: _angular_core.InputSignal<string | undefined>;
4015
+ /** Si se escucha `cardClick`, la card se renderiza como botón clickeable con chevron. */
4016
+ readonly clickable: _angular_core.InputSignalWithTransform<boolean, unknown>;
4017
+ readonly cardClick: _angular_core.OutputEmitterRef<void>;
4018
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatCard, never>;
4019
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatCard, "kf-stat-card", never, { "icon": { "alias": "icon"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "note": { "alias": "note"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; }, { "cardClick": "cardClick"; }, never, never, true, never>;
4020
+ }
4021
+
4022
+ /** Vocabulario semántico único de tono, compartido con `TextTone`. */
4023
+ type StatTileTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
4024
+ type StatTileVariant = 'inline' | 'card';
4025
+ /**
4026
+ * Indicador numérico compacto: label + valor grande con ícono, para grillas
4027
+ * densas de KPIs. Con `cta` en vez de `value` se convierte en un bloque de
4028
+ * llamada a la acción (título, descripción y link, sin tono ni número).
4029
+ * `variant="inline"` quita fondo/borde para una fila horizontal donde el
4030
+ * consumidor pone sus propios separadores.
4031
+ *
4032
+ * `hasProjectedValue` reemplaza el valor de texto por contenido proyectado
4033
+ * (`[valueContent]`) cuando el dato no es un número/string medido — el
4034
+ * caso típico es un estado con su propio `kf-badge`, que necesita su
4035
+ * tono/dot propios en vez del tono del tile:
4036
+ *
4037
+ * <kf-stat-tile label="Estado" hasProjectedValue>
4038
+ * <kf-badge valueContent [tone]="stateTone()">{{ stateLabel() }}</kf-badge>
4039
+ * </kf-stat-tile>
1590
4040
  */
1591
4041
  declare class StatTile {
1592
4042
  readonly cta: _angular_core.InputSignalWithTransform<boolean, unknown>;
1593
4043
  readonly label: _angular_core.InputSignal<string | undefined>;
1594
4044
  readonly value: _angular_core.InputSignal<string | number | undefined>;
1595
4045
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
4046
+ readonly variant: _angular_core.InputSignal<StatTileVariant>;
4047
+ readonly tone: _angular_core.InputSignal<StatTileTone>;
4048
+ /** Tercera línea bajo el valor. Vacía cae en `notePlaceholder`. */
4049
+ readonly note: _angular_core.InputSignal<string | null | undefined>;
4050
+ /** Qué dice la tercera línea cuando no hay nota. Cada consumidor debería
4051
+ * pasar el motivo real de SU dato ausente, no un texto genérico. */
4052
+ readonly notePlaceholder: _angular_core.InputSignal<string | undefined>;
4053
+ /** Rinde el valor como ausencia, no como dato medido. */
4054
+ readonly valueMissing: _angular_core.InputSignalWithTransform<boolean, unknown>;
4055
+ /** El valor es contenido proyectado (`<ng-content select="[valueContent]">`,
4056
+ * ej. un `kf-badge` de estado) en vez del texto de `value()` — para un
4057
+ * dato cuya representación natural no es un número/string medido. */
4058
+ readonly hasProjectedValue: _angular_core.InputSignalWithTransform<boolean, unknown>;
1596
4059
  readonly title: _angular_core.InputSignal<string | undefined>;
1597
4060
  readonly description: _angular_core.InputSignal<string | undefined>;
1598
4061
  readonly actionLabel: _angular_core.InputSignal<string | undefined>;
1599
4062
  readonly actionHref: _angular_core.InputSignal<string | undefined>;
4063
+ /** Texto real de la nota. `null` significa "no reservar la línea": pasa
4064
+ * solo cuando el consumidor no declaró ni nota ni placeholder. */
4065
+ protected readonly noteText: _angular_core.Signal<string | null>;
4066
+ protected readonly noteIsPlaceholder: _angular_core.Signal<boolean>;
1600
4067
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatTile, never>;
1601
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatTile, "kf-stat-tile", never, { "cta": { "alias": "cta"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
4068
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatTile, "kf-stat-tile", never, { "cta": { "alias": "cta"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "note": { "alias": "note"; "required": false; "isSignal": true; }; "notePlaceholder": { "alias": "notePlaceholder"; "required": false; "isSignal": true; }; "valueMissing": { "alias": "valueMissing"; "required": false; "isSignal": true; }; "hasProjectedValue": { "alias": "hasProjectedValue"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; }, {}, never, ["[valueContent]"], true, never>;
1602
4069
  }
1603
4070
 
1604
4071
  /**
@@ -1622,11 +4089,9 @@ interface TabOption {
1622
4089
  icon?: IconName;
1623
4090
  }
1624
4091
  /**
1625
- * Barra de tabs subrayada: un único indicador se desliza horizontalmente hasta
1626
- * la pestaña activa. Como las pestañas tienen ancho de contenido (labels de
1627
- * largo distinto), la posición no se puede resolver con CSS puro: se mide el
1628
- * `offsetLeft`/`offsetWidth` de la pestaña activa después de cada render y la
1629
- * transición del `transform`/`width` queda a cargo de CSS.
4092
+ * Barra de tabs subrayada: un indicador se desliza hasta la pestaña activa.
4093
+ * La posición se mide (`offsetLeft`/`offsetWidth`) después de cada render,
4094
+ * ya que las pestañas tienen ancho de contenido variable.
1630
4095
  *
1631
4096
  * <kf-tabs [options]="tabs" [value]="active" (valueChange)="active = $event" />
1632
4097
  */
@@ -1636,55 +4101,176 @@ declare class Tabs {
1636
4101
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
1637
4102
  private readonly track;
1638
4103
  private readonly destroyRef;
1639
- /** Geometría de la pestaña activa; `null` mientras no haya una medición válida. */
1640
4104
  private readonly activeRect;
1641
4105
  protected readonly indicatorTransform: _angular_core.Signal<string>;
1642
4106
  protected readonly indicatorWidth: _angular_core.Signal<number>;
1643
4107
  constructor();
1644
4108
  private measure;
1645
4109
  select(value: string): void;
1646
- /** Navegación estándar de ARIA tabs: flechas mueven el foco y activan la pestaña. */
1647
- onKeydown(event: KeyboardEvent, currentValue: string): void;
4110
+ protected onKeydown(event: KeyboardEvent, currentValue: string): void;
1648
4111
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Tabs, never>;
1649
4112
  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>;
1650
4113
  }
1651
4114
 
1652
- type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
1653
- type TextVariant = 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption';
1654
- type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
4115
+ interface FolderTabOption {
4116
+ value: string;
4117
+ label: string;
4118
+ icon?: IconName;
4119
+ }
1655
4120
  /**
1656
- * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
1657
- * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
1658
- * ve). Antes de este componente, cada pantalla que consume la librería
1659
- * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
1660
- * (título de página, subtítulo, texto secundario) — auditoría sobre
1661
- * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
1662
- * home sin ningún paso de escala en común. `variant` resuelve eso contra
1663
- * la escala de `tokens/base.css` (`--kf-font-size-*`).
4121
+ * Marca el contenido que `kf-folder-tabs` debe proyectar dentro de su panel.
1664
4122
  *
1665
- * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
1666
- * escape hatch de color libre. Si un caso necesita un color que `tone` no
1667
- * cubre, es una discusión de design system, no una prop nueva acá.
4123
+ * <div kfFolderTabsPanel>...</div>
4124
+ */
4125
+ declare class FolderTabsPanel {
4126
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabsPanel, never>;
4127
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FolderTabsPanel, "[kfFolderTabsPanel]", never, {}, {}, never, never, true, never>;
4128
+ }
4129
+ /**
4130
+ * Tabs con forma de "carpeta": borde superior redondeado, la pestaña activa
4131
+ * se funde visualmente con el panel de contenido de abajo. El panel
4132
+ * (`kfFolderTabsPanel`) es opcional — sin él, se renderiza solo la fila de
4133
+ * pestañas.
1668
4134
  *
1669
- * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
1670
- * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
1671
- * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
4135
+ * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
4136
+ * <div kfFolderTabsPanel>contenido de la pestaña activa</div>
4137
+ * </kf-folder-tabs>
4138
+ *
4139
+ * <!-- o, sin contenido: -->
4140
+ * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event" />
1672
4141
  */
1673
- declare class Text {
1674
- /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
1675
- readonly as: _angular_core.InputSignal<TextAs>;
1676
- readonly variant: _angular_core.InputSignal<TextVariant>;
1677
- readonly tone: _angular_core.InputSignal<TextTone>;
4142
+ declare class FolderTabs {
4143
+ readonly options: _angular_core.InputSignal<FolderTabOption[]>;
4144
+ readonly value: _angular_core.InputSignal<string>;
4145
+ readonly valueChange: _angular_core.OutputEmitterRef<string>;
4146
+ /** Id del panel único — todos los tabs lo referencian vía `aria-controls`. */
4147
+ protected readonly panelId: string;
4148
+ private readonly projectedPanel;
4149
+ protected readonly hasPanel: _angular_core.Signal<boolean>;
4150
+ select(value: string): void;
4151
+ protected onKeydown(event: KeyboardEvent, currentValue: string): void;
4152
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabs, never>;
4153
+ 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>;
4154
+ }
4155
+
4156
+ /**
4157
+ * Textarea de formulario apilado: mismo patrón visual y de integración que
4158
+ * `kf-input` (label arriba, `ControlValueAccessor`, funciona con
4159
+ * `[(ngModel)]` y con Reactive Forms) para el caso de texto libre
4160
+ * multilínea (notas, comentarios, descripciones).
4161
+ *
4162
+ * <kf-textarea label="Notas" formControlName="notes" />
4163
+ * <kf-textarea label="Comentario" [(ngModel)]="comment" [minRows]="2" [maxRows]="10" />
4164
+ *
4165
+ * `minRows`/`maxRows` (3 y 6 por defecto) acotan el crecimiento: el campo
4166
+ * crece con el contenido pero nunca pasa de `maxRows` — superado ese tope el
4167
+ * contenido hace scroll interno (`overflow-y: auto`) en vez de seguir
4168
+ * empujando el layout. Reemplaza el `resize: vertical` nativo (deshabilitado
4169
+ * a propósito) porque un resize manual sin tope es exactamente el problema
4170
+ * que motivó este componente: un `<textarea>` a mano podía crecer hasta
4171
+ * tapar los botones del footer de un modal.
4172
+ *
4173
+ * <kf-textarea label="Detalle" formControlName="detail" [maxRows]="12" />
4174
+ *
4175
+ * `invalid` + `errorMessage` y `readonly`/`disabled` siguen el mismo
4176
+ * contrato que `kf-input` — ver su JSDoc para la diferencia entre ambos
4177
+ * estados.
4178
+ */
4179
+ declare class Textarea implements ControlValueAccessor, AfterViewInit {
4180
+ private static nextId;
4181
+ private readonly nativeTextarea;
4182
+ readonly label: _angular_core.InputSignal<string | undefined>;
4183
+ /** Marca el label con un asterisco visual y aplica `required` al
4184
+ * `<textarea>` nativo — el atributo HTML ya expone el estado obligatorio a
4185
+ * lectores de pantalla (mapeo implícito a `aria-required`), sin necesidad
4186
+ * de un `aria-required` explícito redundante. */
4187
+ readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
4188
+ readonly hint: _angular_core.InputSignal<string | undefined>;
4189
+ /** Borde y aria-invalid en rojo. Ver `errorMessage` para el mensaje asociado. */
4190
+ readonly invalid: _angular_core.InputSignalWithTransform<boolean, unknown>;
4191
+ /** Mensaje de error con `role="alert"` — reemplaza a `hint` mientras esté presente. */
4192
+ readonly errorMessage: _angular_core.InputSignal<string | undefined>;
4193
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
4194
+ /** Ver JSDoc de `kf-input` para la diferencia con `disabled`. */
4195
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, unknown>;
4196
+ readonly placeholder: _angular_core.InputSignal<string>;
4197
+ /** Filas mínimas visibles en reposo — define la altura de arranque del campo. */
4198
+ readonly minRows: _angular_core.InputSignal<number>;
4199
+ /** Filas máximas antes de que el contenido pase a scroll interno en vez de seguir creciendo. */
4200
+ readonly maxRows: _angular_core.InputSignal<number>;
4201
+ protected readonly textareaId: string;
4202
+ protected readonly hintId: string;
4203
+ protected readonly errorId: string;
4204
+ protected readonly value: _angular_core.WritableSignal<string>;
4205
+ protected readonly focused: _angular_core.WritableSignal<boolean>;
4206
+ /** Deshabilitado por Reactive Forms (formControl.disable()). */
4207
+ private readonly disabledByForm;
4208
+ /** Disabled real: por input() directo o por el FormControl asociado. */
4209
+ protected readonly isDisabled: _angular_core.Signal<boolean>;
4210
+ private onChange;
4211
+ private onTouched;
4212
+ ngAfterViewInit(): void;
4213
+ writeValue(value: string): void;
4214
+ registerOnChange(fn: (value: string) => void): void;
4215
+ registerOnTouched(fn: () => void): void;
4216
+ setDisabledState(isDisabled: boolean): void;
4217
+ protected onInput(rawValue: string): void;
4218
+ protected onBlur(): void;
1678
4219
  /**
1679
- * Copia de `as` para uso dentro del template: `as` es palabra reservada
1680
- * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
1681
- * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
1682
- * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
4220
+ * Recalcula la altura según el contenido real (`scrollHeight`), acotada
4221
+ * entre `minRows` y `maxRows` medidos con el line-height ya calculado del
4222
+ * propio elemento — así el tope se mantiene correcto sin importar qué
4223
+ * font-size/line-height termine aplicando cada tema. Superado `maxRows`
4224
+ * el resto del contenido hace scroll interno en vez de seguir creciendo.
1683
4225
  */
1684
- protected readonly tag: _angular_core.Signal<TextAs>;
1685
- protected hostClass(): string;
1686
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
1687
- 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>;
4226
+ private adjustHeight;
4227
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Textarea, never>;
4228
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Textarea, "kf-textarea", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "minRows": { "alias": "minRows"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
4229
+ }
4230
+
4231
+ /**
4232
+ * Da forma al valor de un campo MIENTRAS el usuario escribe — ej.
4233
+ * inserta el guion de un RUT chileno automáticamente, sin que el
4234
+ * usuario tenga que tipearlo — reposicionando el cursor para que
4235
+ * seguir escribiendo se sienta natural (sin esto, cualquier inserción
4236
+ * de caracteres empuja el cursor al final del campo en cada tecla).
4237
+ *
4238
+ * Se aplica junto a `formControlName`/`[formControl]`/`[(ngModel)]`
4239
+ * sobre cualquier input que implemente `ControlValueAccessor` (incluye
4240
+ * `kf-input`, pero no depende de él — funciona igual sobre un
4241
+ * `<input>` nativo):
4242
+ *
4243
+ * <kf-input formControlName="documentId" [kfFormatInput]="formatChileanRut" />
4244
+ *
4245
+ * La función de formateo recibe el valor crudo tecleado y devuelve el
4246
+ * valor con forma — debe ser determinística e idempotente (aplicarla
4247
+ * dos veces seguidas da el mismo resultado), como `formatChileanRut`
4248
+ * en este mismo archivo (`./format.ts`).
4249
+ */
4250
+ declare class FormatInputDirective {
4251
+ private readonly ngControl;
4252
+ /** La función de formateo a aplicar — sin valor, el directive no
4253
+ * hace nada (permite pasar `undefined` condicionalmente sin tener
4254
+ * que quitar el directive del template entero). */
4255
+ readonly kfFormatInput: _angular_core.InputSignal<((value: string) => string) | undefined>;
4256
+ protected onInput(event: Event): void;
4257
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FormatInputDirective, never>;
4258
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FormatInputDirective, "[kfFormatInput]", never, { "kfFormatInput": { "alias": "kfFormatInput"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
4259
+ }
4260
+
4261
+ /**
4262
+ * Grilla de tiles que escalona columnas solo entre DIVISORES de `columns`
4263
+ * (4 → 2 → 1, nunca 3), para que ninguna fila quede huérfana. Mide el
4264
+ * contenedor (`container-type: inline-size`), no el viewport, porque el
4265
+ * mismo bloque se rinde tanto en un drawer angosto como en una página
4266
+ * completa bajo el mismo viewport.
4267
+ */
4268
+ declare class TileGrid {
4269
+ /** Columnas en el ancho máximo. Se acota a 1-6: por encima de 6 un tile
4270
+ * deja de tener ancho para su propio label. */
4271
+ readonly columns: _angular_core.InputSignalWithTransform<number, unknown>;
4272
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TileGrid, never>;
4273
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<TileGrid, "kf-tile-grid", never, { "columns": { "alias": "columns"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1688
4274
  }
1689
4275
 
1690
4276
  /**
@@ -1695,12 +4281,21 @@ declare class Text {
1695
4281
  * semantic role to "work in progress".
1696
4282
  */
1697
4283
  type ToastTone = 'success' | 'error' | 'warning' | 'info' | 'loading';
4284
+ /** Acción opcional (call-to-action) renderizada dentro del toast, junto al botón de cerrar. */
4285
+ interface ToastAction {
4286
+ /** Label corto, ej. "Ver ticket" — 1-3 palabras, el toast mide solo 360px de ancho. */
4287
+ label: string;
4288
+ /** Se invoca al hacer click; el toast se cierra solo después, sin que el llamador tenga que llamar `dismiss()`. */
4289
+ onClick: () => void;
4290
+ }
1698
4291
  /** Options for {@link ToastService.show}. */
1699
4292
  interface ToastOptions {
1700
4293
  /** Color tone. Defaults to `'info'`. */
1701
4294
  tone?: ToastTone;
1702
4295
  /** Milliseconds before auto-dismiss. Ignored when `tone` is `'loading'`. Defaults to `2800`. */
1703
4296
  duration?: number;
4297
+ /** Botón de acción opcional, ej. ir al registro sobre el que avisa el toast. */
4298
+ action?: ToastAction;
1704
4299
  }
1705
4300
  /** Messages for each leg of {@link ToastService.promise}. */
1706
4301
  interface ToastPromiseMessages<T = unknown> {
@@ -1715,7 +4310,12 @@ interface ToastState {
1715
4310
  tone: ToastTone;
1716
4311
  duration: number;
1717
4312
  leaving: boolean;
4313
+ action?: ToastAction;
1718
4314
  }
4315
+ /** What's currently holding the auto-dismiss timer paused. Tracked as a set so hover and
4316
+ * focus can overlap (e.g. mouse leaves while focus is still on the action button) without
4317
+ * resuming the timer early. */
4318
+ type ToastPauseSource = 'hover' | 'focus';
1719
4319
  /**
1720
4320
  * Shows one toast at a time — a new `show()` call replaces whatever is
1721
4321
  * currently up (this mirrors a global status line, not a notification stack).
@@ -1749,10 +4349,31 @@ declare class ToastService {
1749
4349
  private nextId;
1750
4350
  private dismissTimer?;
1751
4351
  private removeTimer?;
4352
+ /** Wall-clock time the running `dismissTimer` is due to fire; used to compute how much
4353
+ * time is left when {@link pause} interrupts it. */
4354
+ private dismissDueAt?;
4355
+ /** Time left on the auto-dismiss countdown while paused. `undefined` when no countdown
4356
+ * is currently paused. */
4357
+ private remainingMs?;
4358
+ private readonly pauseSources;
1752
4359
  /** The single active toast, or `null`. */
1753
4360
  readonly current: _angular_core.Signal<ToastState | null>;
1754
4361
  /** Shows a toast, replacing whatever is currently shown. Returns its id. */
1755
4362
  show(message: string, options?: ToastOptions): number;
4363
+ private scheduleDismiss;
4364
+ /**
4365
+ * Pauses the auto-dismiss countdown for `id` while `source` (pointer hover or focus) is
4366
+ * active on it, so a user reaching for the action/dismiss button never loses the toast
4367
+ * mid-interaction (WCAG 2.2.1). No-op when `id` isn't the active toast, it's leaving, or
4368
+ * it has no running countdown (persistent or `loading` toasts, or already paused by
4369
+ * another source).
4370
+ */
4371
+ pause(id: number, source: ToastPauseSource): void;
4372
+ /**
4373
+ * Resumes a countdown paused by {@link pause}, honoring the time already elapsed before
4374
+ * the pause. No-op while another pause `source` is still active, or nothing is paused.
4375
+ */
4376
+ resume(id: number, source: ToastPauseSource): void;
1756
4377
  /**
1757
4378
  * Starts the exit animation, then removes the toast. No-op when `id` is no
1758
4379
  * longer the active toast, so a stale timer never kills a newer toast.
@@ -1780,10 +4401,65 @@ declare class ToastHost {
1780
4401
  protected readonly toastList: _angular_core.Signal<ToastState[]>;
1781
4402
  protected toastIcon(tone: ToastTone): IconName;
1782
4403
  protected dismiss(id: number): void;
4404
+ /** Pauses the toast's auto-dismiss timer while the pointer or keyboard focus is on it. */
4405
+ protected pause(t: ToastState, source: ToastPauseSource): void;
4406
+ /** Resumes the toast's auto-dismiss timer once neither the pointer nor focus is on it. */
4407
+ protected resume(t: ToastState, source: ToastPauseSource): void;
4408
+ /** Ejecuta la acción del toast (ej. "Ver ticket") y lo cierra — el
4409
+ * llamador no tiene que acordarse de descartarlo desde su propio handler. */
4410
+ protected runAction(t: ToastState): void;
1783
4411
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ToastHost, never>;
1784
4412
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ToastHost, "kf-toast-host", never, {}, {}, never, never, true, never>;
1785
4413
  }
1786
4414
 
4415
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
4416
+ /**
4417
+ * Tooltip de hover/focus — reemplaza el atributo `title` nativo del
4418
+ * navegador (lento en aparecer, sin estilo, inconsistente entre
4419
+ * navegadores/SO) por un panel propio, mismo mecanismo de
4420
+ * `@angular/cdk/overlay` que ya usa `Dropdown`.
4421
+ *
4422
+ * Pensado para el caso "ícono sin label visible necesita comunicar qué
4423
+ * es" (ej. `kf-sidebar-nav-item` colapsado). No es two-way: a diferencia
4424
+ * de `Dropdown`, esto se abre/cierra solo con hover/focus, nunca por click.
4425
+ *
4426
+ * <a [kfTooltip]="label()" kfTooltipPosition="right">…</a>
4427
+ */
4428
+ declare class TooltipDirective {
4429
+ text: string;
4430
+ kfTooltipPosition: TooltipPosition;
4431
+ private readonly host;
4432
+ private readonly overlayService;
4433
+ private readonly viewContainerRef;
4434
+ private readonly injector;
4435
+ private readonly contentId;
4436
+ private overlayRef;
4437
+ /**
4438
+ * `focusin` (no `focus`) a propósito: `focus` no burbujea, así que un
4439
+ * disparador que ENVUELVE al elemento realmente enfocable (ej.
4440
+ * `<kf-button>`, cuyo `<button>` nativo vive en el template del hijo, fuera
4441
+ * de alcance de este host) nunca vería el evento — el tooltip quedaría
4442
+ * mudo por teclado justo en el caso de uso que más lo necesita (un botón
4443
+ * solo-ícono). `focusin` SÍ burbujea y sigue disparando igual para el caso
4444
+ * ya cubierto (un `<a>`/`<button>` nativo que es él mismo el host, ver
4445
+ * `Tooltip.spec.ts`) — sin regresión ahí.
4446
+ */
4447
+ protected show(): void;
4448
+ protected hide(): void;
4449
+ /**
4450
+ * `focusout` (no `blur`, mismo motivo que `show()` usa `focusin`): además,
4451
+ * a diferencia de `mouseleave`/Escape, acá SÍ hace falta revisar
4452
+ * `relatedTarget` — si el host envuelve más de un elemento enfocable, un
4453
+ * foco que se mueve de uno a otro DENTRO del mismo host no debería cerrar
4454
+ * el tooltip (no "salió" del disparador). `Node` en vez de `EventTarget`:
4455
+ * `Node.contains()` es lo que existe para chequear pertenencia al árbol.
4456
+ */
4457
+ protected onFocusOut(event: FocusEvent): void;
4458
+ private positionsFor;
4459
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TooltipDirective, never>;
4460
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TooltipDirective, "[kfTooltip]", never, { "text": { "alias": "kfTooltip"; "required": false; }; "kfTooltipPosition": { "alias": "kfTooltipPosition"; "required": false; }; }, {}, never, never, true, never>;
4461
+ }
4462
+
1787
4463
  /**
1788
4464
  * Estado puntual mostrado junto a la identidad del vehículo (ej. "En ruta",
1789
4465
  * "Mantenimiento", "Fuera de servicio"). `tone` define el color semántico:
@@ -1842,5 +4518,5 @@ declare class ViewToggle {
1842
4518
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ViewToggle, "kf-view-toggle", never, { "view": { "alias": "view"; "required": true; "isSignal": true; }; }, { "viewChange": "viewChange"; }, never, never, true, never>;
1843
4519
  }
1844
4520
 
1845
- 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, FolderTabsPanel, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, VehicleHeader, ViewToggle };
1846
- 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 };
4521
+ export { ActionTile, AdminBadge, Alert, AlertRow, AuthLayout, AuthLayoutSso, Avatar, Badge, Breadcrumb, BucketProgress, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, CompanySelector, ContentSlot, ContentState, ContentViewer, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, DrawerLogout, DrawerSession, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, Link, MAP_CONFIG, MapControls, MapView, MetricCard, MobileFooter, MobileHeader, Modal, MultiSelect, NoteBlock, PageHeader, Pagination, Panel, PortalHeader, Progress, PulseDot, QuotePicker, ReserveBlock, STYLE_BY_THEME, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, Spinner, SsoButton, StatCard, StatTile, StatusPill, THEME_LABEL, THEME_ORDER, Tabs, Text, Textarea, TileGrid, ToastHost, ToastService, TooltipDirective, VehicleHeader, ViewToggle, dataTableViewSignal, formatChileanRut, formatClockTime, formatCurrency, formatNumber, isValidChileanRut, matchMediaSignal, parseCurrency, parseNumber, provideMapConfig, sanitizeDocumentId };
4522
+ export type { AlertRowTone, AlertTone, AuthLayoutBackground, BreadcrumbItem, BucketProgressItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, ContentViewerType, DataTableCardRole, DataTableColumn, DataTableDensity, DataTableSort, DataTableView, DividerOrientation, DocumentRowTone, DrawerSide, DropdownItemTone, DropdownPanelVariant, EventTimelineItem, EventTimelineTone, FilterFieldChange, FilterFieldConfig, FilterFieldType, FilterValue, FolderTabOption, FooterLink, FormatCurrencyOptions, FormatNumberOptions, IconName, IconRotation, IconSize, JourneyStep, JourneyStepStatus, JourneyStepperColorScheme, JourneyStepperSurface, LinkVariant, MapConfig, MapControlKind, MapLatLng, MapMarker, MapMarkerTone, MapPopupAction, MapSize, MapTheme, MapVariant, MultiSelectOption, NoteBlockTone, PageHeaderTitleSize, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, ProgressTone, QuotePickerChip, QuotePickerSortOption, SectionNavSubsectionData, SegmentedOption, SelectableCardOption, SidebarNavItemData, SkeletonVariant, SpinnerSize, SsoProvider, StatCardTone, StatTileTone, StatTileVariant, TabOption, TextAs, TextTone, TextVariant, ToastAction, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, TooltipPosition, VehicleHeaderStatus, ViewToggleOption };