sapenlinea-components 0.13.100 → 0.13.101

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.
package/README.md CHANGED
@@ -482,7 +482,7 @@ Componente de **paginación** para tablas o listas, con opción de selector de t
482
482
  ### 10. `lib-table` (Table)
483
483
 
484
484
  **Qué hace**
485
- Componente de **tabla dinámica** con soporte de ordenamiento, acciones por fila, estados con tonos configurables y traducción de estados mediante enum.
485
+ Componente de **tabla dinámica** con soporte de ordenamiento, acciones por fila, filas expandibles, selección múltiple, totales, column groups, estados con tonos configurables y traducción de estados mediante enum.
486
486
 
487
487
  **Selector:** `lib-table`
488
488
 
@@ -495,8 +495,14 @@ Componente de **tabla dinámica** con soporte de ordenamiento, acciones por fila
495
495
  - `subColumns: SubColumn[]` – Columnas de las sub-filas expandibles.
496
496
  - `subActions: TableAction[] | ((row: TableRow) => TableAction[])` – Acciones por sub-fila.
497
497
  - `showSubActions: boolean = false` – Muestra/oculta acciones en sub-filas.
498
+ - `isRowExpandable: (row: TableRow) => boolean = () => false` – Determina si una fila puede expandirse.
499
+ - `expandedChildren: (row: TableRow) => TableRow[] | null = () => null` – Proveedor de sub-filas al expandir.
500
+ - `rowIdKey: string = 'id'` – Propiedad del row usada como identificador para expansión y selección.
501
+ - `showSelection: boolean = false` – Muestra columna de checkboxes para selección múltiple.
502
+ - `showTotals: boolean = false` – Muestra fila de totales (columnas `money` con `totalizable: true`).
503
+ - `columnGroups: ColumnGroup[] = []` – Grupos de columnas adicionales (checkbox, número, icon-button, porcentaje).
498
504
  - `statusToneMap: StatusToneMap` – Mapa de estado normalizado → tono visual.
499
- - `statusEnumMap: Record<string, string>` – Traduce el valor crudo del estado a la etiqueta visible.
505
+ - `statusEnumMap: Record<number | string, string>` – Traduce el valor crudo del estado a la etiqueta visible.
500
506
 
501
507
  **Tipos de columna (`type`):**
502
508
 
