@kaufmann-lab/ui 0.1.16 → 0.1.18

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaufmann-lab/ui",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Shared Angular component library for the Kaufmann frontends",
5
5
  "license": "MIT",
6
6
  "author": "Kaufmann Lab",
@@ -26,6 +26,30 @@
26
26
  ".": {
27
27
  "types": "./types/kaufmann-lab-ui.d.ts",
28
28
  "default": "./fesm2022/kaufmann-lab-ui.mjs"
29
+ },
30
+ "./auth": {
31
+ "types": "./types/kaufmann-lab-ui-auth.d.ts",
32
+ "default": "./fesm2022/kaufmann-lab-ui-auth.mjs"
33
+ },
34
+ "./business": {
35
+ "types": "./types/kaufmann-lab-ui-business.d.ts",
36
+ "default": "./fesm2022/kaufmann-lab-ui-business.mjs"
37
+ },
38
+ "./core": {
39
+ "types": "./types/kaufmann-lab-ui-core.d.ts",
40
+ "default": "./fesm2022/kaufmann-lab-ui-core.mjs"
41
+ },
42
+ "./map": {
43
+ "types": "./types/kaufmann-lab-ui-map.d.ts",
44
+ "default": "./fesm2022/kaufmann-lab-ui-map.mjs"
45
+ },
46
+ "./mobile": {
47
+ "types": "./types/kaufmann-lab-ui-mobile.d.ts",
48
+ "default": "./fesm2022/kaufmann-lab-ui-mobile.mjs"
49
+ },
50
+ "./shared": {
51
+ "types": "./types/kaufmann-lab-ui-shared.d.ts",
52
+ "default": "./fesm2022/kaufmann-lab-ui-shared.mjs"
29
53
  }
30
54
  },
31
55
  "type": "module"
@@ -0,0 +1,240 @@
1
+ import * as _angular_core from '@angular/core';
2
+
3
+ /**
4
+ * Marca el contenido que va en el slot de SSO de `kf-auth-layout` (típico:
5
+ * un `kf-sso-button`, o varios si el ecosistema suma más de un proveedor).
6
+ * `AuthLayout` la detecta vía `contentChild` para decidir si renderiza el
7
+ * separador "o" entre el bloque de SSO y el formulario tradicional
8
+ * proyectado en el slot por defecto — sin este marcador, `AuthLayout`
9
+ * tendría que adivinar si hubo proyección leyendo el DOM a mano.
10
+ *
11
+ * <kf-auth-layout brand="Mi Portal">
12
+ * <div kfAuthLayoutSso>
13
+ * <kf-sso-button provider="microsoft" (click)="loginWithMicrosoft()" />
14
+ * </div>
15
+ * <form ...>...</form>
16
+ * </kf-auth-layout>
17
+ */
18
+ declare class AuthLayoutSso {
19
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthLayoutSso, never>;
20
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AuthLayoutSso, "[kfAuthLayoutSso]", never, {}, {}, never, never, true, never>;
21
+ }
22
+
23
+ type AuthLayoutBackground = 'plain' | 'gradient' | 'pattern';
24
+ /**
25
+ * Layout a pantalla completa para pantallas de autenticación (login,
26
+ * recuperar contraseña, confirmación de envío) — panel de marca a la
27
+ * izquierda, contenido libre a la derecha en desktop; en mobile el panel de
28
+ * marca se oculta y un logo compacto queda visible arriba del formulario.
29
+ *
30
+ * Resuelve solo el "cascarón": cada portal arma su propio `<form>` con los
31
+ * componentes de la librería y lo proyecta en el slot por defecto.
32
+ *
33
+ * <kf-auth-layout
34
+ * brand="Mi Portal"
35
+ * brandLogoSrc="images/mi-portal-logo-white.png"
36
+ * brandLogoMobileSrc="images/mi-portal-logo-black.png"
37
+ * brandTagline="Frase corta de marca, propia de cada portal."
38
+ * >
39
+ * <form (ngSubmit)="onSubmit()">
40
+ * <kf-input label="Correo" type="email" />
41
+ * <kf-input label="Contraseña" type="password" toggleable />
42
+ * <kf-button type="submit">Ingresar</kf-button>
43
+ * </form>
44
+ * </kf-auth-layout>
45
+ *
46
+ * `backgroundVariant` controla la superficie del panel derecho:
47
+ * `'plain'` (fondo liso, comportamiento histórico), `'gradient'` (default —
48
+ * degradé sutil derivado de `--kf-color-primary`/`--kf-color-surface` vía
49
+ * `color-mix()`, ningún color hardcodeado) o `'pattern'` (el mismo degradé
50
+ * más una textura de puntos, también resuelta con tokens). Los tres se
51
+ * resuelven contra los tokens de tema ACTIVOS — cambiar de tema
52
+ * (`andes-maq`, `divemotor-star`, `kaufmann-ar`, futuros) no requiere tocar
53
+ * este componente ni pasarle un color a mano.
54
+ *
55
+ * `card`, en `false` por defecto (comportamiento histórico: el contenido
56
+ * proyectado flota sin superficie propia), envuelve el slot por defecto con
57
+ * la misma superficie de `kf-card` (fondo, borde, sombra, radio) para que
58
+ * el formulario deje de verse suelto sobre el fondo del panel.
59
+ *
60
+ * `title`/`subtitle` renderizan un encabezado de bienvenida DENTRO de la
61
+ * card, antes que cualquier otro contenido (jerarquía ya resuelta:
62
+ * `title-lg` + `body-muted`, ver `Text.ts`) — así ningún consumidor tiene
63
+ * que adivinar la variante de `kf-text` correcta ni terminar con un
64
+ * "Iniciar sesión" repetido y suelto dentro de su propio `<form>`. Sin
65
+ * `title`, no se renderiza ningún encabezado (comportamiento histórico).
66
+ *
67
+ * El slot `[kfAuthLayoutSso]` (ver `AuthLayoutSso`) es para el patrón
68
+ * "SSO primero, credenciales como alternativa secundaria" — típicamente un
69
+ * `kf-sso-button`. Cuando hay contenido ahí, `AuthLayout` resuelve el
70
+ * separador "o" automáticamente entre ese bloque y el contenido por
71
+ * defecto; sin ese slot, nada cambia frente al comportamiento actual.
72
+ *
73
+ * <kf-auth-layout
74
+ * brand="Mi Portal"
75
+ * backgroundVariant="pattern"
76
+ * card
77
+ * title="Iniciar sesión"
78
+ * subtitle="Usa tu cuenta corporativa o ingresa con contraseña"
79
+ * >
80
+ * <div kfAuthLayoutSso>
81
+ * <kf-sso-button provider="microsoft" (click)="loginWithMicrosoft()" />
82
+ * </div>
83
+ * <form (ngSubmit)="onSubmit()">
84
+ * <kf-input label="Correo" type="email" />
85
+ * <kf-input label="Contraseña" type="password" toggleable />
86
+ * <kf-button type="submit">Ingresar</kf-button>
87
+ * </form>
88
+ * </kf-auth-layout>
89
+ */
90
+ declare class AuthLayout {
91
+ /** Nombre de marca/portal — wordmark de respaldo si no hay logo, y `alt` de ambos logos. */
92
+ readonly brand: _angular_core.InputSignal<string>;
93
+ /** Logo en blanco para el panel oscuro de desktop. Sin esto, se muestra `brand()` como wordmark. */
94
+ readonly brandLogoSrc: _angular_core.InputSignal<string | undefined>;
95
+ /** Frase corta bajo el logo. */
96
+ readonly brandTagline: _angular_core.InputSignal<string | undefined>;
97
+ /** Logo compacto para mobile, sobre fondo claro. Sin esto, reutiliza `brandLogoSrc`. */
98
+ readonly brandLogoMobileSrc: _angular_core.InputSignal<string | undefined>;
99
+ /** Superficie del panel derecho. Default `'gradient'`; `'plain'` preserva el fondo liso histórico. */
100
+ readonly backgroundVariant: _angular_core.InputSignal<AuthLayoutBackground>;
101
+ /** Envuelve el contenido proyectado con la superficie de `kf-card` (fondo, borde, sombra). Default `false` — preserva el comportamiento histórico. */
102
+ readonly card: _angular_core.InputSignalWithTransform<boolean, unknown>;
103
+ /** Título del encabezado dentro de la card (`title-lg`). Sin este input, no se renderiza ningún encabezado. */
104
+ readonly title: _angular_core.InputSignal<string | undefined>;
105
+ /** Subtítulo del encabezado (`body-muted`), debajo de `title`. Sin efecto si `title` no está presente. */
106
+ readonly subtitle: _angular_core.InputSignal<string | undefined>;
107
+ /** Presencia de contenido en el slot `[kfAuthLayoutSso]` — controla si se renderiza el separador "o". */
108
+ protected readonly ssoContent: _angular_core.Signal<AuthLayoutSso | undefined>;
109
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthLayout, never>;
110
+ 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>;
111
+ }
112
+
113
+ /**
114
+ * Botón de cerrar sesión, pensado para proyectarse dentro de
115
+ * `[kf-drawer-footer]` de `kf-drawer`, o junto a `kf-drawer-session` dentro
116
+ * de `[kf-sidebar-footer]` de `kf-sidebar-shell` — separado de
117
+ * `kf-drawer-session` (que solo muestra identidad: brand, avatar, nombre,
118
+ * email), no anidado dentro de ella.
119
+ *
120
+ * Ícono `log-out` + label, color `--kf-color-error` con hover en
121
+ * `--kf-color-error-soft` y foco visible con `--kf-shadow-focus`. Separado
122
+ * de `kf-drawer-session` (solo identidad) por responsabilidad única.
123
+ *
124
+ * No sabe nada de autenticación real — solo emite `logoutClick` cuando el
125
+ * consumidor debe cerrar la sesión real.
126
+ *
127
+ * `collapsed` oculta el label y centra el ícono, exponiéndolo vía `title`
128
+ * — mismo criterio que `SidebarNavItem.collapsed` y
129
+ * `DrawerSession.collapsed`, para cuando este botón vive dentro de un
130
+ * `kf-sidebar-shell` colapsado.
131
+ *
132
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
133
+ */
134
+ declare class DrawerLogout {
135
+ /** Label del botón, en español neutro por defecto. Input opcional por si
136
+ * algún consumidor futuro necesita otro texto. */
137
+ readonly label: _angular_core.InputSignal<string>;
138
+ /** Oculta el label y centra el ícono — ver comentario de la clase. */
139
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
140
+ readonly logoutClick: _angular_core.OutputEmitterRef<void>;
141
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerLogout, never>;
142
+ 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>;
143
+ }
144
+
145
+ /**
146
+ * Bloque de identidad para `[kf-drawer-header]` de `kf-drawer` o
147
+ * `[kf-sidebar-footer]` de `kf-sidebar-shell`: avatar + nombre + email,
148
+ * brand opcional arriba. Solo identidad — el logout vive aparte, en
149
+ * `kf-drawer-logout`.
150
+ *
151
+ * `collapsed` oculta brand/nombre/email y deja solo el avatar (para un
152
+ * `kf-sidebar-shell` colapsado). `interactive` convierte la tarjeta en un
153
+ * botón real que emite `cardClick`, para cuando la identidad es el
154
+ * disparador de un menú de acciones (ej. envuelta en `kf-dropdown`).
155
+ *
156
+ * <kf-dropdown [(open)]="menuOpen">
157
+ * <kf-drawer-session interactive name="Juan Pérez" email="juan@empresa.com" (cardClick)="menuOpen.set(!menuOpen())" />
158
+ * <kf-dropdown-panel variant="dark">
159
+ * <kf-dropdown-item icon="log-out" tone="error" (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
160
+ * </kf-dropdown-panel>
161
+ * </kf-dropdown>
162
+ */
163
+ declare class DrawerSession {
164
+ readonly name: _angular_core.InputSignal<string>;
165
+ readonly email: _angular_core.InputSignal<string>;
166
+ /**
167
+ * Etiqueta de rol o función del usuario dentro de la sesión (ej.
168
+ * "Coordinador", "Supervisor"). Se muestra como badge dentro de la
169
+ * identidad, debajo del email — distinto de `brand`, que es la marca o
170
+ * cliente mostrada como encabezado de todo el bloque, no un atributo de
171
+ * la persona.
172
+ */
173
+ readonly role: _angular_core.InputSignal<string>;
174
+ readonly initials: _angular_core.InputSignal<string>;
175
+ readonly avatarUrl: _angular_core.InputSignal<string>;
176
+ /** Label superior opcional, ej. "Portal Kaufmann LATAM". */
177
+ readonly brand: _angular_core.InputSignal<string>;
178
+ /** Oculta brand/nombre/email y centra el avatar — ver comentario de la clase. */
179
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
180
+ /** Convierte la tarjeta en un botón real con ícono de kebab — ver comentario de la clase. */
181
+ readonly interactive: _angular_core.InputSignalWithTransform<boolean, unknown>;
182
+ /** Se emite al clickear la tarjeta, solo cuando `interactive` es `true`. */
183
+ readonly cardClick: _angular_core.OutputEmitterRef<void>;
184
+ /** Si no se pasa `initials`, se derivan de `name`: primera letra de cada
185
+ * una de las primeras 2 palabras, en mayúscula. */
186
+ protected readonly displayInitials: _angular_core.Signal<string>;
187
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerSession, never>;
188
+ 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>;
189
+ }
190
+
191
+ type SsoProvider = 'microsoft';
192
+ /** Copy y glyph por proveedor — agregar una entrada acá es todo lo que hace
193
+ * falta para soportar un proveedor nuevo (ej. `google`), sin tocar el
194
+ * template ni los estilos del componente. */
195
+ interface SsoProviderConfig {
196
+ readonly label: string;
197
+ }
198
+ /**
199
+ * Botón de inicio de sesión vía SSO corporativo — pensado como pieza suelta,
200
+ * no acoplada a `kf-auth-layout`: cualquier consumidor puede usarlo solo,
201
+ * dentro o fuera del slot `[sso]` de `AuthLayout`.
202
+ *
203
+ * `provider` resuelve copy y glyph — hoy solo `'microsoft'` (el método
204
+ * principal de acceso del ecosistema, cuenta corporativa de Grupo Kaufmann),
205
+ * con la puerta abierta a sumar otros a futuro sin romper la API existente.
206
+ *
207
+ * El glyph es el ícono GENÉRICO de 4 cuadrados de colores que Microsoft
208
+ * publica en sus propias guías de marca para botones de terceros ("Sign in
209
+ * with Microsoft") — nunca el logotipo/wordmark oficial completo. No vive en
210
+ * el sprite de `kf-icon` (`icon-sprite.ts`) a propósito: ese catálogo es
211
+ * monocromático (`currentColor`, ver `Icon.ts`), pensado para trazos de un
212
+ * solo color — este glyph necesita sus 4 colores fijos tal como los define
213
+ * la guía de marca, así que un SVG inline propio es la opción correcta, no
214
+ * un glyph nuevo forzado al sprite compartido.
215
+ *
216
+ * El label por defecto viene de `provider` — pasar contenido propio via
217
+ * `<ng-content>` lo reemplaza (ej. un texto más corto en un lugar angosto).
218
+ *
219
+ * <kf-sso-button provider="microsoft" (ssoClick)="loginWithMicrosoft()" />
220
+ * <kf-sso-button provider="microsoft" [loading]="loggingIn()">
221
+ * Ingresar con cuenta corporativa
222
+ * </kf-sso-button>
223
+ */
224
+ declare class SsoButton {
225
+ readonly provider: _angular_core.InputSignal<"microsoft">;
226
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
227
+ /** Reemplaza el glyph + label por `kf-spinner` y fuerza `disabled` — mismo
228
+ * patrón que `kf-button[loading]`. */
229
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
230
+ /** Igual que `kf-button`: por defecto `submit` dentro de un `<form>`, pero
231
+ * este botón normalmente vive fuera de cualquier form (dispara un
232
+ * redirect a Microsoft Entra ID), así que casi siempre conviene `button`. */
233
+ readonly type: _angular_core.InputSignal<"button" | "submit">;
234
+ protected readonly config: _angular_core.Signal<SsoProviderConfig>;
235
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SsoButton, never>;
236
+ 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>;
237
+ }
238
+
239
+ export { AuthLayout, AuthLayoutSso, DrawerLogout, DrawerSession, SsoButton };
240
+ export type { AuthLayoutBackground, SsoProvider };
@@ -0,0 +1,298 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { TemplateRef } from '@angular/core';
3
+ import { DataTableColumn, IconName } from '@kaufmann-lab/ui/core';
4
+ export { DataTableColumn } from '@kaufmann-lab/ui/core';
5
+
6
+ /** Metadato clave mostrado junto al selector (RUC, identificación, sucursal). */
7
+ interface CompanySelectorMeta {
8
+ label: string;
9
+ value: string;
10
+ }
11
+ /**
12
+ * Conmutador de empresa activa con metadatos clave (página 32) — encabeza
13
+ * la pantalla de inicio del portal. Compone `kf-select` para el propio
14
+ * selector (mismo panel propio, sin `<select>` nativo) y proyecta hasta 3
15
+ * pares label/valor (RUC, Identificación, Sucursal en el ejemplo del PDF).
16
+ *
17
+ * <kf-company-selector
18
+ * label="Selecciona empresa"
19
+ * [companies]="['TRANSPORTES DEL SUR S.A.C.', 'ANDES MAQ LTDA.']"
20
+ * [value]="activeCompany()"
21
+ * (valueChange)="activeCompany.set($event)"
22
+ * [meta]="[
23
+ * { label: 'RUC', value: '20448712359' },
24
+ * { label: 'Identificación', value: '0004002232' },
25
+ * { label: 'Sucursal', value: 'Sucursal Norte' },
26
+ * ]"
27
+ * />
28
+ */
29
+ declare class CompanySelector {
30
+ readonly label: _angular_core.InputSignal<string>;
31
+ readonly companies: _angular_core.InputSignal<string[]>;
32
+ readonly value: _angular_core.InputSignal<string | undefined>;
33
+ readonly meta: _angular_core.InputSignal<CompanySelectorMeta[]>;
34
+ readonly valueChange: _angular_core.OutputEmitterRef<string>;
35
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<CompanySelector, never>;
36
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<CompanySelector, "kf-company-selector", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "companies": { "alias": "companies"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "meta": { "alias": "meta"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
37
+ }
38
+
39
+ /** Chip de filtro genérico con contador — nunca hardcodeado a un dominio
40
+ * concreto (ej. estado de una cotización); el consumidor decide qué
41
+ * representa cada `value` y cómo mapea sus items contra él vía `predicate`.
42
+ *
43
+ * Bug real corregido: la primera versión leía `item.status`/`item.chip` por
44
+ * nombre de campo mágico en vez de un `predicate` — colisionaba con
45
+ * dominios donde `status` ya significa otra cosa (ej. `Quote.status` en
46
+ * `web-otd-frontend` es 'ok'|'error'|'warn' de aprobación, no "tiene saldo
47
+ * pendiente"). Un chip "Con saldo pendiente" sobre cotizaciones terminaba
48
+ * filtrando por un campo que no tenía nada que ver, mostrando 0 resultados
49
+ * siempre. `predicate` es explícito y no depende de convenciones de nombre. */
50
+ interface QuotePickerChip<T = unknown> {
51
+ value: string;
52
+ label: string;
53
+ count: number;
54
+ predicate: (item: T) => boolean;
55
+ }
56
+ /** Una opción del dropdown "Ordenar por", visible solo en `variant="modal"`. */
57
+ interface QuotePickerSortOption {
58
+ value: string;
59
+ label: string;
60
+ }
61
+ /**
62
+ * Selector de un ítem "rico" de una lista buscable/filtrable — generalización
63
+ * del patrón "elegir una cotización" de Pagos y Facturas de OTD, pero sin
64
+ * ningún acoplamiento a `Quote`: todo el contenido de cada fila/card llega
65
+ * por `columns` (mismo `DataTableColumn` de `kf-data-table`, reexportado acá
66
+ * en vez de duplicar el concepto) + `cellTemplates` para contenido custom
67
+ * (thumbnail, monto formateado, etc. — mismo mecanismo `TemplateRef` +
68
+ * `ngTemplateOutlet` que ya usa `kf-data-table`).
69
+ *
70
+ * Un solo componente, dos contenedores (`variant: 'inline' | 'modal'`), UNA
71
+ * sola lógica de búsqueda/filtro/orden interna. Nunca dos implementaciones
72
+ * paralelas: el riesgo real de eso es que un fix de filtro se aplique en un
73
+ * modo y se olvide en el otro. Los `computed()` de más abajo
74
+ * (`filteredItems`, `sortedItems`) son la ÚNICA fuente de verdad para ambos
75
+ * `variant`; lo que cambia entre inline y modal es solo qué contenedor
76
+ * envuelve esos datos ya resueltos.
77
+ *
78
+ * ## Modo `inline` (default)
79
+ * Trigger a ANCHO COMPLETO (nunca una caja angosta de ancho fijo) que
80
+ * muestra el ítem activo colapsado — thumbnail, id + modelo, unidades +
81
+ * fecha + monto, CTA "Cambiar cotización" con chevron-down. Al click, el
82
+ * panel se expande INLINE (empuja el contenido de abajo, nunca flota
83
+ * encima) con buscador propio + chips de filtro + un grid de 2 columnas (1
84
+ * en mobile, breakpoint `max-width: 900px` vía `matchMediaSignal` — mismo
85
+ * mecanismo que ya usa el resto de la librería para decisiones de layout en
86
+ * TypeScript, no solo CSS). Por encima de `expandThreshold` resultados
87
+ * visibles, el panel recorta a un preview de `PREVIEW_ROWS` filas con un
88
+ * fade inferior y un botón dashed "Ver las N en pantalla completa" que
89
+ * emite `expandToModal` — el picker no decide qué pasa después (típicamente
90
+ * el consumidor pone `variant` en `'modal'` y `open` en `true`).
91
+ *
92
+ * ## Modo `modal`
93
+ * Mismo buscador, mismos chips, misma lógica de filtrado — compone
94
+ * `kf-modal` (proyectando el contenido) en vez de reimplementar un
95
+ * `<dialog>` a mano: la API de `Modal` (`[(open)]`, cierre con
96
+ * Escape/backdrop/botón vía `<dialog>` nativo) ya cubre exactamente lo que
97
+ * este modo necesita. Compone también `kf-data-table` para la tabla
98
+ * completa (columnas configurables, fade de overflow horizontal ya
99
+ * resuelto ahí) y `kf-pagination` para paginar — ninguno de los tres se
100
+ * reimplementa. Agrega un dropdown propio de "Ordenar por" (calibrado
101
+ * visualmente contra el trigger de `kf-select`, pero compacto: `kf-select`
102
+ * fuerza un ancho fijo de 240px pensado para formularios, no para una
103
+ * barra de herramientas de tabla — ver JSDoc de `sortOpen` más abajo).
104
+ *
105
+ * En mobile, `kf-data-table[cardable]` con `defaultView` forzado a
106
+ * `'cards'` decide la vista (mismo criterio que `matchMediaSignal` ya
107
+ * resolvió en otros consumidores): dentro de un modal a pantalla completa
108
+ * no tiene sentido ofrecer un toggle tabla/cards que en mobile daría una
109
+ * tabla angosta con scroll horizontal.
110
+ *
111
+ * DEUDA TÉCNICA CONOCIDA (confirmada con el usuario, ver CHANGELOG.md): el
112
+ * diseño validado con mockups pedía que en mobile el modal ocupe el 100%
113
+ * de la pantalla (botón "atrás", sin overlay centrado) — `kf-modal` no
114
+ * tiene ese modo, así que hoy en mobile se ve como un diálogo centrado
115
+ * angosto (`width: min(480px, ...)`). Es funcional (cards legibles,
116
+ * búsqueda, chips, paginación), solo visualmente distinto del mockup.
117
+ *
118
+ * <kf-quote-picker
119
+ * [items]="quotes()"
120
+ * [columns]="columns"
121
+ * [selectedId]="selectedQuoteId()"
122
+ * [idKey]="(q) => q.id"
123
+ * [chips]="statusChips()"
124
+ * [cellTemplates]="{ photo: photoCell(), amount: amountCell() }"
125
+ * (selectedIdChange)="selectedQuoteId.set($event)"
126
+ * (expandToModal)="variant.set('modal'); open.set(true)"
127
+ * />
128
+ */
129
+ declare class QuotePicker<T = unknown> {
130
+ readonly items: _angular_core.InputSignal<T[]>;
131
+ readonly columns: _angular_core.InputSignal<DataTableColumn[]>;
132
+ readonly selectedId: _angular_core.InputSignal<string | null>;
133
+ readonly idKey: _angular_core.InputSignal<(item: T) => string>;
134
+ /**
135
+ * Mapa `columnKey -> TemplateRef`, mismo mecanismo que `kf-data-table`
136
+ * (`ngTemplateOutlet` + contexto `{ $implicit: item, index }`) — se
137
+ * reenvía tal cual a la tabla interna del modo `modal`, y se usa
138
+ * directamente en el grid de cards del modo `inline`. Un solo mapa para
139
+ * ambos modos, consistente con la regla de "una sola lógica interna".
140
+ */
141
+ readonly cellTemplates: _angular_core.InputSignal<Record<string, TemplateRef<{
142
+ $implicit: T;
143
+ index: number;
144
+ }>> | undefined>;
145
+ readonly chips: _angular_core.InputSignal<QuotePickerChip<T>[]>;
146
+ readonly sortOptions: _angular_core.InputSignal<QuotePickerSortOption[]>;
147
+ readonly sort: _angular_core.ModelSignal<string | null>;
148
+ /**
149
+ * Umbral de volumen alto — el consumidor decide el número exacto, nunca
150
+ * hardcodeado a "cotizaciones" ni a ningún caso de uso puntual. Default
151
+ * 15 según el diseño validado con mockups.
152
+ */
153
+ readonly expandThreshold: _angular_core.InputSignal<number>;
154
+ readonly variant: _angular_core.InputSignal<"inline" | "modal">;
155
+ /** Visibilidad del modo `modal`, two-way — mismo patrón que `[(open)]` de `kf-modal`. */
156
+ readonly open: _angular_core.ModelSignal<boolean>;
157
+ readonly title: _angular_core.InputSignal<string>;
158
+ readonly searchPlaceholder: _angular_core.InputSignal<string>;
159
+ /** Ícono de placeholder cuando ninguna columna `cardRole: 'media'` resuelve
160
+ * un thumbnail real vía `cellTemplates` — el picker no asume qué dominio
161
+ * consume (cotización, vehículo, cliente...), así que no hardcodea un
162
+ * ícono fijo. Default `'file'` solo por no dejar el input sin valor. */
163
+ readonly placeholderIcon: _angular_core.InputSignal<IconName>;
164
+ readonly selectedIdChange: _angular_core.OutputEmitterRef<string>;
165
+ /** Disparado al superar `expandThreshold` en modo inline; el consumidor decide pasar a `modal`. */
166
+ readonly expandToModal: _angular_core.OutputEmitterRef<void>;
167
+ protected readonly searchQuery: _angular_core.WritableSignal<string>;
168
+ protected readonly activeChip: _angular_core.WritableSignal<string | null>;
169
+ protected readonly expanded: _angular_core.WritableSignal<boolean>;
170
+ protected readonly page: _angular_core.WritableSignal<number>;
171
+ protected readonly sortOpen: _angular_core.WritableSignal<boolean>;
172
+ /**
173
+ * Mismo breakpoint (`max-width: 900px`) que el resto de la librería usa
174
+ * para decisiones de layout en TypeScript (no solo CSS) — ver
175
+ * `matchMediaSignal`. Decide 1 vs. 2 columnas en el grid inline, y tabla
176
+ * vs. cards apiladas dentro del modal.
177
+ */
178
+ protected readonly isMobile: _angular_core.Signal<boolean>;
179
+ private readonly destroyRef;
180
+ private readonly previewScrollEl;
181
+ /** Mismo mecanismo de fade que `kf-data-table` — ver comentario en QuotePicker.css. */
182
+ protected readonly canFadeBottom: _angular_core.WritableSignal<boolean>;
183
+ readonly selectedItem: _angular_core.Signal<T | undefined>;
184
+ /** Filtro por texto (buscador propio) + filtro por chip activo, en ese orden. */
185
+ protected readonly filteredItems: _angular_core.Signal<T[]>;
186
+ /**
187
+ * Orden solo tiene efecto visible en modo `modal` (el diseño validado no
188
+ * pide dropdown de orden en inline) — se calcula igual en ambos casos
189
+ * porque es la MISMA lógica interna; en inline `sort()` simplemente se
190
+ * queda en `null` salvo que el consumidor lo setee explícitamente.
191
+ */
192
+ protected readonly sortedItems: _angular_core.Signal<T[]>;
193
+ /** Por encima del umbral, el preview inline se recorta salvo que el usuario ya haya expandido. */
194
+ protected readonly isHighVolume: _angular_core.Signal<boolean>;
195
+ protected readonly previewItems: _angular_core.Signal<T[]>;
196
+ protected readonly pages: _angular_core.Signal<number>;
197
+ protected readonly pagedItems: _angular_core.Signal<T[]>;
198
+ protected readonly sortLabel: _angular_core.Signal<string>;
199
+ constructor();
200
+ private updateFadeState;
201
+ private cellText;
202
+ toggleExpandedPanel(): void;
203
+ onSearchInput(event: Event): void;
204
+ selectChip(value: string): void;
205
+ select(item: T): void;
206
+ onItemKeydown(event: KeyboardEvent, item: T): void;
207
+ onSeeAll(): void;
208
+ onTableRowClick(event: {
209
+ row: T;
210
+ index: number;
211
+ }): void;
212
+ toggleSort(): void;
213
+ setSort(value: string): void;
214
+ trackItem(item: T, index: number): string | number;
215
+ /** Arrow function como campo (no un método) para que `[rowKey]` reciba
216
+ * una referencia ESTABLE entre change detections — `trackItem.bind(this)`
217
+ * en el template recrearía una función nueva en cada ciclo, invalidando
218
+ * cualquier optimización de `kf-data-table` que dependa de identidad. */
219
+ protected readonly rowKeyFn: (item: T, index: number) => string | number;
220
+ protected cellValue(item: T, column: DataTableColumn): string;
221
+ protected readonly mediaColumn: _angular_core.Signal<DataTableColumn | undefined>;
222
+ protected readonly titleColumn: _angular_core.Signal<DataTableColumn | undefined>;
223
+ protected readonly fieldColumns: _angular_core.Signal<DataTableColumn[]>;
224
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<QuotePicker<any>, never>;
225
+ 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>;
226
+ }
227
+
228
+ /**
229
+ * Estado vacío de reservas en taller con llamada a agendar (página 36):
230
+ * barra de estado vacío arriba ("No tienes reservas de taller"), panel de
231
+ * invitación al medio (ícono en círculo azul, título, descripción, link) y
232
+ * una fila de dos acciones al pie — un slot `[actions]` proyectado, para no
233
+ * acoplar el componente a botones fijos.
234
+ *
235
+ * Distinto de `kf-empty-state` genérico: este combina la barra + la
236
+ * invitación + las acciones como un bloque compuesto propio del dominio
237
+ * "taller", no un placeholder de una sola pieza.
238
+ *
239
+ * `grow` estira el panel de invitación para llenar el espacio vertical
240
+ * disponible — solo cuando el consumidor comparte fila con una tarjeta más
241
+ * alta (ej. junto a "Total de vehículos"); en un lugar sin esa presión
242
+ * (home, debajo del carousel) se deja en su alto natural.
243
+ *
244
+ * <kf-reserve-block emptyLabel="No tienes reservas de taller" grow
245
+ * title="¿Necesitas mantención?" description="Tenemos la mejor red de talleres."
246
+ * actionLabel="Agenda tu reserva aquí" actionHref="/taller/agendar">
247
+ * <kf-button actions icon="calendar">Reservar hora</kf-button>
248
+ * <kf-button actions variant="outline" icon="list">Ver agenda</kf-button>
249
+ * </kf-reserve-block>
250
+ */
251
+ declare class ReserveBlock {
252
+ readonly emptyLabel: _angular_core.InputSignal<string>;
253
+ readonly title: _angular_core.InputSignal<string>;
254
+ readonly description: _angular_core.InputSignal<string | undefined>;
255
+ readonly actionLabel: _angular_core.InputSignal<string | undefined>;
256
+ readonly actionHref: _angular_core.InputSignal<string | undefined>;
257
+ /** La invitación se estira para llenar la tarjeta (ej. junto a "Total de vehículos"). */
258
+ readonly grow: _angular_core.InputSignalWithTransform<boolean, unknown>;
259
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ReserveBlock, never>;
260
+ 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>;
261
+ }
262
+
263
+ /**
264
+ * Estado puntual mostrado junto a la identidad del vehículo (ej. "En ruta",
265
+ * "Mantenimiento", "Fuera de servicio"). `tone` define el color semántico:
266
+ * "crit" no tiene token propio en base.css, se mapea a var(--kf-color-error).
267
+ */
268
+ interface VehicleHeaderStatus {
269
+ label: string;
270
+ tone?: 'ok' | 'warn' | 'crit';
271
+ icon?: IconName;
272
+ }
273
+ /**
274
+ * Header de identidad de un vehículo de flota: unidad (patente/id), modelo
275
+ * (como badge, no texto plano — página 24: "ABC-123" junto a un badge
276
+ * "TITAN 4X2"), VIN en fuente mono y un estado opcional. Un área de acciones
277
+ * opcional se proyecta vía `<ng-content select="[actions]">` para no acoplar
278
+ * el componente a un set fijo de botones — el patrón real de la spec es
279
+ * `kf-button variant="outline"` (ej. "Llamar", con ícono) + un `primary`
280
+ * (ej. "Crear Ticket", con ícono "+"):
281
+ *
282
+ * <kf-vehicle-header unit="AB123CD" model="Sprinter 415" vin="9BW..."
283
+ * [status]="{ label: 'En ruta', tone: 'ok' }">
284
+ * <kf-button actions variant="outline" icon="phone">Llamar</kf-button>
285
+ * <kf-button actions icon="plus">Crear Ticket</kf-button>
286
+ * </kf-vehicle-header>
287
+ */
288
+ declare class VehicleHeader {
289
+ readonly unit: _angular_core.InputSignal<string>;
290
+ readonly model: _angular_core.InputSignal<string | undefined>;
291
+ readonly vin: _angular_core.InputSignal<string | undefined>;
292
+ readonly status: _angular_core.InputSignal<VehicleHeaderStatus | undefined>;
293
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<VehicleHeader, never>;
294
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<VehicleHeader, "kf-vehicle-header", never, { "unit": { "alias": "unit"; "required": true; "isSignal": true; }; "model": { "alias": "model"; "required": false; "isSignal": true; }; "vin": { "alias": "vin"; "required": false; "isSignal": true; }; "status": { "alias": "status"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
295
+ }
296
+
297
+ export { CompanySelector, QuotePicker, ReserveBlock, VehicleHeader };
298
+ export type { CompanySelectorMeta, QuotePickerChip, QuotePickerSortOption, VehicleHeaderStatus };