@@ -554,9 +560,70 @@ subColumns: SubColumn[] = [
554
560
  { key: 'placa', label: 'PLACA', type: 'text', widthProfile: 'fixed', maxWidth: 120 }
555
561
  ```
556
562
 
557
- `columnGroups` (anexos/checkbox) mantienen celdas estrechas fijas (~56px) sin configuración adicional.
563
+ **Column groups (`columnGroups`):**
558
564
 
559
- Helpers exportados: `resolveTableColumnLayout`, `getTableMinWidth`, `buildChildGridTemplateColumns`.
565
+ Grupos de columnas adicionales al final de la tabla (anexos, checkboxes, acciones inline). Mantienen celdas estrechas fijas (~56px) sin configuración adicional.
566
+
567
+ ```ts
568
+ export interface ColumnGroup {
569
+ groupLabel: string;
570
+ columns: ColumnGroupItem[];
571
+ }
572
+
573
+ export interface ColumnGroupItem {
574
+ key: string;
575
+ label: string;
576
+ description: string;
577
+ type?: 'checkbox' | 'number' | 'icon-button' | 'percentage';
578
+ icon?: string;
579
+ iconAlternate?: string;
580
+ iconActiveKey?: string;
581
+ isIconActive?: (row: TableRow) => boolean;
582
+ disabledKey?: string;
583
+ isIconDisabled?: (row: TableRow) => boolean;
584
+ iconColor?: string;
585
+ iconAlternateColor?: string;
586
+ /** Key del row con URL(s) del archivo a visualizar al hacer clic en checkbox marcado */
587
+ fileUrlKey?: string;
588
+ /** Key del row para el nombre del archivo en el visor (default: label de la columna) */
589
+ fileNameKey?: string;
590
+ }
591
+ ```
592
+
593
+ - Checkbox marcado con `fileUrlKey` (o valor string en la celda) abre el visor PDF/imagen vía `PdfViewerService`.
594
+ - Cambios en checkbox emiten `columnGroupChange`.
595
+
596
+ **Filas expandibles:**
597
+
598
+ ```html
599
+ <lib-table
600
+ [columns]="columns"
601
+ [data]="rows"
602
+ [subColumns]="subColumns"
603
+ [isRowExpandable]="canExpand"
604
+ [expandedChildren]="getChildren"
605
+ rowIdKey="id"
606
+ />
607
+ ```
608
+
609
+ ```ts
610
+ canExpand = (row: TableRow) => !!row.hasChildren;
611
+ getChildren = (row: TableRow) => row.children ?? [];
612
+ ```
613
+
614
+ **Selección múltiple (`showSelection`):**
615
+
616
+ Activa checkboxes en encabezado y filas. El estado interno (`selectedRows`) se gestiona en el componente; expone métodos `toggleRow`, `toggleAllRows`, `isRowSelected`, `isAllSelected` vía la instancia.
617
+
618
+ **Fila de totales (`showTotals`):**
619
+
620
+ Suma automáticamente columnas con `type: 'money'` y `totalizable: true`.
621
+
622
+ **Menú de acciones:**
623
+
624
+ El panel contextual se renderiza con `position: fixed` adjunto a `document.body`, se reposiciona en scroll/resize y elige abrir hacia arriba o abajo según el espacio disponible.
625
+
626
+ Helpers exportados: `resolveTableColumnLayout`, `getTableMinWidth`, `buildChildGridTemplateColumns`, `isMultilineHeaderLabel`, `ColumnWidthProfile`, `ResolvedTableColumnLayout`.
560
627
 
561
628
  **Iconos en acciones y columnas `icon-button`:**
562
629
 
@@ -588,7 +655,8 @@ Propiedades de columna `icon-button`: `icon`, `iconAlternate`, `iconActiveKey`,
588
655
 
589
656
  - `optionSelected: EventEmitter<{ action: string; row: TableRow }>` – Emite la acción seleccionada y la fila principal.
590
657
  - `subOptionSelected: EventEmitter<{ action: string; row: TableRow }>` – Emite la acción seleccionada y la sub-fila.
591
- - `iconAction: EventEmitter<{ action: string; row: TableRow }>` – Emite al hacer clic en un `icon-button`.
658
+ - `iconAction: EventEmitter<{ action: string; row: TableRow }>` – Emite al hacer clic en un `icon-button` (columnas o column groups).
659
+ - `columnGroupChange: EventEmitter<{ key: string; row: TableRow; value: any }>` – Emite al cambiar un valor en `columnGroups` (p. ej. checkbox).
592
660
 
593
661
  **Sub-filas con acciones (`subActions` + `showSubActions`):**
594
662
 
@@ -954,7 +1022,7 @@ Carrusel horizontal de tarjetas de dispositivo con estado (batería, geocerca, c
954
1022
  ### 23. `lib-title-filters` (TitleFilters)
955
1023
 
956
1024
  **Qué hace**
957
- Encabezado de sección con título, botones de acción configurables y grupo de filtros (texto, número, select, fecha, hora).
1025
+ Encabezado de sección con título, botones de acción configurables y grupo de filtros (texto, número, select, fecha, datetime, hora).
958
1026
 
959
1027
  **Selector:** `lib-title-filters`
960
1028
 
@@ -963,16 +1031,18 @@ Encabezado de sección con título, botones de acción configurables y grupo de
963
1031
  | Valor | Descripción |
964
1032
  |---|---|
965
1033
  | `'toggle'` | Botón "Filtros" que muestra/oculta el panel de filtros (default) |
966
- | `'action'` | Botón "Ir al Mapa" fijo, en producción, no modificar |
967
- | `'click'` | Botón primario reutilizable con ícono y texto configurables |
1034
+ | `'action'` | Botón "Ir al Mapa" con ícono SVG fijo; emite `filterButtonClicked` |
1035
+ | `'click'` | Botón primario (`lib-button`) con ícono y texto configurables; emite `filterButtonClicked` |
1036
+ | `'clickRight'` | Igual que `click`, pero el botón primario se renderiza al final de la fila (junto a secundario/exportar) |
1037
+ | `'none'` | No muestra botón de acción/filtros en la barra del título |
968
1038
 
969
1039
  **Inputs:**
970
1040
 
971
1041
  - `title: string = 'Listado'`
972
1042
  - `filtersConfig: TitleFilterConfig[]`
973
- - `buttonMode: 'toggle' | 'action' | 'click' = 'toggle'`
974
- - `clickButtonLabel: string = 'Acción'` — texto del botón en modo `click`
975
- - `clickButtonIcon: string = ''` — clase CSS del ícono en modo `click`
1043
+ - `buttonMode: 'toggle' | 'action' | 'click' | 'clickRight' | 'none' = 'toggle'`
1044
+ - `clickButtonLabel: string = 'Acción'` — texto del botón en modo `click` / `clickRight`
1045
+ - `clickButtonIcon: string = ''` — clase CSS del ícono en modo `click` / `clickRight`
976
1046
  - `alwaysShowFilters: boolean = false` — muestra los filtros permanentemente sin necesidad de toggle
977
1047
  - `showSecondaryButton: boolean = false` — muestra un botón secundario (ej. "Exportar PDF")
978
1048
  - `secondaryButtonLabel: string = 'Exportar PDF'`
@@ -981,7 +1051,7 @@ Encabezado de sección con título, botones de acción configurables y grupo de
981
1051
  - `exportButtonLabel: string = 'Exportar CSV'`
982
1052
  - `exportButtonIcon: string = 'icon-export'`
983
1053
  - `showButtonBack: boolean = false`
984
- - `initialFilters: Record<string, string | number | Date | null> = {}` — estado previo de filtros para restaurar al volver a la pantalla. La estructura es la misma que emite `filtersChange`. Soporta todos los tipos de filtro: texto, número, select (por value) y fecha.
1054
+ - `initialFilters: Record<string, string | number | Date | null> = {}` — estado previo de filtros para restaurar al volver a la pantalla. La estructura es la misma que emite `filtersChange`. Soporta todos los tipos de filtro: texto, número, select (por value), fecha, datetime y hora.
985
1055
 
986
1056
  > Los botones secundario y de exportación se agrupan automáticamente al final de la fila del título.
987
1057
 
@@ -1016,7 +1086,7 @@ onFiltersChange(filters: Record<string, string | number | Date | null>) {
1016
1086
  - `filtersChange: Record<string, string | number | Date | null>`
1017
1087
  - `applyFilters: void`
1018
1088
  - `clearFilters: void`
1019
- - `filterButtonClicked: void` — emitido al hacer clic en el botón `click` o `action`
1089
+ - `filterButtonClicked: void` — emitido al hacer clic en el botón `click`, `clickRight` o `action`
1020
1090
  - `secondaryButtonClicked: void`
1021
1091
  - `exportButtonClicked: void`
1022
1092
  - `clickButtonBack: void`
@@ -1053,11 +1123,37 @@ onFiltersChange(filters: Record<string, string | number | Date | null>) {
1053
1123
 
1054
1124
  ```ts
1055
1125
  export interface TitleFilterConfig {
1056
- type: 'text' | 'number' | 'select' | 'date' | 'datetime' | 'time';
1126
+ type: FilterType; // 'text' | 'number' | 'select' | 'date' | 'datetime' | 'time'
1057
1127
  filters: FilterItem[];
1058
1128
  }
1059
1129
  ```
1060
1130
 
1131
+ Cada entrada de `filtersConfig` renderiza un componente de filtro según su `type`:
1132
+
1133
+ | `type` | Componente interno |
1134
+ |---|---|
1135
+ | `text` | `lib-input-text-filter` |
1136
+ | `number` | `lib-input-number-filter` |
1137
+ | `select` | `lib-input-select-filter` |
1138
+ | `date` / `datetime` | `lib-date-time-filter` |
1139
+ | `time` | `lib-input-time-filter` |
1140
+
1141
+ **Navegación con Enter:** al presionar Enter en un filtro, el foco avanza al siguiente campo visible; en el último filtro se dispara `applyFilters`.
1142
+
1143
+ **Ejemplo — modo `clickRight` con botón de acción alineado a la derecha:**
1144
+
1145
+ ```html
1146
+ <lib-title-filters
1147
+ title="Reportes"
1148
+ buttonMode="clickRight"
1149
+ clickButtonLabel="Nuevo reporte"
1150
+ clickButtonIcon="icon-add"
1151
+ [showSecondaryButton]="true"
1152
+ (filterButtonClicked)="onNuevoReporte()"
1153
+ (secondaryButtonClicked)="onExportPDF()"
1154
+ />
1155
+ ```
1156
+
1061
1157
  ---
1062
1158
 
1063
1159
  ### 24. `lib-notification-modal` (NotificationModal)
@@ -2341,3 +2437,293 @@ readonly brandColors = ['#596300', '#006D2F', '#CF0707', ...];
2341
2437
  ```
2342
2438
 
2343
2439
  ---
2440
+
2441
+ ### 49. `lib-sidebar` (Sidebar) y `SidebarService`
2442
+
2443
+ **Qué hace**
2444
+ Barra lateral reutilizable de tres niveles (módulo → submódulo → aplicativo). Recibe ítems por `@Input`, delega navegación y acciones al contenedor, y es compatible con `ModuleData` de `lib-module-card-list`.
2445
+
2446
+ **Selector:** `lib-sidebar`
2447
+
2448
+ > **Documentación detallada:** [`src/lib/components/sidebar/README.md`](./src/lib/components/sidebar/README.md)
2449
+
2450
+ **Inputs principales:**
2451
+
2452
+ | Input | Default | Descripción |
2453
+ |---|---|---|
2454
+ | `menuItems` | `[]` | Menú principal |
2455
+ | `userMenuItems` | `[]` | Ítems inferiores (configuración, logout, etc.) |
2456
+ | `expanded` | `false` | Two-way: estado expandido/colapsado |
2457
+ | `autoNavigate` | `false` | Navega con `Router` internamente |
2458
+ | `closeOnNavigate` | `true` | Colapsa al navegar |
2459
+ | `closeOnOutsideClick` | `true` | Colapsa al clic fuera |
2460
+ | `closeOnEscape` | `true` | Colapsa con Escape |
2461
+ | `hoverExpand` | `true` | Expansión temporal al mantener hover |
2462
+ | `hoverExpandDelay` | `1000` | Retardo en ms antes de expandir por hover |
2463
+ | `topOffset` | `'75px'` | Distancia desde el borde superior |
2464
+ | `expandedWidth` | `'280px'` | Ancho expandido |
2465
+ | `collapsedWidth` | `'70px'` | Ancho colapsado |
2466
+ | `dangerAction` | `'logout'` | `action` del menú inferior pintado en rojo |
2467
+ | `iconColor` | `'#454733'` | Color de iconos |
2468
+
2469
+ **Outputs:**
2470
+
2471
+ | Output | Descripción |
2472
+ |---|---|
2473
+ | `navigate` | Clic en ítem con `route` |
2474
+ | `userAction` | Clic en ítem del menú inferior con `action` |
2475
+ | `itemClick` | Cualquier clic (auditoría / analytics) |
2476
+
2477
+ **Utilidades exportadas:**
2478
+
2479
+ ```ts
2480
+ import {
2481
+ mapModuleDataToSidebarItems,
2482
+ markActiveSidebarItems,
2483
+ filterVisibleSidebarItems,
2484
+ getInitialOpenState,
2485
+ submoduleOpenKey,
2486
+ SidebarMenuItem,
2487
+ SidebarRouteResolver,
2488
+ } from 'sapenlinea-components';
2489
+ ```
2490
+
2491
+ **`SidebarService`** — estado compartido opcional (solo expandido/colapsado):
2492
+
2493
+ ```ts
2494
+ readonly sidebar = inject(SidebarService);
2495
+ sidebar.toggleSidebar();
2496
+ sidebar.sidebarExpanded();
2497
+ ```
2498
+
2499
+ **Ejemplo mínimo:**
2500
+
2501
+ ```html
2502
+ <lib-sidebar
2503
+ [menuItems]="menuItems()"
2504
+ [userMenuItems]="userMenuItems"
2505
+ [(expanded)]="sidebarExpanded"
2506
+ [topOffset]="'75px'"
2507
+ (navigate)="onNavigate($event)"
2508
+ (userAction)="onUserAction($event)"
2509
+ />
2510
+ ```
2511
+
2512
+ ---
2513
+
2514
+ ### 50. `lib-header` (Header)
2515
+
2516
+ **Qué hace**
2517
+ Encabezado de aplicación con menú hamburguesa, título activo, acción contextual configurable por microfrontend, campana de notificaciones y menú de usuario. Todas las entradas son opcionales para uso como custom element, module federation o dentro del monolito.
2518
+
2519
+ **Selector:** `lib-header`
2520
+
2521
+ **Interfaz:**
2522
+
2523
+ ```ts
2524
+ export interface HeaderAction {
2525
+ /** Texto del botón, p. ej. "Nuevo Usuario". */
2526
+ label: string;
2527
+ /** Ruta interna; si se define, el header emite `navigateTo`. */
2528
+ route?: string;
2529
+ /** Nombre del CustomEvent a despachar en `window` (para abrir modales en MFs remotos). */
2530
+ customEvent?: string;
2531
+ }
2532
+ ```
2533
+
2534
+ **Inputs:**
2535
+
2536
+ | Input | Default | Descripción |
2537
+ |---|---|---|
2538
+ | `isExpanded` | `false` | Estado del sidebar (cambia ícono ☰ / ✕) |
2539
+ | `activeTitle` | `''` | Título de la pantalla activa |
2540
+ | `action` | `null` | Acción contextual del botón `+` (recomendado). Ver `HeaderAction` |
2541
+ | `mfId` | `''` | Identificador del MF. **Deprecated** como driver de la acción; usar `[action]` |
2542
+ | `isMfFailed` | `false` | Oculta la acción si el MF falló al cargar |
2543
+ | `userRole` | `'Administrador'` | Rol mostrado en el menú de usuario |
2544
+ | `notifications` | `[]` | Lista de notificaciones (`LibNotification[]`) |
2545
+ | `notificationsLoading` | `false` | Indica carga de página de notificaciones |
2546
+ | `notificationsHasMore` | `false` | Indica si hay más notificaciones por cargar |
2547
+ | `unreadCount` | `null` | Total real de no leídas (prevalece sobre conteo derivado) |
2548
+
2549
+ **Outputs:**
2550
+
2551
+ | Output | Descripción |
2552
+ |---|---|
2553
+ | `navigateTo` | Navegación a ruta (p. ej. `/home`, `/child/infractions/register`) |
2554
+ | `menuClick` | Clic en botón hamburguesa |
2555
+ | `profileClick` | Clic en perfil de usuario |
2556
+ | `logoutClick` | Clic en cerrar sesión |
2557
+ | `actionClick` | Clic en acción contextual (emite `mfId`) |
2558
+ | `markAllRead` | Marcar todas las notificaciones como leídas |
2559
+ | `notificationClick` | Clic en una notificación |
2560
+ | `loadMoreNotifications` | Solicitud de siguiente página de notificaciones |
2561
+
2562
+ **Acción contextual (`[action]`)**
2563
+
2564
+ El botón `+` se configura desde el **shell host** sin modificar la librería. Al hacer clic:
2565
+
2566
+ 1. Si `action.route` está definido → emite `navigateTo` con esa ruta.
2567
+ 2. Si `action.customEvent` está definido → despacha `window.dispatchEvent(new CustomEvent(...))` para que el MF remoto abra un modal.
2568
+ 3. Siempre emite `actionClick` con el valor de `mfId`.
2569
+
2570
+ **Prioridad:** `[action]` prevalece sobre el mapa legacy interno por `mfId`. El mapa legacy se mantiene solo por compatibilidad con integraciones existentes.
2571
+
2572
+ **Ejemplo — navegación por ruta:**
2573
+
2574
+ ```html
2575
+ <lib-header
2576
+ [activeTitle]="pageTitle()"
2577
+ [mfId]="currentMfId()"
2578
+ [action]="{ label: 'Nueva Infracción', route: '/child/infractions/register' }"
2579
+ (navigateTo)="router.navigateByUrl($event)"
2580
+ />
2581
+ ```
2582
+
2583
+ **Ejemplo — abrir modal en MF remoto (CustomEvent):**
2584
+
2585
+ Configuración en el shell:
2586
+
2587
+ ```html
2588
+ <lib-header
2589
+ [activeTitle]="'Usuarios'"
2590
+ [mfId]="'users'"
2591
+ [action]="{ label: 'Nuevo Usuario', customEvent: 'mf-action-user-nuevo' }"
2592
+ />
2593
+ ```
2594
+
2595
+ Escucha en el MF hijo:
2596
+
2597
+ ```ts
2598
+ @HostListener('window:mf-action-user-nuevo')
2599
+ onNuevoDesdeShell(): void {
2600
+ this.abrirModalNuevoUsuario();
2601
+ }
2602
+ ```
2603
+
2604
+ **Ejemplo — config centralizada en el shell:**
2605
+
2606
+ ```ts
2607
+ import { HeaderAction } from 'sapenlinea-components';
2608
+
2609
+ const HEADER_ACTIONS: Record<string, HeaderAction> = {
2610
+ users: { label: 'Nuevo Usuario', customEvent: 'mf-action-user-nuevo' },
2611
+ infractions: { label: 'Nueva Infracción', route: '/child/infractions/register' },
2612
+ paymentAgreementParameters: { label: 'Nuevo Parámetro', customEvent: 'mf-action-register-parameter' },
2613
+ };
2614
+
2615
+ headerAction = computed(() => HEADER_ACTIONS[this.currentMfId()] ?? null);
2616
+ ```
2617
+
2618
+ ```html
2619
+ <lib-header
2620
+ [isExpanded]="sidebarExpanded()"
2621
+ [activeTitle]="pageTitle()"
2622
+ [mfId]="currentMfId()"
2623
+ [action]="headerAction()"
2624
+ [notifications]="notifications()"
2625
+ [unreadCount]="unreadTotal()"
2626
+ [notificationsHasMore]="hasMore()"
2627
+ (menuClick)="toggleSidebar()"
2628
+ (navigateTo)="router.navigateByUrl($event)"
2629
+ (logoutClick)="onLogout()"
2630
+ (notificationClick)="onNotification($event)"
2631
+ (loadMoreNotifications)="loadNextPage()"
2632
+ />
2633
+ ```
2634
+
2635
+ **Compatibilidad legacy (`mfId` sin `[action]`)**
2636
+
2637
+ Si no se pasa `[action]`, el header resuelve la acción desde mapas internos según `mfId`:
2638
+
2639
+ | `mfId` | Comportamiento |
2640
+ |---|---|
2641
+ | `infractions` | Navega a `/child/infractions/register` |
2642
+ | `paymentAgreements` | Navega a `/child/paymentAgreements/register` |
2643
+ | `trainingCourses-programar` | Navega a `/child/trainingCourses/programacion` |
2644
+ | `paymentAgreementParameters` | Despacha `mf-action-register-parameter` |
2645
+ | `trainingCourses-administrar` | Despacha `mf-action-training-course-nuevo` |
2646
+ | `trainingCourses-catalogos-cia` | Despacha `mf-action-catalogo-cia-nuevo` |
2647
+ | `trainingCourses-catalogos-salones` | Despacha `mf-action-catalogo-salon-nuevo` |
2648
+ | `trainingCourses-catalogos-instructores` | Despacha `mf-action-catalogo-instructor-nuevo` |
2649
+
2650
+ Para nuevos MFs, usar siempre `[action]` desde el shell.
2651
+
2652
+ ---
2653
+
2654
+ ### 51. `lib-notification-center` (NotificationCenter)
2655
+
2656
+ **Qué hace**
2657
+ Panel desplegable de notificaciones con filtro por categoría, scroll infinito y acción "Marcar todas como leídas". Se usa internamente en `lib-header` pero también puede consumirse de forma independiente.
2658
+
2659
+ **Selector:** `lib-notification-center`
2660
+
2661
+ **Interfaz:**
2662
+
2663
+ ```ts
2664
+ export interface LibNotification {
2665
+ id: string;
2666
+ title: string;
2667
+ description: string;
2668
+ category: string;
2669
+ timeAgo: string;
2670
+ isRead: boolean;
2671
+ }
2672
+ ```
2673
+
2674
+ **Inputs:**
2675
+
2676
+ | Input | Default | Descripción |
2677
+ |---|---|---|
2678
+ | `notifications` | `[]` | Lista de notificaciones |
2679
+ | `isOpen` | `false` | Controla visibilidad del panel |
2680
+ | `loading` | `false` | Indica carga de página en curso |
2681
+ | `hasMore` | `false` | Indica si quedan más registros por cargar |
2682
+
2683
+ **Outputs:**
2684
+
2685
+ | Output | Descripción |
2686
+ |---|---|
2687
+ | `markAllRead` | Usuario solicita marcar todas como leídas |
2688
+ | `notificationClick` | Clic en una notificación |
2689
+ | `close` | Cierre del panel |
2690
+ | `loadMore` | El sentinel al final de la lista entró en viewport (scroll infinito) |
2691
+
2692
+ **Comportamiento:**
2693
+
2694
+ - Pestañas de categoría generadas automáticamente desde `category` de cada notificación, más "Todas".
2695
+ - Colores e iconos por categoría inferidos por palabras clave (`ACUERDO`, `FINAN`, `INFRAC`, `SISTEMA`).
2696
+ - `IntersectionObserver` en un sentinel al final de la lista dispara `loadMore` cuando `hasMore` es `true` y no hay carga en curso.
2697
+
2698
+ ---
2699
+
2700
+ ### 52. `lib-bell-with-badge` (BellWithBadge)
2701
+
2702
+ **Qué hace**
2703
+ Icono de campana con badge numérico de notificaciones no leídas. Componente atómico usado por `lib-header`.
2704
+
2705
+ **Selector:** `lib-bell-with-badge`
2706
+
2707
+ **Inputs:**
2708
+
2709
+ | Input | Default | Descripción |
2710
+ |---|---|---|
2711
+ | `count` | `0` | Número a mostrar en el badge (oculto si es 0) |
2712
+ | `active` | `false` | Estado visual activo (panel abierto) |
2713
+ | `ariaLabel` | `'Notificaciones'` | Etiqueta de accesibilidad |
2714
+
2715
+ **Outputs:**
2716
+
2717
+ - `bellClick: void` — emitido al hacer clic en la campana.
2718
+
2719
+ **Ejemplo:**
2720
+
2721
+ ```html
2722
+ <lib-bell-with-badge
2723
+ [count]="unreadCount()"
2724
+ [active]="panelOpen()"
2725
+ (bellClick)="togglePanel()"
2726
+ />
2727
+ ```
2728
+
2729
+ ---